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级别的错误,就会自动发送邮件通知到指定邮箱,便于及时处理问题。

相关推荐
国思RDIF框架11 小时前
RDIFramework.NET CS 敏捷开发框架 V6.3 版本重磅发布!.NET8+Framework双引擎,性能升级全维度进化
后端·.net
心在飞扬11 小时前
ReRank重排序提升RAG系统效果
前端·后端
喝茶与编码11 小时前
Python异步并发控制:asyncio.gather 与 Semaphore 协同设计解析
后端·python
不早睡不改名11 小时前
网络编程基础:从BIO到NIO再到AIO(一)
后端
开源之眼11 小时前
《github star 加星 Taimili.com 艾米莉 》为什么Java里面,Service 层不直接返回 Result 对象?
java·后端·github
心在飞扬11 小时前
RAPTOR 递归文档树优化策略
前端·后端
zone773912 小时前
003:RAG 入门-LangChain 读取图片数据
后端·python·面试
心在飞扬12 小时前
LangChain Parent Document Retriever (父文档检索器)
后端
zone773912 小时前
002:RAG 入门-LangChain 读取文本
后端·算法·面试
用户83562907805112 小时前
在 PowerPoint 中用 Python 添加和定制形状的完整教程
后端·python