spring boot錯誤日志統(tǒng)一寫數(shù)據(jù)庫處理

首先洲脂,應(yīng)用日志直接寫入數(shù)據(jù)庫(關(guān)系型、NoSQL)的話励饵,會極大地影響應(yīng)用的性能和并發(fā)能力驳癌。本人做過壓測實驗,并發(fā)數(shù)到達一定量后役听,業(yè)務(wù)接口沒受到什么影響颓鲜,反倒是應(yīng)用日志由于生產(chǎn)速度過快,導(dǎo)致日志數(shù)據(jù)大量堆積典予,無法寫入數(shù)據(jù)庫甜滨,成為應(yīng)用的瓶頸×鲂洌互聯(lián)網(wǎng)軟件行業(yè)對性能衣摩、并發(fā)要求比較高,通常使用的日志收集系統(tǒng)架構(gòu)有如下幾種: ElasticSearch + Logstash + Kibana(ELK)捂敌、ElasticSearch + Filebeat + Kibana(EFK)艾扮、Kafka + ELK、 Kafka + EFK占婉。每個應(yīng)用服務(wù)器都要安裝agent客戶端從日志文件中收集日志泡嘴,ElasticSearch做存儲,Kibana做展示锐涯。

但是磕诊,傳統(tǒng)軟件行業(yè)很多對性能、并發(fā)性要求并不高纹腌,很多軟件項目可能只有一個管理后臺霎终,如果硬上互聯(lián)網(wǎng)那一套日志收集系統(tǒng),無疑會增加項目的部署和維護難度升薯。這種情況下莱褒,應(yīng)用info級別的日志可以在項目中定義一個AOP切面異步寫入數(shù)據(jù)庫。本文主要介紹錯誤日志的統(tǒng)一存儲涎劈。

在spring boot項目中广凸,默認(rèn)使用的是slf4j + logback日志框架阅茶。只需實現(xiàn)logback的Appender接口,自定義一個錯誤日志處理類即可對錯誤日志進行統(tǒng)一存儲谅海。

先上效果圖:


錯誤日志列表
錯誤日志詳情頁

錯誤日志數(shù)據(jù)庫表設(shè)計


錯誤日志數(shù)據(jù)庫表結(jié)構(gòu)

添加錯誤日志實體類

public class ErrorLogPO {

    private Integer logId;

    private String className;

    private String methodName;

    private String exceptionName;

    private String errMsg;

    private String stackTrace;

    private Date createTime;

    public Integer getLogId() {
        return logId;
    }

    public void setLogId(Integer logId) {
        this.logId = logId;
    }

    public String getClassName() {
        return className;
    }

    public void setClassName(String className) {
        this.className = className;
    }

    public String getMethodName() {
        return methodName;
    }

    public void setMethodName(String methodName) {
        this.methodName = methodName;
    }

    public String getExceptionName() {
        return exceptionName;
    }

    public void setExceptionName(String exceptionName) {
        this.exceptionName = exceptionName;
    }

    public String getErrMsg() {
        return errMsg;
    }

    public void setErrMsg(String errMsg) {
        this.errMsg = errMsg;
    }

    public String getStackTrace() {
        return stackTrace;
    }

    public void setStackTrace(String stackTrace) {
        this.stackTrace = stackTrace;
    }

    public Date getCreateTime() {
        return createTime;
    }

    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }
}

添加錯誤日志寫數(shù)據(jù)庫自定義Appender類

@Component
public class DbErrorLogAppender extends UnsynchronizedAppenderBase<ILoggingEvent> {

    /**
     * 錯誤日志數(shù)據(jù)庫增刪改查服務(wù)
     */
    @Autowired
    private ILogService logService;

    /**
     * DbErrorLogAppender初始化
     */
    @PostConstruct
    public void init() {
        LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();

        ThresholdFilter filter = new ThresholdFilter();
        filter.setLevel("ERROR");
        filter.setContext(context);
        filter.start();
        this.addFilter(filter);
        this.setContext(context);

        context.getLogger("ROOT").addAppender(DbErrorLogAppender.this);

        super.start();
    }

    /**
     * 錯誤日志拼裝成實體類脸哀,寫入數(shù)據(jù)庫
     */
    @Override
    protected void append(ILoggingEvent loggingEvent) {
        IThrowableProxy tp = loggingEvent.getThrowableProxy();

        // ErrorLogPO數(shù)據(jù)表實體類
        ErrorLogPO errorLog = new ErrorLogPO();
        errorLog.setErrMsg(loggingEvent.getMessage());
        errorLog.setCreateTime(new Date(loggingEvent.getTimeStamp()));

        if (loggingEvent.getCallerData() != null && loggingEvent.getCallerData().length > 0) {
            StackTraceElement element = loggingEvent.getCallerData()[0];
            errorLog.setClassName(element.getClassName());
            errorLog.setMethodName(element.getMethodName());
        }

        if (tp != null) {
            errorLog.setExceptionName(tp.getClassName());
            errorLog.setStackTrace(getStackTraceMsg(tp));
        }

        try {
            // 錯誤日志實體類寫入數(shù)據(jù)庫
            logService.addErrorLog(errorLog);
        } catch (Exception ex) {
            this.addError("上報錯誤日志失敗:" + ex.getMessage());
        }
    }

    /**
     * 拼裝堆棧跟蹤信息
     */
    private String getStackTraceMsg(IThrowableProxy tp) {
        StringBuilder buf = new StringBuilder();

        if (tp != null) {
            while (tp != null) {
                this.renderStackTrace(buf, tp);
                tp = tp.getCause();
            }
        }

        return buf.toString();
    }

    /**
     * 堆棧跟蹤信息拼裝成html字符串
     */
    private void renderStackTrace(StringBuilder sbuf, IThrowableProxy tp) {
        this.printFirstLine(sbuf, tp);
        int commonFrames = tp.getCommonFrames();
        StackTraceElementProxy[] stepArray = tp.getStackTraceElementProxyArray();

        for (int i = 0; i < stepArray.length - commonFrames; ++i) {
            StackTraceElementProxy step = stepArray[i];
            sbuf.append("<br />&nbsp;&nbsp;&nbsp;&nbsp;");
            sbuf.append(Transform.escapeTags(step.toString()));
            sbuf.append(CoreConstants.LINE_SEPARATOR);
        }

        if (commonFrames > 0) {
            sbuf.append("<br />&nbsp;&nbsp;&nbsp;&nbsp;");
            sbuf.append("\t... ").append(commonFrames).append(" common frames omitted").append(CoreConstants.LINE_SEPARATOR);
        }

    }

    /**
     * 拼裝堆棧跟蹤信息第一行
     */
    public void printFirstLine(StringBuilder sb, IThrowableProxy tp) {
        int commonFrames = tp.getCommonFrames();
        if (commonFrames > 0) {
            sb.append("<br />").append("Caused by: ");
        }

        sb.append(tp.getClassName()).append(": ").append(Transform.escapeTags(tp.getMessage()));
        sb.append(CoreConstants.LINE_SEPARATOR);
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末扭吁,一起剝皮案震驚了整個濱河市撞蜂,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌侥袜,老刑警劉巖蝌诡,帶你破解...
    沈念sama閱讀 219,188評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異枫吧,居然都是意外死亡浦旱,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評論 3 395
  • 文/潘曉璐 我一進店門九杂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來颁湖,“玉大人,你說我怎么就攤上這事尼酿∫罚” “怎么了?”我有些...
    開封第一講書人閱讀 165,562評論 0 356
  • 文/不壞的土叔 我叫張陵裳擎,是天一觀的道長。 經(jīng)常有香客問我思币,道長鹿响,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,893評論 1 295
  • 正文 為了忘掉前任谷饿,我火速辦了婚禮惶我,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘博投。我一直安慰自己绸贡,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,917評論 6 392
  • 文/花漫 我一把揭開白布毅哗。 她就那樣靜靜地躺著听怕,像睡著了一般。 火紅的嫁衣襯著肌膚如雪虑绵。 梳的紋絲不亂的頭發(fā)上尿瞭,一...
    開封第一講書人閱讀 51,708評論 1 305
  • 那天,我揣著相機與錄音翅睛,去河邊找鬼声搁。 笑死黑竞,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的疏旨。 我是一名探鬼主播很魂,決...
    沈念sama閱讀 40,430評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼檐涝!你這毒婦竟也來了莫换?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,342評論 0 276
  • 序言:老撾萬榮一對情侶失蹤骤铃,失蹤者是張志新(化名)和其女友劉穎拉岁,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體惰爬,經(jīng)...
    沈念sama閱讀 45,801評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡喊暖,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,976評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了撕瞧。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片陵叽。...
    茶點故事閱讀 40,115評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖丛版,靈堂內(nèi)的尸體忽然破棺而出巩掺,到底是詐尸還是另有隱情,我是刑警寧澤页畦,帶...
    沈念sama閱讀 35,804評論 5 346
  • 正文 年R本政府宣布胖替,位于F島的核電站,受9級特大地震影響豫缨,放射性物質(zhì)發(fā)生泄漏独令。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,458評論 3 331
  • 文/蒙蒙 一好芭、第九天 我趴在偏房一處隱蔽的房頂上張望燃箭。 院中可真熱鬧,春花似錦舍败、人聲如沸招狸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽裙戏。三九已至,卻和暖如春弛说,著一層夾襖步出監(jiān)牢的瞬間挽懦,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評論 1 272
  • 我被黑心中介騙來泰國打工木人, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留信柿,地道東北人冀偶。 一個月前我還...
    沈念sama閱讀 48,365評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像渔嚷,于是被迫代替她去往敵國和親进鸠。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,055評論 2 355