从 PHP-FPM 向 Apache 访问日志添加自定义变量

从 PHP-FPM 向 Apache 访问日志添加自定义变量

在 mod_php 中,您可以使用该apache_note()函数在 Apache 访问日志中记录变量:

<?php
apache_note('SCRIPT_TIME', '1234');
?>

使用 Apache 配置:

LogFormat "%h %l %u %t [%D/%{SCRIPT_TIME}n] \"%r\" %>s %b" inc_info
...
CustomLog /path/to/access_log inc_info

apache_note()PHP-FPM中没有该功能。

同样,您不能使用apache_setenv()setenv()设置环境变量(通过 记录%{SCRIPT_TIME}e)。

一种可能性是设置一个 Apache 可以使用“%{SCRIPT_TIME}i”记录的标头,但您需要小心不要让敏感信息发送到客户端(例如,了解登录脚本的准确处理时间可能是一个安全问题)。但更重要的是,如果内容已经发送到客户端,它就不起作用了,因为您无法再发送标头(如下面的完整示例所示)。

或者,PHP 可以编写自己的日志文件,但这将重复 Apache 日志已经执行的大量操作,有可能错过日志条目(例如,如果脚本有错误),并且将在 Apache 用户权限下创建(而不是以 root 身份写入)。


为了更详细,这是我用来记录脚本处理时间的代码:

<?php

define('SCRIPT_START', microtime(true));

function log_shutdown() {
    if (!defined('SCRIPT_END')) {
        define('SCRIPT_END', number_format(round((microtime(true) - SCRIPT_START), 4), 4));
    }
    if (function_exists('apache_note')) {
        apache_note('SCRIPT_TIME', SCRIPT_END);
    }
}

register_shutdown_function('log_shutdown');

?>

从时间角度来看,虽然 Apache 确实提供了“%D”来记录“处理请求所花费的时间”,但这在很大程度上依赖于用户的互联网连接。


这与我尝试在我的服务器上启用 HTTP/2 有关,配置如下:

Protocols h2 http/1.1

<FilesMatch \.php$>
    CGIPassAuth on
    SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1
    SetHandler "proxy:fcgi://127.0.0.1:9001"
</FilesMatch>

同样相关的是Nginx 版本

答案1

如果您仅记录处理时间并使用 nginx,则@MichaelHampton 的建议可以通过记录变量来起作用$upstream_response_time

但我想继续使用 Apache,并存储额外的详细信息(用户 ID、处理某些任务的时间等)...

我的解决方案是创建一个单独的日志文件,PHP 可以在任何时间写入(即使在发送标头之后),我将为每个请求创建一个唯一的参考代码(这样两个日志就可以合并)。

首先,让您的 PHP 脚本生成参考代码,并将其作为标题发送:

<?php 

// A more compact uniqid - which uses hex encoding, and a full UNIX timestamp

$start = microtime(true);
$sec = floor($start);
$usec = round(($start - $sec) * 1000000); // Only accurate to 6 decimal places.

$sec -= strtotime('-' . date('w', $sec) . ' days, 00:00:00', $sec); // Time since Sunday 00:00, max value = 604800 (60*60*24*7)

$code = '';
foreach([$sec, $usec, rand(100000, 999999)] as $code_part) {
    $a = dechex($code_part); // decbin returns a string
    $a = hex2bin(((strlen($a) % 2) == 0 ? '' : '0') . $a);
    $a = base64_encode_rfc4648($a);
    $code .= str_pad($a, 4, '.', STR_PAD_LEFT); // 4 characters max = 999999 -> "0f423f" (hex) -> "D0I/" (base64)
}

define('REQUEST_START', $start);
define('REQUEST_CODE', $code);

header('X-Request-Code: ' . $code); // For Apache

?>

Apache 可以记录该参考代码(更多信息):

Header always note "X-Request-Code" "Request-Code"
Header always unset "X-Request-Code"

LogFormat "%h %l %u [%{%Y-%m-%d %H:%M:%S}t] [%{Request-Code}n] \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" inc_ref

CustomLog /path/to/access_log inc_ref

然后你可以添加一个register_shutdown_function()执行以下操作:

<?php 

$log_values = [
        date('Y-m-d H:i:s'),
        REQUEST_CODE,
        $user_id,
        $_SERVER['REMOTE_ADDR'],
        $_SERVER['REQUEST_METHOD'],
        $_SERVER['REQUEST_URI'],
        http_response_code(),
        round((microtime(true) - REQUEST_START), 4),
    ];

if (($fp = fopen($log_file, 'a')) !== false) {
    fputcsv($fp, $log_values);
    fclose($fp);
}

?>

出于懒惰的原因,我重复了访问日志中的信息(例如,grep 单个日志文件)。

相关内容