在PHP中访问日志记录

时间:2012-01-31 16:52:15

标签: php apache logging

我想记录对/files文件夹中任何文件的访问权限,因此我可以使用PHP处理它以生成一些统计信息。

我不想编写通过RewriteRule调用的自定义PHP处理程序,因为我不想处理状态代码,MIME类型和缓存标头以及文件锁定问题。

我无法访问服务器配置,因此无法使用CustomLog(我可以访问.htacess)。

我无法使用X-Sendfile,因为它未启用。

我无法访问access.log


寻找一个明确的答案。

10 个答案:

答案 0 :(得分:12)

你在那里设置了很多限制。

您可以使用通过PHP include安装的自定义处理程序执行此操作,该处理程序位于每个适用的(或使用__FILE__解析,不适用)脚本的顶部。 您必须拥有一个在每个文件被点击时运行的脚本,并且您已经排除了对服务器配置的更改(包括,我相信.htaccess当您说RewriteRule时还不够好,这意味着你通过基于脚本的网守进行此操作。您不能拥有一个满足您的约束的解决方案,并且用户可以首先访问文件而无需使用PHP(或其他服务器端动态语言)。可以通过将用户重定向到实际文件而不是通过PHP运行静态内容来保留缓存。

您可以将日志信息存储在数据库中,也可以将文件存储在服务器可写入的位置(如果使用文件则注意争用 - 附加模式很棘手)。

编辑:quickshiftin指出了两种可以调用PHP的方法,而无需手动添加include次调用。

答案 1 :(得分:5)

创建一个auto_prepend_file并定义一个函数来记录您想要的w / e。您需要访问.htaccess才能设置这些(并且webhost将需要类似 AllowOverride all 在vhost中),或者使用PHP 5.3,您可以使用per-directory INI feature

<强>的.htaccess

  

php_value auto_prepend_file /path/to/file.php

每个目录的php.ini(PHP 5.3 CGI /快速CGI SAPI)

  

user_ini.auto_prepend_file = /path/to/file.php

然后为你的文件/path/to/file.php(我确定更优雅的东西;)

    

file_put_contents(
    LOG_FILE,
    implode(PHP_EOL . PHP_EOL, array(
                'SERVER: ' . PHP_EOL . print_r($_SERVER, true),
                'REQUEST: ' . PHP_EOL . print_r($_REQUEST, true)
            )),
    FILE_APPEND
);

这种方法的优点是你可能能够逃脱它,你只需要在一个地方定义/包含日志记录代码。

编辑:

回顾后,我发现你希望这适用于任意类型的文件...是的,这将是相当粗糙的。我能想到的最好的选择是将这些文件标记为.php或在.htaccess中定义自定义mime类型。想法是通过PHP解释器运行文件,从而执行 auto_prepend_file ,因为文件中没有PHP标记,所以内容直接发送到客户端。甚至可以在每个内容文件上面设置一小部分PHP来设置 ContentType 标头。我甚至不确定它会起作用但可能会有效。

答案 2 :(得分:3)

考虑到您不需要限制访问,这很简单。

构建一个页面logger.php,输入请求的文件,如:

logger.php?file=abc.exe

logger.php中,您只需记录此访问权限,然后重定向到文件:

file_put_contents('log', $_GET['file'] . ' requested',FILE_APPEND);
header('Location: files/'.$_GET['file']);

只需检查$_GET['file']是否存在恶意文件

当然,您必须替换您网站中的链接:

<a href="files/abc.exe">

<a href="logger.php?file=abc.exe">

答案 3 :(得分:3)

这里的意图似乎是绕过Apache和PHP中固有的所有系统。如果这些限制实际上存在于您的服务器实例上,那么要求更改您的权限要好于设计系统管理员可能会或可能不满意您实施的变通方法。

答案 4 :(得分:3)

可能不完全是你想要的,但你为什么不完全使用不同的解决方案呢?

您可以使用Google Analytics VirtualPageviews通过Javascript跟踪文件下载。

有关详细信息,请参阅此处:http://support.google.com/googleanalytics/bin/answer.py?hl=en&answer=55529

您甚至可以创建自己的JS来通过浏览器跟踪文件下载,而无需使用GA。

<强>更新

正如我所说,你可以轻松创建自己的JS来跟踪它们,而不必费心去做GA。这是jQuery中一个可以工作的愚蠢的例子(没有测试过 - 只是写了我的头脑):

代码示例:

JS方面:

$(document).ready(function() {
  $("a").click(function() {
    if( $(this).attr('href').match(/\/files\/(.*)/) ) {
      $.ajax({
        url: '/tracking/the/file/downloads.php'
        data: {
          'ok': 'let\'s',
          'add': 'some information',
          'about': 'the user that initiated',
          'the': 'request',
          'file': $(this).attr('href')
        }
      });
    }

    return true;
  });
});

答案 5 :(得分:3)

仅适用于mod_php案例。有一些性能损失 - apache_lookup_uri()会执行额外的apache内部子请求。

正如其他人所说,你需要.htaccess就像

一样
RewriteEngine On
RewriteRule ^/handler.php$ - [L]
RewriteRule ^/([a-zA-Z0-9\.]+)$ /handler.php?filename=$1 [L]

在handler.php文件中使用virtual()函数执行apache子请求。 示例:http://www.php.net/manual/en/function.virtual.php#88722

更新并测试(但相当小)的解决方案:

<?php
//add some request logging here
$file = $_GET["filename"];

$file_info = apache_lookup_uri($file);
header('content-type: ' . $file_info -> content_type);
// add other headers?
virtual($file);
exit(0);
?>

答案 6 :(得分:2)

好的,这是个主意。在这方面跟我说,它起初可能看起来不合适,但最后还是读了一下。希望它适用于您所拥有的。在包含文件的文件夹中,放置一个.htaccess,它将所有请求重写到同一目录中的PHP处理程序脚本,如下所示(未经测试):

RewriteEngine On
RewriteRule ^/handler.php$ - [L]
RewriteRule ^/([a-zA-Z0-9\.]+)$ /handler.php?filename=$1 [L]

在PHP脚本中,使用file_put_contents()执行任何必要的记录。然后,使用以下代码创建handler.php:

<?php
if (!file_exists) {
    header("Status: 404 Not Found");
    //if you have a 404 error page, you can use an include here to show it
    exit(0);
}

header("Content-disposition: attachment; filename={$_GET["filename"]}");
header("Content-type: ".get_mime_type($_GET["filename"]));
readfile($filename);

function get_mime_type($filename, $mimePath = '/etc') {
    $fileext = substr(strrchr($filename, '.'), 1);
    if (empty($fileext)) return (false);
    $regex = "/^([\w\+\-\.\/]+)\s+(\w+\s)*($fileext\s)/i";
    $lines = file("$mimePath/mime.types");
    foreach($lines as $line) {
        if (substr($line, 0, 1) == '#') continue; // skip comments
        $line = rtrim($line) . " ";
        if (!preg_match($regex, $line, $matches)) continue; // no match to the extension
        return ($matches[1]);
    }
    return (false); // no match at all
}
?>

基本上,您在文件请求和文件的实际服务之间创建了一个层。此PHP层记录文件访问权限,然后提供文件。你说你不想搞乱状态代码和MIME类型,但这样做的好处在于所有这些都得到了解决。如果文件不存在,它只生成标准404,您可以包含自定义404错误页面。是的,这里正在更改状态标题,但这并不复杂。对于MIME类型,根据Apache使用的相同MIME类型规则为您检测它们。将get_mime_type函数指向服务器上的mime.types文件。如果您不知道它在哪里,只需从here下载副本即可。我承认,这个解决方案可能比你想要的更具技术性,但是你有这个限制,这是一个很好的解决方案。最好的部分是,它对最终用户以及上传内容的人完全透明。

答案 7 :(得分:2)

在没有通过PHP过滤内容的情况下,您可以做的唯一不显眼的监控是检查所有文件并在每次请求任何PHP文件时记下file access times(您只需在PHP文件中添加一个函数或使用重写)。这会带来一点开销,但这是你能得到的唯一不显眼的统计数据。

显然,通过这种方式,您无法获得准确的访问次数,但更像是频率,因此它也是某种(可行的)统计数据。为了获得命中数字之类的东西(这是在2月25日凌晨2点开放1000k次)你需要访问日志或通过PHP或cgi脚本管理它 - 只需要手动计算。

答案 8 :(得分:2)

假设您使用PHP作为已编译的Apache模块,那么virtual()函数可以实现这一点。请参阅:http://www.php.net/manual/en/function.virtual.php

<?php

$fn = $_GET['fn'];

log_file_access($fn); // You define how you want this to happen    
virtual($fn);

然后通过以下方式引用文件:

http://example.com/file.php?fn=files/lolcat.jpg

答案 9 :(得分:1)

我尝试了很多东西,似乎没有简单的解决方案。

我的解决方案使用@ yes123提出的Location标头技巧,但我已经调整它以符合我的偏好。

文件的链接保持不变,所以它仍然是:/files/path/to/my/file.abc 我有RewriteRule

RewriteRule ^files/(.*) path/to/tracker.php?path=/$1

然后在文件中,我通过向网址添加Location并发布前一个?track=no的例外来发布RewriteRule标头:

RewriteCond %{QUERY_STRING} !(&|^)track=no(&|$)

我已经添加了一项优化。我已经启用了电子标签,因此如果客户端发送了E-Tag标头,请查看它是否与文件匹配并返回304 Not Modified而不是Location

$fs = stat($document_root . $path);
$apache_etag = calculate_apache_etag($fs);
if ((isset($_SERVER["HTTP_IF_MATCH"]) && etag_within_range($_SERVER["HTTP_IF_MATCH"], $apache_etag))
    || (isset($_SERVER["HTTP_IF_NONE_MATCH"]) && etag_within_range($_SERVER["HTTP_IF_NONE_MATCH"], $apache_etag))
) {
    header("ETag: " . $apache_etag, true, 304);
    exit;
}

function etag_within_range($etag1, $etag2) {
    list($size1, $mtime1) = explode("-", $etag1);
    list($size2, $mtime2) = explode("-", $etag2);
    $mtime1 = floor(hexdec($mtime1) / 1000000);
    $mtime2 = floor(hexdec($mtime2) / 1000000);
    return $mtime1 === $mtime2 && $size1 === $size2;
}

calculate_apache_etag的实施可在此处找到:How do you make an etag that matches Apache?

etag_withing_range解决了在Apache中与更高精度mtime进行比较的问题。


关于无法解决的解决方案的说明

virtual

测试脚本:

var_dump(apache_response_headers());
virtual("/path/to/image.jpg");
var_dump(apache_response_headers());

输出:

array(1) { ["X-Powered-By"]=> string(10) "PHP/5.2.11" }
[[binary junk]]
array(5) { ["X-Powered-By"]=> string(10) "PHP/5.2.11" ["Keep-Alive"]=> string(18) "timeout=5, max=100" ["Connection"]=> string(10) "Keep-Alive" ["Transfer-Encoding"]=> string(7) "chunked" ["Content-Type"]=> string(9) "text/html" }

Content-Type: text/html reaaaaalllly ? :(

也许PHP5.3的header_remove功能可以解决这个问题?我没有尝试过。