springboot log4j 发送日志到 REDIS

添加引用

    <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 存储的数据

 正常执行

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值