Logback是log4j的增强版,比log4j更具灵活,其提供了将日志输出到数据库的功能,本文将介绍如何将指定的日志输出到mysql中。

一、自定义log标志

由于Logback原生的配置会将所有的日志信息输出到mysql数据表中,故需要自定义标志,继承AbstractMatcherFilter,过滤掉无标志的日志:

1、自定义标志过滤器

  1. public class LogbackMarkerFilter extends AbstractMatcherFilter<ILoggingEvent> {
  2.  
  3. private Marker markerToMatch = null;
  4.  
  5. @Override
  6. public void start() {
  7. if (null != this.markerToMatch) {
  8. super.start();
  9. } else {
  10. addError(" no MARKER yet !");
  11. }
  12. }
  13.  
  14. @Override
  15. public FilterReply decide(ILoggingEvent event) {
  16. Marker marker = event.getMarker();
  17. if (!isStarted()) {
  18. return FilterReply.NEUTRAL;
  19. }
  20. if (null == marker) {
  21. return onMismatch;
  22. }
  23. if (markerToMatch.contains(marker)) {
  24. return onMatch;
  25. }
  26. return onMismatch;
  27. }
  28.  
  29. public void setMarker(String markerStr) {
  30. if (null != markerStr) {
  31. markerToMatch = MarkerFactory.getMarker(markerStr);
  32. }
  33. }
  34. }

2、logback-spring.xml 相关配置文件

  1. <!-- 读取配置文件中参数 -->
  2. <springProperty scope="context" name="driverClass" source="spring.datasource.driver-class-name"/>
  3. <springProperty scope="context" name="url" source="spring.datasource.url"/>
  4. <springProperty scope="context" name="username" source="spring.datasource.username"/>
  5. <springProperty scope="context" name="password" source="spring.datasource.password"/>
  6. <springProperty scope="context" name="logFilePath" source="logging.path"/>
  7. <springProperty scope="context" name="maxHistory" source="logging.maxHistory"/>
  8.  
  9. <!-- 数据库日志记录 -->
  10. <appender name="DB_APPENDER" class="ch.qos.logback.classic.db.DBAppender">
  11.   <filter class="com.cenobitor.logging.filter.LogbackMarkerFilter">
  12.    <!-- 自定义标志 -->
  13.    <marker>DB</marker>
  14.    <onMatch>ACCEPT</onMatch>
  15.    <onMismatch>DENY</onMismatch>
  16. </filter>
  17.   <!-- 配置数据源 springboot默认情况会开启光连接池 -->
  18.   <connectionSource class="ch.qos.logback.core.db.DriverManagerConnectionSource">
  19. <driverClass>${driverClass}</driverClass>
  20. <url>${url}</url>
  21. <user>${username}</user>
  22. <password>${password}</password>
  23. </connectionSource>
  24. </appender>
  25.  
  26. <!-- 异步日志记录 -->
  27. <appender name="ASYNC_APPENDER" class="ch.qos.logback.classic.AsyncAppender">
  28.   <appender-ref ref="DB_APPENDER" />
  29.   <includeCallerData>true</includeCallerData>
  30. </appender>
  31.  
  32. <!-- 日志输出级别 -->
  33. <root level="${LOG_LEVEL}">
  34.   <appender-ref ref="ASYNC_APPENDER" />
  35. </root>

经配置,其需要建三张数据表,分别为日志信息、异常信息、属性信息,其建表语句如下:

  1. BEGIN;
  2. DROP TABLE IF EXISTS logging_event_property;
  3. DROP TABLE IF EXISTS logging_event_exception;
  4. DROP TABLE IF EXISTS logging_event;
  5. COMMIT;
  6.  
  7. BEGIN;
  8. CREATE TABLE logging_event
  9. (
  10. timestmp BIGINT NOT NULL,
  11. formatted_message TEXT NOT NULL,
  12. logger_name VARCHAR(254) NOT NULL,
  13. level_string VARCHAR(254) NOT NULL,
  14. thread_name VARCHAR(254),
  15. reference_flag SMALLINT,
  16. arg0 VARCHAR(254),
  17. arg1 VARCHAR(254),
  18. arg2 VARCHAR(254),
  19. arg3 VARCHAR(254),
  20. caller_filename VARCHAR(254) NOT NULL,
  21. caller_class VARCHAR(254) NOT NULL,
  22. caller_method VARCHAR(254) NOT NULL,
  23. caller_line CHAR(4) NOT NULL,
  24. event_id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY
  25. );
  26. COMMIT;
  27.  
  28. BEGIN;
  29. CREATE TABLE logging_event_property
  30. (
  31. event_id BIGINT NOT NULL,
  32. mapped_key VARCHAR(254) NOT NULL,
  33. mapped_value TEXT,
  34. PRIMARY KEY(event_id, mapped_key),
  35. FOREIGN KEY (event_id) REFERENCES logging_event(event_id)
  36. );
  37. COMMIT;
  38.  
  39. BEGIN;
  40. CREATE TABLE logging_event_exception
  41. (
  42. event_id BIGINT NOT NULL,
  43. i SMALLINT NOT NULL,
  44. trace_line VARCHAR(254) NOT NULL,
  45. PRIMARY KEY(event_id, i),
  46. FOREIGN KEY (event_id) REFERENCES logging_event(event_id)
  47. );
  48. COMMIT;

二、自定义输出日志

由于Logback原生要求建三张表,如何指定指输出一种信息,及自定义日志内容,而异常、属性信息不输出?

通过查看DBAppender发现,插入数据方法,此处只需重写DBAppender,即继承DBAppenderBase<ILoggingEvent>,删除掉异常、属性信息插入的相关方法即可实现只输出指定日志到指定表,而其它信息将不会输出到数据库中,代码如下:

  1. public class LogDBAppender extends DBAppenderBase<ILoggingEvent> {
  2.  
  3. protected String insertSQL;
  4. protected static final Method GET_GENERATED_KEYS_METHOD;
  5.  
  6. private DBNameResolver dbNameResolver;
  7.  
  8. static final int TIMESTMP_INDEX = 1;
  9. static final int FORMATTED_MESSAGE_INDEX = 2;
  10. static final int LOGGER_NAME_INDEX = 3;
  11. static final int LEVEL_STRING_INDEX = 4;
  12. static final int THREAD_NAME_INDEX = 5;
  13. static final int REFERENCE_FLAG_INDEX = 6;
  14. static final int ARG0_INDEX = 7;
  15. static final int ARG1_INDEX = 8;
  16. static final int ARG2_INDEX = 9;
  17. static final int ARG3_INDEX = 10;
  18. static final int CALLER_FILENAME_INDEX = 11;
  19. static final int CALLER_CLASS_INDEX = 12;
  20. static final int CALLER_METHOD_INDEX = 13;
  21. static final int CALLER_LINE_INDEX = 14;
  22. static final int EVENT_ID_INDEX = 15;
  23.  
  24. static final StackTraceElement EMPTY_CALLER_DATA = CallerData.naInstance();
  25.  
  26. static {
  27. // PreparedStatement.getGeneratedKeys() method was added in JDK 1.4
  28. Method getGeneratedKeysMethod;
  29. try {
  30. // the
  31. getGeneratedKeysMethod = PreparedStatement.class.getMethod("getGeneratedKeys", (Class[]) null);
  32. } catch (Exception ex) {
  33. getGeneratedKeysMethod = null;
  34. }
  35. GET_GENERATED_KEYS_METHOD = getGeneratedKeysMethod;
  36. }
  37.  
  38. public void setDbNameResolver(DBNameResolver dbNameResolver) {
  39. this.dbNameResolver = dbNameResolver;
  40. }
  41.  
  42. @Override
  43. public void start() {
  44. if (dbNameResolver == null)
  45. dbNameResolver = new DefaultDBNameResolver();
  46. insertSQL = buildInsertSQL(dbNameResolver);
  47. super.start();
  48. }
  49.  
  50. @Override
  51. protected void subAppend(ILoggingEvent event, Connection connection, PreparedStatement insertStatement) throws Throwable {
  52.  
  53. bindLoggingEventWithInsertStatement(insertStatement, event);
  54. bindLoggingEventArgumentsWithPreparedStatement(insertStatement, event.getArgumentArray());
  55.  
  56. // This is expensive... should we do it every time?
  57. bindCallerDataWithPreparedStatement(insertStatement, event.getCallerData());
  58.  
  59. int updateCount = insertStatement.executeUpdate();
  60. if (updateCount != 1) {
  61. addWarn("Failed to insert loggingEvent");
  62. }
  63. }
  64.  
  65. protected void secondarySubAppend(ILoggingEvent event, Connection connection, long eventId) throws Throwable {
  66. Map<String, String> mergedMap = mergePropertyMaps(event);
  67. //insertProperties(mergedMap, connection, eventId);
  68.  
  69. // if (event.getThrowableProxy() != null) {
  70. // insertThrowable(event.getThrowableProxy(), connection, eventId);
  71. // }
  72. }
  73.  
  74. void bindLoggingEventWithInsertStatement(PreparedStatement stmt, ILoggingEvent event) throws SQLException {
  75. stmt.setLong(TIMESTMP_INDEX, event.getTimeStamp());
  76. stmt.setString(FORMATTED_MESSAGE_INDEX, event.getFormattedMessage());
  77. stmt.setString(LOGGER_NAME_INDEX, event.getLoggerName());
  78. stmt.setString(LEVEL_STRING_INDEX, event.getLevel().toString());
  79. stmt.setString(THREAD_NAME_INDEX, event.getThreadName());
  80. stmt.setShort(REFERENCE_FLAG_INDEX, DBHelper.computeReferenceMask(event));
  81. }
  82.  
  83. void bindLoggingEventArgumentsWithPreparedStatement(PreparedStatement stmt, Object[] argArray) throws SQLException {
  84.  
  85. int arrayLen = argArray != null ? argArray.length : 0;
  86.  
  87. for (int i = 0; i < arrayLen && i < 4; i++) {
  88. stmt.setString(ARG0_INDEX + i, asStringTruncatedTo254(argArray[i]));
  89. }
  90. if (arrayLen < 4) {
  91. for (int i = arrayLen; i < 4; i++) {
  92. stmt.setString(ARG0_INDEX + i, null);
  93. }
  94. }
  95. }
  96.  
  97. String asStringTruncatedTo254(Object o) {
  98. String s = null;
  99. if (o != null) {
  100. s = o.toString();
  101. }
  102.  
  103. if (s == null) {
  104. return null;
  105. }
  106. if (s.length() <= 254) {
  107. return s;
  108. } else {
  109. return s.substring(0, 254);
  110. }
  111. }
  112.  
  113. void bindCallerDataWithPreparedStatement(PreparedStatement stmt, StackTraceElement[] callerDataArray) throws SQLException {
  114.  
  115. StackTraceElement caller = extractFirstCaller(callerDataArray);
  116.  
  117. stmt.setString(CALLER_FILENAME_INDEX, caller.getFileName());
  118. stmt.setString(CALLER_CLASS_INDEX, caller.getClassName());
  119. stmt.setString(CALLER_METHOD_INDEX, caller.getMethodName());
  120. stmt.setString(CALLER_LINE_INDEX, Integer.toString(caller.getLineNumber()));
  121. }
  122.  
  123. private StackTraceElement extractFirstCaller(StackTraceElement[] callerDataArray) {
  124. StackTraceElement caller = EMPTY_CALLER_DATA;
  125. if (hasAtLeastOneNonNullElement(callerDataArray))
  126. caller = callerDataArray[0];
  127. return caller;
  128. }
  129.  
  130. private boolean hasAtLeastOneNonNullElement(StackTraceElement[] callerDataArray) {
  131. return callerDataArray != null && callerDataArray.length > 0 && callerDataArray[0] != null;
  132. }
  133.  
  134. Map<String, String> mergePropertyMaps(ILoggingEvent event) {
  135. Map<String, String> mergedMap = new HashMap<String, String>();
  136. // we add the context properties first, then the event properties, since
  137. // we consider that event-specific properties should have priority over
  138. // context-wide properties.
  139. Map<String, String> loggerContextMap = event.getLoggerContextVO().getPropertyMap();
  140. Map<String, String> mdcMap = event.getMDCPropertyMap();
  141. if (loggerContextMap != null) {
  142. mergedMap.putAll(loggerContextMap);
  143. }
  144. if (mdcMap != null) {
  145. mergedMap.putAll(mdcMap);
  146. }
  147.  
  148. return mergedMap;
  149. }
  150.  
  151. @Override
  152. protected Method getGeneratedKeysMethod() {
  153. return GET_GENERATED_KEYS_METHOD;
  154. }
  155.  
  156. @Override
  157. protected String getInsertSQL() {
  158. return insertSQL;
  159. }
  160.  
  161. static String buildInsertSQL(DBNameResolver dbNameResolver) {
  162. StringBuilder sqlBuilder = new StringBuilder("INSERT INTO ");
  163. sqlBuilder.append(dbNameResolver.getTableName(TableName.LOGGING_EVENT)).append(" (");
  164. sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.TIMESTMP)).append(", ");
  165. sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.FORMATTED_MESSAGE)).append(", ");
  166. sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.LOGGER_NAME)).append(", ");
  167. sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.LEVEL_STRING)).append(", ");
  168. sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.THREAD_NAME)).append(", ");
  169. sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.REFERENCE_FLAG)).append(", ");
  170. sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.ARG0)).append(", ");
  171. sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.ARG1)).append(", ");
  172. sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.ARG2)).append(", ");
  173. sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.ARG3)).append(", ");
  174. sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.CALLER_FILENAME)).append(", ");
  175. sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.CALLER_CLASS)).append(", ");
  176. sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.CALLER_METHOD)).append(", ");
  177. sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.CALLER_LINE)).append(") ");
  178. sqlBuilder.append("VALUES (?, ?, ? ,?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
  179. return sqlBuilder.toString();
  180. }
  181. }

现在只需将配置中引用的DBAppender:

  1. <appender name="DB_APPENDER" class="ch.qos.logback.classic.db.DBAppender">

更改为自己重写的类:

  1. <appender name="DB_APPENDER" class="com.cenobitor.logging.db.LogDBAppender">

表logging_event_property、表logging_event_exception将可删除,至此基本的配置已完成,可以畅快的使用了。

三、使用

  1. log.info(MarkerFactory.getMarker("DB"), "logback!");
  1. 即可异步将该日志输出到数据库中。 

Logback 日志持久化的更多相关文章

  1. Logback日志系统配置攻略

    logback是log4j作者推出的新日志系统,原生支持slf4j通用日志api,允许平滑切换日志系统,并且对简化应用部署中日志处理的工作做了有益的封装. 官方地址为:http://logback.q ...

  2. lombok+slf4j+logback SLF4J和Logback日志框架详解

    maven 包依赖 <dependency> <groupId>org.projectlombok</groupId> <artifactId>lomb ...

  3. mybatis用logback日志不显示sql的解决办法

    mybatis用logback日志不显示sql的解决方法 1.mybatis-config.xml的设定 关于logimpl的设定值还不支持logback,如果用SLF4J是不好用的. 这是官方文档的 ...

  4. Logback日志配置的简单使用

    Logback介绍 Logback是由log4j创始人设计的又一个开源日志组件.logback当前分成三个模块:logback-core,logback- classic和logback-access ...

  5. 在SpringBoot中添加Logback日志处理

    前言 SpringBoot项目中在官方文档中说明,默认已经依赖了一些日志框架.而其中推荐使用的就是Logback,所以这一次我将在我的模版中加入Logback日志的配置,说明一下,SpringBoot ...

  6. 剑指架构师系列-spring boot的logback日志记录

    Spring Boot集成了Logback日志系统. Logback的核心对象主要有3个:Logger.Appender.Layout 1.Logback Logger:日志的记录器 主要用于存放日志 ...

  7. Logback日志基础配置以及自定义配置

    Logback日志基础配置 logback日志配置有很多介绍,但是有几个非常基础的,容易忽略的.下面是最简单的一个配置,注意加粗的描述 <?xml version="1.0" ...

  8. redis快照持久化和aof日志持久化

    持久化就是即使断电/重启需要存储的数据不会丢失,即将数据存储在设备中,一般存在硬盘内 redis的持久化有2种方式 :1-rdb快照  2-aof日志,可以通过配置redis.conf文件进行配置 r ...

  9. springBoot(10)---logback日志

    logback日志 一.概述  和log4j优点: 实际上,这两个日志框架都出自同一个开发者之手,Logback 相对于 Log4J 有更多的优点 (1)logback不仅性能提升了,初始化内存加载也 ...

随机推荐

  1. IPv6 Can't assign requested address

    今天试了下 bind IPv6 的地址,报错  Can't assign requested address http://stackoverflow.com/questions/24780404/p ...

  2. 简单记录常用git 命令

    声明:仅作笔记用 拉取远程代码 1.git pull 2.如果需要,输入账户名密码 将本地代码推送到远程 1.git push 2.如果需要,输入账户名密码 同步远程分支 1.git fetch 2. ...

  3. Redis---基础数据结构

    1.String(字符串) 1.1 概述 字符串 string 是 Redis 最简单的数据结构.Redis 所有的数据结构都是以唯一的 key 字符串作为名称,然后通过这个唯一 key 值来获取相应 ...

  4. D15——C语言基础学PYTHON

    C语言基础学习PYTHON——基础学习D15 20180926内容纲要: 1.CSS介绍 2.CSS的四种引入方式 3.CSS选择器 4.CSS常用属性 5.小结 6.练习 1 CSS介绍 层叠样式表 ...

  5. Python(27)--文件相关处理的应用(增、删、改、查)

    文件名为message,文件内容如下: global log 127.0.0.1 local2 daemon maxconn 256 log 127.0.0.1 local2 info default ...

  6. 【6】JMicro微服务-服务日志监控

    如非授权,禁止用于商业用途,转载请注明出处作者:mynewworldyyl   1. 微服务相关 在前面的1到5节中,总共涉及服务提供者,服务消费者,服务监听服务,发布订阅服务,熔断器服务5种类型的猪 ...

  7. SQLServer——SQLServer链接外部数据源

    学习链接:https://www.cnblogs.com/licin/p/6244169.html 一.新建ODBC数据源 1.打开控制面板→管理工具→ODBC数据源→系统DSN 2.添加新系统数据源 ...

  8. Django模版语言自定义标签-实现前端 关联组合过滤查询

    前端关联 组合过滤查询 实现效果如图: models.py 创建表代码 from django.db import models # Create your models here. class Le ...

  9. 剑指offer七之斐波那契数列

    一.题目 大家都知道斐波那契数列,现在要求输入一个整数n,请你输出斐波那契数列的第n项.n<=39. 二.思路 序号:                  0  1   2   3  4   5  ...

  10. Java动态代理总结

    在之前的代码调用阶段,我们用action调用service的方法实现业务即可. 由于之前在service中实现的业务可能不能够满足当先客户的要求,需要我们重新修改service中的方法,但是servi ...