根据本人写博客的惯例,先交代下背景。在公司的系统中,咱们的配置文件是切分有好几个的,不一样的配置文件里面配置内容有着不一样,对于日志的输出,也须要对不一样的环境作出不一样的输出,这是一个前提,本文即将讲述到的将日志输出到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
套件来实现这个功能。若是是一些不太大的系统,也可使用本文所讲述的方式进行存储。