A mini tracing PHP extension written in Rust, similar to Skywalking/CAT

Cover image source: Hideaki Hamada hamadahideaki

1. Introduction

Recently I've been learning Rust, and happened to see that the Skywalking PHP extension is written in Rust. Anyone who has used monitoring systems like Skywalking/CAT knows that these systems are extremely helpful for our development work, allowing us to quickly pinpoint key issues. For example, if an API request is responding very slowly, we can query the latency of each node in the API request's trace chain from the system's web UI, thereby accurately locating the slow bottleneck.

However, setting up such systems is quite cumbersome, and the cost is relatively high for individual developers or small companies. Therefore, I have streamlined and partially enhanced the apache/skywalking-php extension: I removed the part that reports data to the Skywalking server, and instead write trace logs to a local file. The following content will be recorded in this local file:

1. When calling CURL, record the start and end times and latency, and record error information if an error occurs

{
"trace_id": "b89143d7-0fda-43d5-a688-397aef0ee3ef",
"kind": "CURL",
"name": "https://error.blog.fanscore.cn/a/57/",
"payload": {
"http_code": "0",
"query": "k1=v1&k2=k2&k3=v3",
"curl_error": "Could not resolve host: error.blog.fanscore.cn"
},
"start_time": "10:19:03.596", // 时间格式%H:%M:%S%.3f
"end_time": "10:19:03.602",
"duration_in_micro": 5988 // 耗时
}

{
“trace_id”: “b89143d7-0fda-43d5-a688-397aef0ee3ef”,
“kind”: “CURL”,
“name”: “一种应用于特定场景的支持LRU的线程安全的无锁uint32->uint32 cache实现 | Orlion的博客”,
“payload”: {
“http_code”: “200”,
“curl_error”: “”,
“query”: “k1=v1&k2=k2&k3=v3”
},
“start_time”: “10:19:03.602”,
“end_time”: “10:19:03.969”,
“duration_in_micro”: 366647
}

2. When calling PDO functions, record the start and end times and latency, and record error information if an error occurs

{
"trace_id": "b89143d7-0fda-43d5-a688-397aef0ee3ef",
"kind": "PDO",
"name": "__construct",
"payload": {
"result": "unknown",
"dsn": "mysql:host=127.0.0.1;dbname=blog;charset=utf8mb4"
},
"start_time": "10:19:03.969",
"end_time": "10:19:03.980",
"duration_in_micro": 11175
}
{
"trace_id": "b89143d7-0fda-43d5-a688-397aef0ee3ef",
"kind": "PDO",
"name": "query",
"payload": {
"statement": "select * from article",
"result": "object(PDOStatement)"
},
"start_time": "10:19:03.980",
"end_time": "10:19:03.985",
"duration_in_micro": 5471
}
{
"trace_id": "b89143d7-0fda-43d5-a688-397aef0ee3ef",
"kind": "PDO_STATEMENT",
"name": "fetchAll",
"payload": {
"query_string": "select * from article",
"result": "array(3)"
},
"start_time": "10:19:03.985",
"end_time": "10:19:03.985",
"duration_in_micro": 25
}

3. Capture errors in PHP code

{
"trace_id": "b89143d7-0fda-43d5-a688-397aef0ee3ef",
"kind": "ERROR",
"name": "E_WARNING: Undefined variable $undefined_value in /Users/orlion/workspace/nginx/www/ptrace/index.php on line 32",
"payload": {},
"start_time": "10:19:03.986",
"end_time": "10:19:03.986",
"duration_in_micro": 2
}

4. Capture uncaught exceptions in PHP code

{
"trace_id": "b89143d7-0fda-43d5-a688-397aef0ee3ef",
"kind": "EXCEPTION",
"name": "Exception: test exception in /Users/orlion/workspace/nginx/www/ptrace/index.php on line 34",
"payload": {
"trace": "#0 {main}"
},
"start_time": "10:19:03.986",
"end_time": "10:19:03.986",
"duration_in_micro": 1
}

5. After the request ends, record the request start and end times, status code, and GET/POST parameters

{
"trace_id": "b89143d7-0fda-43d5-a688-397aef0ee3ef",
"kind": "URL",
"name": "/index.php",
"payload": {
"$_GET": "{\"a\":\"1\",\"b\":\"2\",\"c\":\"3\"}",
"$_POST": "[]",
"method": "GET",
"status_code": "200"
},
"start_time": "10:19:03.595",
"end_time": "10:19:03.992",
"duration_in_micro": 397178
}

2. Installation

  1. Requirement

Unfortunately, currently only the Mac ARM64 version is provided. A Linux version will be compiled later, but since the dependent phper-framework/phper library does not support Windows, a Windows version will not be available in the short term.

  1. Go to https://github.com/Orlion/minitrace/releases to download the pre-compiled extension binary file to your local machine.

  2. Assuming you downloaded the extension to /tmp/minitrace-v0.1.0-macos-arm64.dylib in the first step, edit your php.ini configuration file and add the following settings:

[minitrace]
;加载我们的扩展
extension=/tmp/minitrace-v0.1.0-macos-arm64.dylib
;将trace数据输出到/tmp/minitrace.log
minitrace.log_file = /tmp/minitrace.log
  1. Restart FPM

3. Test Usage

Edit the following PHP file:

<!--?php

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, ‘https://error.blog.fanscore.cn/a/57/?k1=v1&amp;k2=k2&amp;k3=v3#aaa’);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, ‘一种应用于特定场景的支持LRU的线程安全的无锁uint32->uint32 cache实现 | Orlion的博客’);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);

$host = ‘127.0.0.1’;
$db = ‘blog’;
$user = ‘root’;
$pass = ‘123456’;
$charset = ‘utf8mb4’;
$dsn = “mysql:host=$host;dbname=$db;charset=$charset”;
$options = [
PDO::ATTR_ERRMODE =–> PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE =&gt; PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES =&gt; false,
];
$pdo = new PDO($dsn, $user, $pass, $options);
$stm = $pdo-&gt;query(‘select * from article’);
$rows = $stm-&gt;fetchAll();
foreach($rows as $row) {
print_r($row);
}

var_dump($undefined_value);

throw new Exception(‘test exception’);
?&gt;

Then request this file in your browser, open /tmp/minitrace.log and you will see the following output:

{"trace_id":"b89143d7-0fda-43d5-a688-397aef0ee3ef","kind":"CURL","name":"https://error.blog.fanscore.cn/a/57/","payload":{"http_code":"0","query":"k1=v1&k2=k2&k3=v3","curl_error":"Could not resolve host: error.blog.fanscore.cn"},"start_time":"10:19:03.596","end_time":"10:19:03.602","duration_in_micro":5988}
{"trace_id":"b89143d7-0fda-43d5-a688-397aef0ee3ef","kind":"CURL","name":"https://blog.fanscore.cn/a/57/","payload":{"http_code":"200","curl_error":"","query":"k1=v1&k2=k2&k3=v3"},"start_time":"10:19:03.602","end_time":"10:19:03.969","duration_in_micro":366647}
{"trace_id":"b89143d7-0fda-43d5-a688-397aef0ee3ef","kind":"PDO","name":"__construct","payload":{"result":"unknown","dsn":"mysql:host=127.0.0.1;dbname=blog;charset=utf8mb4"},"start_time":"10:19:03.969","end_time":"10:19:03.980","duration_in_micro":11175}
{"trace_id":"b89143d7-0fda-43d5-a688-397aef0ee3ef","kind":"PDO","name":"query","payload":{"statement":"select * from article","result":"object(PDOStatement)"},"start_time":"10:19:03.980","end_time":"10:19:03.985","duration_in_micro":5471}
{"trace_id":"b89143d7-0fda-43d5-a688-397aef0ee3ef","kind":"PDO_STATEMENT","name":"fetchAll","payload":{"query_string":"select * from article","result":"array(3)"},"start_time":"10:19:03.985","end_time":"10:19:03.985","duration_in_micro":25}
{"trace_id":"b89143d7-0fda-43d5-a688-397aef0ee3ef","kind":"ERROR","name":"E_WARNING: Undefined variable $undefined_value in /Users/orlion/workspace/nginx/www/ptrace/index.php on line 32","payload":{},"start_time":"10:19:03.986","end_time":"10:19:03.986","duration_in_micro":2}
{"trace_id":"b89143d7-0fda-43d5-a688-397aef0ee3ef","kind":"EXCEPTION","name":"Exception: test exception in /Users/orlion/workspace/nginx/www/ptrace/index.php on line 34","payload":{"trace":"#0 {main}"},"start_time":"10:19:03.986","end_time":"10:19:03.986","duration_in_micro":1}
{"trace_id":"b89143d7-0fda-43d5-a688-397aef0ee3ef","kind":"URL","name":"/index.php","payload":{"$_GET":"{\"a\":\"1\",\"b\":\"2\",\"c\":\"3\"}","$_POST":"[]","method":"GET","status_code":"200"},"start_time":"10:19:03.595","end_time":"10:19:03.992","duration_in_micro":397178}

If this project github.com/Orlion/minitrace is helpful to you, please feel free to leave a star.


This is a discussion topic separated from the original topic at https://juejin.cn/post/7368294440547778598