springboot使用log4j监控日志发送邮件

实现log4j发送邮件功能大致流程:

  1、开启邮箱SMTP服务,获取SMTP登录密码

  2、引入javax.mail、javax.activation依赖

  3、配置log4j文件,指定邮件发送方和接收方以及发送方账号密码等

  4、重写SMTPAppender(不重写也能实现邮件发送功能)

开启邮箱SMTP服务

  这里以qq邮箱为例,首先进入账号管理界面,设置-账号

 

  

   找到POP3/IMAP/SMTP/Exchange/CardDAV/CalDAV服务,绑定手机号即可开启服务,开启服务后会给你一串字符,这个字符就是后边配置SMTP邮箱的邮箱密码

  

 

引入javax.mail、javax.activation依赖

  

<dependency>
<groupId>javax.mail</groupId>
<artifactId>mail</artifactId>
<version>1.4.7</version>
</dependency>
<dependency>
<groupId>javax.activation</groupId>
<artifactId>activation</artifactId>
<version>1.1.1</version>
</dependency>

 配置log4j文件

### 配置根 ###(直接加上MAIl,以,分隔,不要删除原来的配置以免影响日志输出)
log4j.rootLogger = INFO,console,MAIL

  注意 :以下配置是添加,不要删除原来的配置

### 配置输出到邮件 ###
# log4j的邮件发送appender,这里如果重写了SMTPAppender就写自己重写过的appender
#log4j.appender.MAIL=org.apache.log4j.net.SMTPAppender(默认的)
# 自己重写的appender
log4j.appender.MAIL=com.tian.api.ASMTPAppender.ASMTPAppender
#发送邮件的门槛,仅当等于或高于ERROR(比如FATAL)时,邮件才被发送 等级由低到高:debug<info<warn<Error<Fatal;
log4j.appender.MAIL.Threshold=ERROR
#日志发送邮件时关心的包(这里是自己添加的参数,如果不重写SMTPAppender不需要该参数,参数名可以自己命名)
log4j.appender.MAIL.includePackage=com.xxx.xxx.controller
#邮件缓冲区大小
log4j.appender.MAIL.BufferSize=16
#smtp服务地址(这里以qq为例,如果是163或者其他邮箱就换成其他邮箱的SMTP地址)
log4j.appender.MAIL.SMTPHost=smtp.qq.com
#端口(qq平台的端口一定要写587,其他的会卡住;163邮箱的尝试过写465或者587但是都会卡住)
log4j.appender.MAIL.SMTPPort=587
#邮件标题
log4j.appender.MAIL.Subject=ErrorMessage
#发件邮箱
log4j.appender.MAIL.From=123@qq.com
#收件邮箱
log4j.appender.MAIL.To=456@qq.com
#发件箱登陆用户名(与发件邮箱一致)
log4j.appender.MAIL.SMTPUsername=123@qq.com
#发件箱登陆密码(这里在邮箱开启SMTP服务后会给你一串码)
log4j.appender.MAIL.SMTPPassword=123123123
#是否打印调试信息,如果选true,则会输出和SMTP之间的握手等详细信息
log4j.appender.MAIL.SMTPDebug=true
#邮件主题
log4j.appender.MAIL.layout=org.apache.log4j.PatternLayout
log4j.appender.MAIL.layout.ConversionPattern= %d{yyyy-MM-dd HH:mm:ss.SSS} %5l [%5p] - %m%n

  !!!配置到这里就可以发送邮件啦。

重写SMTPAppender

  1、异步发送

  默认的SMTPAppender是同步发送邮件的,就是说如果检测到日志打印了告警级别的日志,业务代码会等待邮件发送完毕再执行,执行效果如下:

 

  

 

  所以最好改造一下进行异步发送,重写的appender如下:

 

复制代码
public class ASMTPAppender extends SMTPAppender implements UnrecognizedElementHandler {

    ExecutorService fixedThreadPool = new ThreadPoolExecutor(2, 5, 3,
        TimeUnit.SECONDS, new LinkedBlockingDeque<>(3), Executors.defaultThreadFactory(), new ThreadPoolExecutor.DiscardOldestPolicy());
    @Override
    protected void sendBuffer() {
        try {
            String s = this.formatBody();
            boolean allAscii = true;

            for(int i = 0; i < s.length() && allAscii; ++i) {
                allAscii = s.charAt(i) <= 127;
            }

            MimeBodyPart part;
            if (allAscii) {
                part = new MimeBodyPart();
                part.setContent(s, this.layout.getContentType());
            } else {
                try {
                    ByteArrayOutputStream os = new ByteArrayOutputStream();
                    Writer writer = new OutputStreamWriter(MimeUtility.encode(os, "quoted-printable"), "UTF-8");
                    writer.write(s);
                    writer.close();
                    InternetHeaders headers = new InternetHeaders();
                    headers.setHeader("Content-Type", this.layout.getContentType() + "; charset=UTF-8");
                    headers.setHeader("Content-Transfer-Encoding", "quoted-printable");
                    part = new MimeBodyPart(headers, os.toByteArray());
                } catch (Exception var7) {
                    StringBuffer sbuf = new StringBuffer(s);

                    for(int i = 0; i < sbuf.length(); ++i) {
                        if (sbuf.charAt(i) >= 128) {
                            sbuf.setCharAt(i, '?');
                        }
                    }

                    part = new MimeBodyPart();
                    part.setContent(sbuf.toString(), this.layout.getContentType());
                }
            }

            Multipart mp = new MimeMultipart();
            mp.addBodyPart(part);
            this.msg.setContent(mp);
            this.msg.setSentDate(new Date());
            // 使用线程池异步发送邮件
            fixedThreadPool.execute(() -> {
                try {
                    Transport.send(this.msg);
                } catch (MessagingException var6) {
                    LogLog.error("Error occured while sending e-mail notification.", var6);
                }
            });
        } catch (MessagingException var8) {
            LogLog.error("Error occured while sending e-mail notification.", var8);
        } catch (RuntimeException var9) {
            LogLog.error("Error occured while sending e-mail notification.", var9);
        }

    }

}
复制代码

 

  改造后的执行效果如下:

 

  

 

  2、过滤监控文件

  默认的SMTPAppender会发送所有高于log4j配置文件中log4j.appender.MAIL.Threshold属性的日志,源码见org.apache.log4j.AppenderSkeleton

 

复制代码
  public synchronized void doAppend(LoggingEvent event) {
        if (this.closed) {
            LogLog.error("Attempted to append to closed appender named [" + this.name + "].");
        } else if (this.isAsSevereAsThreshold(event.getLevel())) {
            Filter f = this.headFilter;

            while(true) {
                if (f != null) {
                    switch (f.decide(event)) {
                        case -1:
                            return;
                        case 0:
                            f = f.getNext();
                            continue;
                        case 1:
                            break;
                        default:
                            continue;
                    }
                }

                this.append(event);
                return;
            }
        }
    }
  // 这个方法会根据log4j配置文件中写入的优先级进行判断,大于等于写入的优先级才会发送邮件
public boolean isAsSevereAsThreshold(Priority priority) { return this.threshold == null || priority.isGreaterOrEqual(this.threshold); }
复制代码

 

  如果我们想监控某一个或者某几个包下的文件日志情况,可以重写append和checkEntryConditions方法对判断条件进行修改

  可以在append方法调用checkEntryConditions的时候传入LoggingEvent

 

复制代码
    public void append(LoggingEvent event) {
        if (this.checkEntryConditions(event)) {
            event.getThreadName();
            event.getNDC();
            event.getMDCCopy();
            if (this.locationInfo) {
                event.getLocationInformation();
            }

            event.getRenderedMessage();
            event.getThrowableStrRep();
            this.cb.add(event);
            if (this.evaluator.isTriggeringEvent(event)) {
                this.sendBuffer();
            }

        }
    }
复制代码

  

  对LoggingEvent中的属性进行判断,如果不符合条件不进行邮件内容的组装和发送

  

复制代码
    protected boolean checkEntryConditions(LoggingEvent event) {
        // 判断是否包含指定包名,这里的includePackage需要在重写的appender中定义并提供getter和setter方法,includePackage参数与log4j.appender.MAIL.includePackage属性对应
        if (includePackage != null && includePackage.contains(event.getLoggerName())) {
            return false;
        }
        if (this.msg == null) {
            this.errorHandler.error("Message object not configured.");
            return false;
        } else if (this.evaluator == null) {
            this.errorHandler.error("No TriggeringEventEvaluator is set for appender [" + this.name + "].");
            return false;
        } else if (this.layout == null) {
            this.errorHandler.error("No layout set for appender named [" + this.name + "].");
            return false;
        } else {
            return true;
        }
    }
复制代码

  写在最后:个人认为在日志中耦合邮件的发送功能并不是一个好的选择,本文只是提供一个实现方案,有如需要可以选择EFK等监控平台进行日志的监控和邮件的发送。

 

posted @   卑微小田  阅读(204)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
点击右上角即可分享
微信分享提示