<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>绿色记忆 &#187; LOG</title>
	<atom:link href="https://blog.gmem.cc/tag/log/feed" rel="self" type="application/rss+xml" />
	<link>https://blog.gmem.cc</link>
	<description></description>
	<lastBuildDate>Fri, 10 Apr 2026 07:50:36 +0000</lastBuildDate>
	<language>en-US</language>
		<sy:updatePeriod>hourly</sy:updatePeriod>
		<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.9.14</generator>
	<item>
		<title>Logback学习笔记</title>
		<link>https://blog.gmem.cc/logback-study-note</link>
		<comments>https://blog.gmem.cc/logback-study-note#comments</comments>
		<pubDate>Thu, 08 Mar 2018 06:07:58 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Java]]></category>
		<category><![CDATA[LOG]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=19567</guid>
		<description><![CDATA[<p>简介 Logback的目标是作为Log4j的继任者。Logback使用模块化的架构，主要分为三个部分：logback-core、logback-classic、logback-access。 模块logback-classic作为Log4j的继任者，它做了根本性的改进。该模块实现了SLF4J API，你可以方便的切换到其它日志框架。模块logback-access和Servlet容器集成，提供HTTP访问日志的功能。在logback-core的基础上，你可以方便的轻易的构建自己的模块。 Logback比起Log4j的优势包括： 在某些关键的执行路径上，提供高于Log4j十倍的性能，同时具有比Log4j更低的内存占用 logback-classic直接实现了SLF4J API，而Log4J需要经过适配才能使用SLF4J 支持XML和Groovy两种配置风格 在配置文件修改后，实时的重新加载，不需要重启应用 优雅的重IO错误中恢复：FileAppender及其子类（例如RollingFileAppender）能在文件服务器宕机恢复后，自动重连，不需要重启应用 自动移除旧日志归档，你可以设置TimeBasedRollingPolicy、SizeAndTimeBasedFNATP的maxHistory属性，以控制日志文件的数量 自动压缩日志归档文件，RollingFileAppender 能够在Rollover时自动、异步的压缩日志文件 Prudent Mode：支持多个JVM中运行的FileAppender安全的向同一个文件进行写入 配置文件的条件式处理，你可以在配置文件中添加[crayon-69d8c675054e4139591305-i/]等元素 过滤器，Logback提供比Log4j更加强大的过滤器机制 SiftingAppender，可以基于任何运行时属性来分离日志。例如，根据用户会话来分割日志，每个会话产生一个日志文件 异常栈增强 —— 支持软件包信息，例如[crayon-69d8c675054eb711512605-i/] <a class="read-more" href="https://blog.gmem.cc/logback-study-note">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/logback-study-note">Logback学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h1" style="padding-left: 30px;"><span class="graybg">简介</span></div>
<p>Logback的目标是作为Log4j的继任者。Logback使用模块化的架构，主要分为三个部分：logback-core、logback-classic、logback-access。</p>
<p>模块logback-classic作为Log4j的继任者，它做了根本性的改进。该模块实现了SLF4J API，你可以方便的切换到其它日志框架。模块logback-access和Servlet容器集成，提供HTTP访问日志的功能。在logback-core的基础上，你可以方便的轻易的构建自己的模块。</p>
<p>Logback比起Log4j的优势包括：</p>
<ol>
<li>在某些关键的执行路径上，提供高于Log4j十倍的性能，同时具有比Log4j更低的内存占用</li>
<li>logback-classic直接实现了SLF4J API，而Log4J需要经过适配才能使用SLF4J</li>
<li>支持XML和Groovy两种配置风格</li>
<li>在配置文件修改后，实时的重新加载，不需要重启应用</li>
<li>优雅的重IO错误中恢复：FileAppender及其子类（例如RollingFileAppender）能在文件服务器宕机恢复后，自动重连，不需要重启应用</li>
<li>自动移除旧日志归档，你可以设置TimeBasedRollingPolicy、SizeAndTimeBasedFNATP的maxHistory属性，以控制日志文件的数量</li>
<li>自动压缩日志归档文件，RollingFileAppender 能够在Rollover时自动、异步的压缩日志文件</li>
<li>Prudent Mode：支持多个JVM中运行的FileAppender安全的向同一个文件进行写入</li>
<li>配置文件的条件式处理，你可以在配置文件中添加<pre class="crayon-plain-tag">&lt;if&gt;</pre>等元素</li>
<li>过滤器，Logback提供比Log4j更加强大的过滤器机制</li>
<li>SiftingAppender，可以基于任何运行时属性来分离日志。例如，根据用户会话来分割日志，每个会话产生一个日志文件</li>
<li>异常栈增强 —— 支持软件包信息，例如<pre class="crayon-plain-tag">struts-1.2.9.jar:1.2.9</pre></li>
</ol>
<div class="blog_h1"><span class="graybg">Maven依赖</span></div>
<pre class="crayon-plain-tag">&lt;dependency&gt;
    &lt;groupId&gt;org.slf4j&lt;/groupId&gt;
    &lt;artifactId&gt;slf4j-api&lt;/artifactId&gt;
    &lt;version&gt;1.7.25&lt;/version&gt;
&lt;/dependency&gt;
&lt;dependency&gt;
    &lt;groupId&gt;ch.qos.logback&lt;/groupId&gt;
    &lt;artifactId&gt;logback-classic&lt;/artifactId&gt;
    &lt;version&gt;1.2.3&lt;/version&gt;
&lt;/dependency&gt;</pre>
<div class="blog_h1"><span class="graybg">自动化配置</span></div>
<p>你可以通过编程，或者指定配置文件，来配置Logback。默认配置行为如下：</p>
<ol>
<li>尝试寻找类路径下的 logback-test.xml</li>
<li>如果找不到，尝试寻找logback.groovy</li>
<li>如果找不到，尝试寻找logback.xml </li>
<li>尝试寻找类路径下META-INF/services/ch.qos.logback.classic.spi.Configurator指定的配置类，获得的全限定名称用于配置Logback</li>
<li>如果以上步骤都失败，使用 BasicConfigurator，直接打印到控制台</li>
</ol>
<p>注意，配置文件位置可以通过系统属性<pre class="crayon-plain-tag">-Dlogback.configurationFile=/path/to/config.xml</pre>指定。</p>
<div class="blog_h1"><span class="graybg">配置文件说明</span></div>
<div class="blog_h2"><span class="graybg">configuration</span></div>
<p>这是根配置元素：</p>
<pre class="crayon-plain-tag">&lt;!--
    debug设置为true，打印Logback状态信息
    scan设置为true，自动扫描配置文件的修改，并重新载入
    scanPeriod 默认每分钟扫描一次，如果不指定单位，则单位ms
    packagingData="true" 在栈信息中附加包信息，1.1.4以后默认禁用
--&gt;
&lt;configuration debug="true" scan="true" scanPeriod="30 seconds" packagingData="true"&gt;
    &lt;!-- 设置LoggerContext名称 --&gt;
    &lt;contextName&gt;logbacktesst&lt;/contextName&gt;

    &lt;!-- 定义属性 --&gt;
    &lt;property name="USER_HOME" value="/home/alex"/&gt;
    &lt;!-- 从JNDI获取属性 --&gt;
    &lt;insertFromJNDI env-entry-name="java:comp/env/userhome" as="USER_HOME"/&gt;
    &lt;!-- 将当前时间定义为属性--&gt;
    &lt;timestamp key="bySecond" datePattern="yyyyMMdd'T'HHmmss"/&gt;

    &lt;!-- 下面的子元素单独阐述 --&gt;
    &lt;include /&gt;
    &lt;if /&gt;
    &lt;appender /&gt;
    &lt;logger /&gt;
&lt;/configuration&gt;</pre>
<div class="blog_h2"><span class="graybg">include</span></div>
<p>用于包含其它文件中的配置信息：</p>
<pre class="crayon-plain-tag">&lt;include file="http://dev.gmem.cc/logback/basic.xml"/&gt;</pre>
<p>被包含的文件，格式必须如下：</p>
<pre class="crayon-plain-tag">&lt;!-- 必须以include为根元素 --&gt;
&lt;included&gt;
    &lt;appender name="includedConsole" class="ch.qos.logback.core.ConsoleAppender" /&gt;
&lt;/included&gt;</pre>
<div class="blog_h2"><span class="graybg">if</span></div>
<p>用于条件化配置：</p>
<pre class="crayon-plain-tag">&lt;if condition='property("HOSTNAME").contains("zircon")'&gt;
    &lt;then&gt;
        &lt;appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"&gt;
            &lt;encoder&gt;
                &lt;pattern&gt;%d %-5level %logger{35} - %msg %n&lt;/pattern&gt;
            &lt;/encoder&gt;
        &lt;/appender&gt;
        &lt;root&gt;
            &lt;appender-ref ref="STDOUT"/&gt;
        &lt;/root&gt;
    &lt;/then&gt;
    &lt;else&gt;
    &lt;/else&gt;
&lt;/if&gt;</pre>
<div class="blog_h2"><span class="graybg">logger</span></div>
<pre class="crayon-plain-tag">&lt;!-- 根日志器 --&gt;
&lt;root level="debug"&gt; &lt;!-- 设置为OFF则关闭根日志器 --&gt;
    &lt;!-- 每个日志器都可以引用多个Appender --&gt;
    &lt;appender-ref ref="STDOUT"/&gt;
    &lt;appender-ref ref="FILE"/&gt;
&lt;/root&gt;
&lt;!-- 自定义日志器，以及其最低级别--&gt;
&lt;logger name="cc.gmem" level="INFO"/&gt;
&lt;!-- 默认情况下，每个日志器会调用自己的Appender，也会使用所有祖先的Appender --&gt;
&lt;!-- 设置additivity为false，则不使用任何祖先Appender --&gt;
&lt;logger name="cc.gmem.study.logback.LogbackTest" level="DEBUG" additivity="false"&gt;
    &lt;appender-ref ref="FILE"/&gt;
&lt;/logger&gt;</pre>
<div class="blog_h2"><span class="graybg">appender </span></div>
<p>定义日志追加器，负责把LogEvent输出到目的地。</p>
<div class="blog_h3"><span class="graybg">ConsoleAppender</span></div>
<pre class="crayon-plain-tag">&lt;!-- 输出到控制台的Appender --&gt;
&lt;appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"&gt;
    &lt;encoder&gt;
        &lt;pattern&gt;%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n&lt;/pattern&gt;
    &lt;/encoder&gt;
    &lt;!-- 也可以输出到System.err --&gt;
    &lt;target&gt;System.out&lt;/target&gt;
    &lt;!-- 设置为true则可以在Windows下支持彩色输出，需要依赖org.fusesource.jansi:jansi:1.9 --&gt;
    &lt;withJansi&gt;false&lt;/withJansi&gt;
&lt;/appender&gt;</pre>
<div class="blog_h3"><span class="graybg">FileAppender</span></div>
<pre class="crayon-plain-tag">&lt;!-- 输出到文件的Appender --&gt;
&lt;appender name="FILE" class="ch.qos.logback.core.FileAppender"&gt;
    &lt;!-- 可以引用定义的属性，进行变量替换 --&gt;
    &lt;file&gt;${USER_HOME}logbacktest.log&lt;/file&gt;
    &lt;!-- 唯一性文件名  --&gt;
    &lt;file&gt;log-${bySecond}.txt&lt;/file&gt;
    &lt;encoder&gt;
        &lt;pattern&gt;%date %level [%thread] %logger{10} [%file:%line] %msg%n&lt;/pattern&gt;
    &lt;/encoder&gt;
    &lt;!-- 默认true，是否附加到文件的尾部，设置为false则文件的原有内容被清除 --&gt;
    &lt;append&gt;true&lt;/append&gt;
    &lt;!-- 默认true，设置为false可以大大提升日志吞吐量，但是程序崩溃可能丢失日志 --&gt;
    &lt;immediateFlush&gt;false&lt;/immediateFlush&gt;
    &lt;!-- 如果设置为true则日志被安全的写入文件，即使存在其它JVM的FileAppender在使用此文件 --&gt;
    &lt;prudent&gt;false&lt;/prudent&gt;
&lt;/appender&gt;</pre>
<div class="blog_h3"><span class="graybg">RollingFileAppender</span></div>
<pre class="crayon-plain-tag">&lt;!-- 滚动文件Appender --&gt;
&lt;appender name="RFILE" class="ch.qos.logback.core.rolling.RollingFileAppender"&gt;
    &lt;file&gt;logFile.log&lt;/file&gt;
    &lt;!--
        滚动策略，基于时间滚动
    --&gt;
    &lt;rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"&gt;
        &lt;!--
            文件名模式：
            %d{yyyy-MM-dd-HH, UTC}
            %d{yyyy-MM-dd}
        --&gt;
        &lt;!-- 每日滚动出一个文件--&gt;
        &lt;fileNamePattern&gt;logFile.%d{yyyy-MM-dd}.log&lt;/fileNamePattern&gt;
        &lt;!-- 最多保留的日志文件数量，老的日志自动移除 --&gt;
        &lt;!-- 保留30天的日志历史 --&gt;
        &lt;maxHistory&gt;30&lt;/maxHistory&gt;
        &lt;!-- 日志占用空间限制，如果超过限制，从最老的文件开始删除，删除是异步的 --&gt;
        &lt;!-- 最多占用3GB --&gt;
        &lt;totalSizeCap&gt;3GB&lt;/totalSizeCap&gt;
        &lt;!-- 如果设置为true，则在启动时移除归档日志 --&gt;
        &lt;cleanHistoryOnStart&gt;false&lt;/cleanHistoryOnStart&gt;
    &lt;/rollingPolicy&gt;
    &lt;!--
        滚动策略，基于时间 + 尺寸
    --&gt;
    &lt;rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"&gt;
        &lt;!-- 每日滚定，%i为文件计数，初始值0 --&gt;
        &lt;fileNamePattern&gt;mylog-%d{yyyy-MM-dd}.%i.txt&lt;/fileNamePattern&gt;
        &lt;!-- 单个文件最多100MB，保留60天历史，最大占用空间20GB --&gt;
        &lt;maxFileSize&gt;100MB&lt;/maxFileSize&gt;
        &lt;maxHistory&gt;60&lt;/maxHistory&gt;
        &lt;totalSizeCap&gt;20GB&lt;/totalSizeCap&gt;
    &lt;/rollingPolicy&gt;
    &lt;!-- 触发策略：负责确定何时执行rollover --&gt;
    &lt;triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy"&gt;
        &lt;!-- 如果当前文件超过5MB，则触发Appender进行是否需要rollover的判断 --&gt;
        &lt;maxFileSize&gt;5MB&lt;/maxFileSize&gt;
    &lt;/triggeringPolicy&gt;
    &lt;encoder&gt;
        &lt;pattern&gt;%-4relative [%thread] %-5level %logger{35} - %msg%n&lt;/pattern&gt;
    &lt;/encoder&gt;
&lt;/appender&gt;</pre>
<div class="blog_h3"><span class="graybg">SocketAppender</span></div>
<pre class="crayon-plain-tag">&lt;!-- 连接到远程日志服务器，推送日志 --&gt;
&lt;!-- 通过套接字发送日志 --&gt;
&lt;appender name="SOCKET" class="ch.qos.logback.classic.net.SocketAppender"&gt;
    &lt;remoteHost&gt;${host}&lt;/remoteHost&gt;
    &lt;port&gt;${port}&lt;/port&gt;
    &lt;reconnectionDelay&gt;10000&lt;/reconnectionDelay&gt;
    &lt;includeCallerData&gt;${includeCallerData}&lt;/includeCallerData&gt;
&lt;/appender&gt;
&lt;!-- 通过SSL发送日志 --&gt;
&lt;appender name="SOCKETSSL" class="ch.qos.logback.classic.net.SSLSocketAppender"&gt;
    &lt;remoteHost&gt;${host}&lt;/remoteHost&gt;
    &lt;port&gt;${port}&lt;/port&gt;
    &lt;reconnectionDelay&gt;10000&lt;/reconnectionDelay&gt;
    &lt;ssl&gt;
        &lt;trustStore&gt;
            &lt;location&gt;${truststore}&lt;/location&gt;
            &lt;password&gt;${password}&lt;/password&gt;
        &lt;/trustStore&gt;
    &lt;/ssl&gt;
&lt;/appender&gt;</pre>
<div class="blog_h3"><span class="graybg">ServerSocketAppender</span></div>
<pre class="crayon-plain-tag">&lt;!-- 创建一个日志服务器并监听，向所有连接到此服务器的客户端推送日志 --&gt;
&lt;!-- 在客户端连接之前产生的日志，简单的丢弃 --&gt;
&lt;appender name="SERVER" class="ch.qos.logback.classic.net.server.ServerSocketAppender"&gt;
    &lt;port&gt;${port}&lt;/port&gt;
    &lt;includeCallerData&gt;${includeCallerData}&lt;/includeCallerData&gt;
&lt;/appender&gt;
&lt;appender name="SERVERSSL" class="ch.qos.logback.classic.net.server.SSLServerSocketAppender"&gt;
    &lt;port&gt;${port}&lt;/port&gt;
    &lt;includeCallerData&gt;${includeCallerData}&lt;/includeCallerData&gt;
    &lt;ssl&gt;
        &lt;keyStore&gt;
            &lt;location&gt;${keystore}&lt;/location&gt;
            &lt;password&gt;${password}&lt;/password&gt;
        &lt;/keyStore&gt;
    &lt;/ssl&gt;
&lt;/appender&gt;</pre>
<div class="blog_h3"><span class="graybg">DBAppender </span></div>
<pre class="crayon-plain-tag">&lt;!-- 将日志记录到数据库 --&gt;
&lt;appender name="DB" class="ch.qos.logback.classic.db.DBAppender"&gt;
    &lt;connectionSource class="ch.qos.logback.core.db.DriverManagerConnectionSource"&gt;
        &lt;driverClass&gt;com.mysql.jdbc.Driver&lt;/driverClass&gt;
        &lt;url&gt;jdbc:mysql://host_name:3306/datebase_name&lt;/url&gt;
        &lt;user&gt;username&lt;/user&gt;
        &lt;password&gt;password&lt;/password&gt;
    &lt;/connectionSource&gt;
&lt;/appender&gt;</pre>
<div class="blog_h3"><span class="graybg">SyslogAppender</span></div>
<pre class="crayon-plain-tag">&lt;!-- 写到Syslog中 --&gt;
&lt;appender name="SYSLOG" class="ch.qos.logback.classic.net.SyslogAppender"&gt;
    &lt;syslogHost&gt;remote_home&lt;/syslogHost&gt;
    &lt;facility&gt;AUTH&lt;/facility&gt;
    &lt;suffixPattern&gt;[%thread] %logger %msg&lt;/suffixPattern&gt;
&lt;/appender&gt;</pre>
<div class="blog_h3"><span class="graybg">SiftingAppender</span></div>
<pre class="crayon-plain-tag">&lt;!-- 根据特定的运行时属性，对日志文件进行分割 --&gt;
&lt;appender name="SIFT" class="ch.qos.logback.classic.sift.SiftingAppender"&gt;
    &lt;!-- 根据运行时属性userid进行分割 --&gt;
    &lt;discriminator class="ch.qos.logback.classic.sift.MDCBasedDiscriminator"&gt;
        &lt;key&gt;userid&lt;/key&gt;
        &lt;defaultValue&gt;unknown&lt;/defaultValue&gt;
    &lt;/discriminator&gt;
    &lt;sift&gt;
        &lt;appender name="FILE-${userid}" class="ch.qos.logback.core.FileAppender"&gt;
            &lt;!-- 运行时属性可以随意引用 --&gt;
            &lt;file&gt;${userid}.log&lt;/file&gt;
            &lt;append&gt;false&lt;/append&gt;
            &lt;layout class="ch.qos.logback.classic.PatternLayout"&gt;
                &lt;pattern&gt;%d [%thread] %level %mdc %logger{35} - %msg%n&lt;/pattern&gt;
            &lt;/layout&gt;
        &lt;/appender&gt;
    &lt;/sift&gt;
&lt;/appender&gt;</pre>
<p>配合此Appender的Java代码：</p>
<pre class="crayon-plain-tag">logger.debug("Application started");
// 设置运行时属性
MDC.put("userid", "Alice");
logger.debug("Alice says hello");</pre>
<div class="blog_h3"><span class="graybg">AsyncAppender</span></div>
<p>此日志追加器能够异步的记录ILoggingEvent，它本身仅仅作为事件分发器，必须引用其它Appender负责实际的日志记录。</p>
<p>AsyncAppender在BlockingQueue中对日志进行缓冲，它会创建一个专门的工作线程，从队列的头部获取日志，并分发给单个子Appender。默认情况下，队列用量超过80%后，AsyncAppender会丢弃TRACE, DEBUG,INFO级别的日志信息。</p>
<p>队列的<span style="background-color: #c0c0c0;">默认容量为256，如果队列满了，调用logger.***的应用程序线程会阻塞</span>。</p>
<p>你必须在关闭JVM进程之前，明确的关闭AsyncAppender，以防止日志事件丢失（未来得及分发）。你可以显式调用：</p>
<pre class="crayon-plain-tag">LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
loggerContext.stop();</pre>
<p>或者使用Java的ShutdownHook：</p>
<pre class="crayon-plain-tag">&lt;configuration debug="true"&gt;
   &lt;shutdownHook class="ch.qos.logback.core.hook.DelayingShutdownHook" /&gt;
&lt;/configuration&gt;</pre>
<p>AsyncAppender配置示例：</p>
<pre class="crayon-plain-tag">&lt;appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender"&gt;
    &lt;!-- 队列容量 --&gt;
    &lt;queueSize&gt;256&lt;/queueSize&gt;
    &lt;!-- 仅剩多少容量时，开始丢弃不重要的日志，设置为0则不丢弃 --&gt;
    &lt;discardingThreshold&gt;20&lt;/discardingThreshold&gt;
    &lt;!-- 当LoggerContext停止时，AsyncAppender.stop()等待多长时间，让日志被刷出到appender-ref --&gt;
    &lt;maxFlushTime&gt;1000&lt;/maxFlushTime&gt;
    &lt;!-- 如果设置为true，队列满了会直接丢弃信息，而不是阻塞 --&gt;
    &lt;neverBlock&gt;false&lt;/neverBlock&gt;

    &lt;!-- 实际负责日志记录的Appender --&gt;
    &lt;appender-ref ref="FILE" /&gt;
&lt;/appender&gt;</pre>
<div class="blog_h2"><span class="graybg">Layout</span></div>
<p>此接口负责格式化日志事件：</p>
<pre class="crayon-plain-tag">public interface Layout&lt;E&gt; extends ContextAware, LifeCycle {
    String doLayout(E event);
    String getFileHeader();
    String getPresentationHeader();
    String getFileFooter();
    String getPresentationFooter();
    String getContentType();
}</pre>
<p>你可以注册自己实现的Layout：</p>
<pre class="crayon-plain-tag">&lt;appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"&gt;
    &lt;encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder"&gt;
        &lt;layout class="cc.gmem.study.logback.JsonLayout" /&gt;
    &lt;/encoder&gt;
&lt;/appender&gt;</pre>
<div class="blog_h3"><span class="graybg">PatternLayout</span></div>
<p>这是一个灵活的Layout，通过一个字符串指定<a href="https://logback.qos.ch/manual/layouts.html#conversionWord">输出格式</a>。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">转换符</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>c{length} <br />lo{length} <br />logger{length}</td>
<td>
<p>原始日志事件的Logger名称</p>
<pre class="crayon-plain-tag">%logger      mainPackage.sub.sample.Bar   mainPackage.sub.sample.Bar
%logger{0}   mainPackage.sub.sample.Bar   Bar
%logger{5}   mainPackage.sub.sample.Bar   m.s.s.Bar
%logger{10}  mainPackage.sub.sample.Bar   m.s.s.Bar
%logger{15}  mainPackage.sub.sample.Bar   m.s.sample.Bar
%logger{16}  mainPackage.sub.sample.Bar   m.sub.sample.Bar
%logger{26}  mainPackage.sub.sample.Bar   mainPackage.sub.sample.Bar</pre>
</td>
</tr>
<tr>
<td>C{length} <br />class{length}</td>
<td>调用日志的代码所属类的全限定名称 </td>
</tr>
<tr>
<td>contextName<br />cn</td>
<td>LoggerContext的名称</td>
</tr>
<tr>
<td>d{pattern} <br />date{pattern} <br />d{pattern, timezone} <br />date{pattern, timezone}</td>
<td>日志事件的名称：<br />
<pre class="crayon-plain-tag">%d                                2006-10-20 14:06:49,812
%date                             2006-10-20 14:06:49,812
%date{ISO8601}                    2006-10-20 14:06:49,812
%date{HH:mm:ss.SSS}               14:06:49.812
%date{dd MMM yyyy;HH:mm:ss.SSS}   20 oct. 2006;14:06:49.812</pre>
</td>
</tr>
<tr>
<td>F / file</td>
<td>调用日志的代码的Java源文件名称</td>
</tr>
<tr>
<td>caller{depth} </td>
<td>调用日志的代码的位置信息</td>
</tr>
<tr>
<td>L / line</td>
<td>调用日志的代码的行号</td>
</tr>
<tr>
<td>m / msg / message</td>
<td>日志内容</td>
</tr>
<tr>
<td>M / method</td>
<td>调用日志的代码所在的方法</td>
</tr>
<tr>
<td>n</td>
<td>依赖于平台的换行符</td>
</tr>
<tr>
<td>p / le / level</td>
<td>日志级别</td>
</tr>
<tr>
<td>r / relative</td>
<td>从JVM启动到日志发生时，流逝的毫秒数</td>
</tr>
<tr>
<td>t / thread</td>
<td>产生日志事件的线程名</td>
</tr>
<tr>
<td>X{key:-defaultVal}<br />mdc{key:-defaultVal}</td>
<td>产生日志线程关联的关联MDC（mapped diagnostic context）中的值</td>
</tr>
<tr>
<td>ex{depth} <br />exception{depth} <br />throwable{depth}</td>
<td>输出关联到日志事件的调用栈信息</td>
</tr>
<tr>
<td>xEx{depth} <br />xException{depth} <br />xThrowable{depth}</td>
<td>类似上面，外加包信息</td>
</tr>
</tbody>
</table>
<p> 格式化修饰符，下面是一些修是%logger的例子：
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">修饰符</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>%20logger</td>
<td>如果宽度不足20，左侧补空白</td>
</tr>
<tr>
<td>%-20logger</td>
<td>如果宽度不足20，右侧补空白</td>
</tr>
<tr>
<td>%.30logger</td>
<td>如果宽度超过30，从左侧截断</td>
</tr>
<tr>
<td>%.-30logger</td>
<td>如果宽度超过30，从右侧截断</td>
</tr>
<tr>
<td>%20.30logger</td>
<td>如果宽度不足20，左侧补空白；如果宽度超过30，从左侧截断</td>
</tr>
</tbody>
</table>
<p>支持颜色代码：%black", "%red", "%green","%yellow","%blue", "%magenta","%cyan", "%white", "%gray", "%boldRed","%boldGreen", "%boldYellow", "%boldBlue", "%boldMagenta""%boldCyan", "%boldWhite" 和 "%highlight" 。</p>
<p>Pattern示例：</p>
<pre class="crayon-plain-tag">[%thread] %highlight(%-5level) %cyan(%logger{15}) - %msg %n
# 输出
[main] WARN  c.l.TrivialMain - a warning message 0
[main] DEBUG c.l.TrivialMain - hello world number1</pre>
<div class="blog_h3"><span class="graybg">logback-access扩展</span></div>
<p>ch.qos.logback.access.PatternLayout扩展了以下转换符：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">转换符</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>a / remoteIP</td>
<td>远程IP地址</td>
</tr>
<tr>
<td>A / localIP</td>
<td>本地IP地址</td>
</tr>
<tr>
<td>b / B / bytesSent</td>
<td>响应长度</td>
</tr>
<tr>
<td>h / clientHost</td>
<td>远程主机名</td>
</tr>
<tr>
<td>H / protocol</td>
<td>请求协议</td>
</tr>
<tr>
<td>reqParameter{paramName}</td>
<td>输出指定请求参数</td>
</tr>
<tr>
<td>reqAttribute{attributeName}</td>
<td>输出指定的请求属性</td>
</tr>
<tr>
<td>i{header} / header{header}</td>
<td>输出指定请求头</td>
</tr>
<tr>
<td>responseHeader{header}</td>
<td>输出指定响应头</td>
</tr>
<tr>
<td>reqCookie{cookie}</td>
<td>输出指定Cookie值</td>
</tr>
<tr>
<td>m / requestMethod</td>
<td>请求的HTTP方法</td>
</tr>
<tr>
<td>r / requestURL</td>
<td>请求URL</td>
</tr>
<tr>
<td>s / statusCode</td>
<td>状态码</td>
</tr>
<tr>
<td>D / elapsedTime</td>
<td>处理请求消耗的时间，单位毫秒</td>
</tr>
<tr>
<td>T / elapsedSeconds</td>
<td>处理请求消耗的时间，单位秒</td>
</tr>
<tr>
<td>t / date</td>
<td>输出时间，例如%t{dd MMM yyyy ;HH:mm:ss,SSS}</td>
</tr>
<tr>
<td>u / user</td>
<td>输出远程用户</td>
</tr>
<tr>
<td>q / queryString</td>
<td>输出查询字符串，包括前缀?</td>
</tr>
<tr>
<td>U / requestURI</td>
<td>请求的URI</td>
</tr>
<tr>
<td>S / sessionID</td>
<td>SessionID</td>
</tr>
<tr>
<td>v / server</td>
<td>服务器名称</td>
</tr>
<tr>
<td>I / threadName</td>
<td>处理请求的线程名称</td>
</tr>
<tr>
<td>localPort</td>
<td>本地端口</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">encoder</span></div>
<p>编码器，负责将日志事件转换为字节数组，并写入到输出流。</p>
<div class="blog_h3"><span class="graybg">logstash-logback-encoder</span></div>
<p>JSON格式编码器，用于配合Logstash，示例：</p>
<pre class="crayon-plain-tag">&lt;appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"&gt;
    &lt;encoder class="net.logstash.logback.encoder.LogstashEncoder"&gt;
        &lt;!-- 对输出的JSON进行格式化 --&gt;
        &lt;jsonGeneratorDecorator class="net.logstash.logback.decorate.PrettyPrintingJsonGeneratorDecorator"/&gt;
        &lt;!-- 时间输出为UNIX时间戳 --&gt;
        &lt;timestampPattern&gt;[UNIX_TIMESTAMP_AS_NUMBER]&lt;/timestampPattern&gt;
        &lt;!-- 对字段进行重命名 --&gt;
        &lt;fieldNames&gt;
            &lt;timestamp&gt;instant&lt;/timestamp&gt;
            &lt;logger&gt;loggerName&lt;/logger&gt;
            &lt;thread&gt;thread&lt;/thread&gt;
            &lt;stackTrace&gt;thrown&lt;/stackTrace&gt;
            &lt;!-- 丢弃字段 --&gt;
            &lt;version&gt;[ignore]&lt;/version&gt;
            &lt;levelValue&gt;[ignore]&lt;/levelValue&gt;
        &lt;/fieldNames&gt;
    &lt;/encoder&gt;
    &lt;target&gt;System.out&lt;/target&gt;
&lt;/appender&gt; </pre>
<div class="blog_h1"><span class="graybg">log-access</span></div>
<div class="blog_h2"><span class="graybg">配合Tomcat</span></div>
<p>当前版本可以和JDK7 + Tomcat 7.0.62配合使用：</p>
<ol>
<li>将logback-access.jar与logback-core.jar复制到$TOMCAT_HOME/lib/目录下 </li>
<li>修改配置文件：<br />
<pre class="crayon-plain-tag">&lt;!-- 添加在Engine或Host元素内部 --&gt;
&lt;!-- quiet进制打印默认的状态信息 --&gt;
&lt;ValveclassName="ch.qos.logback.access.tomcat.LogbackValve" quiet="true" filename="logback-access.xml"/&gt; </pre>
</li>
<li>
<p>logback默认会在$TOMCAT_HOME/conf下查找配置文件logback-access.xml，配置示例：</p>
<pre class="crayon-plain-tag">&lt;configuration&gt;
  &lt;statusListener class="ch.qos.logback.core.status.OnConsoleStatusListener" /&gt;  
  &lt;appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"&gt;
    &lt;encoder&gt;
      &lt;pattern&gt;%h %l %u %user %date "%r" %s %b&lt;/pattern&gt;
    &lt;/encoder&gt;
  &lt;/appender&gt;
  &lt;appender-ref ref="STDOUT" /&gt;
&lt;/configuration&gt;</pre>
</li>
</ol>
<p>重启Tomcat即可。
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/logback-study-note">Logback学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/logback-study-note/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>基于EFK构建日志分析系统</title>
		<link>https://blog.gmem.cc/efk-as-a-log-analysis-system</link>
		<comments>https://blog.gmem.cc/efk-as-a-log-analysis-system#comments</comments>
		<pubDate>Tue, 09 Jan 2018 16:10:18 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[BigData]]></category>
		<category><![CDATA[LOG]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=17536</guid>
		<description><![CDATA[<p>Elasticsearch 参考：ElasticSearch学习笔记 Fluentd Fluentd是一个C编写的开源的日志收集器，支持100+不同系统的日志收集处理。 source 定义Fluentd的输入，需要指定一个输入插件。例如： [crayon-69d8c67506187798947491/] 定义了一个HTTP输入。Fluentd会在8888端口上监听，等待外部传入事件。事件的例子： [crayon-69d8c6750618c673041992/] source捕获到的Fluentd事件，交由Fluentd路由引擎处理。 filter 多个filter可以构成事件处理流水线。使用filter你可以将不需要的事件过滤掉，不再继续下一步处理。例如： [crayon-69d8c6750618e987240069/] 根据正则式匹配输入事件的action字段，如果匹配，路由给match处理，否则丢弃。 match 定义Fluentd的输出，并将匹配的事件传递给目标。例如： [crayon-69d8c67506190157492292/] 会匹配具有Tag：test.cycle的输入事件，并传递给stdout这个输出插件。 label 用于定义一个可以被跳转到的路由片段，打破默认的从上到下的路由搜索顺序。该指令内部可以包含filter、match指令。示例： [crayon-69d8c67506193979499943/] parse <a class="read-more" href="https://blog.gmem.cc/efk-as-a-log-analysis-system">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/efk-as-a-log-analysis-system">基于EFK构建日志分析系统</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h1"><span class="graybg">Elasticsearch</span></div>
<p>参考：<a href="https://blog.gmem.cc/elasticsearch-study-note">ElasticSearch学习笔记</a></p>
<div class="blog_h1"><span class="graybg">Fluentd</span></div>
<p>Fluentd是一个C编写的开源的日志收集器，支持100+不同系统的日志收集处理。</p>
<div class="blog_h2"><span class="graybg">source</span></div>
<p>定义Fluentd的输入，需要指定一个输入插件。例如：</p>
<pre class="crayon-plain-tag">&lt;source&gt;
  # 使用什么插件
  @type http
  # 你可以这样推送日志：http://localhost:8888/tag.name?json={...}
  port 8888
  bind 0.0.0.0
&lt;/source&gt;</pre>
<p>定义了一个HTTP输入。Fluentd会在8888端口上监听，等待外部传入事件。事件的例子：</p>
<pre class="crayon-plain-tag">curl -i -X POST -d 'json={"action":"login","user":2}' http://localhost:8888/test.cycle</pre>
<p>source捕获到的Fluentd事件，交由Fluentd路由引擎处理。</p>
<div class="blog_h2"><span class="graybg">filter</span></div>
<p><span style="background-color: #c0c0c0;">多个filter可以构成事件处理流水线</span>。使用filter你可以将不需要的事件过滤掉，不再继续下一步处理。例如：</p>
<pre class="crayon-plain-tag">&lt;filter test.cycle&gt;
  @type grep
  &lt;exclude&gt;
    key action
    pattern ^logout$
  &lt;/exclude&gt;
&lt;/filter&gt;</pre>
<p>根据正则式匹配输入事件的action字段，如果匹配，路由给match处理，否则丢弃。</p>
<div class="blog_h2"><span class="graybg">match</span></div>
<p>定义Fluentd的输出，并将匹配的事件传递给目标。例如：</p>
<pre class="crayon-plain-tag">&lt;match test.cycle&gt;
  @type stdout
&lt;/match&gt;</pre>
<p>会匹配具有Tag：test.cycle的输入事件，并传递给stdout这个输出插件。</p>
<div class="blog_h2"><span class="graybg">label</span></div>
<p>用于定义一个可以被跳转到的路由片段，打破默认的从上到下的路由搜索顺序。该指令<span style="background-color: #c0c0c0;">内部可以包含filter、match指令</span>。示例：</p>
<pre class="crayon-plain-tag">&lt;source&gt;
  @type http
  bind 0.0.0.0
  port 8888
  # 指定路由标签
  @label @STAGING
&lt;/source&gt;

&lt;filter test.cycle&gt;
&lt;/filter&gt;

# http源直接跳转到这里，不使用上面的filter
&lt;label @STAGING&gt;
  &lt;filter test.cycle&gt;
    @type grep
    &lt;exclude&gt;
      key action
      pattern ^logout$
    &lt;/exclude&gt;
  &lt;/filter&gt;

  &lt;match test.cycle&gt;
    @type stdout
  &lt;/match&gt;
&lt;/label&gt;</pre>
<div class="blog_h2"><span class="graybg">parse</span></div>
<p>可以位于source、match、filter指令的内部。对于那些支持的插件，用于解析原始数据。示例：</p>
<pre class="crayon-plain-tag">&lt;source&gt;
  @type tail
  # 输入插件的参数
  &lt;parse&gt;
    # 解析插件的类型
    @type apache2
    # 解析插件的参数
  &lt;/parse&gt;
&lt;/source&gt;</pre>
<div class="blog_h3"><span class="graybg">通用参数</span></div>
<p>每个Parser都可以覆盖这些参数的值：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 170px; text-align: center;">参数</td>
<td style="width: 60px; text-align: center;">类型</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>types</td>
<td>hash</td>
<td>
<p>指定如何将各字段转换为其它类型：field1:type,field2:type...</p>
<p>支持的类型：string、bool、integer、float、time、array</p>
</td>
</tr>
<tr>
<td>time_key</td>
<td>string</td>
<td>事件的发生事件从什么字段中获取，如果该字段不存在，则取值当前时间</td>
</tr>
<tr>
<td>null_value_pattern</td>
<td>string</td>
<td>空值的Pattern</td>
</tr>
<tr>
<td>null_empty_string</td>
<td>bool</td>
<td>是否将空串替换为nil，默认false</td>
</tr>
<tr>
<td>estimate_current_event</td>
<td>bool</td>
<td>是否以当前时间作为time_key的值，默认false</td>
</tr>
<tr>
<td>keep_time_key</td>
<td>bool</td>
<td>是否保留事件中的时间字段</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">时间参数</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 170px; text-align: center;">参数</td>
<td style="width: 50px; text-align: center;">类型</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>time_type</td>
<td>enum</td>
<td>
<p>可选值：</p>
<p style="padding-left: 30px;">float：UNIX时间.纳秒<br />unixtime： UNIX时间（秒）<br />string：根据后面几个参数决定具体格式</p>
</td>
</tr>
<tr>
<td>time_format</td>
<td>string</td>
<td>
<p>参考Ruby API：<a href="https://docs.ruby-lang.org/en/2.4.0/Time.html#method-i-strftime">时间格式化</a>、<a href="https://docs.ruby-lang.org/en/2.4.0/Time.html#method-c-strptime">时间解析</a></p>
<p>除了遵循Ruby的时间格式化，还可以取值%iso8601</p>
</td>
</tr>
<tr>
<td>localtime</td>
<td>bool</td>
<td>是否使用本地时间而非UTC，默认true</td>
</tr>
<tr>
<td>utc</td>
<td>bool</td>
<td>是否使用UTC而非本地时间，默认false</td>
</tr>
<tr>
<td>timezone</td>
<td>string</td>
<td>指定时区，例如+09:00、+0900、+09、Asia/Tokyo</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">buffer</span></div>
<p>可以位于match指令的内部，指定如何对事件进行缓冲（避免对输出的目的地造成压力）。Fluentd内置了两种缓冲插件：<a href="https://docs.fluentd.org/v1.0/articles/buf_file">memory</a>、<a href="https://docs.fluentd.org/v1.0/articles/buf_memory">file</a>。</p>
<p>使用buffer指令时，你也需要通过@type来指定插件类型。如果省略@type，则使用输出插件（match）指定的默认插件，或者使用memory。</p>
<div class="blog_h3"><span class="graybg">分块键</span></div>
<p>你可以为buffer指定分块键：</p>
<pre class="crayon-plain-tag"># 为空，或者逗号分隔的字符串
&lt;buffer ARGUMENT_CHUNK_KEYS&gt;
&lt;/buffer&gt;</pre>
<p>分块键决定了事件被收集到哪个缓冲块：</p>
<ol>
<li>如果不指定分块键（并且输出插件也没有指定默认分块键），则输出插件将所有的事件都写到单个块中，直到此块充满</li>
<li>如果分块键被设置为“tag”，则不同标签（Tag）的事件被收集到不同的缓冲块</li>
<li>如果分块键被设置为“time”，且指定了timekey参数，则每个Time Key对应一个缓冲块：<br />
<pre class="crayon-plain-tag">&lt;buffer time&gt;
  # 如果不指定单位，默认为秒
  timekey      1h # 每小时一个块
  timekey_wait 5m # 延迟5分钟刷出缓冲
&lt;/buffer&gt;</pre>
</li>
<li>如果分块键被设置为其它值，则认为是<span style="background-color: #c0c0c0;">事件记录的字段名</span></li>
<li>使用事件记录的嵌套字段也支持：<pre class="crayon-plain-tag">&lt;buffer $.nest.field&gt; # 访问记录的nest.field字段</pre></li>
<li>联用多个分块键也支持：<pre class="crayon-plain-tag">&lt;buffer tag,time,$.nest.field&gt;</pre></li>
</ol>
<div class="blog_h3"><span class="graybg">占位符</span></div>
<p>某些输出插件，可以使用分块键作为变量：</p>
<pre class="crayon-plain-tag">&lt;match log.*&gt;
  @type file
  path  /data/${tag}/access.${key1}.${$.nest.field}.log # 输出文件名使用变量，不同块输出到不同文件
  &lt;buffer tag,key1,$.nest.field&gt;
  &lt;/buffer&gt;
&lt;/match&gt;</pre>
<div class="blog_h3"><span class="graybg">缓冲参数</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 170px; text-align: center;">参数</td>
<td style="width: 60px; text-align: center;">类型</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>chunk_limit_size</td>
<td>size</td>
<td>缓冲块的最大尺寸，默认值：内存缓冲8MB，文件缓冲256MB</td>
</tr>
<tr>
<td>chunk_limit_records</td>
<td>integer</td>
<td>限制单个块最多包含的记录数</td>
</tr>
<tr>
<td>total_limit_size</td>
<td>size</td>
<td>此缓冲插件实例的总限制。默认值：内存缓冲512MB，文件缓冲64GB</td>
</tr>
<tr>
<td>chunk_full_threshold</td>
<td>float</td>
<td>刷空缓冲块的阈值，默认0.95，也就是缓冲块占用超过95%刷出</td>
</tr>
<tr>
<td>compress</td>
<td>enum</td>
<td>取值text、gzip，缓冲块的压缩算法。默认text表示不压缩</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">刷空参数</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 170px; text-align: center;">参数</td>
<td style="width: 60px; text-align: center;">类型</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>flush_at_shutdown</td>
<td>bool</td>
<td>关闭前是否刷空</td>
</tr>
<tr>
<td>flush_mode</td>
<td>enum</td>
<td>
<p>刷空模式：</p>
<p style="padding-left: 30px;">interval 以flush_interval为周期刷空<br />lazy 每个timekey刷空一次<br />immediate 事件进入缓冲块后立即刷空</p>
</td>
</tr>
<tr>
<td>flush_interval</td>
<td>time</td>
<td>默认60s</td>
</tr>
<tr>
<td>flush_thread_count</td>
<td>integer</td>
<td>输出插件的线程数量，默认1，增大可以并行刷出缓冲块</td>
</tr>
<tr>
<td>flush_thread_interval</td>
<td>float</td>
<td>如果没有缓冲块等待被刷出，则本次刷空后，线程休眠几秒以进行下一次尝试</td>
</tr>
<tr>
<td>flush_thread_burst_interval</td>
<td>float</td>
<td>如果有缓冲块排队等待被刷出时的休眠间隔</td>
</tr>
<tr>
<td>delayed_commit_timeout</td>
<td>time</td>
<td>输出插件认定异步写操作失败的超时，默认60s</td>
</tr>
<tr>
<td>overflow_action</td>
<td>enum</td>
<td>
<p>当缓冲队列满了，输出插件的行为：</p>
<p style="padding-left: 30px;">throw_exception 抛出异常，打印错误<br />block 阻塞输入插件，禁止它释放新事件<br />drop_oldest_chunk 丢弃最旧的缓冲块</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">重试参数</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 170px; text-align: center;">参数</td>
<td style="width: 60px; text-align: center;">类型</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>retry_timeout</td>
<td>time</td>
<td>重试超时，默认72h</td>
</tr>
<tr>
<td>retry_forever</td>
<td>bool</td>
<td>是否永远重试，默认false</td>
</tr>
<tr>
<td>retry_max_times</td>
<td>integer</td>
<td>最大重试刷空的次数</td>
</tr>
<tr>
<td>retry_type</td>
<td>enum</td>
<td>
<p>重试方式：</p>
<p style="padding-left: 30px;">exponential_backoff 频率指数降低<br />periodic 频率恒定</p>
<p>对于指数方式，底数由参数retry_exponential_backoff_base确定，默认2</p>
<p>对于指数方式，最大重试间隔由retry_max_interval确定</p>
</td>
</tr>
<tr>
<td>retry_wait</td>
<td>time</td>
<td>下一次重试的等待间隔，默认1s</td>
</tr>
<tr>
<td>retry_randomize</td>
<td>bool</td>
<td>是否随机化重试间隔，默认true。可以防止高并发</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">format</span></div>
<p>部分插件支持在内部包含format指令，用来指定如何对日志记录进行格式化。match、filter指令内部可以包含format指令：</p>
<pre class="crayon-plain-tag">&lt;match tag.*&gt;
  @type file
  &lt;format&gt;
    @type json
  &lt;/format&gt;
&lt;/match&gt;</pre>
<p>内置的插件包括：out_file、json、ltsv、csv、msgpack、hash、single_value。下面的配置，将事件的log字段存储到文件：</p>
<pre class="crayon-plain-tag">&lt;match **&gt;
  @type file
  path  /var/log/kubernetes
  &lt;format&gt;
    @type single_value
    message_key log
  &lt;/format&gt;
&lt;/match&gt;</pre>
<div class="blog_h2"><span class="graybg">inject</span></div>
<p>可以位于match、filter指令内部，向事件记录注入额外的字段。</p>
<div class="blog_h2"><span class="graybg">extract</span></div>
<p>可以位于source、match、filter指令内部，从事件记录中抽取值。</p>
<div class="blog_h2"><span class="graybg">storage</span></div>
<p>部分插件支持此指令，用于指定如何存储插件的内部状态。可以位于source、match、filter指令内部。</p>
<div class="blog_h2"><span class="graybg">transport</span></div>
<p>使用server插件助手的source、match、filter插件，支持在内部配置该指令。用于说明如何处理网络连接。</p>
<div class="blog_h2"><span class="graybg">@include </span></div>
<p>该指令用于包含其它配置文件</p>
<div class="blog_h2"><span class="graybg">system</span></div>
<p>该指令用于进行系统级的配置，包括配置项：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 30%; text-align: center;">配置项</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>log_level</td>
<td>日志级别，可以取值debug、info、error、fatal</td>
</tr>
<tr>
<td>suppress_repeated_stacktrace</td>
<td> </td>
</tr>
<tr>
<td>emit_error_log_interval</td>
<td> </td>
</tr>
<tr>
<td>suppress_config_dump</td>
<td> </td>
</tr>
<tr>
<td>without_source</td>
<td> </td>
</tr>
<tr>
<td>process_name</td>
<td>配置fluentd的supervisor和worker进程的名称</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">事件结构</span></div>
<p>每个Fluentd事件包含以下部分：</p>
<ol>
<li>Tag：标签，用于说明事件的“来源”，用于事件路由。<span style="background-color: #c0c0c0;">标签是点号（.）分隔的多个字符串</span></li>
<li>Time：事件发生的时间，必须是UNIX time格式</li>
<li>Record：实际的日志内容，JSON对象形式</li>
</ol>
<div class="blog_h2"><span class="graybg">标签匹配</span></div>
<p>标签（Tag）是日志事件的一种属性。filter、match指令可以指定一个匹配Pattern，来声明它负责处理哪些事件：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">Pattern</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>app.tag</td>
<td>精确匹配</td>
</tr>
<tr>
<td>app.*</td>
<td>匹配app.tag1、app.tag2，但是不匹配app.tag1.xx</td>
</tr>
<tr>
<td>app.**</td>
<td>匹配任何以app开头的标签</td>
</tr>
<tr>
<td>app.{x,y}.*</td>
<td>匹配app.x.*以及app.y.*，其中x、y可以是Pattern，例如app.{x,y.**}</td>
</tr>
<tr>
<td>app.tag app.tag</td>
<td>或</td>
</tr>
</tbody>
</table>
<p>Fluentd<span style="background-color: #c0c0c0;">根据配置文件中声明的顺序，自上而下的尝试匹配</span>，一旦找到匹配日志事件的filter、match就不再继续。</p>
<div class="blog_h2"><span class="graybg">配置文件</span></div>
<p>如果通过td-agent包安装，则配置文件位置为/etc/td-agent/td-agent.conf。</p>
<p>如果通过Ruby Gem安装，则配置文件位置为/etc/fluent/fluent.conf。</p>
<p>要修改配置文件的位置，使用环境变量FLUENT_CONF，或者命令行选项 -c</p>
<div class="blog_h2"><span class="graybg">配置参数</span></div>
<p>任何一个Fluentd插件都暴露若干可配置参数。</p>
<div class="blog_h3"><span class="graybg">参数类型</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 80px; text-align: center;">类型</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>string</td>
<td>字符串</td>
</tr>
<tr>
<td>integer</td>
<td>整数</td>
</tr>
<tr>
<td>float</td>
<td>浮点数</td>
</tr>
<tr>
<td>size</td>
<td>字节数量</td>
</tr>
<tr>
<td>time</td>
<td>时间长度（Duration）</td>
</tr>
<tr>
<td>array</td>
<td>JSON数组，可以<pre class="crayon-plain-tag">["key1", "key2"]</pre>形式，或者<pre class="crayon-plain-tag">key1,key2</pre>形式</td>
</tr>
<tr>
<td>hash</td>
<td>JSON对象，可以<pre class="crayon-plain-tag">{"key1":"value1", "key2":"value2"}</pre>或者<pre class="crayon-plain-tag">key1:value1,key2:value2</pre></td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">通用参数</span></div>
<p>Fluentd定义了一系列以@开头的参数：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">参数</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>@type</td>
<td>插件类型</td>
</tr>
<tr>
<td>@id</td>
<td>插件ID</td>
</tr>
<tr>
<td>@label</td>
<td>指定路由标签</td>
</tr>
<tr>
<td>@log_level</td>
<td>插件的日志级别</td>
</tr>
</tbody>
</table>
<p>type, id 和 log_level是对应上面几个参数，向后兼容用。</p>
<div class="blog_h3"><span class="graybg">内嵌Ruby代码</span></div>
<p>你可以在字符串中包含<pre class="crayon-plain-tag">#{}</pre>标记，其中可以包含合法的Ruby表达式，示例：</p>
<pre class="crayon-plain-tag">host_param "#{Socket.gethostname}"
env_param "foo-#{ENV["FOO_BAR"]}"</pre>
<div class="blog_h2"><span class="graybg">输入插件</span></div>
<div class="blog_h3"><span class="graybg">tail</span></div>
<p>这是一个内置插件，不需要额外的安装步骤。该插件从目标配置文件的尾部开始读取新产生的日志。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 160px; text-align: center;">参数</td>
<td style="width: 80px; text-align: center;">类型</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>tag</td>
<td>string</td>
<td>支持使用通配符 * ，该符号会展开为日志文件的实际路径</td>
</tr>
<tr>
<td>path</td>
<td>string</td>
<td>
<p>需要读取的日志的路径，可以指定多个路径，逗号分隔</p>
<p>通配符*和strftime格式占位符可以使用，用以动态的添加/移除日志文件：</p>
<pre class="crayon-plain-tag"># 仅仅读取default命名空间的日志
path /var/log/containers/*_default_*.log
# 日期
path /path/to/%Y/%m/%d/*</pre>
</td>
</tr>
<tr>
<td>exclude_path</td>
<td>array</td>
<td>
<p>需要排除掉的日志路径，示例：
<pre class="crayon-plain-tag">exclude_path ["/path/to/*.gz", "/path/to/*.zip"]</pre>
</td>
</tr>
<tr>
<td>read_from_head</td>
<td>bool</td>
<td>从文件的头部开始读取日志，而非尾部</td>
</tr>
<tr>
<td>&lt;parse&gt;</td>
<td>directive</td>
<td>你必须为tail配置parse指令，说明如何解析日志内容</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">systemd</span></div>
<p>读取并解析Systemd日志。示例：
<pre class="crayon-plain-tag">&lt;source&gt;
  @type systemd
  @id in_systemd_kubelet
  # 读取Kubelet.service的0-5级别的日志
  matches [{ "_SYSTEMD_UNIT": "kubelet.service", "PRIORITY": [0,1,2,3,4,5] }]
  &lt;storage&gt;
    @type local
    persistent true
    path /var/log/fluentd-journald-kubelet-cursor.json
  &lt;/storage&gt;
  &lt;entry&gt;
    fields_strip_underscores true
  &lt;/entry&gt;
  read_from_head false
  tag kubelet
&lt;/source&gt;</pre>
<div class="blog_h2"><span class="graybg">过滤插件</span></div>
<div class="blog_h3"><span class="graybg">record_transformer</span></div>
<p>支持以多种方式来修改事件。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 160px; text-align: center;">参数</td>
<td style="width: 80px; text-align: center;">类型</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>&lt;record&gt;</td>
<td>directive</td>
<td>
<p>在该指令中，定义需要新增加的字段。配置示例：</p>
<pre class="crayon-plain-tag">&lt;filter kubernetes.**&gt;
  @type record_transformer
  enable_ruby
  &lt;record&gt;
    # 添加主机名
    hostname "#{Socket.gethostname}"
    # 将事件的标签存储为记录字段
    tag ${tag}
    # 读取事件标签的第2部分
    service_name ${tag_parts[1]}
    # 读取记录的字段并进行运算
    avg ${record["total"] / record["count"]}
    # 一个名为message的字段，使用$进行字符串插值
    message yay, ${record["message"]}
  &lt;/record&gt;
&lt;/filter&gt;</pre>
<p>支持以下方式来访问标签：</p>
<p style="padding-left: 30px;">tag_parts[N] 标签的第N段<br />tag_prefix[N] 标签的0-N段<br />tag_suffix[N] 标签的N+段</p>
</td>
</tr>
<tr>
<td>enable_ruby</td>
<td>bool</td>
<td>
<p>默认false。如果为true，则可以在${}中包含Ruby代码，代码可以使用变量：</p>
<p style="padding-left: 30px;">record 当前事件记录<br />time 当前事件的时间对象</p>
<p>配置示例：</p>
<pre class="crayon-plain-tag">&lt;filter kubernetes.**&gt;
  @type record_transformer
  enable_ruby
  remove_keys [ "log" ]
  &lt;record&gt;
    # 从%-5p %d{yyyy-MM-dd HH:mm:ss.SSS} ::: [%15.15t] %-48.48c{36} ::: %m%n%ex
    # 形式的Logback Pattern中抽取字段
    level ${record["log"][0,5].strip}
    timestamp ${record["log"][6,23]}
    thread ${record["log"][35,15].strip}
    class  ${record["log"][52,49].strip}
    message ${record["log"][105..-1].strip}
  &lt;/record&gt;
&lt;/filter&gt;</pre>
<p>代码示例：</p>
<pre class="crayon-plain-tag"># 将记录转换为JSON
${record.to_json}
# 格式化时间
${time.strftime('%Y-%m-%dT%H:%M:%S%z')}
# 取标签的最后一段
${tag_parts.last}
# 访问嵌套字段
${record["payload"]["key"]}</pre>
</td>
</tr>
<tr>
<td>auto_typecast</td>
<td>bool</td>
<td>默认false。是否自动进行类型转换</td>
</tr>
<tr>
<td>renew_record</td>
<td>bool</td>
<td>默认false。如果true则在空的新哈希上进行操作，而非修改incoming的记录</td>
</tr>
<tr>
<td>renew_time_key</td>
<td>string</td>
<td>使用指定的字段来修改事件的时间，目标字段必须是UNIX time</td>
</tr>
<tr>
<td>keep_keys</td>
<td>array</td>
<td>仅当renew_record=true时有意义。列出记录中需要保留的键</td>
</tr>
<tr>
<td>remove_keys</td>
<td>array</td>
<td>列出需要删除的键</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">grep</span></div>
<p>根据事件的字段进行过滤，不匹配的记录被丢弃。
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 160px; text-align: center;">参数</td>
<td style="width: 80px; text-align: center;">类型</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>&lt;and&gt;</td>
<td>directive</td>
<td>
<p>内部指定几个其它指令，进行与操作：</p>
<pre class="crayon-plain-tag">&lt;and&gt;
  &lt;exclude&gt;
  &lt;/exclude&gt;
  &lt;exclude&gt;
  &lt;/exclude&gt;
&lt;/and&gt;</pre>
</td>
</tr>
<tr>
<td>&lt;or&gt;</td>
<td>directive</td>
<td>内部指定几个其它指令，进行或操作</td>
</tr>
<tr>
<td>&lt;regexp&gt;</td>
<td>directive</td>
<td>
<p>指定基于正则式的匹配规则，不匹配的事件会被排除：
<pre class="crayon-plain-tag">&lt;regexp&gt;
  # 检查的字段
  key price
  # 匹配的正则式
  pattern /[1-9]\d*/
&lt;/regexp&gt;</pre>
</td>
</tr>
<tr>
<td>&lt;exclude&gt;</td>
<td>directive</td>
<td>类似上面，但是匹配的事件会被排除</td>
</tr>
</tbody>
</table>
<p>示例：
<pre class="crayon-plain-tag"># 针对所有以calico-node开头的日志
&lt;filter kubernetes.var.log.containers.calico-node-*.log&gt;
  @type grep
  @id filter_grep_container_calico_node
  &lt;regexp&gt;
    # 针对日志记录的log字段
    key log
    # 仅仅保留警告、错误日志
    pattern ^.{25}(W|E)
  &lt;/regexp&gt;
&lt;/filter&gt;

# 仅仅保留具有标签tier=application的Pod产生的日志
&lt;filter kubernetes.**&gt;
  @type grep
  @id filter_grep_kubernetes
  &lt;regexp&gt;
    key $.kubernetes.labels.tier
    pattern ^application$
  &lt;/regexp&gt;
&lt;/filter&gt;</pre>
<div class="blog_h3"><span class="graybg">parser</span></div>
<p>解析日志的字符串字段，并<span style="background-color: #c0c0c0;">把事件记录替换为解析结果</span>：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 200px; text-align: center;">参数</td>
<td style="width: 80px; text-align: center;">类型</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>&lt;parse&gt;</td>
<td>directive</td>
<td>指定解析器及其参数</td>
</tr>
<tr>
<td>key_name</td>
<td>string</td>
<td>需要被解析的记录字段名</td>
</tr>
<tr>
<td>reserve_time</td>
<td>bool</td>
<td>是否在新记录中保留原始事件的时间字段</td>
</tr>
<tr>
<td>reserve_data</td>
<td>bool</td>
<td>是否在新记录中保留原始时间的所有字段</td>
</tr>
<tr>
<td>remove_key_name_field</td>
<td>bool</td>
<td>如果解析成功，是否删除key_name指定的原始事件字段，1.2.2引入</td>
</tr>
<tr>
<td>inject_key_prefix</td>
<td>string</td>
<td>解析结果字段，统一增加的前缀</td>
</tr>
<tr>
<td>hash_value_field</td>
<td>string</td>
<td>解析结果字段，以哈希（对象）形式保存为该参数指定字段的值</td>
</tr>
<tr>
<td>emit_invalid_record_to_error</td>
<td>bool</td>
<td>是否将无法解析的记录发射给@ERROR标签</td>
</tr>
</tbody>
</table>
<p>示例，解析ElasticSearch的JSON格式的日志，并把Wrapping Docker日志替换掉：</p>
<pre class="crayon-plain-tag">&lt;filter kubernetes.var.log.containers.es-master-*.log&gt;
  @type parser
  @id filter_parser_containers_es_master
  key_name log
  reserve_time true
  reserve_data true
  remove_key_name_field true
  &lt;parse&gt;
    @type json
    time_format %Y-%m-%dT%H:%M:%S.%NZ
  &lt;/parse&gt;
&lt;/filter&gt;</pre>
<div class="blog_h3"><span class="graybg">concat</span></div>
<p>该插件非内置，执行<pre class="crayon-plain-tag">gem install fluent-plugin-concat</pre>安装。</p>
<p>用于将多个日志事件合并为一个。具有工作三种模式：</p>
<ol>
<li>n_lines 将连续的N个事件合并为一个</li>
<li>multiline_start_regexp ... 根据正则式匹配来确定该事件是否作为合并后的第一个、中间事件、最后一个</li>
<li>partial_key 取源事件中的某个字段，如果该字段的值为partial_value指定的值，则认为它应该合并到前面的事件</li>
</ol>
<p>注意： 如果超时后仍然没有接收到被合并序例的的最后一个事件，则整个序列会被丢弃。</p>
<p>下面的示例用于处理被Docker的日志驱动按行收集的Java Logback日志信息，它会将日志中的异常栈合并到一起，然后与它们之前的（紧靠着的）那个事件合并：</p>
<pre class="crayon-plain-tag">&lt;filter kubernetes.**&gt;
  @type concat
  @log_level trace
  key log
  multiline_start_regexp /^(TRACE|DEBUG|INFO|WARN|ERROR|FATAL)/
  timeout_label @ES
&lt;/filter&gt;

&lt;match kubernetes.**&gt;
  @type relabel
  @label @ES
&lt;/match&gt;

&lt;label @ES&gt;
...
&lt;/label&gt;</pre>
<div class="blog_h2"><span class="graybg">解析插件</span></div>
<div class="blog_h3"><span class="graybg">regexp</span></div>
<p>根据正则式来解析日志。指定的正则式至少需要指定一个<a href="/regex#named-capture">命名捕获</a>，命名捕获会作为记录的字段，名字为time的命名捕获，会作为事件的发生时间。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 200px; text-align: center;">参数</td>
<td style="width: 80px; text-align: center;">类型</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>time_key</td>
<td>string</td>
<td>事件的发生时间字段，默认time</td>
</tr>
<tr>
<td>time_format</td>
<td>string</td>
<td>时间的格式</td>
</tr>
<tr>
<td>keep_time_key</td>
<td>string</td>
<td>是否在记录中保留时间字段，默认false</td>
</tr>
<tr>
<td>expression</td>
<td>regexp</td>
<td>
<p>解析日志的正则式，需要指定至少一个命名捕获</p>
<p>下面的例子解析Containerd默认日志：</p>
<pre class="crayon-plain-tag">expression /^(?&lt;time&gt;.{30}) (?&lt;stream&gt;\w+) . (?&lt;log&gt;.*)$/ </pre>
</td>
</tr>
<tr>
<td>types</td>
<td>string</td>
<td>
<p>指定解析出的各字段的类型，如果不指定所有字段为string类型。格式：
<pre class="crayon-plain-tag">types &lt;field_name_1&gt;:&lt;type_name_1&gt;,&lt;field_name_2&gt;:&lt;type_name_2&gt;
# 示例
types user_id:integer,paid:bool,paid_usd_amount:float</pre>
<p>支持的类型：string、bool、integer、float、time、array</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">multiline</span></div>
<p>regexp的多行版本，支持将多行日志合并为一个事件，特别适用于解析异常栈。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 200px; text-align: center;">参数</td>
<td style="width: 70px; text-align: center;">类型</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>time_key</td>
<td>string</td>
<td>事件的发生时间字段，默认time</td>
</tr>
<tr>
<td>time_format</td>
<td>string</td>
<td>时间的格式</td>
</tr>
<tr>
<td>format_firstline</td>
<td>string</td>
<td>匹配多行日志事件的第一行的正则式</td>
</tr>
<tr>
<td>formatN</td>
<td>string</td>
<td>N可以是1-20，指定完整的日志事件格式</td>
</tr>
<tr>
<td>keep_time_key</td>
<td>string</td>
<td>是否在记录中保留时间字段，默认false</td>
</tr>
</tbody>
</table>
<p>Java异常日志的例子：</p>
<pre class="crayon-plain-tag">&lt;parse&gt;
  @type multiline
  # 识别新记录的正则式
  format_firstline /\d{4}-\d{1,2}-\d{1,2}/
  # 解析完整记录的正则式
  format1 /^(?&lt;time&gt;\d{4}-\d{1,2}-\d{1,2} \d{1,2}:\d{1,2}:\d{1,2}) \[(?&lt;thread&gt;.*)\] (?&lt;level&gt;[^\s]+)(?&lt;message&gt;.*)/
&lt;/parse&gt;

# 第一个记录
2013-3-03 14:27:33 [main] INFO  Main - Start
# 第二个记录
2013-3-03 14:27:33 [main] ERROR Main - Exception
javax.management.RuntimeErrorException: null
    at Main.main(Main.java:16) ~[bin/:na]
# 第三个记录
2013-3-03 14:27:33 [main] INFO  Main - End</pre>
<p>Rails日志的例子：</p>
<pre class="crayon-plain-tag">&lt;parse&gt;
  @type multiline
  # 识别新记录的正则式
  format_firstline /^Started/
  # 分为多个参数，每个参数对应一行日志信息
  format1 /Started (&lt;method&gt;[^ ]+) "(&lt;path&gt;[^"]+)" for (&lt;host&gt;[^ ]+) at (&lt;time&gt;[^ ]+ [^ ]+ [^ ]+)\n/
  format2 /Processing by (&lt;controller&gt;[^\u0023]+)\u0023(&lt;controller_method&gt;[^ ]+) as (&lt;format&gt;[^ ]+)\n/
  format3 /(  Parameters: (&lt;parameters&gt;[^ ]+)\n)/
  format4 /  Rendered (&lt;template&gt;[^ ]+) within (&lt;layout&gt;.+) \([\d\.]+ms\)\n/
  format5 /Completed (&lt;code&gt;[^ ]+) [^ ]+ in (&lt;runtime&gt;[\d\.]+)ms \(Views: (&lt;view_runtime&gt;[\d\.]+)ms \| ActiveRecord: (&lt;ar_runtime&gt;[\d\.]+)ms\)/
&lt;/parse&gt;

# 第一个记录
Started GET "/users/123/" for 127.0.0.1 at 2013-06-14 12:00:11 +0900
Processing by UsersController#show as HTML
  Parameters: {"user_id"=&gt;"123"}
  Rendered users/show.html.erb within layouts/application (0.3ms)
Completed 200 OK in 4ms (Views: 3.2ms | ActiveRecord: 0.0ms)</pre>
<div class="blog_h2"><span class="graybg">输出插件</span></div>
<p>在Fluentd 1.0，允许三种输出插件的缓冲/刷出模式：</p>
<ol>
<li>无缓冲模式，直接写出到外部系统</li>
<li>同步缓冲模式，使用缓冲块（事件集），缓冲块排队等候刷出。行为由buffer段控制</li>
<li>异步缓冲模式，类似2，但是输出插件在后台异步的提交请求给外部系统</li>
</ol>
<p>每个插件可以支持全部3种模式，也可以仅仅支持一种模式。如果<span style="background-color: #c0c0c0;">对不支持缓冲的插件配置buffer段，fluentd会出错并终止</span>。</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2018/01/fluentd-v0.14-plugin-api-overview.png"><img class="aligncenter  wp-image-23561" src="https://blog.gmem.cc/wp-content/uploads/2018/01/fluentd-v0.14-plugin-api-overview.png" alt="fluentd-v0-14-plugin-api-overview" width="912" height="472" /></a></p>
<div class="blog_h3"><span class="graybg">route</span></div>
<p>该插件非内置，执行<pre class="crayon-plain-tag">gem install fluent-plugin-route</pre>安装。</p>
<p><a href="https://github.com/tagomoris/fluent-plugin-route">fluent-plugin-route</a>支持修改标签，支持定义多个路由规则。可以实现事件<span style="background-color: #c0c0c0;">复制分发</span>：</p>
<pre class="crayon-plain-tag">&lt;match worker.**&gt;
  @type route
  # 修改标签前缀
  remove_tag_prefix worker
  add_tag_prefix metrics.event

  # 复制事件，走路由规则一
  &lt;route **&gt;
    copy # 不使用COPY会导致路由在此结束
  &lt;/route&gt;
  # 复制事件，走路由规则二
  &lt;route **&gt;
    copy
    @label @BACKUP
  &lt;/route&gt;
&lt;/match&gt;

# 路由规则一
&lt;match metrics.event.**&gt;
  @type stdout
&lt;/match&gt;

# 路由规则二
&lt;label @BACKUP&gt;
  &lt;match metrics.event.**&gt;
    @type file
    path /var/log/fluent/bakcup
  &lt;/match&gt;
&lt;/label&gt;</pre>
<div class="blog_h3"><span class="graybg">rewrite_tag_filter</span></div>
<p><a href="https://github.com/fluent/fluent-plugin-rewrite-tag-filter">fluent-plugin-rewrite-tag-filter</a>支持根据日志事件的内容来修改标签。示例：</p>
<pre class="crayon-plain-tag">&lt;match app.**&gt;
  @type rewrite_tag_filter
  # 根据内容的message字段，对齐进行正则式匹配，获取日志级别，捕获为$1，作为Tag的前缀
  rewriterule1 message ^\[(\w+)\] $1.${tag}
&lt;/match&gt;

# 错误消息路由到这里
&lt;match error.app.**&gt;
  @type mail
&lt;/match&gt;
# 其它消息路由到这里
&lt;match *.app.**&gt;
  @type file
&lt;/match&gt;</pre>
<div class="blog_h3"><span class="graybg">elasticsearch</span></div>
<p>将日志记录输出到ES中。插件参数：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 170px; text-align: center;">参数</td>
<td style="width: 70px; text-align: center;">类型</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>include_tag_key</td>
<td>bool</td>
<td>
<p>是否将事件的Fluentd Tag作为ES文档的字段存储</p>
<p>字段名默认为tag，可以用参数tag_key修改</p>
</td>
</tr>
<tr>
<td>logstash_format</td>
<td>bool</td>
<td>
<p>兼容Logstash格式的索引命名，<span style="background-color: #c0c0c0;">设置为true才能使用Kibana</span></p>
<p>如果设置为true自动忽视参数index_name，索引名称自动设置为：</p>
<p><pre class="crayon-plain-tag">#{logstash_prefix}-#{formated_date}</pre></p>
</td>
</tr>
<tr>
<td>time_key</td>
<td>string</td>
<td>默认情况下，@timestamp自动会自动设置为消费日志的时间，如果要修改此行为，通过该参数指定一个记录字段名</td>
</tr>
<tr>
<td>include_timestamp</td>
<td>bool</td>
<td>是否包含一个@timestamp字段到输出文档中</td>
</tr>
<tr>
<td>logstash_prefix</td>
<td>string</td>
<td>索引名前缀，默认logstash</td>
</tr>
<tr>
<td>logstash_dateformat</td>
<td>string</td>
<td>作为索引名后缀的日期格式</td>
</tr>
</tbody>
</table>
<p>示例：</p>
<pre class="crayon-plain-tag">&lt;match *.**&gt;
  @type elasticsearch
  host 10.0.10.2
  port 9200
  user logstash_system
  password logstash_system
  logstash_format true
  logstash_prefix openstack
  enable_ilm true
  index_date_pattern "now/m{yyyy.mm}"
  flush_interval 10s
&lt;/match&gt;</pre>
<div class="blog_h3"><span class="graybg">null</span></div>
<p>简单的丢弃事件，示例：</p>
<pre class="crayon-plain-tag">&lt;match fluent.**&gt;
    @type null
&lt;/match&gt;</pre>
<div class="blog_h3"><span class="graybg">kafka</span></div>
<p>将日志写入到Kafka主题中。</p>
<p>执行下面的命令安装此插件：</p>
<pre class="crayon-plain-tag">fluent-gem install fluent-plugin-kafka</pre>
<p>配置示例：</p>
<pre class="crayon-plain-tag">&lt;match pattern&gt;
  @type kafka_buffered

  # 种子代理列表
  brokers &lt;broker1_host&gt;:&lt;broker1_port&gt;,&lt;broker2_host&gt;:&lt;broker2_port&gt;

  # 缓冲设置
  buffer_type file
  buffer_path /var/log/td-agent/buffer/td
  flush_interval 3s

  # Kafka主题
  default_topic messages

  # 数据类型设置
  output_data_type json
  compression_codec gzip

  # 生产者配置
  max_send_retries 1
  required_acks -1

&lt;/match&gt;</pre>
<div class="blog_h3"><span class="graybg">relabel</span></div>
<p>此插件用于给事件重新打标签，例如：</p>
<pre class="crayon-plain-tag">&lt;match pattern&gt;
  @type relabel
  @label @foo
&lt;/match&gt;

&lt;label @foo&gt;
  &lt;match pattern&gt;
    ...
  &lt;/match&gt;
&lt;/label&gt;</pre>
<p>会给Tag匹配pattern的事件全部打上@foo标签，这些事件会全部交由名为@foo的label区段处理。</p>
<div class="blog_h3"><span class="graybg">file</span></div>
<p>将事件写入到文件。不是记录写入后立即就生成文件，只有<span style="color: #444444;">time_slice_format条件满足时文件才生成，默认情况下该插件每天生成一个文件。</span></p>
<p>插件参数：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 170px; text-align: center;">参数</td>
<td style="width: 70px; text-align: center;">类型</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>path</td>
<td>string</td>
<td>
<p>文件前缀，实际文件路径为<span style="background-color: #c0c0c0;">path + time + .log，</span>其中time取决于time_slice_format</p>
</td>
</tr>
<tr>
<td>append</td>
<td>bool</td>
<td>
<p>刷出的chunk是否覆盖到已经存在的文件。默认情况下每个chunk都输出到不同位置（即取值false）</p>
<p>不同取值对应的文件布局：</p>
<pre class="crayon-plain-tag"># append false
log.20140608_0.log
log.20140608_1.log
log.20140609_0.log
log.20140609_1.log

# append true
log.20140608.log
log.20140609.log </pre>
</td>
</tr>
<tr>
<td>format </td>
<td>string</td>
<td>输出文件格式 </td>
</tr>
<tr>
<td>time_format</td>
<td>string </td>
<td>日期写出格式 </td>
</tr>
<tr>
<td>compress</td>
<td>string</td>
<td>输出压缩算法，默认gzip</td>
</tr>
<tr>
<td>time_slice_format</td>
<td>string</td>
<td>
<p>用于文件名中time部分的、时间的格式化方式：
<p>%Y:  年度<br />%m: 月份 01-12<br />%d: 日期01-31<br />%H: 小时00-23<br />%M: 分钟00-59<br />%S: 秒00-60</p>
<p>默认取值 %Y%m%d%H ，也就是每小时一个文件</p>
</td>
</tr>
<tr>
<td>time_slice_wait</td>
<td>time</td>
<td>Fluentd等待迟到日志到达的最大时间，默认10m。用于处理事件到达fluentd节点有延迟的情况</td>
</tr>
<tr>
<td>flush_interval</td>
<td>time</td>
<td>刷出缓冲的间隔，默认60s</td>
</tr>
</tbody>
</table>
<p>一个实例：</p>
<pre class="crayon-plain-tag">&lt;label @K8S_OUT_FILE&gt;
  &lt;match **&gt;
    @type file
    # 目录 %Y-%m-%d.%H.${$.kubernetes.labels.application} 中会存放缓冲文件，每小时（3600）刷出
    # 文件名称示意 2018-11-14.17.account.log.gz，  log.gz自动添加，不需要在path中声明
    path  /var/log/kubernetes/%Y-%m-%d.%H.${$.kubernetes.labels.application}
    append true
    # 压缩文件可以这样浏览：gzip -dc 2018-11-14.17.account.log.gz  | grep ERROR
    compress gzip
    &lt;buffer time,$.kubernetes.labels.application&gt;
      timekey 3600
      timekey_wait 10
      timekey_zone +0800
    &lt;/buffer&gt;
    &lt;format&gt;
      @type single_value
      message_key log
    &lt;/format&gt;
  &lt;/match&gt;
&lt;/label&gt; </pre>
<div class="blog_h2"><span class="graybg">性能优化</span></div>
<div class="blog_h3"><span class="graybg">安装NTP</span></div>
<p>为了保证节点的时间戳精确，你需要安装NTP守护程序，例如chrony、ntpd。</p>
<div class="blog_h3"><span class="graybg">内核参数</span></div>
<p>文件描述符数量：</p>
<pre class="crayon-plain-tag">root soft nofile 65536
root hard nofile 65536
* soft nofile 65536
* hard nofile 65536</pre>
<p>对于高负载、多个Fluentd的环境，需要修改网络参数：</p>
<pre class="crayon-plain-tag">net.core.somaxconn = 1024
net.core.netdev_max_backlog = 5000
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216relabel
net.ipv4.tcp_wmem = 4096 12582912 16777216
net.ipv4.tcp_rmem = 4096 12582912 16777216
net.ipv4.tcp_max_syn_backlog = 8096
net.ipv4.tcp_slow_start_after_idle = 0
net.ipv4.tcp_tw_reuse = 1</pre>
<p>重启或者<pre class="crayon-plain-tag">sysctl -p</pre>生效。</p>
<div class="blog_h3"><span class="graybg">配置flush_thread_count</span></div>
<p>如果日志的目的地是远程设备、存储，可以使用该选项来并行化输出（默认1）。使用多线程可以缓和网络延迟的影响。</p>
<p>所有输出插件支持该参数。</p>
<div class="blog_h3"><span class="graybg">减少内存占用</span></div>
<p>Ruby提供了若干GC参数，你可以通过环境变量来设置它们。</p>
<p>要减少内存占用，可以设置<span style="color: #444444;">RUBY_GC_HEAP_OLDOBJECT_LIMIT_FACTOR为较小的值，此参数默认值2.0。资源受限环境下可以设置为1.2-</span></p>
<div class="blog_h3"><span class="graybg">多个Worker</span></div>
<p>处理10亿级别的日志输入时，CPU会出现瓶颈，这时考虑增加工作进程数量：</p>
<pre class="crayon-plain-tag">&lt;system&gt;
  workers 8
&lt;/system&gt;</pre>
<div class="blog_h1"><span class="graybg">Kibana</span></div>
<p>Kibana是一个开源的分析和可视化平台，必须配合ES一起使用。可以使用Kibana来检索、分析ES索引中的数据，日志分析是最常用的应用场景。</p>
<div class="blog_h2"><span class="graybg">日志查询</span></div>
<p>用于交互式的日志查询，使用Lucene查询语法：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 250px; text-align: center;">查询串示例</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>level:error</td>
<td>level字段包含单词error</td>
</tr>
<tr>
<td>level:(error OR warn)<br />level:(error warn)</td>
<td>
<p>level字段包含单词error或warn</p>
<p>操作符默认OR</p>
</td>
</tr>
<tr>
<td>message: "Connection Reset"</td>
<td>精确包含短语Connection Reset</td>
</tr>
<tr>
<td>user.\*:(alex)</td>
<td>user的任何字段包含alex</td>
</tr>
<tr>
<td>_exists_:title</td>
<td>title字段不为空 </td>
</tr>
<tr>
<td>level:e?r*</td>
<td>通配符：*匹配任意个数字符，?匹配单个字符 </td>
</tr>
<tr>
<td>name:/joh?n(ath[oa]n)/</td>
<td>支持正则式</td>
</tr>
<tr>
<td>quikc~1</td>
<td>模糊查询操作符~，可以匹配拼写错误的情况。1为距离，默认2，取值1可以捕获80%的拼写错误</td>
</tr>
<tr>
<td>age:&gt;10<br />age:&gt;=10<br />age:&lt;10<br />age:&lt;=10</td>
<td>比较操作符</td>
</tr>
<tr>
<td>(quick OR brown) AND fox</td>
<td>分组操作符</td>
</tr>
<tr>
<td>date:[2012-01-01 TO 2012-12-31]<br />count:[1 TO 5]</td>
<td>范围查询，闭区间</td>
</tr>
<tr>
<td>tag:{alpha TO omega}</td>
<td>范围查询，开区间（不包含首尾）</td>
</tr>
<tr>
<td>count:[10 TO *]</td>
<td>范围查询，无上限</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">可视化和仪表盘</span></div>
<p>Visualize用于设计一个小器件，例如饼图、曲线图。Dashboard则可以将小器件组合为仪表盘。</p>
<div class="blog_h3"><span class="graybg">可视化</span></div>
<p>这里以曲线图为例，点击曲线图的图标，首先要选择数据源，也就是索引。点选fluentd-*索引，看到如下界面：</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2018/01/visualize-1.png"><img class="aligncenter size-full wp-image-24133" src="https://blog.gmem.cc/wp-content/uploads/2018/01/visualize-1.png" alt="visualize-1" width="955" height="766" /></a></p>
<p>&nbsp;</p>
<p>点击顶部的Add a filter，可以添加过滤条件。</p>
<p>Metrics区域用于定义统计指标（度量），支持多个度量。</p>
<p>Buckets区域用于指定如何分组展示，示例：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 30%; text-align: center;">需求</td>
<td style="text-align: center;">配置步骤</td>
</tr>
</thead>
<tbody>
<tr>
<td>X轴显示时间，根据应用程序名称拆分Series</td>
<td>
<ol>
<li><span style="color: #333333; font-family: Ubuntu, sans-serif;"><span style="font-size: 13px; line-height: 22px;">点选X-Axis，聚合方式选择Date Histogram。可以设置X轴的标签</span></span></li>
<li>点击Add sub-bucket，选择Split Series</li>
<li>子聚合方式选择Terms，字段选择kubernetes.labels.app.keyword</li>
<li>点击做面板右上角的“播放”按钮，测试效果</li>
</ol>
</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">仪表盘</span></div>
<p>可以选取在“可视化“中定义的小器件，并拖拽以布局，效果示例：</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2018/01/visualize-2.png"><img class="aligncenter  wp-image-24141" src="https://blog.gmem.cc/wp-content/uploads/2018/01/visualize-2.png" alt="visualize-2" width="1015" height="568" /></a></p>
<div class="blog_h2"><span class="graybg">集群监控</span></div>
<div class="blog_h3"><span class="graybg">总体状态</span></div>
<p>首页是总体状态信息，包括ES、Kibana的健康状态，已用/可用的各类硬件资源信息：</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2018/01/es-monitoring-1.png"><img class="aligncenter size-full wp-image-24113" src="https://blog.gmem.cc/wp-content/uploads/2018/01/es-monitoring-1.png" alt="es-monitoring-1" width="930" height="775" /></a></p>
<p>如果Elasticsearch的健康状态为red，则说明集群存在问题。截图中的情况是主分片尚未分配到节点导致。</p>
<div class="blog_h3"><span class="graybg">ES状态概览</span></div>
<p>点击上图Elasticsearch区域的Overview连接，可以看到ES集群更多的信息：</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2018/01/es-monitoring-2.png"><img class="aligncenter  wp-image-24115" src="https://blog.gmem.cc/wp-content/uploads/2018/01/es-monitoring-2.png" alt="es-monitoring-2" width="1018" height="601" /></a>本页面显示ES的读（检索）、写（索引）性能指标，包括QPS和延迟。</p>
<p>Shard Activity通常是空的，上图中的情况是正在应用事务日志到ES数据节点。Translog是一种写前日志，记录所有针对ES的写操作。</p>
<div class="blog_h3"><span class="graybg">索引状态概览</span></div>
<p>此页面显示索引的列表，如果有红色说明索引存在问题。下图中第一个索引有一个分片不正常。</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2018/01/es-monitoring-3.png"><img class="aligncenter  wp-image-24117" src="https://blog.gmem.cc/wp-content/uploads/2018/01/es-monitoring-3.png" alt="es-monitoring-3" width="1026" height="399" /></a></p>
<p>每个索引包含的文档数量、占用的磁盘空间、读写速率也显示在页面上。</p>
<div class="blog_h3"><span class="graybg">索引状态详情</span></div>
<p>该页面显示单个索引文档数量、占用的磁盘空间、读写速率随时间的变化曲线，以及索引各分片的状态。</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2018/01/es-monitoring-4.png"><img class="aligncenter  wp-image-24119" src="https://blog.gmem.cc/wp-content/uploads/2018/01/es-monitoring-4.png" alt="es-monitoring-4" width="1015" height="722" /></a></p>
<p>上图中，es-data-0分配了序号为4的分片，并且此分片正在初始化。其它分配均正常。</p>
<div class="blog_h2"><span class="graybg">系统管理</span></div>
<div class="blog_h3"><span class="graybg">常用配置参数</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">参数</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>dateFormat</td>
<td>日期显示格式，例如MM-DD HH:mm:ss.SSS</td>
</tr>
<tr>
<td>truncate:maxHeight</td>
<td>检索时，每条日志占用的最大UI高度</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">集成K8S</span></div>
<p>捆绑了X-pack的ElasticSearch镜像：<a href="https://git.gmem.cc/alex/docker-elasticsearch">https://git.gmem.cc/alex/docker-elasticsearch</a></p>
<p>ElasticSearch+Kibana的Helm Chart：<a href="https://git.gmem.cc/alex/oss-charts/src/branch/master/elasticsearch">https://git.gmem.cc/alex/oss-charts/src/branch/master/elasticsearch</a></p>
<p>Fluentd的Helm Chart：<a href="https://git.gmem.cc/alex/oss-charts/src/branch/master/fluentd">https://git.gmem.cc/alex/oss-charts/src/branch/master/fluentd</a></p>
<div class="blog_h1"><span class="graybg">常见问题</span></div>
<div class="blog_h2"><span class="graybg">Fluentd</span></div>
<div class="blog_h3"><span class="graybg">极高IO</span></div>
<p>iotop看到fluentd进程有高达200M/s的读操作，但是定位不到针对的是什么文件</p>
<p>使用csysdig跟踪进程系统调用，发现大量内存映射操作，针对/var/log/journal/4f2be1039e944e028f2e86e02fe410e1目录，删除目录后问题消失。</p>
<div class="blog_h2"><span class="graybg">Kibana</span></div>
<div class="blog_h3"><span class="graybg">容器中节点CPU显示N/A</span></div>
<p>设置Kibana的环境变量XPACK_MONITORING_UI_CONTAINER_ELASTICSEARCH_ENABLED为false </p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/efk-as-a-log-analysis-system">基于EFK构建日志分析系统</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/efk-as-a-log-analysis-system/feed</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Log4J2学习笔记</title>
		<link>https://blog.gmem.cc/log4j2-study-note</link>
		<comments>https://blog.gmem.cc/log4j2-study-note#comments</comments>
		<pubDate>Wed, 18 Oct 2017 09:21:51 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Java]]></category>
		<category><![CDATA[LOG]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=16724</guid>
		<description><![CDATA[<p>简介 Apache Log4j 2是Log4j的重大升级， 引入了Logback包含的大量改进，并修复了Logback架构中某些内在的问题。Log4j2的特性包括： API和实现分离：接口更加稳定 性能提升：使用基于LMAX Disruptor库的异步日志机制。在多线程应用中，异步日志比Log4j、Logback有更低数量级的延迟时间，以及18倍的吞吐量 支持多种API：包括Log4j、SLF4J、Commons Logging 、java.util.logging 方便切换到其它日志实现：由于log4j-to-slf4j的存在，你可以随时切换使用别的日志库 配置自动重新载入：类似于Logback，当配置文件修改后，Log4j2能够自动重新载入。比Logback更好的时，不会因此丢失日志 高级过滤：类似于Logback，Log4j2支持基于上下文数据、Marker、正则式、或者其它日志事件中的组件进行过滤。过滤可以在日志事件传递给Logger之前、传递给Appender之前生效。Loggers可以和过滤器关联 支持Property：你可以在配置文件中使用属性，进行动态解析 Lambda支持：Log4j2支持Java8的Lambda，并且在日志级别不匹配的情况下不会主动解析Lambda，因而能提升性能 不产生内存垃圾：可以减轻JVM垃圾回收器的压力 API 代码示例 [crayon-69d8c67506e3d049052532/] 标记 日志框架的一个主要目的是，仅仅在需要的时候生成调试和诊断信息。并允许对信息进行过滤，避免加重系统负担或者难以阅读。Log4j2使用标记（Markers）实现满足这种需求： <a class="read-more" href="https://blog.gmem.cc/log4j2-study-note">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/log4j2-study-note">Log4J2学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h1"><span class="graybg">简介</span></div>
<p>Apache Log4j 2是Log4j的重大升级， 引入了Logback包含的大量改进，并修复了Logback架构中某些内在的问题。Log4j2的特性包括：</p>
<ol>
<li>API和实现分离：接口更加稳定</li>
<li>性能提升：使用基于LMAX Disruptor库的异步日志机制。在多线程应用中，异步日志比Log4j、Logback有更低数量级的延迟时间，以及18倍的吞吐量</li>
<li>支持多种API：包括Log4j、SLF4J、Commons Logging 、java.util.logging</li>
<li>方便切换到其它日志实现：由于log4j-to-slf4j的存在，你可以随时切换使用别的日志库</li>
<li>配置自动重新载入：类似于Logback，当配置文件修改后，Log4j2能够自动重新载入。比Logback更好的时，不会因此丢失日志</li>
<li>高级过滤：类似于Logback，Log4j2支持基于上下文数据、Marker、正则式、或者其它日志事件中的组件进行过滤。过滤可以在日志事件传递给Logger之前、传递给Appender之前生效。Loggers可以和过滤器关联</li>
<li>支持Property：你可以在配置文件中使用属性，进行动态解析</li>
<li>Lambda支持：Log4j2支持Java8的Lambda，并且在日志级别不匹配的情况下不会主动解析Lambda，因而能提升性能</li>
<li>不产生内存垃圾：可以减轻JVM垃圾回收器的压力</li>
</ol>
<div class="blog_h1"><span class="graybg">API</span></div>
<div class="blog_h2"><span class="graybg">代码示例</span></div>
<pre class="crayon-plain-tag">import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

// 获取日志记录器对象
Logger logger = LogManager.getLogger("HelloWorld");

// 参数替换
logger.debug("Logging in user {} with birthday {}", user.getName(), user.getBirthday());
// 参数数量没有限制
logger.debug( "Batch {} item {} : {}", batchId, i + 1, names );

// 参数格式化，语法类似于Java的Formatter
Logger logger = LogManager.getFormatterLogger("HelloWorld");
logger.debug("Logging in user %s with birthday %s", user.getName(), user.getBirthday());
// 混合使用日志记录器、格式化日志记录器：
Logger logger = LogManager.getLogger("HelloWorld");
logger.printf(Level.INFO, "Logging in user %1$s with birthday %2$tm", user.getName(), user.getBirthday());

// 使用Lambda实现延迟日志
// 立即执行expensiveOperation
if (logger.isTraceEnabled()) {
    logger.trace("Some long-running operation returned {}", expensiveOperation());
}
// 仅仅在必要时才执行expensiveOperation
logger.trace("Some long-running operation returned {}", () -&gt; expensiveOperation());</pre>
<div class="blog_h2"><span class="graybg">标记</span></div>
<p>日志框架的一个主要目的是，仅仅在需要的时候生成调试和诊断信息。并允许对信息进行过滤，避免加重系统负担或者难以阅读。Log4j2使用标记（Markers）实现满足这种需求：</p>
<pre class="crayon-plain-tag">Logger logger = LogManager.getLogger(HelloWorld.class.getName());
// 定义一个Marker
Marker SQL_MARKER = MarkerManager.getMarker("SQL");
// Marker可以形成树状层次
Marker UPDATE_MARKER = MarkerManager.getMarker("SQL_UPDATE").setParents(SQL_MARKER);
Marker QUERY_MARKER = MarkerManager.getMarker("SQL_QUERY").setParents(SQL_MARKER);

// 使用Marker
logger.debug(QUERY_MARKER, "SELECT * FROM {}", table);
logger.debug(UPDATE_MARKER, "UPDATE {} SET {}", table, formatCols());</pre>
<p>上面的代码配合MarkerFilters即可实现仅仅对SQL更新操作记录日志，或者记录所有SQL操作日志，或者记录全部应用日志。</p>
<p>使用Marker时，要注意以下规则：</p>
<ol>
<li>Marker必须是独特唯一的，起名字时要注意，名字是不是已经被依赖项使用了。当然，你特地要使用同样名字也是可以的</li>
<li>父Marker可以被动态的添加、移除。但是这种操作比较昂贵</li>
<li>具有多级祖先的Marker解析起来比较消耗资源 </li>
</ol>
<div class="blog_h2"><span class="graybg">流程跟踪</span></div>
<p>Logger类提供了一些方法，方便你跟踪应用程序的执行路径。这些方法会产生从其它调试日志中单独过滤掉的日志事件。你可以使用这些方法来：</p>
<ol>
<li>在开发阶段辅助问题诊断，而不需要单步调试</li>
<li>在运维阶段辅助问题诊断，如果单步调试不可用</li>
<li> 帮助新用户理解应用程序的行为</li>
</ol>
<p>流程跟踪的主要方法是<pre class="crayon-plain-tag">entry()</pre>、<pre class="crayon-plain-tag">traceEntry()</pre>和<pre class="crayon-plain-tag">exit()</pre>、 <pre class="crayon-plain-tag">traceExit()</pre>。它们都产生TRACE级别的日志。前两个方法通常放置在被跟踪方法的开始处，它们使用ENTER标记，此标记是FLOW的子标记。后两个方法通常放置在被跟踪方法的return语句之前，它们使用EXIT标记，此标记是FLOW的子标记。</p>
<p>此外，<pre class="crayon-plain-tag">throwing()</pre>方法可以在一个不太可能被处理的异常（例如RuntimeException）抛出时使用，确保必要的诊断信息可用。该方法产生ERROR级别的日志，使用THROWING标记，此标记是EXCEPTION的子标记。<pre class="crayon-plain-tag">catching()</pre>方法可以在捕获一个异常，并不准备重新抛出的情况下使用。该方法产生ERROR级别的日志，使用CATCHING标记，此标记是EXCEPTION的子标记。</p>
<p>示例代码：</p>
<pre class="crayon-plain-tag">logger.traceEntry(new JsonMessage(messages));
logger.traceExit();
try {...} catch (Exception ex) {
    logger.catching(ex);
} </pre>
<div class="blog_h1"><span class="graybg">配置</span></div>
<p>对Log4j2的配置可以通过四种方式之一完成：</p>
<ol>
<li>配置文件，支持的格式包括XML、JSON、YAML、Properties</li>
<li>编程式配置，创建ConfigurationFactory和Configuration实现类</li>
<li>编程式配置，调用Configuration接口暴露的API，对默认实现添加组件</li>
<li>编程式配置，调用Logger暴露的接口</li>
</ol>
<div class="blog_h2"><span class="graybg">自动配置</span></div>
<p>Log4j2能够在初始化时进行自动配置，它会定位到所有ConfigurationFactory并根据权重值对它们排序。开箱即用的ConfigurationFactory实现有4种，分别处理XML、JSON、YAML、Properties格式的配置文件。具体配置步骤如下：</p>
<ol>
<li>检查系统属性log4j.configurationFile，如果该属性存在，会尝试利用ConfigurationFactory加载匹配扩展名的文件</li>
<li>如果没有上述属性，尝试加载类路径下的log4j2-test.propertie</li>
<li>如果没有上述文件，尝试加载类路径下的log4j2-test.yaml或者log4j2-test.yml</li>
<li>如果没有上述文件，尝试加载类路径下的log4j2-test.json或者log4j2-test.jsn</li>
<li>如果没有上述文件，尝试加载类路径下的log4j2-test.xml</li>
<li>如果没有上述文件，一次尝试加载log4j2.*版本（没有-test后缀）的配置文件</li>
<li>如果找不到任何配置文件，使用DefaultConfiguration。此配置直接打印日志到stdout</li>
</ol>
<div class="blog_h3"><span class="graybg">默认配置</span></div>
<p>此配置行为如下：</p>
<ol>
<li>根日志级别为ERROR</li>
<li>将ConsoleAppender附加到根日志记录器</li>
<li>将PatternLayout：<pre class="crayon-plain-tag">%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n</pre>附加到ConsoleAppender</li>
</ol>
<p>以XML形式表示默认配置：</p>
<pre class="crayon-plain-tag">&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;Configuration status="WARN"&gt;
  &lt;Appenders&gt;
    &lt;Console name="Console" target="SYSTEM_OUT"&gt;
      &lt;PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/&gt;
    &lt;/Console&gt;
  &lt;/Appenders&gt;
  &lt;Loggers&gt;
    &lt;Root level="error"&gt;
      &lt;AppenderRef ref="Console"/&gt;
    &lt;/Root&gt;
  &lt;/Loggers&gt;
&lt;/Configuration&gt;</pre>
<div class="blog_h2"><span class="graybg">XML配置</span></div>
<div class="blog_h3"><span class="graybg">整体结构</span></div>
<pre class="crayon-plain-tag">&lt;?xml version="1.0" encoding="UTF-8"?&gt;;
&lt;Configuration&gt;
  &lt;Properties&gt;
    &lt;Property name="name1"&gt;value&lt;/property&gt;
    &lt;Property name="name2" value="value2"/&gt;
  &lt;/Properties&gt;
  &lt;Filter type="type" ... /&gt;
  &lt;Appenders&gt;
    &lt;Appender type="type" name="name"&gt;
      &lt;Filter type="type" ... /&gt;
    &lt;/Appender&gt;
    ...
  &lt;/Appenders&gt;
  &lt;Loggers&gt;
    &lt;Logger name="name1"&gt;
      &lt;Filter type="type" ... /&gt;
    &lt;/Logger&gt;
    ...
    &lt;Root level="level"&gt;
      &lt;AppenderRef ref="name"/&gt;
    &lt;/Root&gt;
  &lt;/Loggers&gt;
&lt;/Configuration&gt;</pre>
<div class="blog_h3"><span class="graybg">配置示例</span></div>
<pre class="crayon-plain-tag">&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;!--
    status Log4j2内部事件的日志级别
    strict 是否使用严格XML格式
    name 配置的名称
    packages 此哪些包中加载插件，逗号分隔多个包
    monitorInterval  检查配置文件变更的间隔，单位秒，如果发现变更则重新加载配置
--&gt;
&lt;Configuration status="debug" strict="true" name="XMLConfigTest" monitorInterval="10" packages="org.apache.logging.log4j.test"&gt;
    &lt;!-- 定义属性，属性可以在后续使用${} 引用 --&gt;
    &lt;!--
        Log4j2支持很多预定义的上下文属性对象。可以通过 ${prefix:name} 语法引用
        bundle：资源束对象，用法示例 ${bundle:BundleName:BundleKey}
        ctx：线程上下文映射（Thread Context Map，MDC）
        date：当前时间或者日期
        env：系统环境变量
        jndi：JNDI上下文
        jvmrunargs：JVM参数
        sys：系统属性
    --&gt;
    &lt;Properties&gt;
        &lt;Property name="filename"&gt;target/test.log&lt;/Property&gt;
    &lt;/Properties&gt;
    &lt;!--
        过滤器可以出现在四种地方：
        1、全局级别，可以在日志事件传递给日志记录器（LoggerConfig）之前接受/拒绝它
        2、logger元素内部，针对单个日志记录器
        3、appender元素内部，针对单个Appender
        4、AppenderRef元素内部，针对日志记录器+Appender的组合
    --&gt;
    &lt;!-- 全局过滤器 --&gt;
    &lt;Filter type="ThresholdFilter" level="trace"/&gt;

    &lt;!-- Appender负责向外输出日志内容 --&gt;
    &lt;Appenders&gt;
        &lt;!-- 基于控制台输出 --&gt;
        &lt;Appender type="Console" name="STDOUT"&gt;
            &lt;!-- 日志输出格式 --&gt;
            &lt;Layout type="PatternLayout" pattern="%m MDC%X%n"/&gt;
            &lt;!-- 针对Appender的过滤器 --&gt;
            &lt;Filters&gt;
                &lt;!-- 如果日志事件使用标记FLOW，则拒绝输出日志 --&gt;
                &lt;Filter type="MarkerFilter" marker="FLOW" onMatch="DENY" onMismatch="NEUTRAL"/&gt;
                &lt;Filter type="MarkerFilter" marker="EXCEPTION" onMatch="DENY" onMismatch="ACCEPT"/&gt;
            &lt;/Filters&gt;
        &lt;/Appender&gt;
        &lt;Appender type="Console" name="FLOW"&gt;
            &lt;!--
                日志格式（布局），支持的布局：
                CSV布局，包括子类CsvParameterLayout、CsvLogEventLayout
                GELF布局，将JSON压缩为GZIP/ZLIB格式
                HTML布局，生成HTML页面
                JSON布局，生成一系列JSON字符串
                Pattern布局，自定义格式对LogEvent进行格式化
                RFC5424布局
                Syslog布局，格式化为BSD Syslog记录
                XML布局
                YAML布局
            --&gt;
            &lt;Layout type="PatternLayout" pattern="%C{1}.%M %m %ex%n"/&gt;
            &lt;Filters&gt;
                &lt;Filter type="MarkerFilter" marker="FLOW" onMatch="ACCEPT" onMismatch="NEUTRAL"/&gt;
                &lt;Filter type="MarkerFilter" marker="EXCEPTION" onMatch="ACCEPT" onMismatch="DENY"/&gt;
            &lt;/Filters&gt;
        &lt;/Appender&gt;
        &lt;!-- 基于文件输出 --&gt;
        &lt;Appender type="File" name="File" fileName="${filename}"&gt;
            &lt;Layout type="PatternLayout"&gt;
                &lt;Pattern&gt;%d %p %C{1.} [%t] %m%n&lt;/Pattern&gt;
            &lt;/Layout&gt;
        &lt;/Appender&gt;
    &lt;/Appenders&gt;
    &lt;!-- 日志记录器 --&gt;
    &lt;Loggers&gt;
        &lt;!--
            level 此日志记录器最低输出级别，大小写不敏感
                  支持TRACE, DEBUG, INFO, WARN, ERROR, ALL, OFF。默认ERROR
            additivity：false表示日志事件被此记录器处理过后，不会传递给父记录器再次处理
        --&gt;
        &lt;Logger name="org.apache.logging.log4j.test1" level="debug" additivity="false"&gt;
            &lt;!-- 针对日志记录器的过滤器 --&gt;
            &lt;Filter type="ThreadContextMapFilter"&gt;
                &lt;KeyValuePair key="test" value="123"/&gt;
            &lt;/Filter&gt;
            &lt;!-- 日志记录器关联哪个Appender --&gt;
            &lt;AppenderRef ref="STDOUT"/&gt;
        &lt;/Logger&gt;

        &lt;Logger name="org.apache.logging.log4j.test2" level="debug" additivity="false"&gt;
            &lt;AppenderRef ref="File"/&gt;
        &lt;/Logger&gt;
        &lt;!-- 根记录器，每个配置都必须有一个根记录器 --&gt;
        &lt;Root level="trace"&gt;
            &lt;AppenderRef ref="STDOUT"/&gt;
        &lt;/Root&gt;
    &lt;/Loggers&gt;

&lt;/Configuration&gt;</pre>
<div class="blog_h2"><span class="graybg">YAML配置</span></div>
<div class="blog_h3"><span class="graybg">额外依赖</span></div>
<pre class="crayon-plain-tag">&lt;dependency&gt;
    &lt;groupId&gt;com.fasterxml.jackson.dataformat&lt;/groupId&gt;
    &lt;artifactId&gt;jackson-dataformat-yaml&lt;/artifactId&gt;
    &lt;version&gt;2.5.0&lt;/version&gt;
&lt;/dependency&gt; </pre>
<div class="blog_h3"><span class="graybg">配置示例一</span></div>
<p>和XML配置的结构大体对应：</p>
<pre class="crayon-plain-tag">Configuration:
  status: warn
  name: YAMLConfigTest
  properties:
    property:
      name: filename
      value: target/test-yaml.log
  thresholdFilter:
    level: debug
  appenders:
    Console:
      name: STDOUT
      PatternLayout:
        Pattern: "%m%n"
    File:
      name: File
      fileName: ${filename}
      PatternLayout:
        Pattern: "%d %p %C{1.} [%t] %m%n"
      Filters:
        ThresholdFilter:
          level: error
 
  Loggers:
    logger:
      -
        name: org.apache.logging.log4j.test1
        level: debug
        additivity: false
        ThreadContextMapFilter:
          KeyValuePair:
            key: test
            value: 123
        AppenderRef:
          ref: STDOUT
      -
        name: org.apache.logging.log4j.test2
        level: debug
        additivity: false
        AppenderRef:
          ref: File
    Root:
      level: error
      AppenderRef:
        ref: STDOUT </pre>
<div class="blog_h3"><span class="graybg">配置示例二</span></div>
<p>下面的配置参考Spring Boot的输出格式：</p>
<pre class="crayon-plain-tag">Configuration:
  name: development
  monitorInterval: 5
  thresholdFilter:
    level: trace
  appenders:
    Console:
      name: stdout
      PatternLayout:
        # %d{yyyy-MM-dd HH:mm:ss.SSS} %highlight{%-5p} %style{%t}{Magenta} %style{%c{1.}.%M}{Cyan}(%F:%L)%n%m%n
        Pattern: "%d{yyyy-MM-dd HH:mm:ss.SSS} %highlight{%5p} ${hostName} --- [%15.15t] %-40.40c{1.} : %m%n%ex"
  Loggers:
    logger:
      -
        name: cc.gmem.study.log4j2
        level: debug
        additivity: false
        AppenderRef:
          ref: stdout
    Root:
      level: error
      AppenderRef:
        ref: stdout</pre>
<div class="blog_h2"><span class="graybg">Pattern布局</span></div>
<p>这是最常用的Layout，本节详细讨论它。</p>
<div class="blog_h3"><span class="graybg">参数</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">参数名</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>charset</td>
<td>输出字符串为字节数组时使用的编码方式，如果不指定使用平台默认编码</td>
</tr>
<tr>
<td>pattern</td>
<td>指定如何转换LogEvent为字符串的模式</td>
</tr>
<tr>
<td>patternSelector</td>
<td>类型PatternSelector。根据LogEvent选择一种Pattern，不得和pattern参数同时指定</td>
</tr>
<tr>
<td>alwaysWriteExceptions</td>
<td>类型Boolean。如果你不再pattern中指定异常输出，则使用默认异常格式，附加在Pattern尾部</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">Pattern</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">%项</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>c{precision}</td>
<td>输出日志记录器的名称，可选的指定一个精度：<br />%c{1}将cc.gmem.study.log4j2.Application格式化为：Application<br />%c{2}将cc.gmem.study.log4j2.Application格式化为：log4j2.Application<br />%c{1.}将cc.gmem.study.log4j2.Application格式化为：c.g.s.l.Application</td>
</tr>
<tr>
<td>C{precision}</td>
<td>输出发起日志记录请求的那个类的全限定名称</td>
</tr>
<tr>
<td>d{pattern}</td>
<td>输出当前日期时间：<br />%d{DEFAULT}：2012-11-02 14:34:02,781<br />%d{yyyy-MM-dd HH:mm:ss,SSS}：2012-11-02 14:34:02,781<br />%d{UNIX}：1351866842<br />%d{UNIX_MILLIS}：1351866842781</td>
</tr>
<tr>
<td>highlight{pattern}{style}</td>
<td>
<p>添加ANSI颜色高亮。默认针对各种级别的高亮颜色为：</p>
<p style="padding-left: 30px;"><strong><span style="color: #ff0000;">FATAL</span></strong>、<span style="color: #ff0000;"><strong>ERROR</strong></span>、<strong><span style="color: #ff9900;">WARN</span></strong>、<span style="color: #00ff00;"><strong>INFO</strong></span>、<span style="color: #00ffff;"><strong>DEBUG</strong></span>、<span style="color: #000000;"><strong>TRACE</strong></span></p>
<p>pattern为需要高亮的其它Pattern字段</p>
<p>style取值可以是Default或者Logback。后者亮度较高</p>
</td>
</tr>
<tr>
<td> K{key}</td>
<td> 用于输出MapMessage中的条目</td>
</tr>
<tr>
<td> l</td>
<td>输出调用者的位置信息，包括方法名和行号 </td>
</tr>
<tr>
<td>L </td>
<td>输出调用发起处的行号</td>
</tr>
<tr>
<td>M</td>
<td>输出调用发起处的方法名</td>
</tr>
<tr>
<td>marker</td>
<td>输出Marker的完整名称，如果由父Marker也输出</td>
</tr>
<tr>
<td>n</td>
<td>输出平台相关的换行符</td>
</tr>
<tr>
<td>N</td>
<td>输出System.nanoTime()</td>
</tr>
<tr>
<td>pid{[defaultValue]}</td>
<td>输出PID</td>
</tr>
<tr>
<td>p</td>
<td>
<p>输出日志的级别，示例：</p>
<p>%level{WARN=Warning, DEBUG=Debug, ERROR=Error, TRACE=Trace, INFO=Info}<br />%level{WARN=W, DEBUG=D, ERROR=E, TRACE=T, INFO=I}</p>
</td>
</tr>
<tr>
<td>r</td>
<td>输出自JVM启动依赖流逝的毫秒数</td>
</tr>
<tr>
<td>sn</td>
<td>输出此日志事件的序列号</td>
</tr>
<tr>
<td>style{pattern}{ANSI style}</td>
<td>定制输出样式</td>
</tr>
<tr>
<td>T</td>
<td>输出当前线程ID</td>
</tr>
<tr>
<td>t</td>
<td>输出当前线程名称</td>
</tr>
<tr>
<td>tp</td>
<td>输出当前线程优先级</td>
</tr>
<tr>
<td>u{"RANDOM" | "TIME"}</td>
<td>输出基于时间产生的UUID</td>
</tr>
<tr>
<td>%</td>
<td>输出%</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">JSON布局</span></div>
<div class="blog_h3"><span class="graybg">额外依赖</span></div>
<pre class="crayon-plain-tag">&lt;dependency&gt;
    &lt;groupId&gt;com.fasterxml.jackson.core&lt;/groupId&gt;
    &lt;artifactId&gt;jackson-databind&lt;/artifactId&gt;
    &lt;version&gt;2.5.0&lt;/version&gt;
&lt;/dependency&gt; </pre>
<div class="blog_h3"><span class="graybg">参数</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">参数</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>charset</td>
<td>string=UTF-8，转换字节数组时使用的字符集</td>
</tr>
<tr>
<td>compact</td>
<td>bool=false，是否紧凑输出，如果true，则不进行缩进</td>
</tr>
<tr>
<td>eventEol</td>
<td>bool=false，是否在记录后添加换行符</td>
</tr>
<tr>
<td>complete</td>
<td>bool=false，如果true，则输出JSON眉、脚，并且在记录之间使用逗号分隔</td>
</tr>
<tr>
<td>properties</td>
<td>bool=false，如果true，则输出线程上下文映射到JSON</td>
</tr>
<tr>
<td>locationInfo</td>
<td>bool=false，如果true，则输出代码位置信息</td>
</tr>
<tr>
<td>includeStacktrace</td>
<td>bool=true，如果true，则输出异常栈信息</td>
</tr>
<tr>
<td>stacktraceAsString</td>
<td>bool=false，如果true，则将异常栈格式化为字符串，而非内嵌对象</td>
</tr>
<tr>
<td>includeNullDelimiter</td>
<td>bool=false，是否在每个记录后添加NIL字符</td>
</tr>
<tr>
<td>objectMessageAsJsonObject</td>
<td>bool=false，是否将对象类型的信息格式化为JSON对象</td>
</tr>
</tbody>
</table>
<p>配置示例（请使用最新版本的Log4j2）：</p>
<pre class="crayon-plain-tag">status = error

appender.console.type = Console
appender.console.name = console
appender.console.layout.type = JsonLayout
appender.console.layout.charset = UTF-8
appender.console.layout.compact=false
appender.console.layout.eventEol=true
appender.console.layout.locationInfo=false
appender.console.layout.stacktraceAsString=true

rootLogger.level = info
rootLogger.appenderRef.console.ref = console </pre>
<div class="blog_h3"><span class="graybg">额外字段</span></div>
<p>可以使用KeyValuePair指定额外的输出字段：</p>
<pre class="crayon-plain-tag">&lt;JsonLayout&gt;
  &lt;!-- 常量值--&gt;
  &lt;KeyValuePair key="additionalField1" value="constant value"/&gt;
  &lt;!-- 访问上下文变量 --&gt;
  &lt;KeyValuePair key="additionalField2" value="$${ctx:key}"/&gt;
  &lt;!-- 输出当前时间 --&gt;
  &lt;KeyValuePair key="timestamp" value="$${date:yyyy-MM-dd'T'HH:mm:ss.SSSZ}" /&gt;
&lt;/JsonLayout&gt;</pre>
<div class="blog_h3"><span class="graybg">安全策略</span></div>
<p>在启用安全管理器的情况下，你需要进行如下授权：</p>
<pre class="crayon-plain-tag">grant {
  permission java.lang.RuntimePermission "accessDeclaredMembers";
  permission java.lang.reflect.ReflectPermission "suppressAccessChecks";
};</pre>
<div class="blog_h1"><span class="graybg">集成</span></div>
<div class="blog_h2"><span class="graybg">和Slf4J联用</span></div>
<p>Maven依赖配置如下：</p>
<pre class="crayon-plain-tag">&lt;!-- 使用Log4J2--&gt;
&lt;dependency&gt;
    &lt;groupId&gt;org.apache.logging.log4j&lt;/groupId&gt;
    &lt;artifactId&gt;log4j-api&lt;/artifactId&gt;
    &lt;version&gt;2.8.2&lt;/version&gt;
&lt;/dependency&gt;
&lt;dependency&gt;
    &lt;groupId&gt;org.apache.logging.log4j&lt;/groupId&gt;
    &lt;artifactId&gt;log4j-core&lt;/artifactId&gt;
    &lt;version&gt;2.8.2&lt;/version&gt;
&lt;/dependency&gt;
&lt;!-- 让commons logging使用Log4j2 --&gt;
&lt;dependency&gt;
    &lt;groupId&gt;org.apache.logging.log4j&lt;/groupId&gt;
    &lt;artifactId&gt;log4j-jcl&lt;/artifactId&gt;
    &lt;version&gt;2.8.2&lt;/version&gt;
&lt;/dependency&gt;
&lt;dependency&gt;
    &lt;groupId&gt;org.slf4j&lt;/groupId&gt;
    &lt;artifactId&gt;slf4j-api&lt;/artifactId&gt;
    &lt;version&gt;1.7.21&lt;/version&gt;
&lt;/dependency&gt;
&lt;!-- 让Slf4j使用Log4j2 --&gt;
&lt;dependency&gt;
    &lt;groupId&gt;org.apache.logging.log4j&lt;/groupId&gt;
    &lt;artifactId&gt;log4j-slf4j-impl&lt;/artifactId&gt;
    &lt;version&gt;2.8.2&lt;/version&gt;
&lt;/dependency&gt; </pre>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/log4j2-study-note">Log4J2学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/log4j2-study-note/feed</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>C++日志组件spdlog</title>
		<link>https://blog.gmem.cc/spdlog</link>
		<comments>https://blog.gmem.cc/spdlog#comments</comments>
		<pubDate>Tue, 12 Sep 2017 06:49:38 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[C++]]></category>
		<category><![CDATA[LOG]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=16071</guid>
		<description><![CDATA[<p>基础 spdlog是基于C++ 11的日志组件，它非常轻量，使用时你仅仅需要引入头文件就可以了。 线程安全 名字空间[crayon-69d8c67507447292087521-i/]之下的大多数函数都是线程安全的，除了： [crayon-69d8c6750744b179210803/] 日志器对象的大部分方法也是线程安全的，除了： [crayon-69d8c6750744d218828217/] 所有以_mt结尾的SINK都是线程安全的，以_st结尾的则不是。  代码示例 [crayon-69d8c6750744f106868525/] 输出格式 spdlog默认的输出格式为： [crayon-69d8c67507452777782915/] 要定制输出格式，可以调用： [crayon-69d8c67507455273287451/] 或者实现自己的格式化器： [crayon-69d8c67507457145650895/] Pattern说明 输出格式的Pattern中可以有若干[crayon-69d8c67507459222702246-i/]开头的标记，含义如下表： 标记 说明 <a class="read-more" href="https://blog.gmem.cc/spdlog">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/spdlog">C++日志组件spdlog</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h2"><span class="graybg">基础</span></div>
<p><a href="https://github.com/gabime/spdlog">spdlog</a>是基于C++ 11的日志组件，它非常轻量，使用时你仅仅需要引入头文件就可以了。</p>
<div class="blog_h3"><span class="graybg">线程安全</span></div>
<p>名字空间<pre class="crayon-plain-tag">spdlog::</pre>之下的大多数函数都是线程安全的，除了：</p>
<pre class="crayon-plain-tag">void spdlog::set_pattern(const std::string&amp;);
void spdlog::set_formatter(formatter_ptr);
void spdlog::set_error_handler(log_err_handler);</pre>
<p>日志器对象的大部分方法也是线程安全的，除了：</p>
<pre class="crayon-plain-tag">void spdlog::logger::set_pattern(const std::string&amp;);
void spdlog::logger::set_formatter(formatter_ptr);
void spdlog::set_error_handler(log_err_handler);</pre>
<p>所有以_mt结尾的SINK都是线程安全的，以_st结尾的则不是。 </p>
<div class="blog_h3"><span class="graybg">代码示例</span></div>
<pre class="crayon-plain-tag">#include "spdlog/spdlog.h"
#include &lt;iostream&gt;

// 多线程的基于控制台（stdout）的日志记录器，支持高亮。类似的stdout_color_st是单线程版本
auto console = spdlog::stdout_color_mt( "console" );
// 基于文件的简单日志
auto logger = spdlog::basic_logger_mt("basic_logger", "logs/basic.txt");
// 基于滚动文件的日志，每个文件5MB，三个文件
auto logger = spdlog::rotating_logger_mt("file_logger", "myfilename", 1024 * 1024 * 5, 3);

// 定制输出格式
spdlog::set_pattern("*** [%H:%M:%S %z] [thread %t] %v ***");

// 多个日志器共享SINK
auto daily_sink = std::make_shared&lt;spdlog::sinks::daily_file_sink_mt&gt;("logfile", 23, 59);
// 下面几个同步日志器共享的输出到目标文件
auto net_logger = std::make_shared&lt;spdlog::logger&gt;("net", daily_sink);
auto hw_logger = std::make_shared&lt;spdlog::logger&gt;("hw", daily_sink);
auto db_logger = std::make_shared&lt;spdlog::logger&gt;("db", daily_sink); 

// 一个日志器使用多个SINK
std::vector&lt;spdlog::sink_ptr&gt; sinks;
sinks.push_back( std::make_shared&lt;spdlog::sinks::stdout_sink_st&gt;());
sinks.push_back( std::make_shared&lt;spdlog::sinks::daily_file_sink_st&gt;( "logfile", 23, 59 ));
auto combined_logger = std::make_shared&lt;spdlog::logger&gt;( "name", begin( sinks ), end( sinks ));
spdlog::register_logger( combined_logger );

// 异步
// 每个日志器分配8192长度的队列，队列长度必须2的幂
spdlog::set_async_mode(8192); 
// 程序退出前清理
spdlog::drop_all();

// 注册日志器
spdlog::register_logger(net_logger);
// 注册后，其它代码可以根据名称获得日志器
auto logger = spdlog::get(net_logger);

// 记录日志
// 设置最低级别
console-&gt;set_level(spdlog::level::debug);
console-&gt;debug("Hello World") ;
// 使用占位符
console-&gt;info("Hello {}" ,"World"); 
// 带格式化的占位符：d整数，x十六进制，o八进制，b二进制                
console-&gt;warn("Support for int: {0:d}; hex: {0:x}; oct: {0:o}; bin: {0:b}", 42);
// 带格式化的占位符：f浮点数
console-&gt;info("Support for floats {:03.2f}", 1.23456);
// 左对齐，保证30字符宽度
console-&gt;error("{:&lt;30}", "left aligned");
// 指定占位符位置序号
console-&gt;info("Positional args are {1} {0}..", "too", "supported");

// 记录自定义类型，需要重载&lt;&lt;操作符
#include &lt;spdlog/fmt/ostr.h&gt; 
class Duck{}
std::ostream&amp; operator&lt;&lt;(std::ostream&amp; os, const Duck&amp; duck){ 
    return os &lt;&lt; duck.getName(); 
}
Duck duck;
console-&gt;info("custom class with operator&lt;&lt;: {}..", duck);</pre>
<div class="blog_h2"><span class="graybg">输出格式</span></div>
<p>spdlog默认的输出格式为：</p>
<pre class="crayon-plain-tag">[2014-31-10 23:46:59.678] [info] [my_loggername] Some message</pre>
<p>要定制输出格式，可以调用：</p>
<pre class="crayon-plain-tag">spdlog::set_pattern(pattern_string);
// 示例
spdlog::set_pattern("*** [%H:%M:%S %z] [thread %t] %v ***");</pre>
<p>或者实现自己的格式化器：</p>
<pre class="crayon-plain-tag">spdlog::set_formatter(std::make_shared&lt;my_custom_formatter&gt;());</pre>
<div class="blog_h3"><span class="graybg">Pattern说明</span></div>
<p>输出格式的Pattern中可以有若干<pre class="crayon-plain-tag">%</pre>开头的标记，含义如下表：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 15%; text-align: center;">标记</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>%v</td>
<td>实际需要被日志记录的文本，如果文本中有{占位符}会被替换</td>
</tr>
<tr>
<td>%t</td>
<td>线程标识符</td>
</tr>
<tr>
<td>%P</td>
<td>进程标识符</td>
</tr>
<tr>
<td>%n</td>
<td>日志记录器名称</td>
</tr>
<tr>
<td>%l</td>
<td>日志级别</td>
</tr>
<tr>
<td>%L</td>
<td>日志级别简写</td>
</tr>
<tr>
<td>%a</td>
<td>简写的周几，例如Thu</td>
</tr>
<tr>
<td>%A</td>
<td>周几，例如Thursday</td>
</tr>
<tr>
<td>%b</td>
<td>简写的月份，例如Aug</td>
</tr>
<tr>
<td>%B</td>
<td>月份，例如August</td>
</tr>
<tr>
<td>%c</td>
<td>日期时间，例如Thu Aug 23 15:35:46 2014</td>
</tr>
<tr>
<td>%C</td>
<td>两位年份，例如14</td>
</tr>
<tr>
<td>%Y</td>
<td>四位年份，例如2014</td>
</tr>
<tr>
<td>%D 或 %x</td>
<td>MM/DD/YY格式日期，例如"08/23/14</td>
</tr>
<tr>
<td>%m</td>
<td>月份，1-12之间</td>
</tr>
<tr>
<td>%d</td>
<td>月份中的第几天，1-31之间</td>
</tr>
<tr>
<td>%H</td>
<td>24小时制的小时，0-23之间</td>
</tr>
<tr>
<td>%I</td>
<td>12小时制的小时，1-12之间</td>
</tr>
<tr>
<td>%M</td>
<td>分钟，0-59</td>
</tr>
<tr>
<td>%S</td>
<td>秒，0-59</td>
</tr>
<tr>
<td>%e</td>
<td>当前秒内的毫秒，0-999</td>
</tr>
<tr>
<td>%f</td>
<td>当前秒内的微秒，0-999999</td>
</tr>
<tr>
<td>%F</td>
<td>当前秒内的纳秒， 0-999999999</td>
</tr>
<tr>
<td>%p</td>
<td>AM或者PM</td>
</tr>
<tr>
<td>%r</td>
<td>12小时时间，例如02:55:02 pm</td>
</tr>
<tr>
<td>%R</td>
<td>等价于%H:%M，例如23:55</td>
</tr>
<tr>
<td>%T 或 %X</td>
<td>HH:MM:SS</td>
</tr>
<tr>
<td>%z</td>
<td>时区UTC偏移，例如+02:00</td>
</tr>
<tr>
<td>%+</td>
<td>表示默认格式</td>
</tr>
</tbody>
</table>
<p>&nbsp;</p>
<p>&nbsp;</p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/spdlog">C++日志组件spdlog</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/spdlog/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>使用log4jdbc记录SQL语句的执行情况</title>
		<link>https://blog.gmem.cc/log4jdbc</link>
		<comments>https://blog.gmem.cc/log4jdbc#comments</comments>
		<pubDate>Fri, 11 Jan 2013 01:48:21 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Java]]></category>
		<category><![CDATA[LOG]]></category>
		<category><![CDATA[sqlinform]]></category>

		<guid isPermaLink="false">http://blog.gmem.cc/?p=1200</guid>
		<description><![CDATA[<p>在进行数据库开发时，我们经常需要监测SQL语句的执行情况，一般的手工编码记录、Hibernate日志记录，有如下的缺点： 人工记录太麻烦，需要写很多日志记录语句 无法获取PreparedStatement的传入参数，只能显示为"?" &#160; log4jdbc这个小组件，恰恰解决了这些问题，其官网地址为：log4jdbc网站 该组件具有针对jdbc3、jdbc4的不同版本，如果你使用JDK1.4、1.5，可以使用log4jdbc3，使用JDK1.6以上，可以使用log4jdbc4。同时，log4jdbc需要日志记录框架的支持，建议使用SLF4J和Log4j的组合。 使用log4jdbc时，你需要更改数据源的JDBC URL，添加前缀：jdbc:log4jdbc:，同时，要把JDBC驱动类改为net.sf.log4jdbc.DriverSpy。例如，对于MySQL，情况可能像下面这样： [crayon-69d8c67507766765554752/] log4jdbc根据JDBC URL猜测你用的是哪一种数据库，并按下表确定真实的JDBC驱动类 驱动类名 数据库类型 oracle.jdbc.driver.OracleDriver Older Oracle Driver oracle.jdbc.OracleDriver Newer Oracle Driver com.sybase.jdbc2.jdbc.SybDriver <a class="read-more" href="https://blog.gmem.cc/log4jdbc">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/log4jdbc">使用log4jdbc记录SQL语句的执行情况</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><p>在进行数据库开发时，我们经常需要监测SQL语句的执行情况，一般的手工编码记录、Hibernate日志记录，有如下的缺点：</p>
<ol>
<li>人工记录太麻烦，需要写很多日志记录语句</li>
<li>无法获取PreparedStatement的传入参数，只能显示为"?"</li>
</ol>
<p>&nbsp;</p>
<p>log4jdbc这个小组件，恰恰解决了这些问题，其官网地址为：<a href="http://code.google.com/p/log4jdbc/" target="_blank">log4jdbc网站</a><br />
该组件具有针对jdbc3、jdbc4的不同版本，如果你使用JDK1.4、1.5，可以使用log4jdbc3，使用JDK1.6以上，可以使用log4jdbc4。同时，log4jdbc需要日志记录框架的支持，建议使用SLF4J和Log4j的组合。</p>
使用log4jdbc时，你需要更改数据源的JDBC URL，添加前缀：<span style="color: #008080;"><strong>jdbc:log4jdbc:</strong></span>，同时，要把JDBC驱动类改为net.sf.log4jdbc.DriverSpy。例如，对于MySQL，情况可能像下面这样：<br />
<pre class="crayon-plain-tag">#标准的JDBC URL
mysql://192.168.0.201:3306/gmem
#使用log4jdbc时的URL
jdbc:log4jdbc:mysql://192.168.0.201:3306/gmem

#标准的JDBC驱动类名
com.mysql.jdbc.Driver
#使用log4jdbc时的驱动类名
net.sf.log4jdbc.DriverSpy</pre><br />
log4jdbc根据JDBC URL猜测你用的是哪一种数据库，并按下表确定真实的JDBC驱动类
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="0">
<thead>
<tr>
<td style="text-align: center;"><b>驱动类名</b></td>
<td style="text-align: center;"><b>数据库类型</b></td>
</tr>
</thead>
<tbody>
<tr>
<td>oracle.jdbc.driver.OracleDriver</td>
<td>Older Oracle Driver</td>
</tr>
<tr>
<td>oracle.jdbc.OracleDriver</td>
<td>Newer Oracle Driver</td>
</tr>
<tr>
<td>com.sybase.jdbc2.jdbc.SybDriver</td>
<td>Sybase</td>
</tr>
<tr>
<td>net.sourceforge.jtds.jdbc.Driver</td>
<td>jTDS SQL Server &amp; Sybase driver</td>
</tr>
<tr>
<td>com.microsoft.jdbc.sqlserver.SQLServerDriver</td>
<td>Microsoft SQL Server 2000 driver</td>
</tr>
<tr>
<td>com.microsoft.sqlserver.jdbc.SQLServerDriver</td>
<td>Microsoft SQL Server 2005 driver</td>
</tr>
<tr>
<td>weblogic.jdbc.sqlserver.SQLServerDriver</td>
<td>Weblogic SQL Server driver</td>
</tr>
<tr>
<td>com.informix.jdbc.IfxDriver</td>
<td>Informix</td>
</tr>
<tr>
<td>org.apache.derby.jdbc.ClientDriver</td>
<td>Apache Derby client/server driver, aka the Java DB</td>
</tr>
<tr>
<td>org.apache.derby.jdbc.EmbeddedDriver</td>
<td>Apache Derby embedded driver, aka the Java DB</td>
</tr>
<tr>
<td>com.mysql.jdbc.Driver</td>
<td>MySQL</td>
</tr>
<tr>
<td>org.postgresql.Driver</td>
<td>PostgresSQL</td>
</tr>
<tr>
<td>org.hsqldb.jdbcDriver</td>
<td>HSQLDB pure Java database</td>
</tr>
<tr>
<td>org.h2.Driver</td>
<td>H2 pure Java database</td>
</tr>
</tbody>
</table>
你需要在配置日志记录框架的配置来改变log4jdbc需要记录哪些内容，以log4j为例：<br />
<pre class="crayon-plain-tag">#下面的配置仅仅记录ERROR级别的日志，开发时可以适当改为DEBUG
#只记录执行的SQL语句，PreparedStatement的输入参数自动替换“?”
log4j.logger.jdbc.sqlonly=ERROR
#记录执行的SQL语句，并统计其消耗时间
log4j.logger.jdbc.sqltiming=ERROR
#记录所有JDBC的调用（针对ResultSet的除外）
log4j.logger.jdbc.audit=ERROR
#记录针对ResultSet的所用JDBC调用
log4j.logger.jdbc.resultset=ERROR
#记录数据库连接的打开和关闭，可以用于监控连接泄漏
log4j.logger.jdbc.connection=ERROR</pre><br />
此外，你可以 log4jdbc.properties这个文件中设置下表的配置项，来改变log4jdbc的行为：
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td style="text-align: center;"><b>配置项</b></td>
<td style="width: 90px; text-align: center;"><b>默认值</b></td>
<td style="text-align: center;"><b>描述</b></td>
<td style="width: 90px; text-align: center;"><b>开始版本</b></td>
</tr>
<tr>
<td>log4jdbc.drivers</td>
<td></td>
<td>一个或者多个JDBC驱动的全限定类名，逗号分隔，一般用不到该配置</td>
<td>1.0</td>
</tr>
<tr>
<td>log4jdbc.auto.load.popular.drivers</td>
<td>true</td>
<td>如果设置为false，则不会自动加载上表所属的常用驱动类</td>
<td>1.2beta2</td>
</tr>
<tr>
<td>log4jdbc.debug.stack.prefix</td>
<td></td>
<td>部分或者全部的包名称，通常填写和你的应用有关的包，如果不填写，真实调用JDBC的类名称会显示在日志中——通常是连接池的类</td>
<td>1.0</td>
</tr>
<tr>
<td>log4jdbc.sqltiming.warn.threshold</td>
<td></td>
<td>指定一个毫秒数，如果执行的SQL语句超过此时间，jdbc.sqltiming则会以WARN级别记录日志</td>
<td>1.1beta1</td>
</tr>
<tr>
<td>log4jdbc.sqltiming.error.threshold</td>
<td></td>
<td>指定一个毫秒数，如果执行的SQL语句超过此时间，jdbc.sqltiming则会以ERROR级别记录日志</td>
<td>1.1beta1</td>
</tr>
<tr>
<td>log4jdbc.dump.booleanastruefalse</td>
<td>false</td>
<td>是否把日志中的布尔值显示为true/false，大部分数据库默认是显示为1/0</td>
<td>1.2alpha1</td>
</tr>
<tr>
<td>log4jdbc.dump.sql.maxlinelength</td>
<td>90</td>
<td>日志中SQL语句每行最大的字符数，超过了则自动换行</td>
<td>1.2alpha1</td>
</tr>
<tr>
<td>log4jdbc.dump.fulldebugstacktrace</td>
<td>false</td>
<td>是否显示完整的调用堆栈</td>
<td>1.2alpha1</td>
</tr>
<tr>
<td>log4jdbc.dump.sql.select</td>
<td>true</td>
<td>如果设置为false，则不输出select语句到日志</td>
<td>1.2alpha1</td>
</tr>
<tr>
<td>log4jdbc.dump.sql.insert</td>
<td>true</td>
<td>如果设置为false，则不输出insert语句到日志</td>
<td>1.2alpha1</td>
</tr>
<tr>
<td>log4jdbc.dump.sql.update</td>
<td>true</td>
<td>如果设置为false，则不输出update语句到日志</td>
<td>1.2alpha1</td>
</tr>
<tr>
<td>log4jdbc.dump.sql.delete</td>
<td>true</td>
<td>如果设置为false，则不输出delete语句到日志</td>
<td>1.2alpha1</td>
</tr>
<tr>
<td>log4jdbc.dump.sql.create</td>
<td>true</td>
<td>如果设置为false，则不输出create语句到日志</td>
<td>1.2alpha1</td>
</tr>
<tr>
<td>log4jdbc.dump.sql.addsemicolon</td>
<td>false</td>
<td>是否自动在SQL语句后面加分号;</td>
<td>1.2alpha1</td>
</tr>
<tr>
<td>log4jdbc.trim.sql</td>
<td>true</td>
<td>是否清除SQL头尾的空白符</td>
<td>1.2beta2</td>
</tr>
<tr>
<td>log4jdbc.trim.sql.extrablanklines</td>
<td>true</td>
<td>是否包含多余的空白行</td>
<td>1.2</td>
</tr>
</tbody>
</table>
<p style="color: #000000;">我针对1.2版本（1.2-ext-3），进行了一些简单的扩展，其特性如下：</p>
<ol>
<li>支持基于Hibernate或者sqlinform的SQL语句格式化，增强可读性</li>
<li>支持stacktracefilterincludes、stacktracefilterexcludes等log4jdbc.properties配置项，以便从调用栈里包含、清除包含某些字符的行，防止各种字节码框架生成的类干扰调试</li>
</ol>
1.2-ext-3版本的log4jdbc.properties配置可以如下：<br />
<pre class="crayon-plain-tag">log4jdbc.dump.sql.select=true
log4jdbc.dump.sql.insert=true
log4jdbc.dump.sql.update=true
log4jdbc.dump.sql.delete=true
log4jdbc.dump.sql.create=true

#是否格式化SQL语句
log4jdbc.dump.formatsql=true
#是否显示完整的调用堆栈
log4jdbc.dump.fulldebugstacktrace=true
#仅包含commons或者demo的调用栈的行，才会被记录
log4jdbc.dump.stacktracefilterincludes=commons,demo
#包含$、_的调用栈的行，一定被排除，即使符合stacktracefilterincludes
log4jdbc.dump.stacktracefilterexcludes=$,_</pre><br />
1.2-ext-3版本的SVN地址为：svn://svn.gmem.cc/log4jdbc3/trunk
它需要一个依赖包：<br />
<pre class="crayon-plain-tag">&lt;dependency&gt;
    &lt;groupId&gt;com.sqlinform&lt;/groupId&gt;
    &lt;artifactId&gt;sqlinform&lt;/artifactId&gt;
    &lt;version&gt;0.0.1&lt;/version&gt;
    &lt;optional&gt;true&lt;/optional&gt;
&lt;/dependency&gt;</pre><br />
该包可以从这里下载：<a href="/wp-content/uploads/2013/01/sqlinform-0.0.1.zip" target="_blank">sqlinform-0.0.1</a>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/log4jdbc">使用log4jdbc记录SQL语句的执行情况</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/log4jdbc/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>日志组件Log4cplus的使用</title>
		<link>https://blog.gmem.cc/log4cplus</link>
		<comments>https://blog.gmem.cc/log4cplus#comments</comments>
		<pubDate>Tue, 15 Nov 2011 04:02:56 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[C++]]></category>
		<category><![CDATA[LOG]]></category>

		<guid isPermaLink="false">http://blog.gmem.cc/?p=2096</guid>
		<description><![CDATA[<p>代码示例 [crayon-69d8c675079b4006273376/] 配置文件说明 配置文件示例 [crayon-69d8c675079b9382003941/] 日志输出格式化  符号  说明 %% 转义为% %c 输出logger名称，例如std::string pattern ="%c" 时输出: "cc.gmem"， 也可以控制logger名称的显示层次，比如"%c{1}"时输出"gmem", 其中数字表示层次 %D 显示本地时间，当std::string pattern ="%D" <a class="read-more" href="https://blog.gmem.cc/log4cplus">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/log4cplus">日志组件Log4cplus的使用</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h2"><span class="graybg">代码示例</span></div>
<pre class="crayon-plain-tag">#include &lt;log4cplus/logger.h&gt;
#include &lt;log4cplus/configurator.h&gt;
#include &lt;log4cplus/helpers/loglog.h&gt;

using namespace std;
using namespace log4cplus;

const char* LOG4CPLUS_CONFIG_FILE = "log4cplus.properties";
try
{
    log4cplus::PropertyConfigurator::doConfigure( LOG4CPLUS_CONFIG_FILE );
}
catch ( std::runtime_error&amp; e )
{
    std::cout &lt;&lt; "Failed to initialize log4cplus with config file: " &lt;&lt; LOG4CPLUS_CONFIG_FILE &lt;&lt; " due to: " &lt;&lt; e.what() &lt;&lt; endl;
}

Logger logger = Logger::getRoot();
LOG4CPLUS_DEBUG(logger, "Hello " &lt;&lt; "World");</pre>
<div class="blog_h2"><span class="graybg">配置文件说明</span></div>
<div class="blog_h3"><span class="graybg"><span class="graybg">配置文件示例</span></span><br />
<pre class="crayon-plain-tag">log4cplus.rootLogger=DEBUG, Console, File
log4cplus.logger.AmqCppClient=DEBUG


log4cplus.appender.Console=log4cplus::ConsoleAppender
log4cplus.appender.Console.layout=log4cplus::PatternLayout
log4cplus.appender.Console.layout.ConversionPattern=[%-5p] [%t] %d (%F:%L) %n%m%n

log4cplus.appender.File=log4cplus::RollingFileAppender
log4cplus.appender.File.File=F:/Temp/amq-cpp-client.log
log4cplus.appender.File.MaxFileSize=5MB
log4cplus.appender.File.MaxBackupIndex=5
log4cplus.appender.Console.layout=log4cplus::PatternLayout
log4cplus.appender.Console.layout.ConversionPattern=[%-5p] [%t] %d (%F:%L) %n%m%n</pre>
</div>
<div class="blog_h3"><span class="graybg">日志输出格式化</span></div>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 80px; text-align: center;"> 符号</td>
<td style="text-align: center;"> 说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>%%</td>
<td>转义为%</td>
</tr>
<tr>
<td>%c</td>
<td>
<p>输出logger名称，例如std::string pattern ="%c" 时输出: "cc.gmem"， 也可以控制logger名称的显示层次，比如"%c{1}"时输出"gmem", 其中数字表示层次</p>
</td>
</tr>
<tr>
<td>%D</td>
<td>显示本地时间，当std::string pattern ="%D" 时输出:"2011-11-16 11:00:00"</td>
</tr>
<tr>
<td>%d</td>
<td>
<p>显示标准时间，可以通过%d{...}定义更详细的显示格式，{}中可显示的预定义标识符如下：</p>
<p>%a -- 表示礼拜几，英文缩写形式，比如"Fri"<br />%A -- 表示礼拜几，比如"Friday"<br />%b -- 表示几月份，英文缩写形式，比如"Oct"<br />%B -- 表示几月份，"October"<br />%c -- 标准的日期＋时间格式，如 "Sat Oct 16 18:56:19 2004"<br />%d -- 表示今天是这个月的几号(1-31)"16"<br />%H -- 表示当前时刻是几时(0-23)，如 "18"<br />%I -- 表示当前时刻是几时(1-12)，如 "6"<br />%j -- 表示今天是哪一天(1-366)，如 "290"<br />%m -- 表示本月是哪一月(1-12)，如 "10"<br />%M -- 表示当前时刻是哪一分钟(0-59)，如 "59"<br />%p -- 表示现在是上午还是下午， AM or PM<br />%q -- 表示当前时刻中毫秒部分(0-999)，如 "237"<br />%Q -- 表示当前时刻中带小数的毫秒部分(0-999.999)，如 "430.732"<br />%S -- 表示当前时刻的多少秒(0-59)，如 "32"<br />%U -- 表示本周是今年的第几个礼拜，以周日为第一天开始计算(0-53)，如 "41"<br />%w -- 表示礼拜几，(0-6, 礼拜天为0)，如 "6"<br />%W -- 表示本周是今年的第几个礼拜，以周一为第一天开始计算(0-53)，如 "41"<br />%x -- 标准的日期格式，如 "10/16/04"<br />%X -- 标准的时间格式，如 "19:02:34"<br />%y -- 两位数的年份(0-99)，如 "04"<br />%Y -- 四位数的年份，如 "2004"<br />%Z -- 时区名，比如 "GMT"</p>
</td>
</tr>
<tr>
<td>%F</td>
<td>输出当前记录器所在的文件名称</td>
</tr>
<tr>
<td>%L</td>
<td>输出当前记录器所在的文件行号</td>
</tr>
<tr>
<td>%l</td>
<td>输出当前记录器所在的文件名称和行号</td>
</tr>
<tr>
<td>%m</td>
<td>输出原始日志信息</td>
</tr>
<tr>
<td>%n</td>
<td>换行符</td>
</tr>
<tr>
<td>%p</td>
<td>输出LogLevel</td>
</tr>
<tr>
<td>%t</td>
<td>输出记录器所在的线程ID</td>
</tr>
<tr>
<td>%x</td>
<td>嵌套诊断上下文NDC</td>
</tr>
</tbody>
</table>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/log4cplus">日志组件Log4cplus的使用</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/log4cplus/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>log4j配置文件样本</title>
		<link>https://blog.gmem.cc/log4j-configuration-sample</link>
		<comments>https://blog.gmem.cc/log4j-configuration-sample#comments</comments>
		<pubDate>Sat, 15 Mar 2008 03:01:55 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Java]]></category>
		<category><![CDATA[LOG]]></category>

		<guid isPermaLink="false">http://blog.gmem.cc/?p=1212</guid>
		<description><![CDATA[<p>基本配置 属性文件方式 [crayon-69d8c67507b8a197036889/] layout json 依赖于该项目：https://git.gmem.cc/alex/log4j-json-layout [crayon-69d8c67507b8e216267332/] &#160;</p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/log4j-configuration-sample">log4j配置文件样本</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h2"><span class="graybg">基本配置</span></div>
<div class="blog_h3"><span class="graybg">属性文件方式</span></div>
<pre class="crayon-plain-tag">log4j.rootLogger=INFO, Console,File
log4j.logger.cc.gmem.demo=INFO

log4j.appender.Console=org.apache.log4j.ConsoleAppender
log4j.appender.Console.layout=org.apache.log4j.PatternLayout
log4j.appender.Console.layout.ConversionPattern=[%-5p] [%t] %d{yyyy-MM-dd HH:mm:ss} %l%n%m%n

log4j.appender.File=org.apache.log4j.RollingFileAppender
log4j.appender.File.File=c:/logs/demo.log
log4j.appender.File.Append=true
log4j.appender.File.Threshold=ERROR
log4j.appender.File.MaxFileSize=256MB
log4j.appender.File.MaxBackupIndex=100
log4j.appender.File.layout=org.apache.log4j.PatternLayout
log4j.appender.File.layout.ConversionPattern=[%-5p] [%t] %d{yyyy-MM-dd HH:mm:ss} %l%n%m%n</pre>
<div class="blog_h2"><span class="graybg">layout</span></div>
<div class="blog_h3"><span class="graybg">json</span></div>
<p>依赖于该项目：<a href="https://git.gmem.cc/alex/log4j-json-layout">https://git.gmem.cc/alex/log4j-json-layout</a></p>
<pre class="crayon-plain-tag">&lt;?xml version="1.0" encoding="UTF-8" ?&gt;
&lt;!DOCTYPE log4j:configuration SYSTEM "log4j.dtd"&gt;

&lt;log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/"&gt;

    &lt;appender name="console" class="org.apache.log4j.ConsoleAppender"&gt;
        &lt;param name="Target" value="System.out"/&gt;
        &lt;layout class="org.jetbrains.appenders.JsonLayout"&gt;
            &lt;param name="excludedFields" value="host,@version" /&gt;
        &lt;/layout&gt;
    &lt;/appender&gt;

    &lt;category name="cc.gmem"&gt;
        &lt;priority value="debug"/&gt;
    &lt;/category&gt;
    &lt;root&gt;
        &lt;priority value="info"/&gt;
        &lt;appender-ref ref="console"/&gt;
    &lt;/root&gt;

&lt;/log4j:configuration&gt;</pre>
<p>&nbsp;</p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/log4j-configuration-sample">log4j配置文件样本</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/log4j-configuration-sample/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
