基于责任链与策略模式的轻量级PHP日志库设计

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: 项目日志乱成一团,bug 时好时坏,服务器问题难以复现?我写了个 PHP 日志系统,第一时间发现问题,避免跑路。实现了责任链模式+策略模式,让日志存储更灵活,支持多种输出方式。

你有没有遇到过这样的情况:代码被各种人拷来拷去,散落在不同的服务器上,它们运行着同样的代码,却各有各的脾气。A 服务器风平浪静,B 服务器炸成烟花,C 服务器似乎活着但又不太对劲……而你,每天都在面对来自四面八方的“XX功能炸了”“接口500了”“部署完直接寄了”的灵魂拷问。

最离谱的是,它们都会从你这同步最新的代码,但到底是代码问题还是服务器环境问题,你根本没办法第一时间知道。于是,问题就变成了:如何把这些分散的错误日志规范地收集起来,好让我在别人冲进来质问之前,提前找到问题所在?

于是,我不情不愿地搞了个日志收集方案,顺便写了个 Golang 脚本来专门接收远程日志。虽然我并不想管这些破事,但现实就是,我要是再不解决,估计下次见到我,老板已经换了个人。

先把实现好的仓库放在这里:点击前往GitHub

需求分析与设计目标

在开发PHP工具包时,我需要一个满足以下特性的日志组件:

  1. 多输出渠道:同时支持文件、控制台、远程API等输出方式
  2. 格式解耦:允许不同输出使用不同格式(如开发环境用文本,生产环境用JSON)
  3. 低耦合扩展:新增处理器或格式化器时无需修改现有代码

核心架构设计

1. 接口先行:定义规范

// 日志处理器
interface LogHandlerInterface {
   
    public function handle(
        string $level, 
        string $title,
        string $message,
        array $context = []
    ): void;
}

// 日志格式化
interface LogFormatterInterface {
   
    public function format(
        string $level,
        string $message,
        array $context = []
    ): string;
}

通过接口隔离了「日志处理」与「格式转换」两个关注点,为后续扩展打下基础。

2. 责任链模式实现多路输出

class Logger {
   
    private array $handlers;

    public function __construct(array $handlers) {
   
        $this->handlers = $handlers;
    }

    public function log(...$params) {
   
        foreach ($this->handlers as $handler) {
   
            $handler->handle(...$params);
        }
    }
}

每个处理器独立处理日志,形成处理流水线。典型处理器实现:

文件处理器核心逻辑:

class FileHandler implements LogHandlerInterface {
   
    // 自动滚动日志文件
    private function rotateLogFiles(string $logFile) {
   
        $index = 1;
        while (file_exists("{$logFile}.{$index}")) {
   
            $index++;
        }
        rename($logFile, "{$logFile}.{$index}");
    }
}

远程API处理器:

class RemoteApiHandler implements LogHandlerInterface {
   
    public function handle(...$params) {
   
        // 实际应使用异步HTTP客户端
        HttpClient::post($this->endpoint, $formattedData);
    }
}

3. 策略模式实现格式切换

通过注入不同的格式化器实现格式策略:

// 文本格式化
class DefaultFormatter implements LogFormatterInterface {
   
    public function format(...) {
   
        return "[{$time}] {$level}: {$message} " . json_encode($context);
    }
}

// JSON格式化
class JsonFormatter implements LogFormatterInterface {
   
    public function format(...) {
   
        return json_encode([
            'timestamp' => microtime(true),
            'level' => $level,
            // ...其他字段
        ]);
    }
}

在处理器中组合使用:

$logger = new Logger([
    new FileHandler('app.log'),
    new ConsoleHandler(),
    new RemoteApiHandler('https://log-server.com/api')
]);

关键实现细节

1. 文件处理优化

  • 自动分割:通过maxFileSize​控制单个文件大小
  • 滚动策略:采用file.log.1​递增命名方式,避免覆盖历史日志
  • 目录创建:在首次写入时自动创建日志目录

2. 远程传输设计

  • 格式要求:强制使用JSON格式确保数据可解析
  • 头信息配置:预设Content-Type: application/json
  • 解耦网络层:将具体HTTP实现隔离在处理器之外

3. 异常处理原则

  • 静默失败:单个处理器异常不影响其他处理器执行
  • 开发友好:控制台处理器直接输出原始错误信息
  • 生产安全:文件处理器避免抛出致命错误

扩展实践示例

场景:添加企业微信通知

  1. 实现新处理器:
class WeChatHandler implements LogHandlerInterface {
   
    public function handle(...) {
   
        $markdown = "## {$title}\n**级别**: {$level}\n".$this->formatContext($context);
        $this->sendToWeChat($markdown);
    }
}
  1. 组合使用:
$logger = new Logger([
    new FileHandler(...),
    new WeChatHandler(WEBHOOK_URL),
]);

$logger->log(...)

模式应用总结

  1. 责任链模式的价值

    • 符合单一职责原则:每个处理器只关注自己的输出方式
    • 动态组合:运行时自由搭配不同处理器
    • 可扩展性:新增处理器无需修改核心逻辑
  2. 策略模式的优势

    • 格式转换与业务逻辑解耦
    • 支持不同场景的格式策略快速切换
    • 便于进行格式验证和单元测试

这种设计模式组合特别适合需要灵活扩展的日志系统,在保持核心稳定的同时,为各种定制需求留出了足够的扩展空间。

反正这玩意儿是搞完了。现在项目的日志终于变得清爽了一点,该输出到文件的就乖乖写文件,该打印到控制台的就老实滚屏,至于那些紧急的、可能导致我或者老板跑路的错误,就直接远程通知到我的服务器上。这样一来,我至少能在被质问之前,先假装冷静地说:“哦,这个问题我已经在看了。”

今天先这样,日志收集算是有个着落了。明天再搞个 Go 小脚本,把这些错误信息整理整理,毕竟光收集还不够,还得方便查看,不然到时候一堆日志堆在那,和没收集有什么区别?算了,明天的事就留给明天的自己头疼吧。

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
11天前
|
PHP
基于PHP开发的资源库系统源码
基于PHP开发的资源库系统源码
37 13
|
2月前
|
存储 前端开发 数据可视化
Grafana Loki,轻量级日志系统
本文介绍了基于Grafana、Loki和Alloy构建的轻量级日志系统。Loki是一个由Grafana Labs开发的日志聚合系统,具备高可用性和多租户支持,专注于日志而非指标,通过标签索引而非内容索引实现高效存储。Alloy则是用于收集和转发日志至Loki的强大工具。文章详细描述了系统的架构、组件及其工作流程,并提供了快速搭建指南,包括准备步骤、部署命令及验证方法。此外,还展示了如何使用Grafana查看日志,以及一些基本的LogQL查询示例。最后,作者探讨了Loki架构的独特之处,提出了“巨型单体模块化”的概念,即一个应用既可单体部署也可分布式部署,整体协同实现全部功能。
838 69
Grafana Loki,轻量级日志系统
|
11天前
|
安全 搜索推荐 PHP
一款轻量级的PHP链接发布页面源码
一款轻量级的PHP链接发布页面源码
43 6
|
16天前
|
Linux PHP iOS开发
PHP-Raylib 视 频 游 戏 编 程 库
php-raylib 是基于 PHP-FFI 绑定的 raylib-v5.5 游戏开发库,让 PHP 开发者轻松实现视频游戏编程。相比仅支持 4.+ 版本的原库,本项目适配最新 5.5 版本,并提供友好文档与示例代码(如窗口初始化、文本绘制等)。支持 PHP 7.4+ 和多平台(Windows、Linux、macOS),可通过 Composer 快速安装。欢迎贡献和完善![查看文档](http://raylib.kllxs.top/) 或访问仓库(GitHub/Gitee/Gitcode)。
PHP-Raylib 视 频 游 戏 编 程 库
|
30天前
|
存储 监控 算法
基于 PHP 语言的滑动窗口频率统计算法在公司局域网监控电脑日志分析中的应用研究
在当代企业网络架构中,公司局域网监控电脑系统需实时处理海量终端设备产生的连接日志。每台设备平均每分钟生成 3 至 5 条网络请求记录,这对监控系统的数据处理能力提出了极高要求。传统关系型数据库在应对这种高频写入场景时,性能往往难以令人满意。故而,引入特定的内存数据结构与优化算法成为必然选择。
29 3
|
1月前
|
存储 JSON Go
PHP 日志系统的最佳搭档:一个 Go 写的远程日志收集服务
为了不再 SSH 上去翻日志,我写了个 Go 小脚本,用来接收远程日志。PHP 负责记录日志,Go 负责存储和展示,按天存储、支持 API 访问、可远程管理,终于能第一时间知道项目炸了。
45 10
|
2月前
|
监控 定位技术 PHP
使用PHP接入纯真IP库:实现IP地址地理位置查询
本文介绍了如何使用PHP接入纯真IP库(QQWry),实现IP地址的地理位置查询。纯真IP库是一个轻量级的IP数据库,数据格式简单,查询速度快,适合Web应用。首先,下载并放置`QQWry.dat`文件到项目目录。接着,通过编写PHP类解析该文件,实现IP查询功能。最后,提供了一个完整的案例演示,展示如何查询IP地址对应的国家和地区信息。该工具适用于用户地理位置分析、访问日志分析和风控系统等场景,具有轻量级、查询速度快、数据更新方便等优点。
|
4月前
|
PHP 计算机视觉 UED
Buzz库:PHP图像处理中的异步图像下载和保存
Buzz库:PHP图像处理中的异步图像下载和保存
|
5月前
|
SQL 安全 PHP
PHP开发中防止SQL注入的方法,包括使用参数化查询、对用户输入进行过滤和验证、使用安全的框架和库等,旨在帮助开发者有效应对SQL注入这一常见安全威胁,保障应用安全
本文深入探讨了PHP开发中防止SQL注入的方法,包括使用参数化查询、对用户输入进行过滤和验证、使用安全的框架和库等,旨在帮助开发者有效应对SQL注入这一常见安全威胁,保障应用安全。
174 4
|
6月前
|
Shell 网络安全 数据安全/隐私保护
suuk-s.php.jpg-python 库劫持
suuk-s.php.jpg-python 库劫持
47 0

热门文章

最新文章

下一篇
oss创建bucket