劝退记录:日志处理

日志处理

上线项目最怕的就是出问题没有办法定位,日志基本上是第一排查方向,所有开发之初,设计好日志分类和展示是非常关键的

方案选择

输出到日志文件中,log4j

pom依赖

xml 复制代码
       <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>

项目中写log4j.properties

ini 复制代码
log4j.rootLogger=TRACE, stdout, R
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%-4r %-5p  %d{yyyy-MM-dd HH:mm:ssS} %c -%m%n
log4j.appender.R=org.apache.log4j.RollingFileAppender
log4j.appender.R.File=example.log
log4j.appender.R.MaxFileSize=100KB
log4j.appender.R.MaxBackupIndex=1
log4j.appender.R.layout=org.apache.log4j.PatternLayout
log4j.appender.R.layout.ConversionPattern=[%p] %t %d{yyyy-MM-dd HH:mm:ss,SSS} %c - {%m} %n 
log4j.logger.com.foo=WARN

使用

ini 复制代码
private Logger logger = LoggerFactory.getLogger(ExecutorConfig.class);

logger.info("开启线程池");

一般一个单体项目日志的输出打印就可以支撑了,但是查看日志只能去服务器下载查看,还有日志文件过大需要按时清理

输出到库里

我们目前选择建立一个日志服务,进行日志入库,方便线上直接查看日志 具体方案如下:

先完成基础的日志分类,分别分为通用的日志、接口访问日志、错误日志三种,提供feign接口给其他服务使用,也可以提供消费者给其他服务发送消息,完成消息入库

less 复制代码
@RestController
@AllArgsConstructor
public class LogClient implements ILogClient {

   private final ILogUsualService usualLogService;

   private final ILogApiService apiLogService;

   private final ILogErrorService errorLogService;

   @Override
   @PostMapping(API_PREFIX + "/saveUsualLog")
   public R<Boolean> saveUsualLog(@RequestBody LogUsual log) {
      log.setParams(log.getParams().replace("&amp;", "&"));
      return R.data(usualLogService.save(log));
   }

   @Override
   @PostMapping(API_PREFIX + "/saveApiLog")
   public R<Boolean> saveApiLog(@RequestBody LogApi log) {
      log.setParams(log.getParams().replace("&amp;", "&"));
      return R.data(apiLogService.save(log));
   }

   @Override
   @PostMapping(API_PREFIX + "/saveErrorLog")
   public R<Boolean> saveErrorLog(@RequestBody LogError log) {
      log.setParams(log.getParams().replace("&amp;", "&"));
      return R.data(errorLogService.save(log));
   }
}
  • 接口日志:aop + 自定义注解实现日志入库

自定义注解

less 复制代码
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ApiLog {
    String value() default "日志记录";
}

aop

java 复制代码
@Aspect
public class ApiLogAspect {
    private static final Logger log = LoggerFactory.getLogger(ApiLogAspect.class);

    public ApiLogAspect() {
    }

    @Around("@annotation(apiLog)")
    public Object around(ProceedingJoinPoint point, ApiLog apiLog) throws Throwable {
        String className = point.getTarget().getClass().getName();
        String methodName = point.getSignature().getName();
        long beginTime = System.currentTimeMillis();
        Object result = point.proceed();
        long time = System.currentTimeMillis() - beginTime;
        ApiLogPublisher.publishEvent(methodName, className, apiLog, time);
        return result;
    }
}

事件

typescript 复制代码
public class ApiLogPublisher {
    public ApiLogPublisher() {
    }

    public static void publishEvent(String methodName, String methodClass, ApiLog apiLog, long time) {
        HttpServletRequest request = WebUtil.getRequest();
        LogApi logApi = new LogApi();
        logApi.setType("1");
        logApi.setTitle(apiLog.value());
        logApi.setTime(String.valueOf(time));
        logApi.setMethodClass(methodClass);
        logApi.setMethodName(methodName);
        LogAbstractUtil.addRequestInfoToLog(request, logApi);
        Map<String, Object> event = new HashMap(16);
        event.put("log", logApi);
        SpringUtil.publishEvent(new ApiLogEvent(event));
    }
}

SpringUtil.publishEvent(new ApiLogEvent(event));

这里用到的事件后面有空另外研究

  • 错误日志:异常拦截器 + 监听错误日志进库

使用@RestControllerAdvice注解进行异常拦截

kotlin 复制代码
import javax.servlet.Servlet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.DispatcherServlet;

@Order
@AutoConfiguration
@ConditionalOnWebApplication(
    type = Type.SERVLET
)
@ConditionalOnClass({Servlet.class, DispatcherServlet.class})
@RestControllerAdvice
public class BladeRestExceptionTranslator {
    private static final Logger log = LoggerFactory.getLogger(BladeRestExceptionTranslator.class);
    private final BladeRequestLogProperties properties;

    @ExceptionHandler({ServiceException.class})
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public R handleError(ServiceException e) {
        log.error("业务异常", e);
        return R.fail(e.getResultCode(), e.getMessage());
    }

    @ExceptionHandler({SecureException.class})
    @ResponseStatus(HttpStatus.UNAUTHORIZED)
    public R handleError(SecureException e) {
        log.error("认证异常", e);
        return R.fail(e.getResultCode(), e.getMessage());
    }

    @ExceptionHandler({Throwable.class})
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public R handleError(Throwable e) {
        log.error("服务器异常", e);
        if (this.properties.getErrorLog()) {
            ErrorLogPublisher.publishEvent(e, UrlUtil.getPath(WebUtil.getRequest().getRequestURI()));
        }

        return R.fail(ResultCode.INTERNAL_SERVER_ERROR, Func.isEmpty(e.getMessage()) ? ResultCode.INTERNAL_SERVER_ERROR.getMessage() : e.getMessage());
    }

    public BladeRestExceptionTranslator(final BladeRequestLogProperties properties) {
        this.properties = properties;
    }
}

然后可以使用SpringUtil.publishEvent(new ErrorLogEvent(event));事件,或者集成为消息队列来实现这部分的日志入库

  • 业务日志:feign调用或者mq入库

正常使用feign接口或者调用生产者发送消息即可

最后日志清理这方面,由业务设计来完成,启用定时任务,清理三月之前的日志信息

输出到中间件

elk后续使用时再丰富

相关推荐
东风破_5 小时前
ESLint 是什么?为什么你的项目需要它?
前端·后端·代码规范
嘻哈∠※5 小时前
0061基于 SpringBoot 的投稿与稿件处理系统设计与实现
java·spring boot·后端
卷无止境6 小时前
在 awesome-fastapi 里,哪些库值得一看?
后端·python
卷无止境7 小时前
FastAPI 的Admin面板生态
后端·python
捡田螺的小男孩8 小时前
什么是 Skill?手把手带你写一个简单有用的 Skill!
前端·后端·程序员
IT_陈寒8 小时前
Redis集群这个坑,差点让我通宵
前端·人工智能·后端
用户8356290780519 小时前
Python 自动化 Word 文本框处理:创建、定位、填充内容与管理
后端·python
网渡科技9 小时前
向量数据库选型指南:RAG场景下的性能对比与调优策略
后端
riverNijika9 小时前
项目学习笔记:C++基础同步输出日志库
后端
Lyra_Infra9 小时前
OpenClaw 升级及 Channel 安装故障排查与遗留问题分析
后端·npm