作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.
伊利亚是一名IT顾问, web architect, 拥有12年以上团队建设和领导经验的经理.
可以说,日志是最被低估和未充分利用的工具之一 自由PHP开发人员’s disposal. 尽管日志可以提供丰富的信息,但将日志作为工具的情况并不少见 last 当试图解决问题时,放置开发人员的视线.
事实上,PHP日志文件在很多情况下应该是 first 当问题发生时寻找线索的地方. Often, 它们所包含的信息可以大大减少你揪着头发试图追踪一只粗糙的虫子所花费的时间.
但也许更重要的是, 再加上一点创造力和远见, 可以利用日志文件作为使用信息和分析的有价值的来源. 创造性地使用日志文件可以帮助回答以下问题:访问我的站点最常用的浏览器是什么? 服务器的平均响应时间是多少? 请求到站点根的百分比是多少? 自从我们部署了最新的更新以来,使用情况发生了怎样的变化? And much, much more.
本文提供了一些有关的技巧 如何配置日志文件, as well as 如何处理它们所包含的信息,以便最大限度地发挥它们所提供的效益.
尽管本文主要关注PHP开发人员的日志记录技术, 这里提供的许多信息与技术无关,也与其他语言和技术栈相关.
Note: 本文假定您对Unix shell有基本的了解. 对于那些缺乏这方面知识的人 Appendix 介绍了在Unix系统上访问和读取日志文件所需的一些命令.
作为本文讨论目的的一个示例项目,我们将采用 Symfony Standard 作为一个工作项目,我们将把它安装在Debian 7 Wheezy上 rsyslogd
, nginx
, and PHP-FPM
.
Composer - create-project symfony/framework-standard-edition my "2.6.*"
这很快就为我们提供了一个具有漂亮UI的工作测试项目.
这里有一些关于如何配置日志文件以帮助最大化其价值的指针.
Error logs represent the most basic form of logging; i.e.,在出现问题时捕获额外的信息和细节. 因此,在理想情况下,您希望没有错误,并且错误日志为空. 但当问题发生时(这是不可避免的), 您的错误日志应该是您的 debugging trail.
错误日志通常很容易配置.
For one thing, 所有错误和崩溃消息都可以以与呈现给用户的格式完全相同的格式记录在错误日志中. 通过一些简单的配置, 最终用户将永远不需要在您的站点上看到那些丑陋的错误痕迹, 虽然开发人员仍然能够监视系统并查看这些错误消息的所有细节. 下面是如何在PHP中设置这种日志记录:
log_errors = On
error_reporting = E_ALL
Error_log = /path/to/my/error/log
另外两行很重要,应该包含在活动站点的日志文件中, 避免向用户呈现严重程度的错误细节, are:
display_errors = Off
display_startup_errors =关闭
syslog
) Confguration的许多一般兼容的实现 syslog
Daemon在开源世界中包括:
syslogd
and sysklogd
-最常见于BSD系列系统, CentOS, Mac OS X, and otherssyslog-ng
– default for modern Gentoo and SuSE buildsrsyslogd
– widely used on the Debian and Fedora 操作系统系列(注意:在本文中,我们将使用 rsyslogd
for our examples.)
基本的syslog配置通常足以在系统范围的日志文件中捕获日志消息 /var/log/syslog
; might also be /var/log/messages
or /var/log/system.log
取决于你使用的发行版).
系统日志提供了几个日志工具,其中八个(LOG_LOCAL0
through LOG_LOCAL7
)为用户部署的项目保留. 例如,您可以在这里设置 LOG_LOCAL0
写入4个独立的日志文件,根据日志级别(i.e.,错误,警告,信息,调试):
# /etc/rsyslog.d/my.conf
local0.犯错/var/log/my/err.log
local0.警告/var/log/my/warning.log
local0.信息- / var / log /我/信息.log
local0.调试,/ var / log /我/调试.log
现在,每当您向 LOG_LOCAL0
设施,错误消息将转到 /var/log/my/err.log
,警告信息将转到 /var/log/my/warning.log
, and so on. Note, though, syslog守护进程根据“此级别及更高级别”的规则过滤每个文件的消息. So, in the example above, 所有错误消息将出现在所有四个配置文件中, 警告消息将出现在除错误日志之外的所有日志中, Info消息将出现在Info和debug日志中, 并且调试消息只会转到 debug.log
.
One additional important note; The -
上述配置文件示例中的info和debug级别文件前面的符号表明,对这些文件的写入应该异步执行(因为这些操作是非阻塞的)。. 对于信息和调试日志来说,这通常是很好的(甚至在大多数情况下都是推荐的), 但是对错误日志(很可能还有警告日志)的写操作最好是同步的.
为了关闭不太重要的日志记录级别(例如.g.(在生产服务器上),您只需将相关消息重定向到 /dev/null
(i.e., to nowhere):
local0.-/var/log/my/ Debug.log
一个有用的特定定制, 特别是支持一些PHP日志文件解析,我们将在本文后面讨论, 是否在日志消息中使用TAB作为分隔符. 这可以通过添加以下文件轻松完成 /etc/rsyslog.d
:
# /etc/rsyslog.d/fixtab.conf
EscapeControlCharactersOnReceive美元
And finally, 不要忘记在您做了任何配置更改后重新启动syslog守护进程,以便它们生效:
rsyslog restart服务
与您可以写入的应用程序日志和错误日志不同, 服务器日志由相应的服务器守护进程(例如.g.、web服务器、数据库服务器等.) on each request. 您对这些日志的唯一“控制”是服务器允许您配置其日志记录功能. 虽然这些文件里有很多东西要筛选, 它们通常是清楚地了解服务器“底层”发生了什么的唯一方法.
让我们在nginx环境中使用MySQL存储后端部署Symfony Standard示例应用程序. 下面是我们将要使用的nginx主机配置:
server {
server_name my.log-sandbox;
root /var/www/my/web;
location / {
#尝试直接服务文件,退回到应用程序.php
try_files $uri /app.php$is_args$args;
}
# DEV
#该规则只适用于您的开发环境
在生产环境中,不要包含这个,也不要部署app_dev.php or config.php
位置~ ^/(app_dev|config)\.php(/|$) {
fastcgi_pass unix: / var /运行/ php5-fpm.sock;
fastcgi_split_path_info ^ (.+\.php)(/.*)$;
包括fastcgi_params;
$document_root$fastcgi_script_name;
fastcgi_param HTTPS关闭;
}
# PROD
location ~ ^/app\.php(/|$) {
fastcgi_pass unix: / var /运行/ php5-fpm.sock;
fastcgi_split_path_info ^ (.+\.php)(/.*)$;
包括fastcgi_params;
$document_root$fastcgi_script_name;
fastcgi_param HTTPS关闭;
#阻止包含前控制器的uri. This will 404:
# http://domain.tld/app.php/some-path
删除内部指令以允许这样的uri
internal;
}
error_log /var/log/nginx/my_error.log;
access_log /var/log/nginx/my_access.log;
}
关于上述最后两个指令: access_log
表示一般请求日志,而 error_log
is for errors, and, 与应用程序错误日志一样, 设置额外的监控来提醒问题是值得的,这样您就可以快速做出反应.
Note: 这是一个故意过度简化的nginx配置文件,仅供示例使用. 它几乎不关注安全性和性能,不应该在任何“真实”环境中按原样使用.
这是我们得到的 /var/log/nginx/my_access.log
after typing http://my.log-sandbox/app_dev.php/
在浏览器中点击 Enter
.
192.168.56.1 - - [26/Apr/2015:16:13:28 +0300] "GET /app_dev . exe。.php/ HTTP/1.1“200 6715”-“”Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML,像壁虎)Chrome/42.0.2311.90 Safari/537.36"
192.168.56.1 - - [26/Apr/2015:16:13:28 +0300] "GET /bundles/framework/css/body.css HTTP/1.1“200 6657”http://my.log-sandbox/app_dev.php/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML,像壁虎)Chrome/42.0.2311.90 Safari/537.36"
192.168.56.1 - - [26/Apr/2015:16:13:28 +0300] "GET /bundles/framework/css/structure.css HTTP/1.1“2001191”http://my.log-sandbox/app_dev.php/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML,像壁虎)Chrome/42.0.2311.90 Safari/537.36"
192.168.56.1 - - [26/Apr/2015:16:13:28 +0300] "GET /bundles/acmedemo/css/demo.css HTTP/1.1“2002204”http://my.log-sandbox/app_dev.php/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML,像壁虎)Chrome/42.0.2311.90 Safari/537.36"
192.168.56.1 - - [26/Apr/2015:16:13:28 +0300] "GET /bundles/acmedemo/images/welcome-quick-tour.gif HTTP/1.1" 200 4770 "http://my.log-sandbox/app_dev.php/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML,像壁虎)Chrome/42.0.2311.90 Safari/537.36"
192.168.56.1 - - [26/Apr/2015:16:13:28 +0300] "GET /bundles/acmedemo/images/welcome-demo.gif HTTP/1.1" 200 4053 "http://my.log-sandbox/app_dev.php/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML,像壁虎)Chrome/42.0.2311.90 Safari/537.36"
192.168.56.1 - - [26/Apr/2015:16:13:28 +0300] "GET /bundles/acmedemo/images/welcome-configure.gif HTTP/1.1" 200 3530 "http://my.log-sandbox/app_dev.php/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML,像壁虎)Chrome/42.0.2311.90 Safari/537.36"
192.168.56.1——[26/Apr/2015:16:13:28 +0300] "GET /favicon.ico HTTP/1.1" 200 6518 "http://my.log-sandbox/app_dev.php/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML,像壁虎)Chrome/42.0.2311.90 Safari/537.36"
192.168.56.1 - - [26/Apr/2015:16:13:30 +0300] "GET /app_dev . exe。.php/_wdt/e50d73 HTTP/1.1“200 13265”http://my.log-sandbox/app_dev.php/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML,像壁虎)Chrome/42.0.2311.90 Safari/537.36"
这表明,为了提供一个页面,浏览器实际上执行了9个HTTP调用. 然而,其中7个是对静态内容的请求,这是简单和轻量级的. 然而,它们仍然占用网络资源,这是可以通过使用各种优化的 sprites 还有缩小技术.
而这些优化将在另一篇文章中讨论, 这里相关的是,我们可以使用另一个单独记录请求到静态内容 location
directive for them:
location ~ \.(jpg | jpeg | gif | png | ico css | | zip | tgz |广州| rar | bz2 |获取pdf | txt | tar | wav | bmp | rtf | js) $ {
access_log /var/log/nginx/my_access-static.log;
}
Remember that nginx location
执行简单正则表达式匹配, 因此,您可以包含尽可能多的静态内容扩展,只要您希望在您的站点上分发.
解析此类日志与解析应用程序日志没有什么不同.
另外两个值得一提的PHP日志是调试日志和数据存储日志.
关于nginx日志的另一个方便的事情是调试日志. 我们可以通过替换 error_log
下面的配置行(需要安装nginx调试模块):
error_log /var/log/nginx/my_error.log debug;
同样的设置适用于Apache或您使用的任何其他web服务器.
顺便提一下,调试日志是 not 与错误日志相关,即使它们在 error_log
directive.
虽然调试日志确实可以是详细的(单个nginx请求, for example, 生成了127KB的日志数据!),但它仍然非常有用. 费力地浏览日志文件可能是麻烦和乏味的, 但它通常可以快速提供线索和信息,极大地帮助加快调试过程.
In particular, 调试日志确实可以帮助调试nginx配置, 尤其是最复杂的部分, like location
matching and rewrite
chains.
当然,绝不应该在生产环境中启用调试日志. 它们使用的空间量和存储的信息量也意味着服务器上的大量I/O负载, 哪些会显著降低整个系统的性能.
另一种类型的服务器日志(对调试很有用)是数据存储日志. 在MySQL中,你可以通过添加这些行来打开它们:
[mysqld]
general_log = 1
General_log_file = /var/log/mysql/query.log
这些日志只是按时间顺序包含系统在处理数据库请求时运行的查询列表, 哪一个对各种调试和跟踪需求有帮助. However, 它们不应该在生产系统上保持启用状态, 因为它们会产生额外的不必要的I/O负载, 这会影响性能.
PHP本身提供了打开、写入和关闭日志文件的函数(openlog()
, syslog()
, and closelog()
, respectively).
There are also 众多日志库 用于PHP开发人员,例如 Monolog (popular among Symfony and Laravel users), 以及各种特定于框架的实现, 例如将日志记录功能合并到 CakePHP. 一般来说,像Monolog这样的库不仅封装 syslog()
调用,但也允许使用其他后端功能和工具.
下面是一个如何写入日志的简单示例:
Our call here to openlog
:
syslog()
call has occurredLOG_LOCAL0
作为默认的日志记录工具下面是运行上述代码后日志文件的内容:
# cat /var/log/my/info.log
Mar 2 00:23:29 log-sandbox 54f39161a2e55:它工作!
现在我们都熟悉了理论和基础知识, 让我们看看在尽可能少地更改我们的样例Symfony Standard项目的情况下,我们可以从日志中获得多少信息.
首先,让我们创建脚本 src/log-begin.php
(正确打开和配置我们的日志)和 src/log-end.php
(记录有关成功完成的信息). 请注意,为了简单起见,我们只将所有消息写入信息日志.
# src/log-begin.php
# src/log-end.php
我们需要这些脚本 app.php
:
对于开发环境,我们需要这些脚本 app_dev.php
as well. 这样做的代码将与上面相同,只是我们将设置 MODE
to DEV
rather than PROD
.
我们还想跟踪正在调用的控制器,所以让我们再添加一行 Acme \ DemoBundle \ EventListener \ ControllerListener
,就在……的开头 ControllerListener: onKernelController ()
method:
syslog (LOG_INFO“控制器\ t” . get_class($event->getController()[0]));
注意,这些更改总共只增加了15行代码, 但可以共同产生丰富的信息.
对于初学者,让我们看看每个页面加载需要多少HTTP请求.
下面是一个请求的日志信息,基于我们配置日志的方式:
Mar 3 12:04:20 log-sandbox 54f58724b1ccc:开始
Mar 3 12:04:20 log-sandbox 54f58724b1ccc: URI /app_dev.php/
Mar 3 12:04:20 log-sandbox 54f58724b1ccc: CLIENT.168.56.1 1b101cd
Mar 3 12:04:20 log-sandbox 54f58724b1ccc: MODE DEV
march 3 12:04:23 log-sandbox 54f58724b1ccc: CONTROLLER Acme\DemoBundle\ CONTROLLER \WelcomeController
3 . Mar 3 12:04:25 log-sandbox 54f58724b1ccc:调度时间.51
Mar 3 12:04:25 log-sandbox 54f58724b1ccc:结束
Mar 3 12:04:25 log-sandbox 54f5872967dea:开始
Mar 3 12:04:25 log-sandbox 54f5872967dea: URI /app_dev.php/_wdt/59b8b6
Mar 3 12:04:25 log-sandbox 54f5872967dea: CLIENT.168.56.1 1b101cd
Mar 3 12:04:25 log-sandbox 54f5872967dea: MODE DEV
3 12:04:28 log-sandbox 54f5872967dea: CONTROLLER Symfony\Bundle\WebProfilerBundle\ CONTROLLER \ProfilerController
Mar 3 12:04:29 log-sandbox 54f5872967dea:调度时间.17
Mar 3 12:04:29 log-sandbox 54f5872967dea:结束
因此,现在我们知道每个页面加载实际上是由两个HTTP请求提供的.
实际上这里有两点值得一提. First, 每次页面加载两个请求是用于在开发模式下使用Symfony(我在本文中一直这样做). 您可以通过搜索来识别开发模式调用 /app-dev.php/
URL chunks. 其次,假设每个页面加载都伴随着对Symfony应用程序的两个后续请求. 正如我们之前在nginx访问日志中看到的那样, 实际上有更多的HTTP调用, 其中一些用于静态内容.
OK, 现在让我们浏览一下演示站点(在日志文件中构建数据),看看我们还能从这些日志中学到什么.
自日志文件开始以来总共服务了多少个请求?
# grep -c BEGIN info.log
10
它们中是否有失败的(脚本是否在未到达结尾时关闭)?
# grep -c END info.log
10
我们看到的是 BEGIN
and END
记录匹配,所以这告诉我们所有呼叫都成功了. (如果PHP脚本没有成功完成,它就不会执行 src/log-end.php
script.)
请求到站点根的百分比是多少?
# ' grep -cE "\s/app_dev.php/$" info.log`
2
这告诉我们有2个页面加载站点根. 因为我们之前了解到(a)每次页面加载有2个请求,(b)总共有10个HTTP请求, 请求到站点根的百分比是40% (i.e., 2x2/10).
哪个控制器类负责为站点根提供请求?
# grep -E "\s/$|\s/app_dev.php/$" info.log | head -n1
Mar 3 12:04:20 log-sandbox 54f58724b1ccc: URI /app_dev.php/
# grep 54f58724b1ccc info.log | grep CONTROLLER
march 3 12:04:23 log-sandbox 54f58724b1ccc: CONTROLLER Acme\DemoBundle\ CONTROLLER \WelcomeController
在这里,我们使用请求的唯一ID来检查与该请求相关的所有日志消息. 因此,我们能够确定负责向站点根提供请求的控制器类是 Acme \ DemoBundle \ \ WelcomeController控制器
.
哪些客户端ip地址为子网
192.168.0.0/16
访问过网站?
# grep CLIENT info.Log | cut -d":" -f4 | cut -f2 | sort | uniq
192.168.56.1
正如在这个简单的测试用例中所期望的那样,只有我的主机访问了站点. 这当然是一个非常简单的例子, 但是它所展示的能力(能够分析站点流量的来源)显然是非常强大和重要的.
我的网站有多少流量来自FireFox?
Having 1b101cd
作为我的Firefox User-Agent的哈希值,我可以这样回答这个问题:
# grep -c 1b101cd info.log
8
# grep -c CLIENT info.log
10
Answer: 80% (i.e., 8/10)
产生缓慢响应的请求的百分比是多少?
对于本示例而言, 我们将“慢”定义为需要超过5秒的时间来提供响应. Accordingly:
# grep“调度时间”信息.log | grep -cE "\s[0-9]{2,}\.|\s[5-9]\."
2
Answer: 20% (i.e., 2/10)
有人提供过GET参数吗?
# grep URI info.log | grep \?
No, Symfony标准只使用URL段, 这也告诉我们没有人试图入侵这个网站.
这些只是一些相对基本的示例,说明可以创造性地利用日志文件来产生有价值的使用信息,甚至是基本的分析.
另一个需要注意的是安全问题. 您可能认为记录请求是一个好主意,在大多数情况下确实如此. However, 在将任何潜在的敏感用户信息存储到日志中之前,一定要非常小心地删除它们,这一点非常重要.
因为日志文件是文本文件,您总是 append 信息,它们在不断增长. 因为这是一个众所周知的问题, 有一些相当标准的方法可以控制日志文件的增长.
The easiest is to rotate the logs. Rotating logs means:
最常见的解决方案是 logrotate
,它预装在大多数*nix发行版中. 让我们看一个简单的配置文件来旋转我们的日志:
/var/log/my/debug.log
/var/log/my/info.log
/var/log/my/warning.log
/var/log/my/error.log
{
rotate 7
daily
missingok
notifempty
delaycompress
compress
sharedscripts
postrotate
invoke-rc.d rsyslog rotate > /dev/null
endscript
}
另一种更先进的方法是 rsyslogd
它将消息写入文件,根据当前日期和时间动态创建. 这仍然需要一个自定义的解决方案来删除旧文件, 但是可以让开发人员精确地管理每个日志文件的时间框架. For our example:
/var/log/my/error-%$NOW%-%$HOUR%.log"
$template DynaLocal0Info, "/var/log/my/info-%$NOW%-%$HOUR%.log"
$template DynaLocal0Warning, "/var/log/my/warning-%$NOW%-%$HOUR%.log"
/var/log/my/debug-%$NOW%-%$HOUR%.log"
local1.err -?DynaLocal0Err
local1.info -?DynaLocal0Info
local1.warning -?DynaLocal0Warning
local1.debug -?DynaLocal0Debug
This way, rsyslog
会每小时创建一个单独的日志文件吗, 并且不需要旋转它们并重新启动守护进程. 以下是如何删除超过5天的日志文件来实现此解决方案:
找到/var/log/my/ -mtime +5 -print0 | xargs -0 rm
随着项目的增长,从日志中解析信息需要越来越多的资源. This not only means creating extra server load; it also means creating peak load on the CPU and disk drives at the times when you parse logs, 这会降低用户的服务器响应时间(或者在最坏的情况下甚至会使站点瘫痪)。.
要解决这个问题,可以考虑设置一个 集中式日志服务器. 您所需要的是另一个打开UDP端口514(默认)的盒子. To make rsyslogd
监听连接,在其配置文件中添加以下行:
$UDPServerRun 514
有了这些,设置客户端就很容易了:
*.* @HOSTNAME:514
(where HOSTNAME
是远程日志服务器的主机名).
本文展示了一些创造性的方法,通过这些方法,日志文件可以提供比您以前想象的更有价值的信息, 重要的是要强调,我们只是触及了可能性的表面. 可以记录的内容的范围、范围和格式几乎是无限的. 这意味着——如果你想从日志中提取使用或分析数据——你只需要以一种易于后续解析和分析的方式记录它. 此外,该分析通常可以使用标准的Linux命令行工具执行,例如 grep
, sed
, or awk
.
实际上,PHP日志文件是一个非常强大的工具,可以带来巨大的好处.
Code on GitHub: http://github.com/isanosyan/toptal-blog-logs-post-example
下面简要介绍一些比较常用的*nix命令行工具,您需要熟悉这些工具来读取和操作日志文件.
cat
也许是最简单的一个. 它将整个文件打印到输出流. 例如,下面的命令将打印 logfile1
to the console:
cat logfile1
>
字符允许用户重定向输出,例如重定向到另一个文件. 以写模式打开目标流(这意味着擦除目标内容). 的内容是这样替换的 tmpfile
with contents of logfile1
:
cat logfile1 > tmpfile
>>
重定向输出并以追加模式打开目标流. 目标文件的当前内容将被保留,新的行将被添加到底部. This will append logfile1
contents to tmpfile
:
cat logfile1 >> tmpfile
grep
按某些模式过滤文件并只打印匹配的行. 命令将只打印 logfile1
containing Bingo
message:
grep Bingo logfile1
cut
打印单列的内容(按编号从1开始). 默认情况下,搜索制表符作为列之间的分隔符. 例如,如果文件中充满了格式的时间戳 YYYY-MM-DD HH:MM:SS
,这将允许您只打印年份:
cut -d"-" -f1 logfile1
head
只显示文件的第一行
tail
只显示文件的最后几行
sort
对输出中的行进行排序
uniq
过滤掉重复的行
wc
计数单词(或行) -l
flag)
|
(i.e.(“pipe”符号)将一个命令的输出作为下一个命令的输入. Pipe对于组合命令非常方便. 例如,下面是我们如何在一组时间戳中找到2014年的月份:
grep - e "^2014" logfile1 | cut -d"-" -f2 | sort | uniq
这里,我们首先根据正则表达式“从2014年开始”匹配行,然后截断月份. 最后,我们使用of的组合 sort
and uniq
只打印一次.
世界级的文章,每周发一次.
世界级的文章,每周发一次.
Join the Toptal® community.