springboot2.7.x 集成log4j2配置写入日志到mysql自定义表格
在阅读之前请先查看
本文暂不考虑抽象等实现方式,只限于展示如何自定义配置log4j2并写入mysql数据库(自定义结构)
先看下log4j2的配置
<?xml version="1.0" encoding="UTF-8"?>
./logs/demo/log4j2/
注意看如下这段
我们在此处并没有采用官方的jdbc配置方式而是采用了自己的nirvana.core.logger.LogConnectionFactory
nirvana.core.logger.LogConnectionFactory 的代码
package nirvana.core.logger;
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.boot.env.YamlPropertySourceLoader;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.ClassPathResource;
import org.yaml.snakeyaml.Yaml;
import javax.sql.DataSource;
import java.io.IOException;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
import java.util.Objects;
import java.util.Properties;
/**
* ConnectionFactory
*
* @author linkanyway
* @version 1.0
* @date 2022/05/26 20:05
*/
public class LogConnectionFactory {
private DataSource dataSource;
private static String PRE_FIX = "spring.datasource.";
/**
* constructor
*/
private LogConnectionFactory() {
ClassPathResource ymlResource = new ClassPathResource ("application.yml");
ClassPathResource propertyResource = new ClassPathResource ("application.properties");
ClassPathResource resource;
if (!(resource = new ClassPathResource ("application.yml")).exists ()) {
resource = new ClassPathResource ("application.properties");
if (!resource.exists ()) {
throw new RuntimeException ("no application configuration file found");
}
}
Yaml yaml = new Yaml ();
YamlPropertySourceLoader loader = new YamlPropertySourceLoader ();
try {
List> list = loader.load ("log-datasource", resource);
PropertySource<?> prop = list.get (0);
Properties props = new Properties ();
props.setProperty ("druid.url", Objects.requireNonNull (prop.getProperty (PRE_FIX + "url")).toString ());
props.setProperty ("druid.username",
Objects.requireNonNull (prop.getProperty (PRE_FIX + "username")).toString ());
props.setProperty ("druid.password",
Objects.requireNonNull (prop.getProperty (PRE_FIX + "password")).toString ());
props.setProperty ("druid.driverClassName",
Objects.requireNonNull (prop.getProperty (PRE_FIX + "driver" + "-class-name")).toString ());
this.dataSource = new DruidDataSource ();
((DruidDataSource) this.dataSource).configFromPropety (props);
} catch (IOException e) {
throw new RuntimeException (e);
}
}
/**
* get connection
*
* @return
* @throws SQLException
*/
public static Connection getConnection() throws SQLException {
return Singleton.INSTANCE.dataSource.getConnection ();
}
/**
* only used inner
*/
private interface Singleton {
LogConnectionFactory INSTANCE = new LogConnectionFactory ();
}
}
此处只是粗糙的码了一个读取properties和yaml文件的类,结合配置文件内制定的factory即实现了log4j2读取mysql connection的目地
建立自定义的表结构
database sql
/*
Navicat Premium Data Transfer
Source Server : localhost
Source Server Type : MySQL
Source Server Version : 80023
Source Host : localhost:3306
Source Schema : log4j2
Target Server Type : MySQL
Target Server Version : 80023
File Encoding : 65001
Date: 27/05/2022 11:35:19
*/
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for logs
-- ----------------------------
DROP TABLE IF EXISTS `logs`;
CREATE TABLE `logs` (
`ID` varchar(50) NOT NULL,
`TRACE_ID` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL,
`DATE_TIME` timestamp NULL DEFAULT NULL,
`CLASS` varchar(100) DEFAULT NULL,
`LEVEL` varchar(10) DEFAULT NULL,
`MESSAGE` text,
`EXCEPTION` text,
`IP` text,
PRIMARY KEY (`ID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
SET FOREIGN_KEY_CHECKS = 1;
这个表结构是对应的配置文件中的如下内容
如果想知道表结构的列是如何和日志对上的接着往下看,
核心调用类LoggerManager
package nirvana.core.logger;
import nirvana.core.context.WebContext;
import nirvana.core.utils.NetUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.ThreadContext;
import org.apache.logging.log4j.core.net.TcpSocketManager;
import org.apache.logging.log4j.message.StringMapMessage;
import org.apache.logging.log4j.util.Strings;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.support.RequestContextUtils;
import javax.servlet.http.HttpServletRequest;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.time.LocalDateTime;
import java.util.UUID;
/**
* LogManager
*
* @author linkanyway
* @version 1.0
* @date 2022/05/27 11:06
*/
public class LoggerManager {
// database sql
// /*
// Navicat Premium Data Transfer
//
// Source Server : localhost
// Source Server Type : MySQL
// Source Server Version : 80023
// Source Host : localhost:3306
// Source Schema : log4j2
//
// Target Server Type : MySQL
// Target Server Version : 80023
// File Encoding : 65001
//
// Date: 27/05/2022 11:35:19
//*/
//
// SET NAMES utf8mb4;
// SET FOREIGN_KEY_CHECKS = 0;
//
//-- ----------------------------
// -- Table structure for logs
//-- ----------------------------
// DROP TABLE IF EXISTS `logs`;
// CREATE TABLE `logs` (
// `ID` varchar(50) NOT NULL,
// `TRACE_ID` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL,
// `DATE_TIME` timestamp NULL DEFAULT NULL,
// `CLASS` varchar(100) DEFAULT NULL,
// `LEVEL` varchar(10) DEFAULT NULL,
// `MESSAGE` text,
// `EXCEPTION` text,
// `IP` text,
// PRIMARY KEY (`ID`)
//) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
//
// SET FOREIGN_KEY_CHECKS = 1;
/**
* todo: use normal class
* logger
*/
public Logger logger;
/**
* get logger
*
* @param loggerName
* @return
*/
public static LoggerManager getLogger(String loggerName) {
return new LoggerManager (loggerName);
}
private LoggerManager(String loggerName) {
this.logger = LogManager.getLogger (loggerName);
}
/**
* error
*
* @param message error message
* @param ex exception instance
*/
public void error(String message, @NotNull Exception ex) {
StringMapMessage msg = generate (message, "error", ex);
logger.error (msg);
}
/**
* info
*
* @param message info message
*/
public void info(String message) {
StringMapMessage msg = generate (message, "info");
logger.info (msg);
}
/**
* trace
*
* @param message trace message
*/
public void trace(String message) {
StringMapMessage msg = generate (message, "info");
logger.trace (msg);
}
/**
* debug
*
* @param message debug message
*/
public void debug(String message) {
StringMapMessage msg = generate (message, "debug");
logger.debug (msg);
}
/**
* warn
*
* @param message warn message
*/
public void warn(String message) {
StringMapMessage msg = generate (message, "warn");
logger.warn (msg);
}
/**
* generate map message
*
* @param message message content
* @param level level
* @return StringMapMessage instance
*/
private StringMapMessage generate(String message, String level) {
return generate (message, level, null);
}
/**
* get caller information
*
* @return caller class name
*/
private String getCallerName() {
StackTraceElement[] traces = Thread.currentThread ().getStackTrace ();
for (Integer i = 1; i < traces.length - 1; i++) {
StackTraceElement element = traces[i];
if (element.getClassName ().indexOf (this.getClass ().getPackageName ()) != 0) {
return element.getClassName ();
}
}
return Strings.EMPTY;
}
/**
* generate message
*
* @param message message content
* @param level level
* @param ex exception
* @return StringMapMessage instance
*/
private StringMapMessage generate(String message, String level, Exception ex) {
String sourceClassName = getCallerName ();
StringMapMessage msg = new StringMapMessage ();
if (ex != null) {
// have to deal with the exception to prevent throw again
try {
StringWriter sw = new StringWriter ();
PrintWriter pw = new PrintWriter (sw, true);
ex.printStackTrace (pw);
msg.put ("EXCEPTION", sw.getBuffer ().toString ());
} catch (Exception e) {
throw e;
}
} else {
msg.put ("EXCEPTION", "");
}
msg.put ("LEVEL", level);
msg.put ("ID", UUID.randomUUID ().toString ());
msg.put ("TRACE_ID", WebContext.getRequestId ());
msg.put ("DATE_TIME", LocalDateTime.now ().toString ());
msg.put ("CLASS", sourceClassName);
msg.put ("MESSAGE", message);
msg.put ("IP", NetUtils.getRemoteIp ());
return msg;
}
}
注意代码中这段
msg.put ("LEVEL", level);
msg.put ("ID", UUID.randomUUID ().toString ());
msg.put ("TRACE_ID", WebContext.getRequestId ());
msg.put ("DATE_TIME", LocalDateTime.now ().toString ());
msg.put ("CLASS", sourceClassName);
msg.put ("MESSAGE", message);
msg.put ("IP", NetUtils.getRemoteIp ());
其实我们只是使用了StringMapMessage实现了自定义消息结构
而如下代码则是为了获取调用类,此处只是利用调用堆栈寻取了第一个和当前日志记录类LoggerManagger的package不同包的第一个类,实际对于调用堆栈获取需要的caller还是需要自己按照自己的方式寻找
/**
* get caller information
*
* @return caller class name
*/
private String getCallerName() {
StackTraceElement[] traces = Thread.currentThread ().getStackTrace ();
for (Integer i = 1; i < traces.length - 1; i++) {
StackTraceElement element = traces[i];
if (element.getClassName ().indexOf (this.getClass ().getPackageName ()) != 0) {
return element.getClassName ();
}
}
return Strings.EMPTY;
}