svn服务端本身不直接输出结构化日志,但可通过日志路径分离(access.log/commit.log/authz.log)、格式标准化(tsv或json)、logstash多源采集适配三步,使elk高效收集解析。

SVN服务端本身不直接输出结构化日志,但可通过日志路径分离 + 格式标准化 + 采集适配三步,让ELK(Elasticsearch、Logstash、Kibana)高效收集和解析。关键不是“让SVN原生支持JSON”,而是把它的操作行为转化为ELK可消费的输入源。
日志按用途分离输出到不同文件
SVN没有内置多通道日志,需手动拆分,便于Logstash按类型配置input与filter:
-
访问日志(连接、认证、命令执行)
用svnserve --log-file指定独立文件,例如/var/log/svn/access.log
启动命令示例:svnserve -d -r /home/svn --log-file /var/log/svn/access.log
此日志含时间、用户、IP、操作类型(
checkout/commit/update)、路径,但不含文件变更详情。 -
提交事件日志(结构化、含变更列表)
用post-commit钩子脚本生成专用日志,例如/var/log/svn/commit.log
在仓库hooks/post-commit中写入:#!/bin/bash REPOS="$1" REV="$2" AUTHOR=$(svnlook author -r "$REV" "$REPOS") DATE=$(svnlook date -r "$REV" "$REPOS" | cut -d' ' -f1,2) CHANGED=$(svnlook changed -r "$REV" "$REPOS" | awk '{$1=""; print $0}' | sed 's/^ //') echo "$(date -Iseconds) | $AUTHOR | r$REV | $DATE | $CHANGED" >> /var/log/svn/commit.log输出示例:
2026-06-19T10:22:35+08:00 | alice | r127 | 2026-06-19 10:22:34 +0800 | A trunk/src/main.java M trunk/pom.xml 权限/配置变更日志(可选)
监控conf/authz和conf/passwd文件变化,用inotifywait触发记录,存为/var/log/svn/authz.log
统一使用TSV或JSON格式写入
避免空格分隔带来的解析歧义,推荐用制表符(TSV)或标准JSON:
- TSV更轻量,Logstash可用
csvfilter 解析(设separator => "\t") - JSON更通用,钩子中改用
jq构造:jq -n --arg t "$(date -Iseconds)" --arg u "$AUTHOR" --arg r "$REV" --arg c "$CHANGED" \ '{timestamp: $t, user: $u, revision: $r, changed: $c}' >> /var/log/svn/commit.json输出:
{"timestamp":"2026-06-19T10:22:35+08:00","user":"alice","revision":"127","changed":"A trunk/src/main.java M trunk/pom.xml"}
Logstash配置适配多源日志
在 logstash.conf 中定义三个独立input,分别对应三类日志:
input {
file {
path => "/var/log/svn/access.log"
start_position => "end"
sincedb_path => "/var/lib/logstash/svn-access.sincedb"
tags => ["svn", "access"]
}
file {
path => "/var/log/svn/commit.log"
start_position => "end"
sincedb_path => "/var/lib/logstash/svn-commit.sincedb"
tags => ["svn", "commit"]
}
file {
path => "/var/log/svn/commit.json"
codec => "json"
start_position => "end"
sincedb_path => "/var/lib/logstash/svn-commit-json.sincedb"
tags => ["svn", "commit", "json"]
}
}
filter {
if "access" in [tags] {
grok { match => { "message" => "%{TIMESTAMP_ISO8601:timestamp} %{WORD:action} %{USER:user} %{IPORHOST:client_ip} %{PATH:repo_path}" } }
}
if "commit" in [tags] and "json" not in [tags] {
csv {
separator => "\t"
columns => ["timestamp", "user", "revision", "date_raw", "changed"]
convert => { "revision" => "integer" }
}
}
mutate { remove_field => ["@version", "host", "path"] }
}
output {
elasticsearch { hosts => ["http://es:9200"] index => "svn-%{+YYYY.MM.dd}" }
}
不复杂但容易忽略











