SpringBoot?Loki安裝簡(jiǎn)介及實(shí)戰(zhàn)思路
前言
因?yàn)榫W(wǎng)上好多都沒有通過Loki的API自己實(shí)現(xiàn)對(duì)日志監(jiān)控系統(tǒng),所以我就下定決心自己出一版關(guān)于loki與springboot的博文供大家參考,這個(gè)可以說是比較實(shí)用,很適合中小型企業(yè)。因此我醞釀了挺久了,對(duì)于loki的研究也比較久,希望各位讀者能有新的收獲。
簡(jiǎn)介
Loki是Grafana Labs團(tuán)隊(duì)的開源項(xiàng)目,可以組成一個(gè)功能齊全的日志堆棧。Loki是一個(gè)水平可擴(kuò)展,高可用性,多租戶的日志聚合系統(tǒng)。它的設(shè)計(jì)非常經(jīng)濟(jì)高效且易于操作,因?yàn)樗粫?huì)為日志內(nèi)容編制索引,而是為每個(gè)日志流編制一組標(biāo)簽。Loki是用來存儲(chǔ)日志和處理查詢,需要通過promtail來收集日志,也可是通過后端的logback等日志框架來收集日志,通過grafana提供的loki可視化查看日志,當(dāng)然了loki也提供了API,可以根據(jù)自己的需求來自己實(shí)現(xiàn)可視化界面,能夠減少三方插件的使用。
安裝
上一篇文章已經(jīng)介紹了如何安裝以及使用Grafana+loki+promtail進(jìn)行搭建日志系統(tǒng),http://chabaoo.cn/article/248318.htm可以看看這篇文章。接下來筆者要介紹的是通過Loki的API編寫自己可視化界面,并且通過logback來實(shí)現(xiàn)收集日志。 大致的結(jié)構(gòu)如圖
簡(jiǎn)單介紹一下,主要就是通過springboot后端的logback日志框架來收集日志,在推送到loki中存儲(chǔ),loki執(zhí)行對(duì)日志的查詢,通過API根據(jù)標(biāo)簽等信息去查詢?nèi)罩静⑶以谧远x的前端界面中展示。
整體思路
其實(shí)宏觀來看,要達(dá)成這個(gè)需求說起來是十分簡(jiǎn)單的,只需配置logback配置,在通過MDC寫入、收集日志,這里可以好多的寫法,可以是通過反射寫入日志,也可以是在需要打印的地方寫入日志,并且是將日志區(qū)分為不同的標(biāo)簽。在前端就可以根據(jù)所定義的標(biāo)簽來查看相應(yīng)的日志。前端獲取日志信息邏輯也很簡(jiǎn)單,就只是通過Loki提供的API獲取每行的日志。接下來我就一一詳細(xì)的介紹SpringBoot與Loki的那些事。 可以查看此圖便于理解:
Loki實(shí)戰(zhàn)開發(fā)
接下來就詳細(xì)講解筆者在實(shí)戰(zhàn)開發(fā)中是如何編寫的,本次介紹只是對(duì)編寫的代碼進(jìn)行詳講,對(duì)于代碼可能不會(huì)全部粘貼,不然冗余起來效果不好,各位讀者可以各自發(fā)揮,更加完善。其實(shí)整個(gè)業(yè)務(wù)也不難,基本都是loki自身提供的API,讀者可以通過Loki官方網(wǎng)站grafana.com/docs/loki/l… 去進(jìn)一步對(duì)Loki的API進(jìn)行查閱,后面筆者可能也會(huì)出一篇來專門對(duì)Loki的API以及配置進(jìn)行介紹。好了,廢話不多說,馬上進(jìn)入正題。
springboot中的配置
首先需要配置向Loki推送日志,也就是需要通過Loki的API:POST /loki/api/v1/push ,可以直接將地址通過appender寫死在logback日志框架中,但是在項(xiàng)目開發(fā)中,要考慮到環(huán)境的不同,應(yīng)該是能夠根據(jù)需要來修改loki服務(wù)器的地址,因此將loki的服務(wù)器地址配置在application-dev.yml中。
loki: url: http://localhost:3100/loki/api/v1
配置logback日志框架
先獲取yml配置的地址,通過appender添加到日志框架中,當(dāng)然,配置客戶端也不一定是LogBack框架,還有Log4j2框架也是能夠使用的,具體配置可以看官網(wǎng)github.com/loki4j/loki… 和 github.com/tkowalcz/tj… ,本章只對(duì)loki進(jìn)行講解,對(duì)于日志框架,后期也會(huì)一一列出,各位讀者有什么不了解的,可以先到網(wǎng)上查閱資料。因?yàn)楣P者不是部署多臺(tái)Loki服務(wù)器,不同的系統(tǒng)采用system這個(gè)標(biāo)簽來進(jìn)行區(qū)分。
<springProperty scope="context" name="lokiUrl" source="loki.url"/> <property name="LOKI_URL" value="${lokiUrl}"/> <!--添加loki--> <appender name="lokiAppender" class="com.github.loki4j.logback.Loki4jAppender"> <batchTimeoutMs>1000</batchTimeoutMs> <http class="com.github.loki4j.logback.ApacheHttpSender"> <url>${LOKI_URL}/push</url> </http> <format> <label> <pattern>system=${SYSTEM_NAME},level=%level,logType=%X{log_file_type:-logType}</pattern> </label> <message> <pattern>${log.pattern}</pattern> </message> <sortByTime>true</sortByTime> </format> </appender>
注解與切面寫入日志
自定義注解,并且設(shè)置日志標(biāo)簽值。
/** * @author: lyd * @description: 自定義日志注解,用作LOKI日志分類 * @Date: 2022/10/10 */ @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.METHOD}) @Documented public @interface LokiLog { LokiLogType type() default LokiLogType.DEFAULT; }
通過枚舉的方式來定義日志類型的標(biāo)簽值
/** * @author: lyd * @description: 枚舉便簽值 - 類型自己定義 * @Date: 2022/10/11 */ public enum LokiLogType { DEFAULT("默認(rèn)"), A("A"), B("B"), C("C"); private String desc; LokiLogType(String desc) { this.desc=desc; } public String getDesc() { return desc; } }
編寫切面,寫入日志(詳情可以參照這篇文章http://chabaoo.cn/article/230135.htm,內(nèi)部通過MDC.put("log_file_type", logType.getDesc());(MDC ( Mapped Diagnostic Contexts ),它是一個(gè)線程安全的存放診斷日志的容器。可以參照 http://chabaoo.cn/article/232986.htm可以理解為log_file_type是標(biāo)簽名,logType.getDesc()是標(biāo)簽值。
/** * @author: lyd * @description: 自定義日志切面:https://cloud.tencent.com/developer/article/1655923 * @Date: 2022/10/10 */ @Aspect @Slf4j @Component public class LokiLogAspect { /** * 切到所有OperatorLog注解修飾的方法 */ @Pointcut("@annotation(org.nl.wms.log.LokiLog)") public void operatorLog() { // 空方法 } /** * 利用@Around環(huán)繞增強(qiáng) * * @return */ @Around("operatorLog()") public synchronized Object around(ProceedingJoinPoint pjp) throws Throwable { // ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); // HttpServletRequest request = attributes.getRequest(); // HttpServletResponse response = attributes.getResponse(); Signature signature = pjp.getSignature(); MethodSignature methodSignature = (MethodSignature) signature; Method method = methodSignature.getMethod(); LokiLog lokiLog = method.getAnnotation(LokiLog.class); // 獲取描述信息 LokiLogType logType = lokiLog.type(); MDC.put("log_file_type", logType.getDesc()); log.info("輸入?yún)?shù):" + JSONObject.toJSONString(pjp.getArgs())); Object proceed = pjp.proceed(); log.info("返回參數(shù):" + JSONObject.toJSONString(proceed)); MDC.remove("log_file_type"); return proceed; } }
使用注解,在方法中引用注解即可
@LokiLog(type = LokiLogType.A)
前端界面與后端接口
前端界面介紹起來可能比較麻煩,畢竟寫的代碼也比較多,這里就選取講解,代碼量比較多,也不會(huì)是全部代碼粘貼,樣式之類的,我相信讀者會(huì)根據(jù)自己的需求去實(shí)現(xiàn),這里主要的是記錄開發(fā)的思路。
日志的初步獲取
前端的界面就如圖,本次是以el-admin這個(gè)為基礎(chǔ)制作的demo。
查找日志是需要通過標(biāo)簽與標(biāo)簽值來獲取日志信息,因此首先需要的是攜帶標(biāo)簽對(duì)到后端訪問Loki的API拿到數(shù)據(jù),讀者可以查閱官網(wǎng)的API,結(jié)合著學(xué)習(xí)。
一開始當(dāng)vue視圖渲染的時(shí)候,就會(huì)從后端獲取loki日志標(biāo)簽,具體后端接口的業(yè)務(wù)代碼如下:
/** * 獲取labels和values樹 * * @return */ @Override public JSONArray getLabelsValues() { JSONArray result = new JSONArray(); // 獲取所有標(biāo)簽 String labelString = HttpUtil.get(lokiUrl + "/labels", CharsetUtil.CHARSET_UTF_8); JSONObject parse = (JSONObject) JSONObject.parse(labelString); JSONArray labels = parse.getJSONArray("data"); for (int i=0; i<labels.size(); i++) { // 獲取標(biāo)簽下的所有值 String valueString = HttpUtil.get(lokiUrl + "/label/" + labels.getString(i) + "/values", CharsetUtil.CHARSET_UTF_8); JSONObject parse2 = (JSONObject) JSONObject.parse(valueString); JSONArray values = parse2.getJSONArray("data"); JSONArray children = new JSONArray(); // 組成樹形狀態(tài) 兩級(jí) for (int j=0; j<values.size(); j++) { JSONObject leaf = new JSONObject(); leaf.put("label", values.getString(j)); leaf.put("value", values.getString(j)); children.add(leaf); } JSONObject node = new JSONObject(); node.put("label", labels.getString(i)); node.put("value", labels.getString(i)); node.put("children", children); result.add(node); } return result; }
核心代碼就只有通過Hutool工具包去訪問API獲取標(biāo)簽HttpUtil.get(lokiUrl + "/labels", CharsetUtil.CHARSET_UTF_8); 以及 獲取標(biāo)簽值HttpUtil.get(lokiUrl + "/label/" + labels.getString(i) + "/values", CharsetUtil.CHARSET_UTF_8); 因?yàn)槲业那岸耸怯胑lment-ui的樹來接收的,因此我就將返回的數(shù)據(jù)設(shè)計(jì)成相應(yīng)的形式。
<el-form-item label="日志標(biāo)簽"> <el-cascader v-model="labelAndValue" :options="labelsOptions" placeholder="請(qǐng)選擇標(biāo)簽" @change="queryData" /> </el-form-item>
模糊查找與更多參數(shù)
loki提供了相應(yīng)的API來進(jìn)行模糊查找日志,無非就是通過loki的API攜帶關(guān)鍵字進(jìn)行模糊查找日志,筆者的做法是獲取含有關(guān)鍵字的日志內(nèi)容。
"/query_range?query={system=\"" + systemName + "\", " + logLabel + "=\"" + logLabelValue + "\"} |= `" + text + "`"
并且還能夠通過時(shí)間段來查詢,筆者實(shí)現(xiàn)了的效果如圖
不僅可以通過關(guān)鍵字,還有時(shí)間段時(shí)間范圍以及查找的方向和一次性顯示的條數(shù),最好是建議不要超過1000條數(shù)據(jù),滾動(dòng)步數(shù)是實(shí)現(xiàn)滾動(dòng)下拉的時(shí)候獲取新的日志數(shù)據(jù)的條目數(shù)。 后端代碼如下,簡(jiǎn)單介紹一下,就是提供所需要的查詢條件來對(duì)日志進(jìn)行篩選。不管是獲取日志數(shù)據(jù)還是滾動(dòng)下拉獲取的日志數(shù)據(jù)都可以通用這個(gè)接口,然而主要的參數(shù)設(shè)置可以在前端進(jìn)行打磨,以下代碼還有優(yōu)化的空間,畢竟當(dāng)時(shí)剛開始寫的時(shí)候沒考慮這么多。
@Override public JSONObject getLogData(JSONObject json) { String logLabel = ""; String logLabelValue = ""; Long start = 0L; Long end = 0L; String text = ""; String limit = "100"; String direction = "backward"; if (json.get("logLabel") != null) logLabel = json.getString("logLabel"); if (json.get("logLabelValue") != null) logLabelValue = json.getString("logLabelValue"); if (json.get("text") != null) text = json.getString("text"); if (json.get("start") != null) start = json.getLong("start"); if (json.get("end") != null) end = json.getLong("end"); if (json.get("limits") != null) limit = json.getString("limits"); if (json.get("direction") != null) direction = json.getString("direction"); /** * 組織參數(shù) * 納秒數(shù) * 1660037391880000000 * 1641453208415000000 * http://localhost:3100/loki/api/v1/query_range?query={host="localhost"} |= ``&limit=1500&start=1641453208415000000&end=1660027623419419002 */ JSONObject parse = null; String query = lokiUrl + "/query_range?query={system=\"" + systemName + "\", " + logLabel + "=\"" + logLabelValue + "\"} |= `" + text + "`"; String result = ""; if (start==0L) { result = HttpUtil.get(query + "&limit=" + limit + "&direction=" + direction, CharsetUtil.CHARSET_UTF_8); } else { result = HttpUtil.get(query + "&limit=" + limit + "&start=" + start + "&end=" + end + "&direction=" + direction, CharsetUtil.CHARSET_UTF_8); } try { parse = (JSONObject) JSONObject.parse(result); } catch (Exception e) { // reslut的值可能為:too many outstanding requests,無法轉(zhuǎn)化成Json System.out.println("reslut:" + result); // e.printStackTrace(); } return parse; }
前端的邏輯是比較復(fù)雜的,因?yàn)樾枰龃罅康馁x值與設(shè)置。 前端js方法代碼,主要是對(duì)參數(shù)數(shù)據(jù)的組織,這里需要注意的是,因?yàn)閘oki需要的是納秒級(jí)別的時(shí)間戳,這里就需要十分注意前端js的精度。還有一點(diǎn)就是,如果后端日志是有顏色標(biāo)簽的,那么前端直接渲染就會(huì)顯示標(biāo)簽,所以這里需要進(jìn)行相應(yīng)的處理,就是用過AnsiUp插件進(jìn)行操作,詳細(xì)看此篇文章:http://chabaoo.cn/article/266641.htm
queryData() { console.log(this.labelAndValue) // 清空查詢數(shù)據(jù) this.clearParam() if (this.labelAndValue.length > 0) { queryParam.logLabel = this.labelAndValue[0] queryParam.logLabelValue = this.labelAndValue[1] } if (queryParam.logLabelValue === null) { // 判空 this.$message({ showClose: true, message: '請(qǐng)選擇標(biāo)簽', type: 'warning' }) this.showEmpty = true this.emptyText = '請(qǐng)選擇標(biāo)簽' return } if (this.timeRange.length !== 0) { // 如果是輸入時(shí)間范圍 queryParam.start = (new Date(this.timeRange[0]).getTime() * 1000000).toString() queryParam.end = (new Date(this.timeRange[1]).getTime() * 1000000).toString() } if (this.timeZoneValue) { const time = new Date() queryParam.start = ((time.getTime() - this.timeZoneValue) * 1000000).toString() queryParam.end = (time.getTime() * 1000000).toString() } if (this.text) { queryParam.text = this.text.replace(/^\s*|\s*$/g, '') // 去空 } if (this.limits) { queryParam.limits = this.limits } queryParam.direction = this.direction var ansi_up = new AnsiUp() logOperation.getLogData(queryParam).then(res => { this.showEmpty = false if (res.data.result.length === 1) { this.logs = res.data.result[0].values for (const i in res.data.result[0].values) { this.logs[i][1] = ansi_up.ansi_to_html(res.data.result[0].values[i][1]) } } else if (res.data.result.length > 1) { // 清空 this.logs = [] for (const j in res.data.result) { // 用push的方式將所有日志數(shù)組添加進(jìn)去 for (const values_index in res.data.result[j].values) { this.logs.push(res.data.result[j].values[values_index]) } } for (const k in this.logs) { this.logs[k][1] = ansi_up.ansi_to_html(this.logs[k][1]) } if (this.direction === 'backward') { // 由于使用公共標(biāo)簽會(huì)導(dǎo)致時(shí)間順序錯(cuò)亂,因此對(duì)二維數(shù)組進(jìn)行排序 this.logs.sort((a, b) => b[0] - a[0]) } else { this.logs.sort((a, b) => a[0] - b[0]) } } else { this.showEmpty = true this.emptyText = '暫無日志信息,請(qǐng)選擇時(shí)間段試試' } }) },
通過AnsiUp插件可以將帶有顏色標(biāo)簽的日志以顏色展示,代碼如下:
<div style="margin: 3px; min-height: 80vh;"> <!--數(shù)據(jù)判空--> <el-empty v-if="showEmpty" :description="emptyText" /> <!--數(shù)據(jù)加載--> <el-card v-else shadow="hover" style="width: 100%" class="log-warpper"> <div style="width: 100%"> <div v-for="(log, index) in logs" :key="index"> <div style="margin-bottom: 5px; font-size: 12px;" v-html="log[1]" /> </div> </div> </el-card> </div>
向后端請(qǐng)求日志返回的結(jié)果是如下圖所示
滾動(dòng)追加日志
其實(shí)下拉滾動(dòng)的代碼與上面直接獲取日志的是差不多的,只是在數(shù)據(jù)的追加是不一樣的做法,這里需要注意的是要考慮日志的展示是正序還是逆序,不同的順序計(jì)算時(shí)間范圍是不一樣的,就如下代碼
if (this.direction === 'backward') { // 設(shè)置時(shí)間區(qū)間 queryParam.start = (this.logs[this.logs.length - 1][0] - zone).toString() queryParam.end = this.logs[this.logs.length - 1][0] } else { queryParam.start = this.logs[this.logs.length - 1][0] queryParam.end = (parseFloat(this.logs[this.logs.length - 1][0]) + parseFloat(zone.toString())).toString() }
在滾動(dòng)獲取日志的思路是獲取最后一條數(shù)據(jù)的時(shí)間,往后推一定的時(shí)間差,所以需要考慮是正序還是倒序,默認(rèn)是6小時(shí)。
mounted() { window.addEventListener('scroll', this.handleScroll) } methods: { handleScroll() { // 滾動(dòng)事件 const scrollTop = document.documentElement.scrollTop// 滾動(dòng)高度 const clientHeight = document.documentElement.clientHeight// 可視高度 const scrollHeight = document.documentElement.scrollHeight// 內(nèi)容高度 const bottomest = Math.ceil(scrollTop + clientHeight) if (bottomest >= scrollHeight) { // 加載新數(shù)據(jù) queryParam.limits = this.scrollStep queryParam.direction = this.direction // 獲取時(shí)間差 let zone = queryParam.end - queryParam.start if (this.timeRange.length) { // 如果是輸入時(shí)間范圍 zone = ((new Date(this.timeRange[1]).getTime() - new Date(this.timeRange[0]).getTime()) * 1000000).toString() } if (this.timeZoneValue) { zone = this.timeZoneValue * 1000000 } if (zone === 0) { zone = 3600 * 1000 * 6 } if (this.direction === 'backward') { // 設(shè)置時(shí)間區(qū)間 queryParam.start = (this.logs[this.logs.length - 1][0] - zone).toString() queryParam.end = this.logs[this.logs.length - 1][0] } else { queryParam.start = this.logs[this.logs.length - 1][0] queryParam.end = (parseFloat(this.logs[this.logs.length - 1][0]) + parseFloat(zone.toString())).toString() } var ansi_up = new AnsiUp() logOperation.getLogData(queryParam).then(res => { console.log(res) this.showEmpty = false if (res.data.result.length === 1) { // 如果返回的日志是一樣的就不顯示 if (res.data.result[0].values.length === 1 && ansi_up.ansi_to_html(res.data.result[0].values[0][1]) === this.logs[this.logs.length - 1][1]) { this.$notify({ title: '警告', duration: 1000, message: '當(dāng)前時(shí)間段日志已最新!', type: 'warning' }) return } const log = res.data.result[0].values for (const i in res.data.result[0].values) { log[i][1] = ansi_up.ansi_to_html(res.data.result[0].values[i][1]) this.logs.push(log[i]) } } else if (res.data.result.length > 1) { const tempArray = [] // 數(shù)據(jù)需要處理,由于是追加數(shù)組,所以需要用額外變量來存放 // 刷新就是添加,不清空原數(shù)組 for (const j in res.data.result) { // 用push的方式將所有日志數(shù)組添加進(jìn)去 for (const values_index in res.data.result[j].values) { tempArray.push(res.data.result[j].values[values_index]) } } if (this.direction === 'backward') { // 由于使用公共標(biāo)簽會(huì)導(dǎo)致時(shí)間順序錯(cuò)亂,因此對(duì)二維數(shù)組進(jìn)行排序 tempArray.sort((a, b) => b[0] - a[0]) } else { tempArray.sort((a, b) => a[0] - b[0]) } for (const k in tempArray) { tempArray[k][1] = ansi_up.ansi_to_html(tempArray[k][1]) // 數(shù)據(jù)轉(zhuǎn)換 this.logs.push(tempArray[k]) // 追加數(shù)據(jù) } } else { this.$notify({ title: '警告', duration: 1000, message: '暫無以往日志數(shù)據(jù)!', type: 'warning' }) } }) } } }
定時(shí)刷新日志
當(dāng)然,日志的獲取也是需要實(shí)時(shí)刷新的,這種不僅可以使用定時(shí)器還能夠使用websocket,筆者使用的是定時(shí)器,因?yàn)檫@個(gè)寫起來比較簡(jiǎn)單。相關(guān)的代碼以及解析如下: 視圖
<el-form-item> <el-dropdown split-button type="primary" size="mini" @click="queryData"> 查詢{{ runStatu }} <el-dropdown-menu slot="dropdown"> <el-dropdown-item v-for="(item, index) in runStatuOptions" :key="index" @click.native="startInterval(item)">{{ item.label }}</el-dropdown-item> </el-dropdown-menu> </el-dropdown> </el-form-item>
方法代碼 代碼大致也和上面兩種情況是類似的,思路是獲取當(dāng)前時(shí)間前(時(shí)間差)的時(shí)間到當(dāng)前時(shí)間的日志信息。這里不需要管日志的時(shí)序方向,只需要做好始終時(shí)間,注意納秒級(jí)別,還有定時(shí)器不要忘記銷毀。
startInterval(item) { this.runStatu = item.label console.log(item.value) if (item.value !== 0) { this.timer = setInterval(() => { // 定時(shí)刷新 this.intervalLogs() }, item.value) } else { console.log('銷毀了') clearInterval(this.timer) } }, intervalLogs() { // 定時(shí)器的方法 // 組織參數(shù) // 設(shè)置開始時(shí)間和結(jié)束時(shí)間 // 開始為現(xiàn)在時(shí)間 const start = new Date() const end = new Date() // 時(shí)差判斷 let zone = queryParam.end - queryParam.start if (this.timeRange.length) { // 如果是輸入時(shí)間范圍 zone = ((new Date(this.timeRange[1]).getTime() - new Date(this.timeRange[0]).getTime()) * 1000000).toString() } if (this.timeZoneValue) { zone = this.timeZoneValue * 1000000 } if (zone === 0) { // 防止空指針 start.setTime(start.getTime() - 3600 * 1000 * 6) queryParam.start = (start.getTime() * 1000000).toString() } else { queryParam.start = (start.getTime() * 1000000 - zone).toString() } queryParam.end = (end.getTime() * 1000000).toString() queryParam.limits = this.limits console.log('定時(shí)器最后參數(shù):', queryParam) var ansi_up = new AnsiUp() // 后端日志格式轉(zhuǎn)化 logOperation.getLogData(queryParam).then(res => { console.log('res', res) this.showEmpty = false debugger if (res.data.result.length === 1) { this.logs = res.data.result[0].values for (const i in res.data.result[0].values) { // 格式轉(zhuǎn)換 this.logs[i][1] = ansi_up.ansi_to_html(res.data.result[0].values[i][1]) } } else if (res.data.result.length > 1) { // 清空 this.logs = [] for (const j in res.data.result) { // 用push的方式將所有日志數(shù)組添加進(jìn)去 for (const values_index in res.data.result[j].values) { this.logs.push(res.data.result[j].values[values_index]) } } for (const k in this.logs) { this.logs[k][1] = ansi_up.ansi_to_html(this.logs[k][1]) } if (this.direction === 'backward') { // 由于使用公共標(biāo)簽會(huì)導(dǎo)致時(shí)間順序錯(cuò)亂,因此對(duì)二維數(shù)組進(jìn)行排序 this.logs.sort((a, b) => b[0] - a[0]) } else { this.logs.sort((a, b) => a[0] - b[0]) } } else { this.showEmpty = true this.emptyText = '暫無日志信息,請(qǐng)選擇時(shí)間段試試' } }) },
最后粘一小段展示的界面
總結(jié)
loki是輕量級(jí)的分布式日志查詢框架,特別適合中小型企業(yè),尤其是工業(yè)項(xiàng)目,在項(xiàng)目上線的時(shí)候可以通過這樣的一個(gè)界面來觀察日志,確實(shí)能夠得到很大的幫助,但是這個(gè)loki不是特別的穩(wěn)定,最為常見的是會(huì)出現(xiàn)ERP ERROR,這種錯(cuò)誤是最頭疼的,個(gè)人感覺可能是計(jì)算機(jī)或者網(wǎng)絡(luò)的因素造成。
以上就是SpringBoot Loki安裝簡(jiǎn)介及實(shí)戰(zhàn)思路的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot Loki安裝的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- 使用?Loki?實(shí)現(xiàn)?Kubernetes?容器日志監(jiān)控的方法
- JavaScript實(shí)現(xiàn)的內(nèi)存數(shù)據(jù)庫LokiJS介紹和入門實(shí)例
- springboot使用小工具之Lombok、devtools、Spring Initailizr詳解
- SpringBoot文件上傳與下載功能實(shí)現(xiàn)詳解
- springboot如何設(shè)置請(qǐng)求參數(shù)長(zhǎng)度和文件大小限制
- springboot中報(bào)錯(cuò)Invalid character found in the request的解決
- springboot中關(guān)于classpath:路徑使用及說明
相關(guān)文章
RocketMQ4.5.2 修改mqnamesrv 和 mqbroker的日志路徑操作
這篇文章主要介紹了RocketMQ 4.5.2 修改mqnamesrv 和 mqbroker的日志路徑操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07Java實(shí)現(xiàn)每日給女友微信發(fā)送早安信息
這篇文章主要為大家詳細(xì)介紹了Java如何實(shí)現(xiàn)每日給女友微信發(fā)送早安等微信信息,文中的示例代碼講解詳細(xì),具有一定的借鑒價(jià)值,需要的可以了解一下2022-12-12Java中使用BigDecimal進(jìn)行精確運(yùn)算
這篇文章主要介紹了Java中使用BigDecimal進(jìn)行精確運(yùn)算的方法,非常不錯(cuò),需要的朋友參考下2017-02-02redis實(shí)現(xiàn)隊(duì)列的阻塞、延時(shí)、發(fā)布和訂閱
本文主要介紹了redis實(shí)現(xiàn)隊(duì)列的阻塞、延時(shí)、發(fā)布和訂閱,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-06-06org.apache.zookeeper.KeeperException.BadVersionException異常的解
在使用Apache ZooKeeper進(jìn)行分布式協(xié)調(diào)時(shí),你可能會(huì)遇到org.apache.zookeeper.KeeperException.BadVersionException異常,本文就來介紹一下解決方法,感興趣的可以了解一下2024-03-03SpringCloud Zuul在何種情況下使用Hystrix及問題小結(jié)
這篇文章主要介紹了SpringCloud Zuul在何種情況下使用Hystrix 及問題小結(jié),感興趣的朋友跟隨小編一起看看吧2018-11-11