日志记录一直是应用程序开发的关键部分。但是操作系统虚拟化、应用程序容器和云规模日志记录解决方案的兴起已经将日志记录变成了比管理本地调试文件更重要的事情。
现代应用程序和服务现在有望提供日志聚合和分析堆栈(ELK、Graylog、Loggly、Splunk 等)。这可以通过多种方式完成,在这篇文章中,我想专注于修改 log4j2,以便它直接发送到 rsyslog 服务器。
尽管我们在这篇文章中专注于发送到 Ubuntu ryslog 服务器,但这可以是任何侦听 syslog 流量的实体,例如Logstash。
在接收 Ubuntu 服务器时启用 rsyslog
第一个任务是在接收 Ubuntu 服务器上启用 rsyslog。如下图,修改'/etc/rsyslog.conf',去掉监听514 UDP端口的行的注释。此外,添加一行定义模板“jsonRfc5424Template”,这将允许我们将日志信息写入 json。
# provides UDP syslog reception, uncomment the two lines below
$ModLoad imudp
$UDPServerRun 514
$UDPServerAddress 127.0.0.1
$ActionFileDefaultTemplate RSYSLOG_TraditionalFileFormat
# add the line below which provides json output
$template $template jsonRfc5424Template,"{\"type\":\"syslog\",\"host\":\"%HOSTNAME%\",\"message\":\"<%PRI%>1 %TIMESTAMP:::date-rfc3339% %HOSTNAME% %APP-NAME% %PROCID% %MSGID% %STRUCTURED-DATA% %msg:::json%\"}\n"
这是记录额外元数据的机会,在这里我们为“类型”和“主机”添加元数据字段。这可以极大地帮助Logstash和其他解决方案的处理管道。
我们现在可以重新启动 rsyslog 服务,日志输出将以传统的 BSD 样式格式转到“/var/log/syslog”。但是让我们更进一步,让我们的应用程序日志以 json 格式写入他们自己的文件。
使用以下内容创建“/etc/rsyslog.d/30-testlog4j.conf”,它告诉 syslog 将来自“testlog4j”的任何系统日志消息以 json 格式写入它们自己的文件,然后停止对该消息的任何进一步处理。
if $programname == 'testlog4j' or $syslogtag == 'testlog4j' then /var/log/testlog4j/testlog4j.log;jsonRfc5424Template
& stop
附带说明一下,如果系统日志以较旧的 BSD 样式发送,则“程序名称”检查将为真,而“系统日志标签”在以较新的 RFC5424 样式发送时会捕获消息。
现在,创建日志目录,确保在本地防火墙上启用了 syslog 端口 514 并重新启动 rsyslog 服务:
# mkdir -p /var/log/testlog4j
# chown syslog:syslog /var/log/testlog4j
# chmod 755 /var/log/testlog4j
# ufw allow 514/udp
# service rsyslog restart
如果 rsyslog 服务未启动(“ps -A | grep rsyslog”),则可以通过以下方式找到 rsyslog 配置中的错误:
# rsyslogd -N1
验证系统日志处理
在我们开始通过 Java 和 log4j2 框架发送消息之前,让我们从控制台做一个理智的思考。最好从 Java 应用程序服务器实际运行的主机上,使用标准的 Ubuntu 'logger' 实用程序通过 UDP 发送系统日志消息(-u 用于解决错误 )。
> logger -p local0.warn -d -n myhost "test message to catchall" -u /ignore/socket
您会在 syslog 服务器端注意到此消息被发送到 /var/log/syslog,其格式类似于以下内容:
Oct 16 19:58:51 myhost myuser: test message to catch all
现在,让我们运行相同的命令,但这次我们将指定一个“testlog4j”的系统日志标签
> logger -t testlog4j -p local0.warn -d -n myhost "to testlog4j" -u /ignore/socket
现在可以看到输出来自 /var/log/testlog4j/testlog4j.log 并且看起来类似于以下内容:
{"type":"syslog","host":"myhost","message":"<132>1 2016-10-16T20:16:16-05:00 myhost testlog4j - - - to testlog4j"}
这证明 syslog 正在侦听,并且标记为“testlog4j”的传入消息实际上会使用专门的 json 模板处理到它们自己的文件中。
将 log4j2 消息发送到 Syslog
最后一步实际上是将 log4j 消息发送到我们的 syslog 服务器。这是一个简单的Java程序:
import org.apache.logging.log4j.*;
public class TestLog4j {
private static final Logger logger = LogManager.getLogger(TestLog4j.class);
public static void main(String[] args) throws Exception
{
logger.debug("debug message");
logger.info("info message");
logger.warn("warn message");
logger.error("error message");
try {
int i = 1/0;
}catch(Exception exc) {
logger.error("error message with stack trace",
new Exception("I forced this exception",exc));
}
logger.fatal("fatal message");
}
}
您将需要类路径中的 log4j-api -<version>.jar 和 log4j-core-<version>.jar jar 来编译和运行上面的程序。
您还需要类路径中的“log4j2.xml”资源文件:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn">
<Appenders>
<Console name="console" target="SYSTEM_OUT">
<PatternLayout pattern="TOCONSOLE %d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
<!-- OPTION#1: Use standard syslog and add fields with LoggerFields -->
<Syslog name="syslog" format="RFC5424" host="myhost" port="514"
protocol="UDP" appName="testlog4j" includeMDC="false" mdcId="testlog4j"
facility="LOCAL0" enterpriseNumber="18060" newLine="false"
messageId="Audit">
<LoggerFields>
<KeyValuePair key="thread" value="%t"/>
<KeyValuePair key="priority" value="%p"/>
<KeyValuePair key="category" value="%c"/>
<KeyValuePair key="exception" value="%ex"/>
</LoggerFields>
</Syslog>
<!-- OPTION#2: Use socket with explicit pattern -->
<Socket name="syslogsocket" host="myhost" port="514" protocol="UDP">
<PatternLayout
pattern="<134>%d{MMM dd HH:mm:ss} ${hostName} testlog4j: {
"thread":"%t",
"priority":"%p",
"category":"%c{1}",
"exception":"%exception"
}%n"
/>
</Socket>
</Appenders>
<Loggers>
<Root level="warn">
<AppenderRef ref="console"/>
<AppenderRef ref="syslog"/>
</Root>
</Loggers>
</Configuration>
在 UDP 514 上写入 syslog 套接字有两个选项。第一个是使用 <Syslog> 元素,这是我们上面使用的选项。第二种是简单地使用 <Socket> appender 并明确定义一个与 syslog 兼容的模式。
向 <Syslog> 元素添加 Java 线程和 Java 堆栈异常等额外字段意味着修改 <LoggerFields>。向 <Socket> 添加额外字段意味着修改 <PatternLayout>。我个人更喜欢 Socket 生成的干净的 json,但这完全取决于您在日志管道的后续步骤中更喜欢如何解析它。
使用 <Syslog> 元素,您将获得以下输出:
{"type":"syslog","host":"myhost","message":"<132>1 2016-10-16T21:25:20.931-05:00 myhost testc - Audit [testlog4j@18060 category="TestLog4j" exception="" priority="WARN" thread="main"] warn message"}
{"type":"syslog","host":"myhost","message":"<131>1 2016-10-16T21:25:20.933-05:00 myhost testc - Audit [testlog4j@18060 category="TestLog4j" exception="" priority="ERROR" thread="main"] error message"}
{"type":"syslog","host":"myhost","message":"<131>1 2016-10-16T21:25:20.933-05:00 myhost testc - Audit [testlog4j@18060 category="TestLog4j" exception="java.lang.Exception: I forced this exception#012#011at TestLog4j.main(TestLog4j.java:26)#012Caused by: java.lang.ArithmeticException: / by zero#012#011at TestLog4j.main(TestLog4j.java:23)#012" priority="ERROR" thread="main"] error message with stack trace"}
{"type":"syslog","host":"myhost","message":"<129>1 2016-10-16T21:25:20.936-05:00 myhost testc - Audit [testlog4j@18060 category="TestLog4j" exception="" priority="FATAL" thread="main"] fatal message"}
而 <Socket> 产生以下内容:
{"type":"syslog","host":"myhost","message":"<134>1 2016-10-16T21:28:31-05:00 myhost testc - - - { \"thread\":\"main\", \"priority\":\"WARN\", \"category\":\"TestLog4j\", \"exception\":\"\" }"}
{"type":"syslog","host":"myhost","message":"<134>1 2016-10-16T21:28:31-05:00 myhost testc - - - { \"thread\":\"main\", \"priority\":\"ERROR\", \"category\":\"TestLog4j\", \"exception\":\"\" }"}
{"type":"syslog","host":"myhost","message":"<134>1 2016-10-16T21:28:31-05:00 myhost testc - - - { \"thread\":\"main\", \"priority\":\"ERROR\", \"category\":\"TestLog4j\", \"exception\":\" java.lang.Exception: I forced this exception#012#011at TestLog4j.main(TestLog4j.java:26)#012Caused by: java.lang.ArithmeticException: / by zero#012#011at TestLog4j.main(TestLog4j.java:23)#012\" }"}
{"type":"syslog","host":"myhost","message":"<134>1 2016-10-16T21:28:31-05:00 myhost testc - - - { \"thread\":\"main\", \"priority\":\"FATAL\", \"category\":\"TestLog4j\", \"exception\":\"\" }"}
希望很明显,您也可以将这些消息直接写入任何系统日志服务器,例如侦听端口 514 的Logstash。
但是如果你想对日志进行预处理,你也可以考虑使用像 ELK Filebeat代理这样的东西来跟踪系统日志,解析 json,然后将它转发到Redis、Logstash或任何位于你的处理管道前面的实体。