SpringCloud + kafka + ELK 搭建微服务日志管理平台
2019-12-31,写在前面的话
今天是2019最后一天了,最近几天都在搞这块微服务日志管理的事情,有很多种方案实现,每种都有各自的优点,但是适合当前涉及的业务场景的不多,想法是尽可能多减少开发人员和实施及运维人员的工作量,生产环境的资源有条件让我可以放手去干,那么就在开发环境下先研究一下。整个项目不同以往在Linux平台,这次所有基础环境业务系统服务统一在windows平台,可能会涉及一些坑。
简单说明一下资源有限的开发环境:windows平台 ~~
涉及基础环境 ELK 3台 + kafka 3台 每台(16G+2核Intel Xeon Gold 5118+500g)
微服务部署就很随便了。
实现目的,开发人员只需关注该方法需不需要进行日志收集与统一管理,设计提供了系统、数据库、业务操作、登陆登出等这些日志类型管理。
同时日志实现又可根据实际需要灵活多变,无论是filebeat、kafka、redis、database等等都可自行拓展实现。
首先,关于kafka+ELK的部署方案可根据服务器资源和业务需求进行设计,这里不再赘述。
微服务的日志管理很麻烦,每个服务都有相应的日志,在生产环境下想要及时的去追踪一个问题变得繁琐起来,通常我们会从应用层定位问题然后对应去服务端查看相关服务日志确认问题根源。
那么微服务架构下,各个服务器的管理,各个服务的部署,各个基础环境、服务等维护都是简单却麻烦的工作,这其中日志的管理就变得让人眼花缭乱,定位一个问题往往把技术人员和运维人员推上风口浪尖。
如何去解决这个问题,怎么才能让问题暴露的更明显,让管理变得跟简单,让开发人员开发起来更轻松而无需去关注具体实现呢。
不过这里提一提日志管理的设计思路是:服务——>Kafka—logstash—>ES->Kibana
同样也可以不用中间件kafka,直接通过ES Api的接口,直接将日志信息从服务写入到ES。
为什么要搞一层kafka:因为不想部署起来那么麻烦搞每个服务的日志收集,直接由性能强大的kafka当中介即可,这样在部署基础环境的时候就可以将日志平台搭建好了。微服务的日志当然也要统一约定且规范了,所以核心代码贴下:
日志方法级注解:Log
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface Log {
String id() default "";
Class<?> name() default Object.class;
LogType[] type() default {LogType.SYSTEM};
}
注解实现类Logs
/*******************************
* Copyright (C),2018-2099, ZJJ
* Title :
* File name : Logs
* Author : zhoujiajun
* Date : 2020/1/2 14:42
* Version : 1.0
* Description :
******************************/
public class Logs implements savvy.wit.framework.core.base.annotations.Log {
private String id;
private Class<?> name;
private LogType[] types;
public Logs() {
}
public Logs(String id, Class<?> name, LogType[] types) {
this.id = id;
this.name = name;
this.types = types;
}
public void setId(String id) {
this.id = id;
}
public void setName(Class<?> name) {
this.name = name;
}
public void setTypes(LogType[] types) {
this.types = types;
}
@Override
public String id() {
return id;
}
@Override
public Class<?> name() {
return name;
}
@Override
public LogType[] type() {
return types;
}
/**
* Returns the annotation type of this annotation.
*
* @return the annotation type of this annotation
*/
@Override
public Class<? extends Annotation> annotationType() {
return null;
}
}
日志类型:LogType
public enum LogType {
SYSTEM,
BUSINESS,
EXCEPTION,
ERROR,
DDL,
DML,
CURD,
ADD,
REMOVE,
UPDATE,
FETCH,
QUERY,
LOGIN,
LOGOUT,
PWD,
}
日志回调:LogCallBack
public interface LogCallBack {
/**
* 日志回调
* @param joinPoint 连接点
* @param log 注解
* @param result 目标结果
*/
void execute(ProceedingJoinPoint joinPoint, Log log,Object result);
}
服务端抽象日志切面:AbstractLogAspectJ
public abstract class AbstractLogAspectJ {
/**
* 线程池 异步记录日志
*/
private static ExecutorService logExecutorService = Executors.newFixedThreadPool(10);
/**
* 定义切入点:对要拦截的方法进行定义与限制,如包、类
*
*/
@Pointcut("@annotation(com.xxxx.alien.core.log.Log)")
protected void log () {
}
/**
* 前置通知:在目标方法执行前调用
*/
@Before("log()")
public void begin() {
}
/**
* 后置通知:在目标方法执行后调用,若目标方法出现异常,则不执行
*/
@AfterReturning("log()")
public void afterReturning() {
}
/**
* 后置/最终通知:无论目标方法在执行过程中出现异常都会在它之后调用
*/
@After("log()")
public void after() {
}
/**
* 异常通知:目标方法抛出异常时执行
* 任何被切入点定义的方法在运行时发生异常都可捕获到并进行统一回调处理
* @param joinPoint 连接点
* @param throwable 异常
*/
@AfterThrowing(value = "log()", throwing = "throwable")
public void afterThrowing(JoinPoint joinPoint, Throwable throwable) {
handleThrowable(joinPoint, throwable, throwableBack());
}
/**
* 环绕通知: 灵活自由的在目标方法中切入代码
* @param joinPoint 连接点
* @throws Throwable 异常
*/
@Around("log()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
// 进行源方法
Object result = joinPoint.proceed();
//保存日志 注意如果方法执行错误这不会记录日志
logExecutorService.submit(() -> {
try {
handleMessage(joinPoint, result, logback());
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
});
return result;
}
/**
* 提供重写的回调
* @return callback
*/
public LogCallBack logback() {
return (joinPoint, log, result) -> {
};
}
/**
* 提供throwable回调
* @return callback
*/
public LogThrowableCallBack throwableBack() {
return (joinPoint, throwable) -> {
};
}
/**
* 消息处理
* @param joinPoint 切入点
* @param result 结果
* @param callBack 回调
* @throws NoSuchMethodException
*/
private void handleMessage(ProceedingJoinPoint joinPoint, Object result, LogCallBack callBack) throws NoSuchMethodException {
Log log = ClassUtil.me().getDeclaredAnnotation(joinPoint, Log.class);
/*
2020-01-02:add
用log的实现类,传递参数
在log id为默认情况下时
动态提供uuid
*/
Logs logs = new Logs(StringUtil.isBlank(log.id()) ? StringUtil.uuid() : log.id(), log.name(), log.type());
callBack.execute(joinPoint, logs, result);
}
/**
* 异常处理
* @param joinPoint 切入点
* @param throwable 异常
* @param callBack 回调
*/
private void handleThrowable(JoinPoint joinPoint, Throwable throwable, LogThrowableCallBack callBack) {
callBack.execute(joinPoint, throwable);
}
}
提供一个通用日志Model: LogMessage
public class LogMessage {
private String type;
private String timestamp;
private String prefix;
private Object[] param;
private Object result;
private String suffix;
public String getType() {
return type;