添加引用
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<!--排除掉logging 因为使用log4j2-->
<exclusions>
<exclusion>
<artifactId>spring-boot-starter-logging</artifactId>
<groupId>org.springframework.boot</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--采用log4j2来处理log-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
<!-- FastJSON 序列化/反序列化 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.58</version>
</dependency>
添加 日志 发送类 RedisLogAppender.java
package com.example.xx;
import com.alibaba.fastjson.JSONObject;
import org.apache.logging.log4j.core.Appender;
import org.apache.logging.log4j.core.Core;
import org.apache.logging.log4j.core.Filter;
import org.apache.logging.log4j.core.Layout;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.appender.AbstractAppender;
import org.apache.logging.log4j.core.config.Property;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
import org.apache.logging.log4j.core.config.plugins.PluginElement;
import org.apache.logging.log4j.core.config.plugins.PluginFactory;
import org.apache.logging.log4j.core.layout.PatternLayout;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import java.io.Serializable;
import java.util.Objects;
@Plugin(name = "RedisLogAppender", category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE, printObject = true)
public class RedisLogAppender extends AbstractAppender {
private String redisKey = null;
private String startsWith = null;
private String endsWith = null;
private String regex = null;
protected RedisLogAppender(String name, Filter filter, Layout<? extends Serializable> layout, boolean ignoreExceptions,
String redisKey, String startsWith, String endsWith, String regex) {
super(name, filter, layout, ignoreExceptions, Property.EMPTY_ARRAY);
if (Objects.nonNull(redisKey) && !redisKey.isEmpty()) {
this.redisKey = redisKey;
}
if (Objects.nonNull(startsWith) && !startsWith.isEmpty()) {
this.startsWith = startsWith;
}
if (Objects.nonNull(endsWith) && !endsWith.isEmpty()) {
this.endsWith = endsWith;
}
if (Objects.nonNull(regex) && !regex.isEmpty()) {
this.regex = regex;
}
}
/** 接收配置文件中的参数 */
@PluginFactory
public static RedisLogAppender createAppender(@PluginAttribute("name") String name,
@PluginElement("Filter") final Filter filter,
@PluginElement("Layout") Layout<? extends Serializable> layout,
@PluginAttribute("ignoreExceptions") boolean ignoreExceptions,
@PluginAttribute("key") String key,
@PluginAttribute("startsWith") String startsWith,
@PluginAttribute("endsWith") String endsWith,
@PluginAttribute("regex") String regex) {
if (name == null) {
System.err.println("no name defined in conf.");
return null;
}
if (layout == null) {
layout = PatternLayout.createDefaultLayout();
}
return new RedisLogAppender(name, filter, layout, ignoreExceptions, key, startsWith, endsWith, regex);
}
@Override
public void append(LogEvent event) {
StringRedisTemplate redis = AppContext.getBean(StringRedisTemplate.class);
if (Objects.isNull(redis)) {
return;
}
checkKey();
// 设置发送规则
String msg = event.getMessage().getFormattedMessage();
if (nonPush(msg)) {
return;
}
String lvName = event.getLevel().getStandardLevel().name();
JSONObject object = new JSONObject();
object.put("level", lvName);
object.put("msg", msg);
object.put("time", System.currentTimeMillis());
msg = object.toJSONString();
redis.opsForList().leftPush(this.redisKey, msg);
System.err.println(msg);
}
/** 日志启动在spring boot 之前,避免 启动前发送日志 */
private void checkKey() {
if (Objects.nonNull(this.redisKey)) {
return;
}
this.redisKey = AppContext.getProperty("logs.cache.key", null);
if (Objects.nonNull(this.redisKey)) {
return;
}
this.redisKey = AppContext.getProperty("spring.application.name", "spring:application:name");
this.redisKey += ":log";
}
/**
* 不推送
*
* @param msg 需要验证的文本
* @return 是否不推送
*/
private boolean nonPush(String msg) {
if (Objects.nonNull(this.startsWith) && !msg.startsWith(this.startsWith)) {
return true;
}
if (Objects.nonNull(this.endsWith) && !msg.endsWith(this.endsWith)) {
return true;
}
if (Objects.nonNull(this.regex) && !msg.matches(regex)) {
return true;
}
return false;
}
}
日志接受类 RedisLogReader.java
package com.example.xx;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import java.util.Objects;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
/** 日志存储 */
@Service
public class RedisLogReader implements InitializingBean, DisposableBean {
private AtomicBoolean isRun = new AtomicBoolean(false);
private AtomicBoolean disposable = new AtomicBoolean(false);
@Value("${logs.cache.save.corePoolSize:0}")
private int corePoolSize;
@Value("${logs.cache.key:}")
private String redisKey;
StringRedisTemplate redis;
private ConcurrentHashMap<String, ConcurrentHashMap<String, Object>> allMap = new ConcurrentHashMap<>();
protected ThreadPoolExecutor executor;
protected final BlockingQueue<Runnable> linkedBlockingQueue = new LinkedBlockingQueue<>();
@Override
public void afterPropertiesSet() {
if (corePoolSize < 1) {
return;
}
start();
}
/** 开始 */
private void start() {
// 线程实例
long keepAliveTime = 0l;
this.executor = new ThreadPoolExecutor(corePoolSize, this.corePoolSize, keepAliveTime, TimeUnit.MILLISECONDS,
linkedBlockingQueue, new DefaultThreadFactory());
this.executor.prestartAllCoreThreads();
for (int i = 0; i < corePoolSize; i++) {
this.executor.execute(new DefaultTimerTask("save_log_task_" + i));
}
redis = AppContext.getBean(StringRedisTemplate.class);
}
private void readLogs() {
if (this.isDisposable()) {
return;
}
this.checkRedis();
if (!this.isRun.get()) {
return;
}
if (Objects.isNull(redisKey)) {
return;
}
if (!redis.hasKey(redisKey)) {
return;
}
for (int i = 0; i < 9999; i++) {
if (!redis.hasKey(redisKey)) {
break;
}
String logTxt = redis.opsForList().rightPop(redisKey);
if (Objects.isNull(logTxt)) {
break;
}
try {
execLog(logTxt);
} catch (Exception e) {
e.printStackTrace();
}
}
}
private void execLog(String log) {
//TODO 执行日志保存
System.err.println("execLog = > " + log);
}
public boolean isDisposable() {
return this.disposable.get();
}
/** 用于检查是否获取到了redis对象 , 当 */
private void checkRedis() {
if (this.isRun.get()) {
return;
}
this.redis = AppContext.getBean(StringRedisTemplate.class);
if (Objects.isNull(this.redis)) {
return;
}
this.isRun.set(true);
}
@Override
public void destroy() throws Exception {
this.disposable.set(true);
System.err.println("容器销毁!!!!!!");
if (this.executor != null) {
this.executor.shutdownNow();
System.err.println("关闭线程!!!!!!");
}
}
class DefaultTimerTask implements Runnable {
/** 任务名称 */
private String name;
public DefaultTimerTask(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
@Override
public void run() {
while (true) {
try {
Thread.sleep(100);
} catch (Exception e) {
break;
}
try {
readLogs();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
/** 这个工厂 复制 于 Executors.DefaultThreadFactory 修改 名字,方便查看 线程情况 */
static class DefaultThreadFactory implements ThreadFactory {
private static final AtomicInteger poolNumber = new AtomicInteger(1);
private final ThreadGroup group;
private final AtomicInteger threadNumber = new AtomicInteger(1);
private final String namePrefix;
DefaultThreadFactory() {
SecurityManager s = System.getSecurityManager();
group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup();
namePrefix = "log-save-" + poolNumber.getAndIncrement() + "-thread-";
}
public Thread newThread(Runnable r) {
Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement(), 0);
if (t.isDaemon()) {
t.setDaemon(false);
}
if (t.getPriority() != Thread.NORM_PRIORITY) {
t.setPriority(Thread.NORM_PRIORITY);
}
return t;
}
}
}
配置log4j2.xml
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="debug">
<Appenders>
<!-- 输出到控制台 -->
<Console name="Console" target="SYSTEM_OUT">
<ThresholdFilter level="INFO" onMatch="ACCEPT" onMismatch="DENY" />
<PatternLayout pattern="%-d{yyyy-MM-dd HH:mm:ss}-[%p] [%t] [%l] %m%n" />
</Console>
<!-- 与properties文件中位置存在冲突,如有问题,请注意调整 -->
<RollingFile name="logFile" fileName="logs/game.log" filePattern="logs/game-%d{yyyy-MM-dd}-%i.log.gz">
<ThresholdFilter level="INFO" onMatch="ACCEPT" onMismatch="DENY" />
<PatternLayout pattern="%-d{yyyy-MM-dd HH:mm:ss}-[%p] [%t] [%l] %m%n" />
<Policies>
<!-- 按天递计算频率 -->
<TimeBasedTriggeringPolicy interval="1" modulate="true" />
<SizeBasedTriggeringPolicy size="200 MB" />
<OnStartupTriggeringPolicy />
</Policies>
<!-- 删除策略配置 -->
<DefaultRolloverStrategy max="5">
<Delete basePath="logs/" maxDepth="1">
<IfFileName glob="*.log.gz" />
<IfLastModified age="7d" />
</Delete>
</DefaultRolloverStrategy>
</RollingFile>
<Async name="Async" bufferSize="2000" blocking="false">
<AppenderRef ref="logFile" />
<AppenderRef ref="REDIS" />
</Async>
<RedisLogAppender name="REDIS" cacheTime="7200">
<ThresholdFilter level="INFO" onMatch="ACCEPT" onMismatch="DENY" />
<PatternLayout pattern="%m%n" />
</RedisLogAppender>
</Appenders>
<Loggers>
<Root level="DEBUG" includeLocation="true">
<appender-ref ref="Console" />
<appender-ref ref="Async" />
</Root>
</Loggers>
</Configuration>
在项目启动中配置 application.properties
#指定redis信息 (如 host, ip, password)
spring.redis.host=localhost
spring.redis.port=6379
#没有密码可以不用配置这个
#spring.redis.password=123456
logs.cache.key=xx.log
logs.cache.save.corePoolSize=10
当发送消息未消费时 Redis 存储的数据
正常执行