根據本人寫博客的慣例,先交代下背景。在公司的系統中,咱們的配置文件是切分有好幾個的,不一樣的配置文件裏面配置內容有着不一樣,對於日誌的輸出,也須要對不一樣的環境作出不一樣的輸出,這是一個前提,本文即將講述到的將日誌輸出到oracle數據庫就是分環境輸出的,本地測試的日誌是很是多的,服務也時常重啓,調試等,所以本地環境的日誌不宜輸出到數據庫,而線上環境不一樣,線上環境的日誌輸出比本地要少不少,也不常常重啓服務。所以本文要講的內容有如下兩點:java
本文將在如下軟件版本中進行,不一樣版本是否存在差別還沒有測試mysql
springboot v2.1.0 logback v1.2.3 oracle 11.2 java 1.8.0_171
在中國,但凡是有什麼不懂的,第一個想到的就是百度,固然,本人第一反應也是先百度,果不其然,第一頁各類解決方案琳琅滿目,不過通過本人的實踐,得出如下比較有用的,也是本人最終採用的方案,各位看官若是看到這篇文章,能夠直接參考。git
網上的說法有不少,有些是定義多個logback的文件,例如logback-dev.xml
和logback-prod.xml
等等諸如此類的,而後在application.yml
內進行根據不一樣的profiles.active
來選擇不一樣的日誌配置文件。不過這種方式我並無測試過,重點講一下下面的方法。github
在同一個logback配置文件中,根據不一樣的<springProfile>
來區分,具體能夠相似如下的寫法:spring
<springProfile name="prod"> <root level="INFO"> <appender-ref ref="CONSOLE"/> <appender-ref ref="infoAppender"/> <appender-ref ref="errorAppender"/> <appender-ref ref="DbAppender"/> </root> </springProfile> <springProfile name="dev"> <root level="INFO"> <appender-ref ref="CONSOLE"/> <!--<appender-ref ref="infoAppender"/>--> <!--<appender-ref ref="errorAppender"/>--> <appender-ref ref="DbAppender"/> </root> </springProfile>
經過不一樣的<springProfile>
標籤來區分不一樣的環境使用那些appender,可是當本人在原來的logback.xml
中這樣配置的時候,並不起做用。打開了logback的日誌,發現以下的報錯sql
21:35:42,913 |-ERROR in ch.qos.logback.core.joran.spi.Interpreter@64:32 - no applicable action for [springProfile], current ElementPath is [[configuration][springProfile]] 21:35:42,913 |-ERROR in ch.qos.logback.core.joran.spi.Interpreter@65:28 - no applicable action for [root], current ElementPath is [[configuration][springProfile][root]] 21:35:42,913 |-ERROR in ch.qos.logback.core.joran.spi.Interpreter@66:42 - no applicable action for [appender-ref], current ElementPath is [[configuration][springProfile][root][appender-ref]] 21:35:42,914 |-ERROR in ch.qos.logback.core.joran.spi.Interpreter@67:47 - no applicable action for [appender-ref], current ElementPath is [[configuration][springProfile][root][appender-ref]] 21:35:42,914 |-ERROR in ch.qos.logback.core.joran.spi.Interpreter@68:48 - no applicable action for [appender-ref], current ElementPath is [[configuration][springProfile][root][appender-ref]] 21:35:42,914 |-ERROR in ch.qos.logback.core.joran.spi.Interpreter@69:45 - no applicable action for [appender-ref], current ElementPath is [[configuration][springProfile][root][appender-ref]] 21:35:42,914 |-ERROR in ch.qos.logback.core.joran.spi.Interpreter@72:31 - no applicable action for [springProfile], current ElementPath is [[configuration][springProfile]] 21:35:42,914 |-ERROR in ch.qos.logback.core.joran.spi.Interpreter@73:28 - no applicable action for [root], current ElementPath is [[configuration][springProfile][root]] 21:35:42,914 |-ERROR in ch.qos.logback.core.joran.spi.Interpreter@74:42 - no applicable action for [appender-ref], current ElementPath is [[configuration][springProfile][root][appender-ref]] 21:35:42,914 |-ERROR in ch.qos.logback.core.joran.spi.Interpreter@77:45 - no applicable action for [appender-ref], current ElementPath is [[configuration][springProfile][root][appender-ref]]
日誌的輸出也是按照默認的輸出,這是爲何呢?正在本人百思不得其解的時候,忽然靈機一動,下面開始敲黑板了!既然是<springProfile>
那麼是否是和spring有關的?若是spring的context沒有被加載,那麼怎麼知道究竟是哪一個profiles起做用呢?數據庫
對於這個問題,本人嘗試將logback.xml
替換成了logback-spring.xml
,重啓應用,發現能夠經過。springboot
logback.xml
在springboot中,是先於spring上下文
加載的,所以,在加載這個配置文件時,還不知道到底採用哪一個profile,也就是說<springProfile>
這個標籤並不會在這個階段起做用。logback-spring.xml
是先初始化spring上下文
,這個時候<springProfile>
才生效oracle
我這裏採用的是oracle數據庫,所以針對oracle數據庫作一些簡單說明,而且指出所遇到的坑。app
可能有朋友會問,我想將日誌輸出到數據庫,那麼數據庫的表我是要本身建嗎?要建成怎麼樣的呢?答案是不須要的,logback官方有提供有對應的sql腳本,直接找到對應的數據庫腳本進行建立便可,要否則你本身建的表,logback也不認識啊對吧。 logback數據庫腳本
如下是針對oracle的數據庫腳本
CREATE SEQUENCE logging_event_id_seq MINVALUE 1 START WITH 1; CREATE TABLE logging_event ( timestmp NUMBER(20) NOT NULL, formatted_message VARCHAR2(4000) NOT NULL, logger_name VARCHAR(254) NOT NULL, level_string VARCHAR(254) NOT NULL, thread_name VARCHAR(254), reference_flag SMALLINT, arg0 VARCHAR(254), arg1 VARCHAR(254), arg2 VARCHAR(254), arg3 VARCHAR(254), caller_filename VARCHAR(254) NOT NULL, caller_class VARCHAR(254) NOT NULL, caller_method VARCHAR(254) NOT NULL, caller_line CHAR(4) NOT NULL, event_id NUMBER(10) PRIMARY KEY ); -- the / suffix may or may not be needed depending on your SQL Client -- Some SQL Clients, e.g. SQuirrel SQL has trouble with the following -- trigger creation command, while SQLPlus (the basic SQL Client which -- ships with Oracle) has no trouble at all. CREATE TRIGGER logging_event_id_seq_trig BEFORE INSERT ON logging_event FOR EACH ROW BEGIN SELECT logging_event_id_seq.NEXTVAL INTO :NEW.event_id FROM DUAL; END; / CREATE TABLE logging_event_property ( event_id NUMBER(10) NOT NULL, mapped_key VARCHAR2(254) NOT NULL, mapped_value VARCHAR2(1024), PRIMARY KEY(event_id, mapped_key), FOREIGN KEY (event_id) REFERENCES logging_event(event_id) ); CREATE TABLE logging_event_exception ( event_id NUMBER(10) NOT NULL, i SMALLINT NOT NULL, trace_line VARCHAR2(254) NOT NULL,--# 此處須要注意 PRIMARY KEY(event_id, i), FOREIGN KEY (event_id) REFERENCES logging_event(event_id) );
上述腳本大致上並不會有什麼大的問題,可是要注意logging_event_exception
這個表的trace_line
這個字段,在這裏是定義了254個長度,這個字段主要是用來記錄異常堆棧的,一條堆棧對應一條記錄,那麼有時候堆棧的信息遠遠不止254個字符,所以,這個長度就會形成不夠長而報錯。針對這種狀況,建議設置成1024個長度。
有些包名類名就比較長的堆棧,怎麼可能只有254個長度呢!
建立好數據表以後,接下來就是配置logback-spring.xml
了,爲了提升性能,採用了數據庫鏈接池,因爲項目中採用的是druid
,所以,沿用項目中的數據庫鏈接池。在通過一番搜索以後,獲得了很多相似下面的配置,呃呃呃呃呃
從上面的配置看,不難看出,配置了c3p0
的數據庫鏈接和數據庫方言,看着彷佛沒有什麼問題,動手試試看吧,直接複製粘貼到本身的配置文件,運行^^^ 彷佛很差使啊!再仔細認真看一遍,彷佛沒有錯誤啊,只不過是把C3P0換成了Druid而已啊
<appender name="DbAppender" class="ch.qos.logback.classic.db.DBAppender"> <connectionSource class="ch.qos.logback.core.db.DataSourceConnectionSource"> <dataSource class="com.alibaba.druid.pool.DruidDataSource"> <driverClass>oracle.jdbc.OracleDriver</driverClass> <user>username</user> <password>pass</password> <url>jdbc:oracle:thin:@ip:1521:orcl</url> <sqlDialect class="ch.qos.logback.core.db.dialect.OracleDialect"/> </dataSource> </connectionSource> <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> <level>ERROR</level> </filter> </appender>
結果,報錯了……心碎
10:25:33,859 |-ERROR in ch.qos.logback.core.joran.spi.Interpreter@47:30 - no applicable action for [driverClass], current ElementPath is [[configuration][appender][connectionSource][dataSource][driverClass]] 10:25:33,864 |-ERROR in ch.qos.logback.core.joran.spi.Interpreter@48:23 - no applicable action for [user], current ElementPath is [[configuration][appender][connectionSource][dataSource][user]] 10:25:33,867 |-ERROR in ch.qos.logback.core.joran.spi.Interpreter@51:83 - no applicable action for [sqlDialect], current ElementPath is [[configuration][appender][connectionSource][dataSource][sqlDialect]]
最主要的報錯緣由和最上面的springProfile相似,就是沒有適用的action……巴拉巴拉,奇了怪了!後面猜想是否是由於指定了數據源,不一樣的數據源裏面的配置不同?在初始化數據庫鏈接池的時候,經過反射構造鏈接池的時候,沒有找到對應名字的字段?因而乎根據druid的配置,換成了以下的配置
<appender name="DbAppender" class="ch.qos.logback.classic.db.DBAppender"> <connectionSource class="ch.qos.logback.core.db.DataSourceConnectionSource"> <dataSource class="com.alibaba.druid.pool.DruidDataSource"> <driverClassName>oracle.jdbc.OracleDriver</driverClassName> <username>username</username> <password>pass</password> <url>jdbc:oracle:thin:@ip:1521:orcl</url> <sqlDialect class="ch.qos.logback.core.db.dialect.OracleDialect"/> </dataSource> </connectionSource> <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> <level>ERROR</level> </filter> </appender>
從新運行,此次雖然仍是沒有成功,可是報錯明顯變少了,僅有sqlDialect報錯……啊哈哈,彷佛發現了黎明以前的黑暗!
對於不一樣的數據庫鏈接池,logback是不知道內部跟jdbc相關的配置的名稱是怎麼樣的,所以,使用不一樣的數據庫鏈接池時,要根據其內部的名稱來配置
dataSource
標籤!根據這個規律,一些數據庫的其餘配置,例如最大鏈接數,最大空閒鏈接數等應該也是能夠修改的,本人並無測試。
在掙扎了一段時間以後,實在想不出爲何<sqlDialect>
會報錯,是logback不支持嗎?仍是?看來只能一探源碼方知究竟了。在查看了logback的源碼以後,發如今一個叫DBUtil
的類裏面終於找到了真相!
public class DBUtil extends ContextAwareBase { private static final String POSTGRES_PART = "postgresql"; private static final String MYSQL_PART = "mysql"; private static final String ORACLE_PART = "oracle"; // private static final String MSSQL_PART = "mssqlserver4"; private static final String MSSQL_PART = "microsoft"; private static final String HSQL_PART = "hsql"; private static final String H2_PART = "h2"; private static final String SYBASE_SQLANY_PART = "sql anywhere"; private static final String SQLITE_PART = "sqlite"; public static SQLDialectCode discoverSQLDialect(DatabaseMetaData meta) { SQLDialectCode dialectCode = SQLDialectCode.UNKNOWN_DIALECT; try { String dbName = meta.getDatabaseProductName().toLowerCase(); if (dbName.indexOf(POSTGRES_PART) != -1) { return SQLDialectCode.POSTGRES_DIALECT; } else if (dbName.indexOf(MYSQL_PART) != -1) { return SQLDialectCode.MYSQL_DIALECT; } else if (dbName.indexOf(ORACLE_PART) != -1) { return SQLDialectCode.ORACLE_DIALECT; } else if (dbName.indexOf(MSSQL_PART) != -1) { return SQLDialectCode.MSSQL_DIALECT; } else if (dbName.indexOf(HSQL_PART) != -1) { return SQLDialectCode.HSQL_DIALECT; } else if (dbName.indexOf(H2_PART) != -1) { return SQLDialectCode.H2_DIALECT; } else if (dbName.indexOf(SYBASE_SQLANY_PART) != -1) { return SQLDialectCode.SYBASE_SQLANYWHERE_DIALECT; } else if (dbName.indexOf(SQLITE_PART) != -1) { return SQLDialectCode.SQLITE_DIALECT; } else { return SQLDialectCode.UNKNOWN_DIALECT; } } catch (SQLException sqle) { // we can't do much here } return dialectCode; } // 如下代碼省略…… }
原來logback是能夠根據Connection
獲取到DatabaseMetaData
對象,而後根據meta來獲取究竟是哪一個數據庫產品,從而自動返回對應的方言。換句話說就是,根本不須要手動配置什麼sqlDialect,自動能夠獲取,(其實根據jdbcurl都知道是什麼數據庫了)那麼咱們在裏面設置方言其實有點多此一舉了。
可能之前的歷史版本是須要手動設置dialect的,在如今的版本應該是改進了,本人也沒有去比對過歷史源碼,只是猜想而已!有興趣的看官能夠本身去github比對下。
好了,到了這裏,踩坑就結束了,下面就貼上logback+druid鏈接池的appender配置,對於其餘數據庫鏈接池,我想若是看了上面的內容,應該也都會怎麼配置避免掉進這個坑裏面了。
<appender name="DbAppender" class="ch.qos.logback.classic.db.DBAppender"> <connectionSource class="ch.qos.logback.core.db.DataSourceConnectionSource"> <dataSource class="com.alibaba.druid.pool.DruidDataSource"> <driverClassName>oracle.jdbc.OracleDriver</driverClassName> <username>username</username> <password>pass</password> <url>jdbc:oracle:thin:@ip:1521:orcl</url> </dataSource> </connectionSource> <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> <level>ERROR</level> </filter> </appender>
針對一些場景,報錯日誌比較多的狀況下,異常堆棧也是比較多的,存放在數據庫視狀況而定,從性能的角度來講,數據庫並非最優的選擇。本文也只是拋磚引玉而已,若是對日誌的搜索和存儲性能有比較大的需求,並不建議直接存放到數據庫。若是是一套比較大的系統,仍是建議使用ELK
套件來實現這個功能。若是是一些不太大的系統,也可使用本文所講述的方式進行存儲。