日志处理
上线项目最怕的就是出问题没有办法定位,日志基本上是第一排查方向,所有开发之初,设计好日志分类和展示是非常关键的
方案选择
输出到日志文件中,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("&", "&"));
return R.data(usualLogService.save(log));
}
@Override
@PostMapping(API_PREFIX + "/saveApiLog")
public R<Boolean> saveApiLog(@RequestBody LogApi log) {
log.setParams(log.getParams().replace("&", "&"));
return R.data(apiLogService.save(log));
}
@Override
@PostMapping(API_PREFIX + "/saveErrorLog")
public R<Boolean> saveErrorLog(@RequestBody LogError log) {
log.setParams(log.getParams().replace("&", "&"));
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后续使用时再丰富