Logback統(tǒng)一配置及環(huán)境變量加載問題

關鍵字: logback.xml in jar logback environment variable

最近實現(xiàn)了一下將logback.xml的配置統(tǒng)一在基礎框架的jar包中,如此所有業(yè)務應用都不用關心日志的規(guī)范格式或者存放位置,只要做一下簡單的配置即可。
對于logback.xml與log4j的兼容或者適配問題,本文不做詳細討論。
參見:Java日志框架slf4j、jcl、jul、log4j1、log4j2、logback大總結

1. logback.xml配置

基本上也是主流配置,幾個特別說明的點:

格式化的輸出說明

請參照 http://logback.qos.ch/manual/layouts.html

<b>片段</b>:
%20logger false 20 none Left pad with spaces if the logger name is less than 20 characters long.
%-20logger true 20 none Right pad with spaces if the logger name is less than 20 characters long.
%.30logger NA none 30 Truncate from the beginning if the logger name is longer than 30 characters.
%20.30logger false 20 30 Left pad with spaces if the logger name is shorter than 20 characters. However, if logger name is longer than 30 characters, then truncate from the beginning.
%-20.30logger true 20 30 Right pad with spaces if the logger name is shorter than 20 characters. However, if logger name is longer than 30 characters, then truncate from the beginning.
%.-30logger NA none 30 Truncate from the end if the logger name is longer than 30 characters.

%d表示日期,
%thread: 表示線程名
%level:日志級別
%msg:日志消息
%logger: Java類名(含包名,這里設定了36位,若超過36位,包名會精簡為類似a.b.c.JavaBean)
%line: Java類的行號

NEUTRAL使用

因為正常日志和錯誤異常日志是拆分成兩個文件的,所以在appender=FILE中如果使用ThresholdFilter配置的level是最低閥值(一般是INFO),
ERROR也會進入到sys.log中,這時需要將ERROR過濾掉,設置Match Error 直接DENY, Mismatch Error則Neutral中立繼續(xù)走下面的filter。

環(huán)境變量的使用

這里配置了大量的環(huán)境變量,比如log.path(日志的路徑),app.name(應用名稱),log.root.level(日志root級別)等,
這些變量若在每個業(yè)務系統(tǒng)的本地,可以基于maven的profile filter resources進行變量替換即可,
但是當前的設計是想將logback.xml放在一個公共組件的jar包內, 這個maven是無法替換jar包內文件的內容的。引申出我們下面的實現(xiàn)。
(當然你可以使用一些maven plugin進行unzip,修改后再zip一把,實在太麻煩 >_<!)

<configuration>
    <!--<statusListener class="ch.qos.logback.core.status.NopStatusListener" />-->
    <jmxConfigurator/>
    
    <!-- 控制臺輸出 -->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>
                [%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-36.36thread] [%-5level] [%-36.36logger{36}:%-4.4line] - %msg%n
            </pattern>
        </encoder>
    </appender>
    <!-- 按照每天生成日志文件 -->
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${log.path}/${app.name}/sys.log</file>
        <!--拒絕ERROR日志-->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>ERROR</level>
            <onMatch>DENY</onMatch>
            <onMisMatch>NEUTRAL</onMisMatch>
        </filter>
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>${log.lowest.level}</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <FileNamePattern>${log.path}/${app.name}/sys-%d{yyyy-MM-dd}-%i.log</FileNamePattern>
            <MaxHistory>90</MaxHistory>
            <TimeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <MaxFileSize>10MB</MaxFileSize>
            </TimeBasedFileNamingAndTriggeringPolicy>
        </rollingPolicy>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>
                [%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-36.36thread] [%-5level] [%-36.36logger{36}:%-4.4line] - %msg%n
            </pattern>
        </encoder>
    </appender>
    <appender name="FILE-ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${log.path}/${app.name}/sys-err.log</file>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>ERROR</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <FileNamePattern>${log.path}/${app.name}/sys-err-%d{yyyy-MM-dd}-%i.log</FileNamePattern>
            <MaxHistory>90</MaxHistory>
            <TimeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <MaxFileSize>10MB</MaxFileSize>
            </TimeBasedFileNamingAndTriggeringPolicy>
        </rollingPolicy>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-36.36thread] [%-5level] [%-36.36logger{36}:%-4.4line] - %msg%n
            </pattern>
        </encoder>
    </appender>

    <!-- show parameters for hibernate sql 專為 Hibernate 定制 -->
    <logger name="org.hibernate.type.descriptor.sql.BasicBinder"    additivity="true" level="${log.hibernate.level}" />
    <logger name="org.hibernate.type.descriptor.sql.BasicExtractor" additivity="true" level="${log.hibernate.level}" />
    <logger name="org.hibernate.SQL"                                additivity="true" level="${log.hibernate.level}" />
    <logger name="org.springframework"                              additivity="true" level="${log.spring.level}"/>
    <logger name="com.myown"                                        additivity="true" level="${log.root.level}"/>

    <!-- 日志輸出級別 -->
    <root level="${log.root.level}">
        <appender-ref ref="STDOUT"/>
        <appender-ref ref="FILE"/>
        <appender-ref ref="FILE-ERROR"/>
    </root>

</configuration>

2. web.xml 監(jiān)聽器的實現(xiàn)

上面logback.xml里設置了大量的環(huán)境變量,而這些環(huán)境變量都必須在Web應用啟動之前設置完畢。
實現(xiàn)方式其實比較多:
a. 第一種: 設置JVM的啟動參數(shù) -Dlog.path=/logs/app/ -Dlog.root.level=INFO
b. 第二種:設置應用服務器的啟動參數(shù),比如tomcat,在bin文件夾下新增一個bat(windows)或者shell文件(linux),命名為setenv.bat/sh,

setenv.bat內容:
set log.path=/usr/local/src/logs/test
set log.root.level=INFO
....

c. 第三種:前兩種處理方式都有些不妥的地方就是,開發(fā)人員pull代碼之后無法直接部署tomcat運行,還需要添加setenv文件,容易遺漏;
參數(shù)的設置不靈活:不能根據(jù)OS, 開發(fā)/測試/生產(chǎn)的環(huán)境變量動態(tài)調整log level或者log path。
(其實,在不同環(huán)境下的tomcat只要設定一次啟動參數(shù),業(yè)務應用日后的部署都是一勞永逸的, 不排除以后采用第二種方案 _
現(xiàn),決定,通過監(jiān)聽器來實現(xiàn)。

監(jiān)聽器示例代碼如下

public class LogbackListener implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent event) {
        ServletContext sc = event.getServletContext();
        //添加系統(tǒng)屬性示例代碼
        if (org.apache.commons.lang3.SystemUtils.IS_OS_WINDOWS) {
           System.setProperty("log.path", "${CATALINA_HOME}" + SystemUtils.FILE_SEPARATOR + "logs");
        } else {//linux
           System.setProperty("log.path", "/logs");
        }

        if (isProductEnv) {
           System.setProperty("log.root.level", "INFO");
        } else {//非生產(chǎn)環(huán)境
           System.setProperty("log.root.level", "DEBUG");
        } 

        ..........................

        WebLogbackConfigurer.initLogging(sc);
    }

    @Override
    public void contextDestroyed(ServletContextEvent event){
        WebLogbackConfigurer.shutdownLogging(event.getServletContext());
    }
}

web.xml添加監(jiān)聽器配置,該監(jiān)聽器的配置盡量位于第一位,至少保證在其他有可能使用日志打印的監(jiān)聽器之前。

············
<!--logback日志環(huán)境變量配置-->
<listener>
    <listener-class>com.myown.framework.LogbackListener</listener-class>
</listener>
<!--Spring上下文-->
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
············

3. TOMCAT + JNDI + Druid連接池的坑

因為某個核心系統(tǒng)期望能監(jiān)控一下數(shù)據(jù)庫連接方面的性能和請求情況,采用了開源連接池Druid, 在context.xml配置的jndi是這樣的:

 <!--
 1. tomcat lib需添加mysql驅動 mysql-connector-java-5.1.38.jar
     2. 添加 druid-1.0.16.jar
 3. filter若使用log4j,tomcat lib需添加log4j.jar;若使用slf4j基于logback,需添加slf4j-api-1.7.13.jar
 -->     
<Resource 
     name="jndi/nicholas"
     factory="com.alibaba.druid.pool.DruidDataSourceFactory"
     auth="Container"
     type="javax.sql.DataSource"
     driverClassName="com.mysql.jdbc.Driver"
     url="jdbc:mysql://127.0.0.1:3306/nicholas?useUnicode=true&characterEncoding=utf-8"
     username="root" 
     password="root"
     initialSize="10"
     minIdle="10"
     maxActive="50"
     maxWait="10000"    
     timeBetweenEvictionRunsMillis="60000" 
     minEvictableIdleTimeMillis="300000"
     removeabandoned="true" 
     removeabandonedtimeout="180"
     logabandoned="true"
     filters="stat,wall,slf4j"/>

具體druid的配置和使用就不詳談,注意最后的filters 配置了slf4j, 它的實現(xiàn)是基于web應用配置的logback,請看下圖首次進入LoggerContext的調用鏈情況。
其實此時的Tomcat啟動優(yōu)先加載了druid jndi連接池,而druid又需要打印日志,所以在我們的監(jiān)聽器初始化之前, logback的配置已經(jīng)加載完畢了,而加載的logback.xml中的一堆變量都是undefined!
問題粗線了!現(xiàn)在看起來好像前面的第一種或者第二種方法才行得通了。。。這尼瑪!

應用啟動首次進入LoggerContext的堆棧信息

一個開源組件的依賴會帶來如此不堪忍受的問題,這個事件的教訓就是設計一個越基礎的組件越要減少對其他組件的依賴,不然會有各種升級或者兼容層面的潛在隱患

目前本人的解決方案是在剛才的 LogbackListener 監(jiān)聽器中重置logback上下文!

  @Override
  public void contextInitialized(ServletContextEvent event) {

    //系統(tǒng)環(huán)境變量設置 System.setProperty()
    ..............
    LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
    //進入Web之前判斷是否已加載了Logger,若已加載需要重置!
    if (loggerContext != null && loggerContext.getLoggerList().size() > 0) {
        //必須清空一下,否則之前加載的logger堆棧信息還保留著StatusPrinter.print會打印出之前的狀態(tài)
        loggerContext.getStatusManager().clear();
        loggerContext.reset();
        ContextInitializer ci = new ContextInitializer(loggerContext);
        try {
            ci.autoConfig();
        } catch (JoranException e) {
            sc.log("-=-=-= Reset Logback status Failed =-=-=- \n" + ExceptionUtil.getStackTrace(e));
        }
    }

    WebLogbackConfigurer.initLogging(sc);
    StatusPrinter.print(loggerContext);
}

4. 默認賦值問題

某些情況下需要變量設置個默認值,以防出現(xiàn)比較惡心的 _IS_UNDEFINED 后綴( log4j不存在的變量會留空)
只要使用" :- " 操作符即可(冒號+減號)。
比如 log.path 沒有定義, 使用該變量的地方就會變成** log.path_IS_UNDEFINED**, 給他一個默認值

${log.path:-/var/logs/myapp}

5. 注意事項

Spring的WARN日志無法打印出來

檢查spring是否exclude自帶的commons-logging 即 jcl jar包,并同時添加 jcl-over-slf4j的jar包,將其適配給slf4j

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>${spring.version}</version>
    <exclusions>
        <exclusion>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>jcl-over-slf4j</artifactId>
</dependency>
Zookeeper/ZkClient等默認使用log4j的組件無法打印日志

需要exclude log4j包,并添加log4j-over-slf4j依賴 (注意 slf4j-log4j12 與 log4j-over-slf4j的環(huán)形依賴會導致異常)

<dependency>
        <groupId>com.github.sgroschupf</groupId>
        <artifactId>zkclient</artifactId>
        <version>0.1</version>
        <exclusions>
            <exclusion>
                <groupId>log4j</groupId>
                <artifactId>log4j</artifactId>
            </exclusion>
        </exclusions>
 </dependency>
 <!--zkClient和disconf都會依賴zookeeper,而zookeeper會依賴slf4j-log4j12和log4j,
 在我們使用logback的情況下,需要將log4j轉為logback,則需依賴log4j-over-slf4j。
 slf4j-log4j12會與log4j-over-slf4j循環(huán)依賴導致沖突,
 所以所有依賴zookeeper的jar必須exclude掉slf4j-log4j12-->
<dependency>
    <groupId>org.apache.zookeeper</groupId>
    <artifactId>zookeeper</artifactId>
    <version>${zookeeper.version}</version>
    <exclusions>
        <exclusion>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
        </exclusion>
        <!--
        https://issues.apache.org/jira/browse/ZOOKEEPER-1371
        zookeeper Remove dependency on log4j in the source code.
        slf4j-log4j12會與log4j-over-slf4j循環(huán)依賴導致沖突
        -->
        <exclusion>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>log4j-over-slf4j</artifactId>
</dependency>
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

  • Log Java日志:(slf4j、log4j、logback、common-logging ) slf4j 是規(guī)...
    年少懵懂丶流年夢閱讀 17,998評論 1 11
  • 在應用程序中添加日志記錄總的來說基于三個目的:監(jiān)視代碼中變量的變化情況,周期性的記錄到文件中供其他應用進行統(tǒng)計分析...
    時待吾閱讀 5,203評論 1 13
  • 幾種日志的區(qū)別 commons-loggingapache最早提供的日志的門面接口。避免和具體的日志方案直接耦合。...
    maxwellyue閱讀 35,469評論 3 23
  • 寫Java也有一段時間了,一直都有用slf4j log4j輸出日志的習慣。但是始終都是抱著“拿來主義”的態(tài)度,復制...
    Minimumy閱讀 1,465評論 1 7
  • 湖光山色居遠方,柴米油鹽駐身旁。 遙望風景美如畫,近觀生活亂如麻。
    堯_212a閱讀 231評論 2 0

友情鏈接更多精彩內容