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

一、自定义log标志

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

1、自定义标志过滤器

 public class LogbackMarkerFilter extends AbstractMatcherFilter<ILoggingEvent> {

     private Marker markerToMatch = null;

     @Override
public void start() {
if (null != this.markerToMatch) {
super.start();
} else {
addError(" no MARKER yet !");
}
} @Override
public FilterReply decide(ILoggingEvent event) {
Marker marker = event.getMarker();
if (!isStarted()) {
return FilterReply.NEUTRAL;
}
if (null == marker) {
return onMismatch;
}
if (markerToMatch.contains(marker)) {
return onMatch;
}
return onMismatch;
} public void setMarker(String markerStr) {
if (null != markerStr) {
markerToMatch = MarkerFactory.getMarker(markerStr);
}
}
}

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

 <!-- 读取配置文件中参数 -->
<springProperty scope="context" name="driverClass" source="spring.datasource.driver-class-name"/>
<springProperty scope="context" name="url" source="spring.datasource.url"/>
<springProperty scope="context" name="username" source="spring.datasource.username"/>
<springProperty scope="context" name="password" source="spring.datasource.password"/>
<springProperty scope="context" name="logFilePath" source="logging.path"/>
<springProperty scope="context" name="maxHistory" source="logging.maxHistory"/> <!-- 数据库日志记录 -->
<appender name="DB_APPENDER" class="ch.qos.logback.classic.db.DBAppender">
  <filter class="com.cenobitor.logging.filter.LogbackMarkerFilter">
   <!-- 自定义标志 -->
   <marker>DB</marker>
   <onMatch>ACCEPT</onMatch>
   <onMismatch>DENY</onMismatch>
</filter>
  <!-- 配置数据源 springboot默认情况会开启光连接池 -->
  <connectionSource class="ch.qos.logback.core.db.DriverManagerConnectionSource">
<driverClass>${driverClass}</driverClass>
<url>${url}</url>
<user>${username}</user>
<password>${password}</password>
</connectionSource>
</appender> <!-- 异步日志记录 -->
<appender name="ASYNC_APPENDER" class="ch.qos.logback.classic.AsyncAppender">
  <appender-ref ref="DB_APPENDER" />
  <includeCallerData>true</includeCallerData>
</appender> <!-- 日志输出级别 -->
<root level="${LOG_LEVEL}">
  <appender-ref ref="ASYNC_APPENDER" />
</root>

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

 BEGIN;
DROP TABLE IF EXISTS logging_event_property;
DROP TABLE IF EXISTS logging_event_exception;
DROP TABLE IF EXISTS logging_event;
COMMIT; BEGIN;
CREATE TABLE logging_event
(
timestmp BIGINT NOT NULL,
formatted_message TEXT 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 BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY
);
COMMIT; BEGIN;
CREATE TABLE logging_event_property
(
event_id BIGINT NOT NULL,
mapped_key VARCHAR(254) NOT NULL,
mapped_value TEXT,
PRIMARY KEY(event_id, mapped_key),
FOREIGN KEY (event_id) REFERENCES logging_event(event_id)
);
COMMIT; BEGIN;
CREATE TABLE logging_event_exception
(
event_id BIGINT NOT NULL,
i SMALLINT NOT NULL,
trace_line VARCHAR(254) NOT NULL,
PRIMARY KEY(event_id, i),
FOREIGN KEY (event_id) REFERENCES logging_event(event_id)
);
COMMIT;

二、自定义输出日志

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

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

 public class LogDBAppender extends DBAppenderBase<ILoggingEvent> {

     protected String insertSQL;
protected static final Method GET_GENERATED_KEYS_METHOD; private DBNameResolver dbNameResolver; static final int TIMESTMP_INDEX = 1;
static final int FORMATTED_MESSAGE_INDEX = 2;
static final int LOGGER_NAME_INDEX = 3;
static final int LEVEL_STRING_INDEX = 4;
static final int THREAD_NAME_INDEX = 5;
static final int REFERENCE_FLAG_INDEX = 6;
static final int ARG0_INDEX = 7;
static final int ARG1_INDEX = 8;
static final int ARG2_INDEX = 9;
static final int ARG3_INDEX = 10;
static final int CALLER_FILENAME_INDEX = 11;
static final int CALLER_CLASS_INDEX = 12;
static final int CALLER_METHOD_INDEX = 13;
static final int CALLER_LINE_INDEX = 14;
static final int EVENT_ID_INDEX = 15; static final StackTraceElement EMPTY_CALLER_DATA = CallerData.naInstance(); static {
// PreparedStatement.getGeneratedKeys() method was added in JDK 1.4
Method getGeneratedKeysMethod;
try {
// the
getGeneratedKeysMethod = PreparedStatement.class.getMethod("getGeneratedKeys", (Class[]) null);
} catch (Exception ex) {
getGeneratedKeysMethod = null;
}
GET_GENERATED_KEYS_METHOD = getGeneratedKeysMethod;
} public void setDbNameResolver(DBNameResolver dbNameResolver) {
this.dbNameResolver = dbNameResolver;
} @Override
public void start() {
if (dbNameResolver == null)
dbNameResolver = new DefaultDBNameResolver();
insertSQL = buildInsertSQL(dbNameResolver);
super.start();
} @Override
protected void subAppend(ILoggingEvent event, Connection connection, PreparedStatement insertStatement) throws Throwable { bindLoggingEventWithInsertStatement(insertStatement, event);
bindLoggingEventArgumentsWithPreparedStatement(insertStatement, event.getArgumentArray()); // This is expensive... should we do it every time?
bindCallerDataWithPreparedStatement(insertStatement, event.getCallerData()); int updateCount = insertStatement.executeUpdate();
if (updateCount != 1) {
addWarn("Failed to insert loggingEvent");
}
} protected void secondarySubAppend(ILoggingEvent event, Connection connection, long eventId) throws Throwable {
Map<String, String> mergedMap = mergePropertyMaps(event);
//insertProperties(mergedMap, connection, eventId); // if (event.getThrowableProxy() != null) {
// insertThrowable(event.getThrowableProxy(), connection, eventId);
// }
} void bindLoggingEventWithInsertStatement(PreparedStatement stmt, ILoggingEvent event) throws SQLException {
stmt.setLong(TIMESTMP_INDEX, event.getTimeStamp());
stmt.setString(FORMATTED_MESSAGE_INDEX, event.getFormattedMessage());
stmt.setString(LOGGER_NAME_INDEX, event.getLoggerName());
stmt.setString(LEVEL_STRING_INDEX, event.getLevel().toString());
stmt.setString(THREAD_NAME_INDEX, event.getThreadName());
stmt.setShort(REFERENCE_FLAG_INDEX, DBHelper.computeReferenceMask(event));
} void bindLoggingEventArgumentsWithPreparedStatement(PreparedStatement stmt, Object[] argArray) throws SQLException { int arrayLen = argArray != null ? argArray.length : 0; for (int i = 0; i < arrayLen && i < 4; i++) {
stmt.setString(ARG0_INDEX + i, asStringTruncatedTo254(argArray[i]));
}
if (arrayLen < 4) {
for (int i = arrayLen; i < 4; i++) {
stmt.setString(ARG0_INDEX + i, null);
}
}
} String asStringTruncatedTo254(Object o) {
String s = null;
if (o != null) {
s = o.toString();
} if (s == null) {
return null;
}
if (s.length() <= 254) {
return s;
} else {
return s.substring(0, 254);
}
} void bindCallerDataWithPreparedStatement(PreparedStatement stmt, StackTraceElement[] callerDataArray) throws SQLException { StackTraceElement caller = extractFirstCaller(callerDataArray); stmt.setString(CALLER_FILENAME_INDEX, caller.getFileName());
stmt.setString(CALLER_CLASS_INDEX, caller.getClassName());
stmt.setString(CALLER_METHOD_INDEX, caller.getMethodName());
stmt.setString(CALLER_LINE_INDEX, Integer.toString(caller.getLineNumber()));
} private StackTraceElement extractFirstCaller(StackTraceElement[] callerDataArray) {
StackTraceElement caller = EMPTY_CALLER_DATA;
if (hasAtLeastOneNonNullElement(callerDataArray))
caller = callerDataArray[0];
return caller;
} private boolean hasAtLeastOneNonNullElement(StackTraceElement[] callerDataArray) {
return callerDataArray != null && callerDataArray.length > 0 && callerDataArray[0] != null;
} Map<String, String> mergePropertyMaps(ILoggingEvent event) {
Map<String, String> mergedMap = new HashMap<String, String>();
// we add the context properties first, then the event properties, since
// we consider that event-specific properties should have priority over
// context-wide properties.
Map<String, String> loggerContextMap = event.getLoggerContextVO().getPropertyMap();
Map<String, String> mdcMap = event.getMDCPropertyMap();
if (loggerContextMap != null) {
mergedMap.putAll(loggerContextMap);
}
if (mdcMap != null) {
mergedMap.putAll(mdcMap);
} return mergedMap;
} @Override
protected Method getGeneratedKeysMethod() {
return GET_GENERATED_KEYS_METHOD;
} @Override
protected String getInsertSQL() {
return insertSQL;
} static String buildInsertSQL(DBNameResolver dbNameResolver) {
StringBuilder sqlBuilder = new StringBuilder("INSERT INTO ");
sqlBuilder.append(dbNameResolver.getTableName(TableName.LOGGING_EVENT)).append(" (");
sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.TIMESTMP)).append(", ");
sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.FORMATTED_MESSAGE)).append(", ");
sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.LOGGER_NAME)).append(", ");
sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.LEVEL_STRING)).append(", ");
sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.THREAD_NAME)).append(", ");
sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.REFERENCE_FLAG)).append(", ");
sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.ARG0)).append(", ");
sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.ARG1)).append(", ");
sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.ARG2)).append(", ");
sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.ARG3)).append(", ");
sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.CALLER_FILENAME)).append(", ");
sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.CALLER_CLASS)).append(", ");
sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.CALLER_METHOD)).append(", ");
sqlBuilder.append(dbNameResolver.getColumnName(ColumnName.CALLER_LINE)).append(") ");
sqlBuilder.append("VALUES (?, ?, ? ,?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
return sqlBuilder.toString();
}
}

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

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

更改为自己重写的类:

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

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

三、使用

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

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. Android常用布局属性解析 -- Layout_weight

    Layout_weight是Android开发中一个比较常用的布局属性,在面试中也经常被问到.下面通过实例彻底搞懂Layout_weight的用法. 先看下面的布局代码: <?xml versi ...

  2. Swift5 语言指南(二十四) 泛型

    通用代码使您能够根据您定义的要求编写可以使用任何类型的灵活,可重用的函数和类型.您可以编写避免重复的代码,并以清晰,抽象的方式表达其意图. 泛型是Swift最强大的功能之一,Swift标准库的大部分内 ...

  3. javascript之快速排序

    快速排序思想其实还是挺简单的,分三步走: 1.在数组中找到基准点,其他数与之比较. 2.建立两个数组,小于基准点的数存储在左边数组,大于基准点的数存储在右边数组. 3.拼接数组,然后左边数组与右边数组 ...

  4. flaks___git

    今天呢  我给大家分享一个超实用的一个把代码分享到云端的一种操作 比如我们在家里,要想做项目的话可以直接从云端上拉取下来代码直接开始工作了 而且还可以随时修改,没有地点的局限性了,只要你想敲,随时随地 ...

  5. [原创]K8飞刀 新增Acunetix WVS 远程漏洞 反制黑客

    工具: K8飞刀20150603组织: K8搞基大队[K8team]作者: K8拉登哥哥博客: http://qqhack8.blog.163.com发布: 2015/6/3 20:41:29 简介: ...

  6. odoo第三方市场 -- 模块推荐

    odoo 除了开源,另一个非常给力的地方就是,强大的第三方应用市场: 你入坑后,会发现非常的好玩,全球还有这么多小伙伴并肩前行,共同成长. 第三方市场有很多不错的模块,当然,好东西,不是完全免费的! ...

  7. cmd/git设置alias提高效率

    cmd设置alias 在cmd或者git中有有些命令是比较长的,却需要频繁的使用,那么我们就可以设置alias来简化操作,无形中减少大量的宝贵时间,具体步骤如下. 第一步: 创建cmd_alias.b ...

  8. 安装python-devel开发包

    1.概述 有时在安装某些软件的时候,会报错: Error: must have python development packages -devel, python2.-devel, python2. ...

  9. docker使用非root用户启动容器出现“running exec setns process for init caused \"exit status 40\"": unknown”

    环境为centos7,linux内核版本为3.10 出现该问题的原因是内核3.10的bug,升级linux内核即可,升级办法如下,升级完成后重启系统,选择对应的内核版本启动即可. .导入key rpm ...

  10. Spring Boot + Spring Cloud 实现权限管理系统 后端篇(七):集成 Druid 数据源

    数据库连接池负责分配.管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个:释放空闲时间超过最大空闲时间的数据库连接来避免因为没有释放数据库连接而引起的数据库连接遗漏 ...