springboot项目中使用的Logback如何重写Logback获取error信息,获取到error信息后如何发送错误邮件

我来为你详细讲解如何在Spring Boot 2.5.10 + JDK8项目中重写Logback获取error信息并发送邮件通知。

一、项目依赖配置

1. pom.xml 添加依赖

xml

复制代码
<dependencies>
    <!-- Spring Boot Starter Web (已包含) -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    
    <!-- Spring Boot Mail 依赖 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-mail</artifactId>
    </dependency>
    
    <!-- Lombok (可选) -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>

2. QQ邮箱配置(推荐,配置简单)

application.yml 配置:

yaml

复制代码
spring:
  # QQ邮箱配置(推荐)
  mail:
    host: smtp.qq.com
    port: 465  # SSL端口
    username: 123456789@qq.com  # 你的QQ邮箱
    password: xxxxxxxxxxxxxxxx  # QQ邮箱授权码(不是登录密码!)
    protocol: smtps
    default-encoding: UTF-8
    properties:
      mail:
        smtp:
          ssl:
            enable: true
          auth: true
          starttls:
            enable: true
            required: true
          connectiontimeout: 5000
          timeout: 3000
          writetimeout: 5000

# 自定义邮件配置
error:
  email:
    enabled: true  # 是否启用邮件通知
    from: 123456789@qq.com  # 发件人邮箱
    to:  # 收件人列表
      - your-manager@qq.com
      - ops-team@company.com
    cc:  # 抄送列表(可选)
      - backup@qq.com
    bcc:  # 密送列表(可选)
      - monitor@qq.com
    subject-prefix: "[系统告警] "  # 邮件主题前缀
    # 邮件发送频率控制
    interval: 60000  # 发送间隔(毫秒),避免频繁发送
    batch-size: 10   # 批量发送条数

3. 163邮箱配置(备用方案)

如果你使用163邮箱,修改配置如下:

yaml

复制代码
spring:
  mail:
    host: smtp.163.com
    port: 465
    username: your-email@163.com
    password: xxxxxxxxxxxx  # 163邮箱授权码
    protocol: smtps
    # ... 其他配置同上

注意:无论是QQ邮箱还是163邮箱,都需要:

  1. 开启SMTP服务

  2. 获取授权码(不是登录密码)

二、创建自定义Logback Appender

1. ErrorLogAppender.java - 自定义Appender

java

复制代码
package com.example.logging;

import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.classic.spi.IThrowableProxy;
import ch.qos.logback.classic.spi.StackTraceElementProxy;
import ch.qos.logback.core.AppenderBase;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;

/**
 * 自定义Logback Appender,捕获ERROR日志并发送邮件
 */
@Slf4j
@Component
public class ErrorLogAppender extends AppenderBase<ILoggingEvent> {
    
    // 错误日志队列(线程安全)
    private final BlockingQueue<ILoggingEvent> errorQueue = new ArrayBlockingQueue<>(1000);
    
    // 邮件发送服务
    @Autowired(required = false)
    private ErrorEmailService errorEmailService;
    
    // 配置参数
    @Value("${error.email.enabled:true}")
    private boolean emailEnabled;
    
    @Value("${error.email.interval:60000}")
    private long emailInterval;
    
    // 处理线程
    private Thread processorThread;
    private volatile boolean running = true;
    
    @PostConstruct
    public void init() {
        if (emailEnabled && errorEmailService != null) {
            startProcessor();
            log.info("ErrorLogAppender 初始化完成,邮件通知已启用");
        } else {
            log.warn("ErrorLogAppender 初始化完成,邮件通知未启用");
        }
    }
    
    @PreDestroy
    public void destroy() {
        running = false;
        if (processorThread != null) {
            processorThread.interrupt();
            try {
                processorThread.join(5000);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }
    
    /**
     * 启动处理线程
     */
    private void startProcessor() {
        processorThread = new Thread(() -> {
            log.info("错误日志处理器线程启动");
            while (running) {
                try {
                    // 从队列中取出错误日志
                    ILoggingEvent event = errorQueue.poll(1, TimeUnit.SECONDS);
                    if (event != null) {
                        processErrorEvent(event);
                    }
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    break;
                } catch (Exception e) {
                    log.error("处理错误日志时发生异常", e);
                }
            }
            log.info("错误日志处理器线程停止");
        });
        processorThread.setName("Error-Log-Processor");
        processorThread.setDaemon(true);
        processorThread.start();
    }
    
    /**
     * Logback Appender的核心方法
     */
    @Override
    protected void append(ILoggingEvent event) {
        // 只处理ERROR级别的日志
        if (event.getLevel().isGreaterOrEqual(Level.ERROR)) {
            // 添加到队列
            boolean success = errorQueue.offer(event);
            if (!success) {
                log.warn("错误日志队列已满,丢弃日志: {}", event.getMessage());
            }
        }
    }
    
    /**
     * 处理错误日志事件
     */
    private void processErrorEvent(ILoggingEvent event) {
        try {
            // 构建错误信息
            String errorMessage = buildErrorMessage(event);
            
            // 发送邮件
            if (emailEnabled && errorEmailService != null) {
                errorEmailService.sendErrorEmail(errorMessage, event);
            }
            
            // 也可以在这里添加其他处理逻辑
            // 如:保存到数据库、发送到消息队列等
            
        } catch (Exception e) {
            log.error("处理错误日志事件失败", e);
        }
    }
    
    /**
     * 构建详细的错误信息
     */
    private String buildErrorMessage(ILoggingEvent event) {
        StringBuilder sb = new StringBuilder();
        
        sb.append("======= 系统错误告警 =======\n\n");
        sb.append("【时间】: ").append(formatTimestamp(event.getTimeStamp())).append("\n");
        sb.append("【级别】: ").append(event.getLevel()).append("\n");
        sb.append("【类名】: ").append(event.getLoggerName()).append("\n");
        sb.append("【线程】: ").append(event.getThreadName()).append("\n");
        sb.append("【消息】: ").append(event.getFormattedMessage()).append("\n\n");
        
        // MDC上下文信息(如果有)
        if (event.getMDCPropertyMap() != null && !event.getMDCPropertyMap().isEmpty()) {
            sb.append("【上下文信息】:\n");
            event.getMDCPropertyMap().forEach((key, value) -> 
                sb.append("  ").append(key).append(": ").append(value).append("\n"));
            sb.append("\n");
        }
        
        // 异常堆栈
        IThrowableProxy throwableProxy = event.getThrowableProxy();
        if (throwableProxy != null) {
            sb.append("【异常堆栈】:\n");
            sb.append(getStackTrace(throwableProxy));
        }
        
        sb.append("===========================");
        
        return sb.toString();
    }
    
    /**
     * 格式化时间戳
     */
    private String formatTimestamp(long timestamp) {
        return new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS")
                .format(new java.util.Date(timestamp));
    }
    
    /**
     * 获取堆栈跟踪信息
     */
    private String getStackTrace(IThrowableProxy throwableProxy) {
        StringBuilder sb = new StringBuilder();
        int depth = 0;
        while (throwableProxy != null && depth < 10) { // 限制深度,避免过长
            sb.append(throwableProxy.getClassName())
              .append(": ")
              .append(throwableProxy.getMessage())
              .append("\n");
            
            StackTraceElementProxy[] stackTraceElements = throwableProxy.getStackTraceElementProxyArray();
            if (stackTraceElements != null) {
                // 只取前20行堆栈
                int limit = Math.min(stackTraceElements.length, 20);
                for (int i = 0; i < limit; i++) {
                    sb.append("    at ").append(stackTraceElements[i].getStackTraceElement()).append("\n");
                }
                if (stackTraceElements.length > 20) {
                    sb.append("    ... ").append(stackTraceElements.length - 20).append(" more\n");
                }
            }
            
            throwableProxy = throwableProxy.getCause();
            if (throwableProxy != null) {
                sb.append("Caused by: ");
                depth++;
            }
        }
        return sb.toString();
    }
}

三、邮件发送服务

1. ErrorEmailService.java - 邮件服务

java

复制代码
package com.example.logging;

import ch.qos.logback.classic.spi.ILoggingEvent;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;
import java.util.Date;
import java.util.List;

/**
 * 错误邮件发送服务
 */
@Slf4j
@Service
public class ErrorEmailService {
    
    @Autowired
    private JavaMailSender mailSender;
    
    @Value("${error.email.from:}")
    private String fromEmail;
    
    @Value("#{'${error.email.to:}'.split(',')}")
    private List<String> toEmails;
    
    @Value("#{'${error.email.cc:}'.split(',')}")
    private List<String> ccEmails;
    
    @Value("#{'${error.email.bcc:}'.split(',')}")
    private List<String> bccEmails;
    
    @Value("${error.email.subject-prefix:[系统告警] }")
    private String subjectPrefix;
    
    @Value("${spring.mail.username:}")
    private String defaultFromEmail;
    
    // 上次发送时间,用于控制频率
    private long lastSendTime = 0;
    
    /**
     * 发送错误邮件
     */
    @Async("emailTaskExecutor")
    public void sendErrorEmail(String errorContent, ILoggingEvent event) {
        if (!isEmailConfigured()) {
            log.warn("邮件配置不完整,无法发送错误邮件");
            return;
        }
        
        // 频率控制(避免频繁发送)
        long currentTime = System.currentTimeMillis();
        if (currentTime - lastSendTime < 60000) { // 1分钟内不重复发送
            log.debug("邮件发送频率控制,跳过本次发送");
            return;
        }
        
        try {
            // 使用HTML格式邮件
            sendHtmlEmail(errorContent, event);
            lastSendTime = currentTime;
            log.info("错误邮件发送成功");
        } catch (Exception e) {
            log.error("发送错误邮件失败", e);
        }
    }
    
    /**
     * 发送HTML格式邮件
     */
    private void sendHtmlEmail(String errorContent, ILoggingEvent event) throws MessagingException {
        MimeMessage mimeMessage = mailSender.createMimeMessage();
        MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true, "UTF-8");
        
        // 设置邮件基本信息
        String from = fromEmail != null && !fromEmail.isEmpty() ? fromEmail : defaultFromEmail;
        helper.setFrom(from);
        helper.setTo(toEmails.toArray(new String[0]));
        
        // 设置抄送
        if (!CollectionUtils.isEmpty(ccEmails) && !(ccEmails.size() == 1 && ccEmails.get(0).isEmpty())) {
            helper.setCc(ccEmails.toArray(new String[0]));
        }
        
        // 设置密送
        if (!CollectionUtils.isEmpty(bccEmails) && !(bccEmails.size() == 1 && bccEmails.get(0).isEmpty())) {
            helper.setBcc(bccEmails.toArray(new String[0]));
        }
        
        // 设置主题
        String subject = subjectPrefix + "系统发生错误 - " + 
                        event.getLoggerName() + " - " + 
                        new java.text.SimpleDateFormat("MM-dd HH:mm").format(new Date());
        helper.setSubject(subject);
        
        // 构建HTML内容
        String htmlContent = buildHtmlContent(errorContent, event);
        helper.setText(htmlContent, true);
        
        // 发送邮件
        mailSender.send(mimeMessage);
    }
    
    /**
     * 发送简单文本邮件(备用方案)
     */
    private void sendSimpleEmail(String errorContent, ILoggingEvent event) {
        SimpleMailMessage message = new SimpleMailMessage();
        
        String from = fromEmail != null && !fromEmail.isEmpty() ? fromEmail : defaultFromEmail;
        message.setFrom(from);
        message.setTo(toEmails.toArray(new String[0]));
        
        if (!CollectionUtils.isEmpty(ccEmails) && !(ccEmails.size() == 1 && ccEmails.get(0).isEmpty())) {
            message.setCc(ccEmails.toArray(new String[0]));
        }
        
        String subject = subjectPrefix + "系统发生错误 - " + 
                        event.getLoggerName() + " - " + 
                        new java.text.SimpleDateFormat("MM-dd HH:mm").format(new Date());
        message.setSubject(subject);
        message.setText(errorContent);
        
        mailSender.send(message);
    }
    
    /**
     * 构建HTML邮件内容
     */
    private String buildHtmlContent(String errorContent, ILoggingEvent event) {
        String html = "<!DOCTYPE html>\n" +
                "<html>\n" +
                "<head>\n" +
                "    <meta charset=\"UTF-8\">\n" +
                "    <style>\n" +
                "        body { font-family: Arial, sans-serif; margin: 20px; }\n" +
                "        .header { background-color: #f44336; color: white; padding: 10px; border-radius: 5px; }\n" +
                "        .content { background-color: #f9f9f9; padding: 15px; border: 1px solid #ddd; border-radius: 5px; margin-top: 10px; }\n" +
                "        .info-item { margin: 5px 0; }\n" +
                "        .stacktrace { background-color: #fff; padding: 10px; border: 1px solid #ccc; border-radius: 3px; font-family: monospace; white-space: pre-wrap; }\n" +
                "        .footer { margin-top: 20px; color: #666; font-size: 12px; }\n" +
                "    </style>\n" +
                "</head>\n" +
                "<body>\n" +
                "    <div class=\"header\">\n" +
                "        <h2>⚠️ 系统错误告警</h2>\n" +
                "    </div>\n" +
                "    \n" +
                "    <div class=\"content\">\n" +
                "        <div class=\"info-item\"><strong>时间:</strong>" + formatTimestamp(event.getTimeStamp()) + "</div>\n" +
                "        <div class=\"info-item\"><strong>级别:</strong><span style=\"color: red; font-weight: bold;\">" + event.getLevel() + "</span></div>\n" +
                "        <div class=\"info-item\"><strong>类名:</strong>" + event.getLoggerName() + "</div>\n" +
                "        <div class=\"info-item\"><strong>线程:</strong>" + event.getThreadName() + "</div>\n" +
                "        <div class=\"info-item\"><strong>消息:</strong>" + event.getFormattedMessage() + "</div>\n";
        
        // 添加上下文信息
        if (event.getMDCPropertyMap() != null && !event.getMDCPropertyMap().isEmpty()) {
            html += "        <div class=\"info-item\"><strong>上下文信息:</strong></div>\n";
            html += "        <div style=\"margin-left: 20px;\">\n";
            for (var entry : event.getMDCPropertyMap().entrySet()) {
                html += "            <div>" + entry.getKey() + ": " + entry.getValue() + "</div>\n";
            }
            html += "        </div>\n";
        }
        
        // 添加堆栈信息
        if (event.getThrowableProxy() != null) {
            html += "        <div class=\"info-item\"><strong>异常堆栈:</strong></div>\n";
            html += "        <div class=\"stacktrace\">" + 
                    getStackTraceHtml(event.getThrowableProxy()) + 
                    "</div>\n";
        }
        
        html += "    </div>\n" +
                "    \n" +
                "    <div class=\"footer\">\n" +
                "        <p>此邮件由系统自动发送,请勿回复。</p>\n" +
                "        <p>发送时间:" + new Date() + "</p>\n" +
                "    </div>\n" +
                "</body>\n" +
                "</html>";
        
        return html;
    }
    
    private String formatTimestamp(long timestamp) {
        return new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS")
                .format(new java.util.Date(timestamp));
    }
    
    private String getStackTraceHtml(IThrowableProxy throwableProxy) {
        StringBuilder sb = new StringBuilder();
        int depth = 0;
        while (throwableProxy != null && depth < 5) {
            sb.append("<div style=\"color: #d32f2f;\">")
              .append(throwableProxy.getClassName())
              .append(": ")
              .append(throwableProxy.getMessage())
              .append("</div>");
            
            StackTraceElementProxy[] elements = throwableProxy.getStackTraceElementProxyArray();
            if (elements != null) {
                int limit = Math.min(elements.length, 15);
                for (int i = 0; i < limit; i++) {
                    sb.append("<div style=\"margin-left: 20px; color: #666;\">")
                      .append("at ")
                      .append(elements[i].getStackTraceElement())
                      .append("</div>");
                }
                if (elements.length > 15) {
                    sb.append("<div style=\"margin-left: 20px; color: #999;\">")
                      .append("... ")
                      .append(elements.length - 15)
                      .append(" more lines")
                      .append("</div>");
                }
            }
            
            throwableProxy = throwableProxy.getCause();
            if (throwableProxy != null) {
                sb.append("<div style=\"margin-top: 5px;\"><strong>Caused by:</strong></div>");
                depth++;
            }
        }
        return sb.toString();
    }
    
    /**
     * 检查邮件配置是否完整
     */
    private boolean isEmailConfigured() {
        if (mailSender == null) {
            log.error("mailSender 未注入");
            return false;
        }
        
        if (CollectionUtils.isEmpty(toEmails) || (toEmails.size() == 1 && toEmails.get(0).isEmpty())) {
            log.error("收件人邮箱未配置");
            return false;
        }
        
        if ((fromEmail == null || fromEmail.isEmpty()) && 
            (defaultFromEmail == null || defaultFromEmail.isEmpty())) {
            log.error("发件人邮箱未配置");
            return false;
        }
        
        return true;
    }
}

四、配置Logback使用自定义Appender

1. logback-spring.xml 配置文件

xml

复制代码
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="30 seconds">
    
    <!-- 定义常量 -->
    <property name="LOG_HOME" value="./logs" />
    <property name="APP_NAME" value="my-application" />
    
    <!-- 引入Spring Boot默认配置 -->
    <include resource="org/springframework/boot/logging/logback/defaults.xml" />
    <include resource="org/springframework/boot/logging/logback/console-appender.xml" />
    
    <!-- 注册自定义Appender -->
    <appender name="ERROR_CAPTURE" class="com.example.logging.ErrorLogAppender">
        <!-- 只处理ERROR级别 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>ERROR</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>
    
    <!-- ERROR级别文件输出 -->
    <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_HOME}/error.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${LOG_HOME}/error.%d{yyyy-MM-dd}.log</fileNamePattern>
            <maxHistory>30</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n%ex</pattern>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>ERROR</level>
        </filter>
    </appender>
    
    <!-- 异步Appender,提高性能 -->
    <appender name="ASYNC_ERROR" class="ch.qos.logback.classic.AsyncAppender">
        <discardingThreshold>0</discardingThreshold>
        <queueSize>512</queueSize>
        <neverBlock>true</neverBlock>
        <appender-ref ref="ERROR_FILE" />
        <appender-ref ref="ERROR_CAPTURE" />
    </appender>
    
    <!-- 控制台输出 -->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>
    
    <!-- 根日志配置 -->
    <root level="INFO">
        <appender-ref ref="CONSOLE" />
        <appender-ref ref="ASYNC_ERROR" />
    </root>
    
    <!-- 应用特定包日志 -->
    <logger name="com.example" level="DEBUG" additivity="false">
        <appender-ref ref="CONSOLE" />
        <appender-ref ref="ASYNC_ERROR" />
    </logger>
    
</configuration>

五、配置异步任务执行器

1. AsyncConfig.java - 异步配置

java

复制代码
package com.example.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;

@Configuration
@EnableAsync
public class AsyncConfig {
    
    /**
     * 邮件发送线程池
     */
    @Bean("emailTaskExecutor")
    public Executor emailTaskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // 核心线程数
        executor.setCorePoolSize(2);
        // 最大线程数
        executor.setMaxPoolSize(5);
        // 队列大小
        executor.setQueueCapacity(100);
        // 线程名前缀
        executor.setThreadNamePrefix("email-task-");
        // 拒绝策略
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        // 等待所有任务结束后再关闭线程池
        executor.setWaitForTasksToCompleteOnShutdown(true);
        // 等待时间
        executor.setAwaitTerminationSeconds(60);
        // 初始化
        executor.initialize();
        return executor;
    }
}

六、测试使用

1. 测试Controller

java

复制代码
package com.example.controller;

import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@Slf4j
@RestController
public class TestController {
    
    @GetMapping("/test/error")
    public String testError(@RequestParam(defaultValue = "test") String message) {
        // 设置MDC上下文信息(会在邮件中显示)
        MDC.put("userId", "user123");
        MDC.put("requestId", "req-" + System.currentTimeMillis());
        MDC.put("ip", "127.0.0.1");
        
        try {
            // 模拟业务逻辑
            log.info("处理请求,参数: {}", message);
            
            // 模拟一个错误
            if ("error".equals(message)) {
                throw new RuntimeException("测试异常:" + message);
            }
            
            // 手动记录ERROR日志
            if ("logerror".equals(message)) {
                log.error("手动记录错误日志,参数: {}", message, 
                         new IllegalArgumentException("非法参数"));
                return "错误日志已记录";
            }
            
            return "请求成功: " + message;
            
        } catch (Exception e) {
            log.error("处理请求时发生异常", e);
            return "发生错误: " + e.getMessage();
        } finally {
            // 清除MDC
            MDC.clear();
        }
    }
}

2. 启动类

java

复制代码
package com.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {
    
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

七、邮箱配置指南

QQ邮箱配置步骤

  1. 登录QQ邮箱 → 设置 → 账户

  2. 开启服务:找到"POP3/SMTP服务"和"IMAP/SMTP服务"

  3. 开启服务(需要短信验证)

  4. 生成授权码:会生成一个16位的授权码

  5. 在application.yml中使用授权码作为password

163邮箱配置步骤

  1. 登录163邮箱 → 设置 → POP3/SMTP/IMAP

  2. 开启服务:开启SMTP服务

  3. 获取授权码(需要短信验证)

  4. 使用授权码作为password

八、运行测试

  1. 启动应用

  2. 访问测试接口

    • http://localhost:8080/test/error?message=logerror 记录错误日志

    • http://localhost:8080/test/error?message=error 触发异常

  3. 查看控制台日志,确认错误被捕获

  4. 检查邮箱,查看是否收到错误通知邮件

九、故障排查

如果邮件发送失败,检查:

  1. 邮箱配置是否正确

    • 确认使用授权码而非登录密码

    • 确认邮箱已开启SMTP服务

  2. 网络连接

    • 检查是否能连接到SMTP服务器

    • 检查防火墙是否屏蔽了465端口

  3. 查看应用日志

    • 查看ErrorLogAppender的启动日志

    • 查看邮件发送失败的异常堆栈

十、优化建议

  1. 频率控制:避免同一错误频繁发送邮件

  2. 错误分类:根据不同错误类型发送到不同负责人

  3. 邮件模板:可以提取邮件模板到外部文件

  4. 重试机制:邮件发送失败时重试

  5. 开关控制:提供API动态开启/关闭邮件通知

这样配置后,系统一旦发生ERROR级别的错误,就会自动发送邮件通知到指定邮箱,便于及时处理问题。

相关推荐
青云计划9 小时前
知光项目知文发布模块
java·后端·spring·mybatis
Victor3569 小时前
MongoDB(9)什么是MongoDB的副本集(Replica Set)?
后端
Victor3569 小时前
MongoDB(8)什么是聚合(Aggregation)?
后端
yeyeye11110 小时前
Spring Cloud Data Flow 简介
后端·spring·spring cloud
Tony Bai11 小时前
告别 Flaky Tests:Go 官方拟引入 testing/nettest,重塑内存网络测试标准
开发语言·网络·后端·golang·php
+VX:Fegn089511 小时前
计算机毕业设计|基于springboot + vue鲜花商城系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计
程序猿阿伟11 小时前
《GraphQL批处理与全局缓存共享的底层逻辑》
后端·缓存·graphql
小小张说故事12 小时前
SQLAlchemy 技术入门指南
后端·python
识君啊12 小时前
SpringBoot 事务管理解析 - @Transactional 的正确用法与常见坑
java·数据库·spring boot·后端
CaracalTiger12 小时前
如何解决Unexpected token ‘<’, “<!doctype “… is not valid JSON 报错问题
java·开发语言·jvm·spring boot·python·spring cloud·json