Apache Chainsaw 2.1.0 快速入门与集成示例

Apache Chainsaw 2.1.0 快速入门实测:五分钟加载真实日志文件、实时追尾验证、无图形 Java 集成与 UDP 网络收发示例,全部命令与输出均来自真实环境。

Apache ChainsawApache Chainsaw2026/10/15

Apache Chainsaw 2.1.0 快速入门与集成示例

目标:从零启动 Apache Chainsaw 2.1.0,加载一份真实应用日志并验证实时追尾;同时给出无图形环境下的 Java 集成示例(LogFilePatternReceiver 解析、UDP 网络收发),全部实测通过。

一、环境信息

项目值
操作系统Ubuntu 22.04(x86_64)
实测服务器chainsaw-ubuntu-01(实验专用环境)
Java 运行时OpenJDK 17.0.20.1(openjdk-17-jdk)
Chainsaw 版本Apache Chainsaw 2.1.0(standalone 包,2021-06-12 发布)
显示环境Xvfb :99(1280x1024x24 虚拟显示)
演示日志/root/chainsaw-demo/app.log(模拟应用服务器日志,含中文与异常堆栈)

Chainsaw 是 Log4j 生态的日志分析 GUI:以"接收器(Receiver)+ 事件表 + 过滤器/颜色规则"为核心模型,支持从文件、UDP、组播、Socket、数据库等来源实时接收 log4j 事件。版本说明:官方下载目录当前仅有 2.1.0 一个版本,项目处于低维护状态,本文即以 2.1.0 为准。

安装步骤(下载、校验、解压、JDK 兼容性处理)见《Ubuntu 22.04 安装 Apache Chainsaw 指南》,本文直接从已安装的 /opt/apache-chainsaw-2.1.0 出发。

二、准备演示环境

2.1 演示日志文件

一份贴近真实形态的应用日志:4 个 INFO/WARN/ERROR/DEBUG 级别样例、一条异常堆栈(首行 + 两个 \tat 帧):

mkdir -p /root/chainsaw-demo
cat > /root/chainsaw-demo/app.log <<'EOF'
2026-10-15 10:00:00,001 INFO  [com.example.service.UserService] 用户登录成功 userId=1001 ip=192.168.10.25
2026-10-15 10:00:02,417 WARN  [com.example.service.UserService] 登录重试次数达到阈值 userId=1002 retries=5
2026-10-15 10:00:05,090 ERROR [com.example.dao.UserDao] 查询用户失败: connection timeout after 3000ms
2026-10-15 10:00:07,552 DEBUG [com.example.dao.UserDao] SELECT * FROM t_user WHERE id=1001
2026-10-15 10:00:09,878 INFO  [com.example.web.OrderController] 订单提交成功 orderId=202610159876
2026-10-15 10:00:11,204 ERROR [com.example.web.OrderController] 提交订单异常
java.sql.SQLException: Connection refused
	at com.example.dao.UserDao.query(UserDao.java:88)
	at com.example.web.OrderController.submit(OrderController.java:41)
EOF

2.2 接收器配置文件

Chainsaw 自身不解析任意格式的文本日志,需要告诉它"字段怎么排布"。LogFilePatternReceiver 的格式模板与日志行一一对应:

cat > /root/chainsaw-demo/chainsaw-gui-config.xml <<'EOF'
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
  <plugin name="demoApp" class="org.apache.log4j.varia.LogFilePatternReceiver">
    <param name="logFormat" value="TIMESTAMP LEVEL [LOGGER] MESSAGE"/>
    <param name="timestampFormat" value="yyyy-MM-dd HH:mm:ss,SSS"/>
    <param name="fileURL" value="file:///root/chainsaw-demo/app.log"/>
    <param name="tailing" value="true"/>
  </plugin>
</log4j:configuration>
EOF

四个参数的含义:logFormat 是列模板(大写关键字为占位符,方括号等字面字符原样匹配);timestampFormat 是时间戳的 SimpleDateFormat;fileURL 支持本地 file:// 与远程 URL(VFS,含 sftp);tailing=true 表示读完整个文件后继续轮询文件尾部(轮询间隔 waitMillis 默认 2000 毫秒,实测从退出时自动保存的 receiver-config.xml 中确认)。

三、五分钟上手:启动并加载真实日志

3.1 启动命令

LogUI.main 会把第一个命令行参数当作 log4j 配置 URL 立即加载,其中的 <plugin> 注册为接收器——这是免交互启动的标准方式:

export DISPLAY=:99
export JAVA_OPTS='--add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.io=ALL-UNNAMED --add-opens java.base/java.text=ALL-UNNAMED --add-opens java.base/java.time=ALL-UNNAMED --add-opens java.base/java.math=ALL-UNNAMED --add-opens java.base/java.net=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED --add-opens java.base/java.util.concurrent=ALL-UNNAMED --add-opens java.desktop/java.awt=ALL-UNNAMED --add-opens java.desktop/java.awt.font=ALL-UNNAMED'
/opt/apache-chainsaw-2.1.0/bin/chainsaw file:///root/chainsaw-demo/chainsaw-gui-config.xml >/tmp/chainsaw-run.log 2>&1 &
sleep 40
ps aux | grep 'chainsaw[.]LogUI' | grep -v grep | wc -l

实测回显:

1

进程存活。若在普通桌面环境直接运行,去掉 DISPLAY 与 JAVA_OPTS(Java 11 无需 add-opens;Java 17 必需,原因见安装指南 FAQ)。

3.2 界面导览(截图验证)

对虚拟显示整屏截图:

DISPLAY=:99 import -window root /root/chainsaw-demo/shot1-main.png
ls -l /root/chainsaw-demo/shot1-main.png

实测回显:

-rw-r--r-- 1 root root 27342 Oct 15 15:03 /root/chainsaw-demo/shot1-main.png

截图证据:/tmp/commons-batch1-b2/chainsaw/ubuntu-shot1-main.png。界面自左至右、自上而下依次为:

区域实测内容
菜单栏File、View、Current tab、Connect to、Help
工具栏暂停、重新加载等操作按钮
标签页Welcome(欢迎/帮助)、Zeroconf(局域网发现)、file-/root/chainsaw-demo/app.log(日志标签页,以文件路径命名)、chainsaw-log(Chainsaw 自身运行日志)
事件表列:ID、TIMESTAMP、MARKER、LEVEL、LOGGER、MESSAGE、THROWABLE、THREAD;实测 6 行事件全部呈现
颜色规则WARN 行黄色底、ERROR 行红色底、DEBUG 行灰色底(默认颜色规则自动生效)
左侧面板Root Logger 树(可见 com 节点)
状态栏Looks like ZeroConf is available... WooHoo!、Filtered/Total: 6:6(过滤数/总数)

6 行日志、1 条异常堆栈全部正确入表,中英文混排渲染正常,无需任何手工点击。

3.3 实时追尾实测

保持 Chainsaw 运行,向被监听文件追加 3 行新日志:

cat >> /root/chainsaw-demo/app.log <<'EOF'
2026-10-15 10:05:21,330 WARN  [com.example.service.AuthService] 密码连续错误锁定账号 userId=1007
2026-10-15 10:05:24,871 ERROR [com.example.service.AuthService] 鉴权服务调用 LDAP 超时 elapsed=5000ms
2026-10-15 10:05:27,118 INFO  [com.example.service.AuthService] 账号解锁成功 userId=1007
EOF
sleep 8
DISPLAY=:99 import -window root /root/chainsaw-demo/shot2-tailing.png

截图证据:/tmp/commons-batch1-b2/chainsaw/ubuntu-shot2-tailing.png。实测结果:事件表从 6 行增长到 9 行,新增的 3 行(锁定账号 WARN 黄底、LDAP 超时 ERROR 红底、解锁成功 INFO)自动出现在表尾,状态栏计数变为 Filtered/Total: 9:9,并出现事件速率 1/s。从追加到入表约 2~4 秒(与默认 waitMillis=2000 轮询间隔吻合)。

3.4 正常退出与配置落盘

File 菜单实测包含:Load Chainsaw configuration、Open log4j XML-formatted file (xml or zip)…(Ctrl+O)、Open util.logging XML-formatted file…、Open remote log4j/util.logging XML-formatted file…、Save displayed events as…(Ctrl+S)、MRU…、Exit(Ctrl+X)。(菜单截图证据:/tmp/commons-batch1-b2/chainsaw/ubuntu-shot3-filemenu.png)

通过 File → Exit 正常退出后,$HOME/.chainsaw/ 下生成(实测文件清单):

chainsaw.settings.properties   receiver-config.xml
chainsaw.settings.xml          mru.xml
tab-settings.xml               Default.colors
file-%2Froot%2Fchainsaw-demo%2Fapp.log.xml
file-%2Froot%2Fchainsaw-demo%2Fapp.log.xml.colors
file-%2Froot%2Fchainsaw-demo%2Fapp.log.xml.properties
chainsaw-log.xml

其中 receiver-config.xml 即退出时自动保存的接收器配置(含 waitMillis=2000 等运行参数),下次启动可通过 File → Load Chainsaw configuration 或命令行参数复用。

💡 技术拓展:receiver-config.xml 采用 log4j 原生 XML 配置语法,<plugin> 元素正是 log4j 1.x 的插件机制。这意味着同一份配置既能给 GUI 用,也能在自研 Java 程序里经 PluginRegistry 复用,两端行为一致。

四、无图形方式集成:Java 程序调用接收器

服务器上没有显示环境、或需要在自研工具里复用 Chainsaw 的解析能力时,可直接调用其接收器 API。Chainsaw 的事件模型与 log4j 1.x 完全一致,Receiver.doPost() 的分发逻辑是:按事件 logger 名取 Logger,级别过阈值后 callAppenders() 沿层级传播——因此把自定义 Appender 挂到自建 LoggerRepository 的根上即可收集全部事件。

4.1 编译期准备

classpath 使用 standalone 包 repo/ 中的全部 jar(或 bin 包根目录的 10 个 jar)。注意编译与运行都要用 Java 9+(主 jar 字节码 major 53)。

4.2 完整示例代码

import org.apache.log4j.AppenderSkeleton;
import org.apache.log4j.Hierarchy;
import org.apache.log4j.Level;
import org.apache.log4j.spi.LoggerRepository;
import org.apache.log4j.spi.LoggingEvent;
import org.apache.log4j.spi.RootLogger;
import org.apache.log4j.varia.LogFilePatternReceiver;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

public class QuickStartDemo {

    static final SimpleDateFormat TS = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss,SSS");

    static class Collect extends AppenderSkeleton {
        final List<LoggingEvent> events = new ArrayList<>();
        protected void append(LoggingEvent e) { events.add(e); }
        public void close() {}
        public boolean requiresLayout() { return false; }
    }

    public static void main(String[] args) {
        LoggerRepository repo = new Hierarchy(new RootLogger(Level.DEBUG));
        repo.getLogger("org.apache.log4j").setLevel(Level.OFF);  // 屏蔽接收器自身日志
        Collect cap = new Collect();
        repo.getRootLogger().addAppender(cap);                   // 事件收集器挂根

        LogFilePatternReceiver r = new LogFilePatternReceiver();
        r.setName("demoApp");
        r.setLoggerRepository(repo);
        r.setFileURL("file:///root/chainsaw-demo/app.log");
        r.setLogFormat("TIMESTAMP LEVEL [LOGGER] MESSAGE");
        r.setTimestampFormat("yyyy-MM-dd HH:mm:ss,SSS");
        r.setTailing(false);          // 一次性读完整文件
        r.setUseCurrentThread(true);  // 当前线程同步处理,便于测试
        r.activateOptions();

        System.out.println("解析事件数=" + cap.events.size());
        for (LoggingEvent e : cap.events) {
            System.out.println("[" + TS.format(new Date(e.getTimeStamp())) + "] "
                    + e.getLevel() + " " + e.getLoggerName() + " - "
                    + e.getRenderedMessage().replace("\n", "\\n"));
        }
        System.out.println("首事件属性表=" + cap.events.get(0).getProperties());
    }
}

说明两点:repo.getLogger("org.apache.log4j").setLevel(Level.OFF) 用于屏蔽接收器把自身工作日志(attempting to load file、processing complete 等)也当事件投递的行为(实测确认存在该行为);setUseCurrentThread(true) 让文件在 activateOptions() 调用线程内同步处理完,测试无需等待后台线程。

4.3 实测输出

cd /root/chainsaw-demo
# dist/ 为 bin 包解压目录(10 个 jar 平铺,见安装指南方案A)
CP=$(ls dist/*.jar | tr '\n' ':' | sed 's/:$//')
javac -encoding UTF-8 -cp "$CP" QuickStartDemo.java
java -Dfile.encoding=UTF-8 -cp ".:$CP" QuickStartDemo

实测回显(app.log 为 9 行版本,含后补的 3 行):

解析事件数=9
[2026-10-15 10:00:00,001] INFO com.example.service.UserService - 用户登录成功 userId=1001 ip=192.168.10.25
[2026-10-15 10:00:02,417] WARN com.example.service.UserService - 登录重试次数达到阈值 userId=1002 retries=5
[2026-10-15 10:00:05,090] ERROR com.example.dao.UserDao - 查询用户失败: connection timeout after 3000ms
[2026-10-15 10:00:07,552] DEBUG com.example.dao.UserDao - SELECT * FROM t_user WHERE id=1001
[2026-10-15 10:00:09,878] INFO com.example.web.OrderController - 订单提交成功 orderId=202610159876
[2026-10-15 10:00:11,204] ERROR com.example.web.OrderController - 提交订单异常\njava.sql.SQLException: Connection refused
[2026-10-15 10:05:21,330] WARN com.example.service.AuthService - 密码连续错误锁定账号 userId=1007
[2026-10-15 10:05:24,871] ERROR com.example.service.AuthService - 鉴权服务调用 LDAP 超时 elapsed=5000ms
[2026-10-15 10:05:27,118] INFO com.example.service.AuthService - 账号解锁成功 userId=1007
首事件属性表={hostname=file, application=/root/chainsaw-demo/app.log, receiver=demoApp}

与 GUI 呈现完全一致:时间戳、级别、logger、消息逐字段正确;异常首行并入 MESSAGE;每个事件自动携带 hostname、application(此处为文件 URL)、receiver(接收器名)三个属性,与 GUI 事件表右侧的属性列一致。

4.4 tailing 模式的无头验证

把 setTailing(true) 并去掉 setUseCurrentThread(true),接收器转入后台轮询线程。实测:启动时 1 条事件,追加 2 行后约 2 秒轮询周期内自动收集到 3 条:

初始事件数=1
已追加 2 行, 等待接收器轮询...
追加后事件数=3
  [2026-10-15 11:00:00,100] INFO com.example.boot.App - 应用启动完成 port=8080
  [2026-10-15 07:38:32,410] WARN com.example.boot.App - 堆内存使用率超过 80% used=82%
  [2026-10-15 07:38:32,910] ERROR com.example.boot.App - 健康检查失败: downstream unreachable
TailingDemo DONE

这与 GUI 的实时追尾(3.3 节)互为印证。

五、与 Log4j 1.x 应用集成:UDP 网络收发

比读文件更贴近生产的方式:应用侧用 log4j 的 UDPAppender 把事件发往网络,Chainsaw 用 UDPReceiver 接收。同一台服务器上收发两端分离实测:

// 关键片段(完整实测程序见第四章模式,收集器与 Repository 相同)
LoggerRepository repo = new Hierarchy(new RootLogger(Level.DEBUG));
repo.getLogger("org.apache.log4j").setLevel(Level.OFF);
Collect cap = new Collect();
repo.getRootLogger().addAppender(cap);

UDPReceiver r = new UDPReceiver();
r.setName("udpDemo");
r.setPort(14560);
r.setLoggerRepository(repo);
r.activateOptions();
Thread.sleep(800);

Logger send = Logger.getLogger("udp.source");      // 应用侧,默认仓库
send.setAdditivity(false);
UDPAppender ap = new UDPAppender("127.0.0.1", 14560);
ap.setLayout(new XMLLayout());
send.addAppender(ap);

send.info("UDP 转发消息 one");
send.warn("UDP 转发消息 two");
send.error("UDP 转发消息 three", new IllegalStateException("bad state"));
Thread.sleep(3000);

实测回显:

== UDPReceiver@14560 事件数=3
  [2026-10-15 07:39:49,259] INFO udp.source - UDP 转发消息 one
  [2026-10-15 07:39:49,267] WARN udp.source - UDP 转发消息 two
  [2026-10-15 07:39:49,267] ERROR udp.source - UDP 转发消息 three

3 条 UDP 事件全部到达。要点:UDPAppender 的 Layout 必须是 XMLLayout(事件按 XML 编码成数据报,UDPReceiver 内部用 XMLDecoder 解码);GUI 中对应操作是在"Load events into Chainsaw"对话框里选择 UDP Receiver 并填端口 14560,事件同样实时入表。

六、验证与观测要点清单

启动集成后,建议按以下实测可观测点逐项确认:

  1. 进程存活:ps aux | grep 'chainsaw[.]LogUI' 数量为 1;
  2. 标签页出现:以 fileURL 命名的日志标签页(file-/root/chainsaw-demo/app.log);
  3. 计数正确:状态栏 Filtered/Total 与日志行数一致(实测 6:6 → 9:9);
  4. 字段正确:时间戳/级别/logger/消息与源文件逐行核对(实测见 4.3 输出);
  5. 颜色规则生效:WARN 黄底、ERROR 红底(默认规则,无需配置);
  6. 实时性:追加日志后 2~4 秒入表(waitMillis=2000 轮询);
  7. 异常呈现:堆栈首行进 MESSAGE 列,\tat 帧进 THROWABLE 列(GUI 实测;无头 API 下堆栈帧不进入 ThrowableInformation,见 Receivers 详解篇的 limitations 说明)。

七、常见问题(FAQ)

Q1:启动后没有出现日志标签页,也没有报错? 命令行参数必须是合法 URL(file:///...,三道斜杠)。相对路径会被静默忽略,源码中 main 方法对 args[0] 直接 new URL(),非法 URL 抛异常但 GUI 仍会以"无接收器"模式启动。

Q2:事件表出现但行数比文件行数少? 检查 logFormat 与日志行是否严格对齐(分隔符、方括号、空格数量)。对不上号的行会被丢弃而非报错——这是 LogFilePatternReceiver 的设计行为。

Q3:界面上接收器列表为空、总弹"Load events into Chainsaw"对话框? 该对话框仅在"启动时无任何接收器"时出现。要么走命令行配置 URL,要么在对话框里手工配置一次后正常退出(配置自动保存,下次启动不再弹)。

Q4:Java 17 下启动即退出,日志有 InaccessibleObjectException? 缺 --add-opens,完整 11 项清单见安装指南。Java 11 则不需要。

Q5:能同时监听多个文件吗? 可以。配置文件里放多个 <plugin>(name 各异、fileURL 各异),每个接收器一个标签页;实测退出时自动保存的 receiver-config.xml 也是这个结构。

八、参考链接

  • Apache Chainsaw 官方下载目录:https://downloads.apache.org/logging/chainsaw/
  • Chainsaw 2.1.0 包内离线快速导览:/opt/apache-chainsaw-2.1.0/site/quicktour.html
  • LogFilePatternReceiver 类文档(发行包 src/ 内):src/main/java/org/apache/log4j/varia/LogFilePatternReceiver.java
  • log4j 1.2 API(LoggingEvent/AppenderSkeleton):https://logging.apache.org/log4j/1.2/apidocs/
  • 本系列:《Ubuntu 22.04 安装 Apache Chainsaw 指南》《Apache Chainsaw 2.1.0 常用功能与 Receivers 详解》