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:因爲不想部署起來那麼麻煩搞每個服務的日誌收集,直接由性能強大的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.gsafety.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;
}
public void setType(String type) {
this.type = type;
}
public String getTimestamp() {
return timestamp;
}
public void setTimestamp(String timestamp) {
this.timestamp = timestamp;
}
public String getPrefix() {
return prefix;
}
public void setPrefix(String prefix) {
this.prefix = prefix;
}
public Object[] getParam() {
return param;
}
public void setParam(Object[] param) {
this.param = param;
}
public Object getResult() {
return result;
}
public void setResult(Object result) {
this.result = result;
}
public String getSuffix() {
return suffix;
}
public void setSuffix(String suffix) {
this.suffix = suffix;
}
}
及其建造者類:LogMsgBuilder
public class LogMsgBuilder {
private static LogMessage message;
public LogMsgBuilder() {
}
public static LogMsgBuilder create() {
message = new LogMessage();
return LazyInit.INITIALIZATION;
}
public LogMsgBuilder type(LogType type) {
message.setType(type.name());
return this;
}
public LogMsgBuilder time(String timestamp) {
message.setTimestamp(timestamp);
return this;
}
public LogMsgBuilder prefix(String... prefixes) {
StringBuilder prefix = new StringBuilder();
for (String p : prefixes) {
prefix.append("[" + p + "]");
}
message.setPrefix(prefix.toString());
return this;
}
public LogMsgBuilder param(Object... params) {
if (params.length > 0)
message.setParam(params);
else
message.setParam(null);
return this;
}
public LogMsgBuilder result(Object result) {
message.setResult(result);
return this;
}
public LogMsgBuilder suffix(String... suffixes) {
StringBuilder suffix = new StringBuilder();
for (String s : suffixes) {
suffix.append("[" + s + "]");
}
message.setSuffix(suffix.toString());
return this;
}
public LogMessage build() {
return message;
}
private static class LazyInit {
private static LogMsgBuilder INITIALIZATION = new LogMsgBuilder();
}
}
--------------------------------業務層------------------------------------
根據業務需求定製日誌切面重寫日誌回調函數
@Aspect
@Component
@Lazy(false)
public class LoggerAspectJ extends AbstractLogAspectJ {
@Override
public LogCallBack logback() {
return (joinPoint, log, result) -> {
};
}
}
就到此吧。