拦截应用 error日志并发送到钉钉群|Java 开发实战

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: 现在应用都需要对日志进行监控或者报警,现在普遍的做法是采用EKL收集日志,然后再由Grafana进行内容展示和及告警策略等,那如果项目架构比较简单(单体应用),又不想搞那么多中间件依赖怎么办,这里有一种简单的方式可以实现~

开篇

现在应用都需要对日志进行监控或者报警,现在普遍的做法是采用EKL收集日志,然后再由Grafana进行内容展示和及告警策略等,那如果项目架构比较简单(单体应用),又不想搞那么多中间件依赖怎么办,这里有一种简单的方式可以实现~

继承UnsynchronizedAppenderBase

Springboot默认集成的是logback,所以自定义Appender非常简单,继承一下AppenderBase类即可。

3e4a8fed7d47221d62f4668cf608b03.png

再来看看AppenderBase之上的Appender下都有哪些实现

UnsynchronizedAppenderBase  ch.qos.logback.core)  
cAsyncAppenderBase (ch.qos.ogback.core) cOutputStreamAppender (ch.qos.logback.core9:DBAppenderBase (ch.qos.logback.core.db)
SendErrorMsgAppender (com.mty.jls.config)(cmAppenderBase (ch.qos.loqback.core)
AbstractServerSocketAppender(ch.qos.logback.core.net.se C:NOPAppender (ch.qos.logback.core.helpers)@=SyslogAppenderBase (ch.gos.logback.core.net)(CSMTPAppenderBase (ch.qos.logback.core.net)@SiftingAppenderBase (ch.qos.logback.core.sift)
CCycicBufferAppender (ch.qos.logback.core.read)@AbstractSocketAppender (ch.qos.logback.core.net)
ListAppender (ch.qos.logback.core.read) 

UnsynchronizedAppenderBase。从名字就能看出来是异步的、普通的、不加锁的。 它类似于AppenderBase,只是派生的Appender实现类需要自己处理线程同步。

演示

定义一个SendErrorMsgAppender

这个类要继承UnsynchronizedAppenderBase,并实现其中的抽象方法,不需要手动让它线程同步,异步就好,这样可以避免出错了导致业务操作回滚。

/**
 * UnsynchronizedAppenderBase 用于异步处理,不阻塞主线程, 拦截error日志,并发送到钉钉群
 */
@Getter
@Setter
public class SendErrorMsgAppender extends UnsynchronizedAppenderBase<ILoggingEvent> {
    // ILoggingEvent里面是日志的内容
    Layout<ILoggingEvent> layout;
    //自定义配置
    String printString;
    @Override
    public void start() {
        //这里可以做些初始化判断 比如layout不能为null ,
        if (layout == null) {
            addWarn("Layout was not defined");
        }
        //或者写入数据库、redis时的初始化连接等等
        super.start();
    }
    @Override
    public void stop() {
        //释放相关资源,如数据库连接,redis线程池等等
        if (!isStarted()) {
            return;
        }
        super.stop();
    }
    @Override
    public void append(ILoggingEvent event) {
        if (event.getLevel() == Level.ERROR) {
            try {
                var isEnableSendLog = "true".equals(SpringUtil.environment(PropertiesConstant.IS_ENABLE_SEND_LOG));
                if (isEnableSendLog) {
                    //获取服务器Ip,告知哪台服务器抛异常
                    var ip = InetAddress.getLocalHost().getHostAddress();
                    var message = ip + "  " + layout.doLayout(event);
                    sendMsgToDingDing(message);
                }
            } catch (UnknownHostException ignored) {
                addWarn("获取服务器ip失败");
            }
        }
    }
    /**
    * 发送日志消息到钉钉的逻辑
    **/
    private void sendMsgToDingDing(String msg) {
        Text text = new Text();
        text.setContent(msg);
        DdMsgBody msgBody = DdMsgBody.builder().msgtype("text").text(text).build();
        Long timestamp = System.currentTimeMillis();
        String secret = SpringUtil.environment(PropertiesConstant.OAPI_DINGTALK_SECRET);
        var charsetName = "UTF-8";
        // 把timestamp+"\n"+密钥当做签名字符串
        String stringToSign = timestamp + "\n" + secret;
        try {
            // 使用HmacSHA256算法计算签名
            Mac mac = Mac.getInstance("HmacSHA256");
            mac.init(new SecretKeySpec(secret.getBytes(charsetName), "HmacSHA256"));
            byte[] signData = mac.doFinal(stringToSign.getBytes());
            // 最后再把签名参数再进行urlEncode,得到最终的签名(需要使用UTF-8字符集)
            String sign = URLEncoder.encode(Base64.getEncoder().encodeToString(signData), charsetName);
            var paramMap = Map.of("timestamp", timestamp, "sign", sign);
            var sendUrl = RestTemplateUtils.buildGetUrlByMap(SpringUtil.environment(PropertiesConstant.OAPI_DINGTALK_URL), paramMap);
            RestTemplateUtils.executeHttpPost(sendUrl, msgBody);
        } catch (Exception e) {
        }
    }
    @Builder
    @Data
    private static class DdMsgBody {
        private String msgtype;
        private Text text;
        private Markdown markdown;
    }
    @Accessors(chain = true)
    @Data
    private class Markdown {
        private String title;
    }
    @Accessors(chain = true)
    @Data
    private class Text {
        private String content;
    }
}
复制代码

追加配置

<appender> 标签的name随意设置,但<appender-ref>需要引用该nameclass属性的值就是刚刚上面定义实现UnsynchronizedAppenderBase的类

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <appender name="sendErrorMsg" class="com.mty.jls.config.SendErrorMsgAppender">
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>INFO</level>
        </filter>
        <layout class="ch.qos.logback.classic.PatternLayout">
            <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符 -->
            <pattern>%d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m%n</pattern>
        </layout>
    </appender>
    <root level="info">
        <appender-ref ref="stdout"/>
        <appender-ref ref="sendErrorMsg"/>
    </root>
</configuration>
复制代码

效果如下:

192.168.561 2020-10-3117:15:06「http-nio-80-exec-2;12234031-[ERROR I
mybatisPlusInterceptor 没有获取到登录人的tenantId,可能是登录成功前调用了sql操作
com.mty.jls.contract.exception.BaseException:登录状态过期
atcom.mtyjls.utils.RbacUtil.getSecurityUser(RbacUtil.java:47)
at
com.mtyjls.config.mybatisplus.MybatisPlusConfig$1.getTenantId(MybatisPlusConfig java:53
at
com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor.builderExpr ession(TenantLineInnerInterceptor.java:295)
at
com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor.processJoin( TenantLineInnerInterceptor.java:285)
com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor.lambda$pro cessPlainSelect$2(TenantLineInnerInterceptor.iava:239)
at java.base/iava.util.ArrayList.forEach(ArrayListjava:1511)
at
com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor.processPlai nSelect(TenantLineInnerInterceptor.java:238)


相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
目录
相关文章
|
4月前
|
Rust 前端开发 JavaScript
Tauri 开发实践 — Tauri 日志记录功能开发
本文介绍了如何为 Tauri 应用配置日志记录。Tauri 是一个利用 Web 技术构建桌面应用的框架。文章详细说明了如何在 Rust 和 JavaScript 代码中设置和集成日志记录,并控制日志输出。通过添加 `log` crate 和 Tauri 日志插件,可以轻松实现多平台日志记录,包括控制台输出、Webview 控制台和日志文件。文章还展示了如何调整日志级别以优化输出内容。配置完成后,日志记录功能将显著提升开发体验和程序稳定性。
187 1
Tauri 开发实践 — Tauri 日志记录功能开发
|
4月前
|
存储 Java 开发者
Java Map实战:用HashMap和TreeMap轻松解决复杂数据结构问题!
【10月更文挑战第17天】本文深入探讨了Java中HashMap和TreeMap两种Map类型的特性和应用场景。HashMap基于哈希表实现,支持高效的数据操作且允许键值为null;TreeMap基于红黑树实现,支持自然排序或自定义排序,确保元素有序。文章通过具体示例展示了两者的实战应用,帮助开发者根据实际需求选择合适的数据结构,提高开发效率。
111 2
|
16天前
|
存储 缓存 Java
Java中的分布式缓存与Memcached集成实战
通过在Java项目中集成Memcached,可以显著提升系统的性能和响应速度。合理的缓存策略、分布式架构设计和异常处理机制是实现高效缓存的关键。希望本文提供的实战示例和优化建议能够帮助开发者更好地应用Memcached,实现高性能的分布式缓存解决方案。
32 9
|
30天前
|
Java Windows
【Azure Function】部署Java Function失败:报错deploy [ERROR] Status code 401和警告 'China North 3' may not be a valid region
1:deploy [ERROR] Status code 401, (empty body). 2: China North 3 may not be a valid region,please refer to https://aka.ms/maven_function_configuration#supported-regions for values. 3:  <azure.functions.maven.plugin.version>1.36.0</azure.functions.maven.plugin.version>
34 11
|
2月前
|
Java
Java基础却常被忽略:全面讲解this的实战技巧!
本次分享来自于一道Java基础的面试试题,对this的各种妙用进行了深度讲解,并分析了一些关于this的常见面试陷阱,主要包括以下几方面内容: 1.什么是this 2.this的场景化使用案例 3.关于this的误区 4.总结与练习
|
2月前
|
Java 程序员
Java基础却常被忽略:全面讲解this的实战技巧!
小米,29岁程序员,分享Java中`this`关键字的用法。`this`代表当前对象引用,用于区分成员变量与局部变量、构造方法间调用、支持链式调用及作为参数传递。文章还探讨了`this`在静态方法和匿名内部类中的使用误区,并提供了练习题。
52 1
|
3月前
|
安全 Java 开发者
Java 多线程并发控制:深入理解与实战应用
《Java多线程并发控制:深入理解与实战应用》一书详细解析了Java多线程编程的核心概念、并发控制技术及其实战技巧,适合Java开发者深入学习和实践参考。
90 7
|
3月前
|
监控 开发者
鸿蒙5.0版开发:使用HiLog打印日志(ArkTS)
在HarmonyOS 5.0中,HiLog是系统提供的日志系统,支持DEBUG、INFO、WARN、ERROR、FATAL五种日志级别。本文介绍如何在ArkTS中使用HiLog打印日志,并提供示例代码。通过合理使用HiLog,开发者可以更好地调试和监控应用。
290 16
|
3月前
|
存储 安全 Java
Java多线程编程中的并发容器:深入解析与实战应用####
在本文中,我们将探讨Java多线程编程中的一个核心话题——并发容器。不同于传统单一线程环境下的数据结构,并发容器专为多线程场景设计,确保数据访问的线程安全性和高效性。我们将从基础概念出发,逐步深入到`java.util.concurrent`包下的核心并发容器实现,如`ConcurrentHashMap`、`CopyOnWriteArrayList`以及`BlockingQueue`等,通过实例代码演示其使用方法,并分析它们背后的设计原理与适用场景。无论你是Java并发编程的初学者还是希望深化理解的开发者,本文都将为你提供有价值的见解与实践指导。 --- ####
|
3月前
|
Java Maven Spring
超实用的SpringAOP实战之日志记录
【11月更文挑战第11天】本文介绍了如何使用 Spring AOP 实现日志记录功能。首先概述了日志记录的重要性及 Spring AOP 的优势,然后详细讲解了搭建 Spring AOP 环境、定义日志切面、优化日志内容和格式的方法,最后通过测试验证日志记录功能的准确性和完整性。通过这些步骤,可以有效提升系统的可维护性和可追踪性。

热门文章

最新文章