Apache log4php 2.3.0 集成使用实战详解

基于Ubuntu 22.04实测log4php 2.3.0的源码引入、Composer引入、properties与XML配置实战及已知缺陷规避。

Apache log4phplog4phpApachePHP日志2026/10/15

Apache log4php 2.3.0 集成使用实战详解

目标:在 Ubuntu 22.04 + PHP 8.1 上完成 log4php 2.3.0 的获取与引入(源码 include 与 Composer 两种方式)、properties 配置实战、配置级过滤与滚动文件压测、XML 配置与 MDC/NDC 输出,并沉淀两个已知缺陷的实测规避。全程命令与回显已在 Ubuntu 22.04 实测通过。再次强调:项目已进入 Apache Attic 退役,终版 2.3.0,仅建议存量维护参考,新项目请选 Monolog 等替代。

一、环境信息

项目说明
操作系统Ubuntu 22.04.5 LTS
架构x86_64
运行时PHP 8.1.2-1ubuntu2.26(php-cli)
组件版本Apache log4php 2.3.0(终版,源码归档)
目录源码 /opt/log4php/apache-log4php-2.3.0,示例 /opt/log4php/demo,Composer 工程 /opt/log4php/composer-lab

逐项说明:root 账号执行;PHP 为系统发行版仓库安装的 8.1 系列;log4php 无强制扩展依赖,但本机装了 php-xml(XML 配置解析)与 php-zip(Composer 解包),依赖清单见第二节。

二、前置依赖

apt-get update
DEBIAN_FRONTEND=noninteractive apt-get install -y php-cli php-xml php-mbstring wget

实测回显尾部:

Processing triggers for php8.1-cli (8.1.2-1ubuntu2.26) ...
PHP 8.1.2-1ubuntu2.26 (cli) (built: Sep  2 2026 12:42:10) (NTS)
Copyright (c) The PHP Group

逐项解说:php-cli 提供命令行解释器;php-xml 是 XML 配置解析依赖;php-mbstring 供字符串处理;log4php 本体无强制扩展。Composer 方式还需 php-zip 与 unzip(见 5.2 节实测)。验证版本:

php --version | head -2

实测回显:

PHP 8.1.2-1ubuntu2.26 (cli) (built: Sep  2 2026 12:42:10) (NTS)
Copyright (c) The PHP Group

三、源码获取与引入

3.1 下载归档源码

log4php 已退役,2.3.0 只能从 Apache 归档目录获取:

mkdir -p /opt/log4php && cd /opt/log4php
wget https://archive.apache.org/dist/logging/log4php/2.3.0/apache-log4php-2.3.0-src.tar.gz
tar -xzf apache-log4php-2.3.0-src.tar.gz
ls -la /opt/log4php && ls apache-log4php-2.3.0/

实测回显:

total 1836
drwxr-xr-x 2 root root    4096 Oct 14 16:47 .
drwxr-xr-x 1 root root    4096 Oct 14 16:47 ..
-rw-r--r-- 1 root root 1871161 Jan 31  2013 apache-log4php-2.3.0-src.tar.gz
CHANGELOG
INSTALL
LICENSE
NOTICE
README
apidocs
build.xml
package-config.php
package.php
pom.xml
site
src

逐项解说:源码包 1871161 字节、时间戳 2013 年 1 月,直观印证终版年代;src/main/php/Logger.php 是唯一入口文件。

3.2 直接 include 引入

require_once '/opt/log4php/apache-log4php-2.3.0/src/main/php/Logger.php';

逐项解说:Logger.php 内置自动加载(LoggerAutoloader),引入这一个文件即可使用全部类;生产工程更推荐 Composer 方式(第五节),include 方式适合快速验证与存量老工程。

四、第一个日志程序(properties 配置)

4.1 配置文件

log4php.rootLogger = DEBUG, console
log4php.appender.console = LoggerAppenderConsole
log4php.appender.console.layout = LoggerLayoutPattern
log4php.appender.console.layout.conversionPattern = %d{Y-m-d H:i:s.u} [%t] %-5p %c - %m%n

逐项解说:root 级别 DEBUG 挂 console;Appender 类名直接写短名(LoggerAppenderConsole);%d{Y-m-d H:i:s.u} 输出微秒时间戳,%t 输出进程号,%-5p 补齐级别列,%c 输出 Logger 名,%m 消息,%n 换行。

4.2 示例代码 demo1.php

<?php
// 文件:/opt/log4php/demo/demo1.php
require_once '/opt/log4php/apache-log4php-2.3.0/src/main/php/Logger.php';

Logger::configure('/opt/log4php/demo/log4php.properties');

$logger = Logger::getLogger('OrderService');
$daoLogger = Logger::getLogger('OrderService.Dao');

$daoLogger->setLevel(LoggerLevel::getLevelInfo());

$logger->debug('调试信息:订单服务启动参数加载完成');
$logger->info('普通信息:用户 admin 于 2026-10-15 登录系统');
$daoLogger->debug('这条 DEBUG 会被 Dao 子 logger 的 INFO 级别过滤掉');
$daoLogger->info('Dao 层 INFO:查询订单 1001 成功');

运行:

php /opt/log4php/demo/demo1.php

实测输出:

2026-10-14 16:55:10.444 [8364] DEBUG OrderService - 调试信息:订单服务启动参数加载完成
2026-10-14 16:55:10.448 [8364] INFO  OrderService - 普通信息:用户 admin 于 2026-10-15 登录系统
2026-10-14 16:55:10.448 [8364] DEBUG OrderService - 这条 DEBUG 会被 Dao 子 logger 的 INFO 级别过滤掉
2026-10-14 16:55:10.448 [8364] INFO  OrderService.Dao - Dao 层 INFO:查询订单 1001 成功

逐项解说与缺陷定位:第 3 行异常——按 log4j 语义 daoLogger 设为 INFO 后其 DEBUG 应被完全拦截,但实测消息输出了,且 Logger 名显示为父 Logger OrderService。查看 2.3.0 源码 Logger::log() 定位根因:

public function log(LoggerLevel $level, $message, $throwable = null) {
    if($this->isEnabledFor($level)) {
        $event = new LoggerLoggingEvent($this->fqcn, $this, $level, $message, null, $throwable);
        $this->callAppenders($event);
    }
    if(isset($this->parent) && $this->getAdditivity()) {
        if (isset($event)) {
            $this->parent->logEvent($event);
        } else {
            $this->parent->log($level, $message, $throwable);
        }
    }
}

逐项解说:additivity 转发分支写在 isEnabledFor 判断之外——DEBUG 被本 Logger 过滤($event 未创建)后,代码仍进入转发分支,调用 $this->parent->log() 重建事件,消息便以父 Logger 的名义再走一遍父链级别判断(父是 DEBUG 所以放行)。规避方案二选一:给该 Logger 配置 additivity=false,或改用配置文件定义级别并关闭叠加(下一节实测)。

五、进阶实战

5.1 配置级过滤与滚动文件压测

配置文件 log4php2.properties:

log4php.rootLogger = DEBUG, console, rolling
log4php.logger.OrderService.Dao = INFO
log4php.additivity.OrderService.Dao = false
log4php.appender.console = LoggerAppenderConsole
log4php.appender.console.layout = LoggerLayoutPattern
log4php.appender.console.layout.conversionPattern = %d{Y-m-d H:i:s.u} [%t] %-5p %c - %m%n
log4php.appender.rolling = LoggerAppenderRollingFile
log4php.appender.rolling.file = /opt/log4php/demo/roll.log
log4php.appender.rolling.maxFileSize = 10KB
log4php.appender.rolling.maxBackupIndex = 3
log4php.appender.rolling.append = true
log4php.appender.rolling.layout = LoggerLayoutPattern
log4php.appender.rolling.layout.conversionPattern = %d [%t] %-5p %c - %m%n

逐项解说:log4php.logger.OrderService.Dao = INFO 在配置层定级;关键规避行 log4php.additivity.OrderService.Dao = false 关闭叠加,堵住第四节的过滤泄漏;rolling 是 LoggerAppenderRollingFile,10KB 触发滚动、保留 3 份。

💡 技术拓展:同步与异步 Appender——log4php 2.3.0 全部 Appender 同步写出,没有异步实现;写文件的高峰请求会承担 IO 抖动。现代替代方案(Monolog + 异步 Handler 或集中采集)从根本上解决该问题。本文按同步 Appender 实测,压测数据可作为存量系统容量评估参考。

demo2.php:

<?php
require_once '/opt/log4php/apache-log4php-2.3.0/src/main/php/Logger.php';

Logger::configure('/opt/log4php/demo/log4php2.properties');

$logger = Logger::getLogger('OrderService');
$daoLogger = Logger::getLogger('OrderService.Dao');

for ($i = 1; $i <= 150; $i++) {
    $logger->info('滚动压测第 ' . $i . ' 条:订单 1001 状态更新为 PAID');
}
$daoLogger->debug('这条 DEBUG 会被配置级别的 Dao logger 过滤掉');
$daoLogger->info('Dao 层 INFO:查询订单 1001 成功');
echo '已写入 150 条 INFO 日志' . PHP_EOL;

运行并验证:

php /opt/log4php/demo/demo2.php
ls -la /opt/log4php/demo/roll.log*

实测输出(尾部):

2026-10-14 16:57:50.566 [8434] INFO  OrderService - 滚动压测第 150 条:订单 1001 状态更新为 PAID
已写入 150 条 INFO 日志
total 32
-rw-r--r-- 1 root root  6547 Oct 14 16:58 roll.log
-rw-r--r-- 1 root root 10295 Oct 14 16:58 roll.log.1

逐项解说:加 additivity=false 后,被过滤的 DEBUG 彻底消失(对比未加时会出现 DEBUG root 泄漏行,见 FAQ Q2);滚动生效——roll.log.1 约 10 KB 存前段内容,当前 roll.log 接续写入;maxBackupIndex=3 本次只滚 1 次故仅一个历史文件。

5.2 Composer 方式引入

下载 Composer 并安装 log4php:

export http_proxy=http://192.168.1.62:7897 https_proxy=http://192.168.1.62:7897
wget https://getcomposer.org/download/latest-stable/composer.phar -O /tmp/composer.phar
php /tmp/composer.phar --version
mkdir -p /opt/log4php/composer-lab && cd /opt/log4php/composer-lab
php /tmp/composer.phar require apache/log4php:2.3.0 --no-interaction

首次执行实测报错(排障过程):

In ZipDownloader.php line 81:

  The zip extension and unzip/7z commands are both missing, skipping.
  Your command-line PHP is using multiple ini files. Run `php --ini` to show
  them.

排障定位:composer 下载发行包后需要 PHP zip 扩展或系统 unzip 解包,环境两者皆缺。安装后重试:

DEBIAN_FRONTEND=noninteractive apt-get install -y php-zip unzip
php /tmp/composer.phar require apache/log4php:2.3.0 --no-interaction

实测回显:

  - Installing apache/log4php (2.3.0): Extracting archive
Package apache/log4php is abandoned, you should avoid using it. No replacement was suggested.
Generating autoload files
No security vulnerability advisories found.

逐项解说:包管理器层面再次确认项目退役(abandoned 标记);autoload 生成 vendor/autoload.php。

Composer 工程示例 comdemo.php(PHP 数组配置,第三种配置格式实测):

<?php
require_once __DIR__ . '/vendor/autoload.php';

Logger::configure(array(
    'rootLogger' => array(
        'level' => 'debug',
        'appenders' => array('console'),
    ),
    'appenders' => array(
        'console' => array(
            'class' => 'LoggerAppenderConsole',
            'layout' => array(
                'class' => 'LoggerLayoutPattern',
                'params' => array(
                    'conversionPattern' => '%d{Y-m-d H:i:s} [%t] %-5p %c - %m%n',
                ),
            ),
        ),
    ),
));

$logger = Logger::getLogger('ComposerDemo');
$logger->info('composer 方式引入 log4php 成功');
php /opt/log4php/composer-lab/comdemo.php

实测输出:

2026-10-14 17:00:13 [8854] INFO  ComposerDemo - composer 方式引入 log4php 成功

逐项解说:vendor/autoload.php 引入后 Logger 类直接可用;数组配置无需外部文件,配置即代码,适合框架内嵌场景;rootLogger.level 与 appenders 数组键与 properties 语义一致。

5.3 XML 配置与 MDC/NDC 输出

配置 log4php.xml:

<?xml version="1.0" encoding="UTF-8"?>
<configuration xmlns="http://logging.apache.org/log4php/">
  <appender name="jsonAppender" class="LoggerAppenderConsole">
    <layout class="LoggerLayoutPattern">
      <param name="conversionPattern" value="{&quot;time&quot;:&quot;%d{Y-m-d H:i:s}&quot;,&quot;level&quot;:&quot;%p&quot;,&quot;logger&quot;:&quot;%c&quot;,&quot;userId&quot;:&quot;%X{userId}&quot;,&quot;message&quot;:&quot;%m&quot;}%n" />
    </layout>
  </appender>
  <appender name="xmlAppender" class="LoggerAppenderConsole">
    <layout class="LoggerLayoutXml" />
  </appender>
  <logger name="JsonDemo">
    <level value="info" />
    <appender_ref ref="jsonAppender" />
  </logger>
  <logger name="XmlDemo" additivity="false">
    <level value="info" />
    <appender_ref ref="xmlAppender" />
  </logger>
  <root>
    <level value="warn" />
    <appender_ref ref="xmlAppender" />
  </root>
</configuration>

逐项解说:jsonAppender 用 PatternLayout 手拼 JSON 行(2.3.0 无内置 JSON 布局,实测配置 LoggerLayoutJson 会告警回退)," 是 XML 内的双引号转义,%X{userId} 取 MDC;XmlDemo 用 LoggerLayoutXml 且 additivity=false 防重复输出。

demo3.php:

<?php
require_once '/opt/log4php/apache-log4php-2.3.0/src/main/php/Logger.php';

Logger::configure('/opt/log4php/demo/log4php.xml');

LoggerMDC::put('userId', 'admin');
LoggerNDC::push('web-request-1001');

$jsonLogger = Logger::getLogger('JsonDemo');
$xmlLogger = Logger::getLogger('XmlDemo');

$jsonLogger->info('JSON 布局输出:订单 1001 已支付');
$jsonLogger->warn('JSON 布局输出:库存低于安全阈值');
$xmlLogger->error('XML 布局输出:消息队列连接超时');

LoggerNDC::pop();
LoggerMDC::remove('userId');
php /opt/log4php/demo/demo3.php

实测输出(节选):

{"time":"2026-10-14 16:58:54","level":"INFO","logger":"JsonDemo","userId":"admin","message":"JSON 布局输出:订单 1001 已支付"}
{"time":"2026-10-14 16:58:54","level":"WARN","logger":"JsonDemo","userId":"admin","message":"JSON 布局输出:库存低于安全阈值"}
<log4php:eventSet xmlns:log4php="http://logging.apache.org/log4php/" version="0.3" includesLocationInfo="true">
<log4php:event logger="XmlDemo" level="ERROR" thread="8466" timestamp="1791997134193">
<log4php:message><![CDATA[XML 布局输出:消息队列连接超时]]></log4php:message>
<log4php:NDC><![CDATA[<![CDATA[web-request-1001]]>]]></log4php:NDC>
<log4php:properties>
<log4php:data name="userId" value="admin" />
</log4php:properties>
<log4php:locationInfo class="main" file="/opt/log4php/demo/demo3.php" line="14" method="main" />
</log4php:event>
</log4php:eventSet>

逐项解说:PatternLayout 手拼 JSON 行合法且 %X{userId} 成功取到 MDC 值 admin;LoggerLayoutXml 输出事件集包装(eventSet),单事件含消息 CDATA、NDC 栈、MDC properties 与位置信息——排障上下文完整;XmlDemo 的事件只出现一次(additivity=false 生效)。

六、常见问题(FAQ)

Q1:直接 include 和 Composer 两种引入方式怎么选? A:存量老工程、只想快速验证——include Logger.php 一个文件即可(第三节实测);规范工程用 Composer(vendor/autoload.php + abandoned 风险告知)。两者实测都通过,区别在依赖管理与升级路径。

Q2:为什么设置了子 Logger 级别,DEBUG 消息还是漏出来了? A:2.3.0 缺陷——Logger::log() 的 additivity 转发写在级别判断之外,被过滤消息会以父 Logger 名义重建事件输出。实测两种复现:运行时 setLevel(消息挂在父名下)与配置级定级(消息挂在 root 名下)。规避:给该 logger 配 additivity=false(实测彻底消除),这是存量维护必须知道的坑。

Q3:Composer 安装报 The zip extension and unzip/7z commands are both missing 怎么办? A:装 php-zip 或系统 unzip(实测 apt install php-zip unzip 后成功解包)。该错误发生在下载完成后的解包阶段,与镜像源无关。

Q4:Packagist 上的 abandoned 警告需要处理吗? A:需要严肃对待。实测输出 Package apache/log4php is abandoned, you should avoid using it. No replacement was suggested.——包作者与社区均不再维护。存量系统应制定迁移计划(Monolog 等替代),并避免在公网暴露任何使用该库解析外部输入的路径。

Q5:XML 配置里如何写双引号? A:XML 属性值内的双引号用 " 转义(实测 JSON 行模板即用此写法),XML 元素文本中可直接写。转义遗漏会导致 XML 解析失败,报错行号即配置行号。

Q6:maxFileSize 支持哪些单位?默认多大? A:支持 KB/MB 后缀(实测 10KB 生效),不带后缀按字节;未配置时默认 10MB。maxBackupIndex 默认 1。滚动文件命名为 roll.log.1、roll.log.2 递增。

Q7:MDC 在 Web 请求间会串吗? A:MDC 是按进程静态存储,PHP-FPM 模式下每个请求一个进程实例、请求结束进程内存回收,不会跨请求残留(实测 CLI 中手动 remove 演示)。长驻 Swoole 等模式则必须请求结束时清理,否则串数据。

Q8:日志里的时间戳格式怎么定制微秒? A:conversionPattern 用 %d{Y-m-d H:i:s.u},u 即微秒(实测输出 2026-10-14 16:55:10.444)。注意与 date() 函数的格式符一致,不带花括号时为默认格式。

Q9:XML 配置的 additivity="false" 和 properties 的 additivity 键效果一样吗? A:语义一致,实测两者都能消除重复/泄漏输出;properties 键为 log4php.additivity.<logger名> = false,XML 为 logger 元素属性。存量系统迁移时两种写法等价替换。

Q10:想只放行某个级别的日志怎么办? A:Appender 下挂 Filter 链:LoggerFilterLevelMatch(levelToMatch=warn、acceptOnMatch=true)+ LoggerFilterDenyAll 收口。实测三条 INFO/WARN/ERROR 仅 WARN 输出。注意 acceptOnMatch=false 时语义反转,WARN 反而被拒。

七、参考链接

  1. log4php 归档发行目录:https://archive.apache.org/dist/logging/log4php/2.3.0/
  2. Apache Attic 项目名录(log4php 退役页):https://attic.apache.org/projects/log4php.html
  3. Composer 官网与安装:https://getcomposer.org/
  4. Monolog(推荐替代):https://github.com/Seldaek/monolog
  5. PSR-3 Logger Interface:https://www.php-fig.org/psr/psr-3/
  6. PHP 8.1 官方文档:https://www.php.net/releases/8.1/