言午月月鸟
编程,带娃以及思考人生
首页
编程
带娃
思考人生
编程画图秀
PHP系统解析-日志
dingusxp
2503
## 为什么? 你是否有碰到过这种情况: 系统就没有记录日志或者只有少量随性记录的不规范日志。 平时也没人关注,在系统出问题的时候才去查。一查发现缺少某些关键信息,无法定位,得先上一个版本加上日志才能继续。 一个线上小bug要搞上一个小时甚至半天才能解决,变成了事故。 是否可以更及时地发现、定位和解决问题? 一个重要支撑就是,重视并规划好日志模块。 ## 是什么? > 一种记录系统运行过程中各种状态和数据的信息,可以用来回溯系统运行时刻的场景。 主要用途: - 系统问题诊断和辅助监控的必备良药,为系统本身的稳定保驾护航; - 打通日志记录,采集,分析整个链条打通后,也可用于支撑业务数据的分析。 日志模块 在系统中通常应该具备 `稳定性好,实时性高、性能耗损低` 的特性。 ## 怎么做? 主要就是 “记什么”、“怎么记”、“如何用“ 的问题。下面展开聊一聊: ### 记什么 内容上通常包含但不限于: 系统标识、时间点、日志类型/等级、追踪ID、运行上下文、自定义文本消息。 考虑到系统日志的记录不属于业务主流程,我们应该在保证信息完备的前提下,尽量减少记录日志带来的系统性能损耗。 一个实践建议是,将日志分类记录: - 请求日志(必须)。每个请求有且仅有一条,用于复现请求当时场景。包含但不限于以下信息: 链路追踪ID,客户端IP,服务标识,请求参数,返回结果,请求耗时,所有下游服务调用的 服务连接信息、请求参数、返回结果、请求耗时(← 以上信息通常可以由框架自动实现),以及 业务关键节点信息(结合业务实际主动添加)。 - 错误日志(必须)。用于监控告警辅助的信息采集,所以必须包含告警等级信息(可以直接复用日志级别)。 - 调试日志(可选)。在生成环境默认不应该打开。但可以通过某个开关控制(请求参数 或 特定API触发),方便临时线上定位问题。 - 其它辅助日志(可选)。如用于某些特定需求的BI数据分析的辅助信息源。 说明:脚本类的程序日志不在此讨论范围内。脚本可以当做独立程序,按需设计对应其打印方案。 ### 怎么记 主要包括: 日志类的实现 与 日志的存储。 #### 日志类接口 [PSR-3: Logger Interface](https://www.php-fig.org/psr/psr-3/) (← 点击查看详情) 定义了PHP日志类(Logger)的规范,主要是 日志等级 和 日志记录类接口。**强烈建议**实现的日志类遵循该规范。 本文也整理了一个简要介绍,见附页: [PSR-3标准简要介绍](https://www.wolai.com/iJcQ192bAQoCZConK1CqC5) 为了在系统任意环节都拥有追踪记录的能力,日志类通常会封装出全局可用的调用方法(通过 函数、类的静态方法 直接记录或者获取到类实例)。 **一个注意点:** 很多框架会在捕捉系统异常/错误时记录日志。因此务必确保自己实现的日志记录方法足够稳定可靠,如果日志方法本身也触发了异常/错误的话,可能就会导致无限递归了。 #### 日志记录 **日志级别定义** 一般参照PSR规范中的日志级别定义即可。 **日志格式** 要方便人工查看和日志分析系统的解析。通常采用 特定符号(如tab、短横线) 分割基本信息,复杂对象信息则序列化(通常query string格式或者 json格式)。 **日志ID 的生成与继承** 每个请求的日志应该有相同的ID(如 requestId),以便相互串联,辅助判断。 requestId 用现成的uuid,也可以自定义。如: ```PHP define('BUSINESS_CODE', '0001'); function generateLogId() { $id = intval(1000 * microtime(true)).BUSINESS_CODE.mt_rand(100000000, 999999999); return base_convert($id, 10, 36); } ``` 另外为了方便复杂调用链中日志的串联,在各层级调用上,应该继承 源调用方的 请求ID,作为 追踪ID(traceId)。当然也可以用成熟的链路追踪方案,如 zipkin 等。 **请求日志打印** 请求日志 通常一个请求只有一条。在数据流中,只设置 key value 信息,在请求结束时,再写入磁盘。 如: ```PHP class Logger { // 日志参数 private static $_logParam = array(); public static function setParam($key, $value = null, $isMulti = false) { static $_keyPostfixIndex = array(); // 支持参数数组,批量设置 if (is_array($key)) { foreach ($key as $k => $v) { self::setParam($k, $v); } return; } // 自增 key 后缀 if ($isMulti ) { $idx = isset($_keyPostfixIndex[$key]) ? $_keyPostfixIndex[$key] + 1 : 1; $_keyPostfixIndex[$key] = $idx; $key = $key.'_'.$idx; } self::$_logParam[$key] = $value; } public static function logRequest() { $logger = Container::get(LoggerInterface::class); $logger->info("OK", self::$_logParam); } } ``` **注意:** 如果只是简单把最后打印日志的语句放在类似 `$controller->doAction` 结尾执行,则当代码中有触发PHP Fatal错误,未捕获的异常,终止语句(exit / die)时,会直接终止请求,导致日志丢失。因此建议把请求日志的打印通过 `register_shutdown_function `提前注册。 即: ```PHP register_shutdown_function([Logger::class, 'logRequest']); ``` #### 日志存储 日志 通常记录为文本文件;当然如果愿意也可以 扔到队列、UDP服务 或 存到MongoDB 等。 此处主要讨论文件存储方式。 开发上需要注意: 1. 日志目录要规范(通常按 应用名、模块、类型/日志级别 分),并设置好权限; 2. 大量的文件IO,有一定性能耗损。有些日志模块,会使用C实现PHP扩展,做一下写缓冲策略; 在运维上通常会配合: 1. 按日期切分,避免大文件处理。有些类库本身已经提供了自动按日期存储的能力;如果没有,可以借助定时任务和shell命令实现; 2. 磁盘监控。无直接关系,但 日志文件 往往是磁盘空间持续增长的一个主要原因。定期压缩或删除历史日志。 #### 成熟类库 [monolog](https://github.com/Seldaek/monolog) 其特点是:1) 多实例支持,可以配置不同的日志使用场景;2)自定义日志格式;3)配置日志通道(输出到 文件、数据库、MongoDB等)。 了解更多,可以参考 [这篇文章](https://www.jianshu.com/p/b99dc5c3b760) ### 如何用 单机实时追查,可以直接服务器使用 grep/tail/awk 等常用文本命令操作。 **一个注意点:** 避免直接使用 cat 之类内存占用巨大的命令,而应该多使用支持 流式管道 的命令。 而集群部署时,日志可能落在任意一台机器,直接在服务器查询的方式就比较麻烦了。这时通常先把日志采集集中起来再统一分析。 其中比较成熟的方案是 ELK套件。 ### 拓展知识 linux syslog日志协议
粤ICP备19051469号-1
Copyright©dingusxp.com - All Rights Reserved
Template by
OS Templates