目錄
業務背景
存取步驟
一、日誌結構設計
1. 將日誌抽象化為事件
事件元欄位
#請求元字段
資料欄位
2. 日誌改造輸出
升級關鍵節點的日誌
相容舊的日誌呼叫方式
改造日誌輸出格式
二、日誌擷取
使用者要求
下游依赖
一些建议
一些原则
三、ES 索引模版定义
首頁 web前端 js教程 Node框架接入ELK的過程小結

Node框架接入ELK的過程小結

Nov 21, 2018 am 11:23 AM
node.js 前端

這篇文章帶給大家的內容是關於Node框架接入ELK的過程小結,有一定的參考價值,有需要的朋友可以參考一下,希望對你有幫助。

我們都有過上機器查日誌的經歷,當叢集數量增多的時候,這種原始的操作帶來的低效率不僅給我們定位現網問題帶來極大的挑戰,同時,我們也無法對我們服務架構的各項指標進行有效的量化診斷,更無從談有針對性的最佳化和改進。這時候,建立具備資訊查找,服務診斷,資料分析等功能的即時日誌監控系統尤其重要。

ELK (ELK Stack: ElasticSearch, LogStash, Kibana, Beats) 是一套成熟的日誌解決方案,其開源及高效能在各大公司廣泛使用。而我們業務所使用的服務框架,如何接取 ELK 系統呢?

業務背景

我們的業務框架背景:

  • #業務框架是基於NodeJs 的WebServer

  • 服務使用winston 日誌模組將日誌本地化

  • 服務產生的日誌儲存在各自機器的磁碟上

  • 服務部署在不同地域多台機器

存取步驟

我們將整個框架存取ELK 簡單歸納為下面幾個步驟:

  • 日誌結構設計:由傳統的純文字日誌改成結構化物件並輸出為JSON.

  • 日誌擷取:在框架中請求生命週期的一些關鍵節點輸出日誌

  • ES 索引模版定義:建立JSON 到ES 實際儲存的對應

一、日誌結構設計

傳統的,我們在做日誌輸出的時候,是直接輸出日誌的等級(level)和日誌的內容字串(message)。然而我們不僅關注什麼時間,發生了什麼,可能還需要關注類似的日誌發生了多少次,日誌的細節與上下文,以及關聯的日誌。因此我們不只是簡單地將我們的日誌結構化為對象,還要提取出日誌關鍵的欄位。

1. 將日誌抽象化為事件

我們將每一個日誌的發生抽象化為一個事件。事件包含:

事件元欄位

事件發生時間:datetime, timestamp

事件等級:level, 例如: ERROR, INFO, WARNING, DEBUG

事件名稱:  event,  例如:client-request

事件發生的相對時間(單位:奈秒):reqLife, 此欄位為事件相對請求開始發生的時間(間隔)

事件發生的位置: line,代碼位置;  server, 伺服器的位置

#請求元字段

請求唯一ID: reqId, 此字段貫穿整個請求連結上發生的所有事件

請求使用者ID: reqUid, 此欄位為使用者標識,可以追蹤使用者的存取或請求連結

資料欄位

不同類型的事件,需要輸出的細節不盡相同,我們將這些細節(非元欄位)統一放到d -- data,之中。讓我們的事件結構更加清晰,同時,也能避免資料欄位對元字段造成污染。

e.g. 如client-init事件,該事件會在每次伺服器接收到使用者要求時列印,我們將使用者的ip, url等事件獨有的統一歸為資料欄位放到d 物件中

舉個完整的例子

{
    "datetime":"2018-11-07 21:38:09.271",
    "timestamp":1541597889271,
    "level":"INFO",
    "event":"client-init",
    "reqId":"rJtT5we6Q",
    "reqLife":5874,
    "reqUid": "999793fc03eda86",
    "d":{
        "url":"/",
        "ip":"9.9.9.9",
        "httpVersion":"1.1",
        "method":"GET",
        "userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36",
        "headers":"*"
    },
    "browser":"{"name":"Chrome","version":"70.0.3538.77","major":"70"}",
    "engine":"{"version":"537.36","name":"WebKit"}",
    "os":"{"name":"Mac OS","version":"10.14.0"}",
    "content":"(Empty)",
    "line":"middlewares/foo.js:14",
    "server":"127.0.0.1"
}
登入後複製

一些字段,如:browser, os, engine為什麼在外層有時候我們希望日誌盡量扁平(最大深度為2),以避免ES 不必要的索引帶來的效能損耗。在實際輸出的時候,我們會將深度大於1的值輸出為字串。而有時候有些物件欄位是我們關注的,所以我們將這些特殊欄位放在外層,以確保輸出深度不大於2的原則。

一般的,我們在列印輸出日誌的時候,只須關注事件名稱資料欄位即可。其他,我們可以在列印日誌的方法中,透過存取上下文統一獲取,計算,輸出。

2. 日誌改造輸出

前面我們提到如何定義一個日誌事件, 那麼,我們如何基於已有日誌方案做升級,同時,相容於舊程式碼的日誌呼叫方式。

升級關鍵節點的日誌

// 改造前
logger.info('client-init => ' + JSON.stringfiy({
    url,
    ip,
    browser,
    //...
}));

// 改造后
logger.info({
    event: 'client-init',
    url,
    ip,
    browser,
    //...
});
登入後複製

相容舊的日誌呼叫方式

logger.debug('checkLogin');
登入後複製

因為winston 的日誌方法本身就支援string 或object 的傳入方式, 所以對於舊的字串傳入寫入法,formatter 接收的其實是{ level: 'debug', message: 'checkLogin' }。 formatter 是winston 的日誌輸出前調整日誌格式的一道工序, 這一點使我們在日誌輸出前有機會將這類調用方式輸出的日誌,轉為一個純輸出事件-- 我們稱它們為raw-log事件,而不需要修改呼叫方式。

改造日誌輸出格式

前面提到winston 輸出日誌前,會經過我們預先定義的formatter,因此除了相容邏輯的處理外,我們可以將一些公共邏輯統一放在這裡處理。而呼叫上,我們只關注字段本身即可。

  • 元字段提取及處理

  • 字段長度控制

  • 相容邏輯處理

如何提取元字段,這裡涉及上下文的建立與使用,這裡簡單介紹domain 的建立與使用。

//--- middlewares/http-context.js
const domain = require('domain');
const shortid = require('shortid');

module.exports = (req, res, next) => {
    const d = domain.create();
    d.id =  shortid.generate(); // reqId;
    d.req = req;
    
    //...

    res.on('finish', () => process.nextTick(() => {
        d.id = null;
        d.req = null;
        d.exit();
    });

    d.run(() => next());
}

//--- app.js
app.use(require('./middlewares/http-context.js'));

//--- formatter.js
if (process.domain) {
    reqId = process.domain.id;
}
登入後複製

這樣,我們就可以將 reqId 輸出到一次請求中所有的事件, 從而達到關聯事件的目的。

二、日誌擷取

現在,我們知道怎麼輸出一個事件了,那麼下一步,我們該考慮兩個問題:

  1. 我們要在哪裡輸出事件?

  2. 事件要輸出什麼細節?

換句話說,整個請求連結中,哪些節點是我們關注的,出現問題,可以透過哪個節點的資訊快速定位到問題?除此之外,我們還可以透過哪些節點的資料做統計分析?

結合一般常見的請求連結(使用者要求,服務側接收請求,服務請求下游伺服器/資料庫(*多次),資料聚合渲染,服務回應),如下方的流程圖

Node框架接入ELK的過程小結

那麼,我們可以這樣定義我們的事件:

使用者要求

client-init: 印在框架接收到請求(未解析), 包括:請求位址,請求頭,Http 版本和方法,使用者IP 和瀏覽器

client-request: 列印於框架接收到請求(已解析),包括:請求位址,請求頭,Cookie, 請求包體

client-response: 列印於框架回傳請求,包括:請求位址,回應碼,回應頭,回應包體

#

下游依赖

http-start: 打印于请求下游起始:请求地址,请求包体,模块别名(方便基于名字聚合而且域名)

http-success: 打印于请求返回 200:请求地址,请求包体,响应包体(code & msg & data),耗时

http-error:  打印于请求返回非 200,亦即连接服务器失败:请求地址,请求包体,响应包体(code & message & stack),耗时。

http-timeout:  打印于请求连接超时:请求地址,请求包体,响应包体(code & msg & stack),耗时。

字段这么多,该怎么选择? 一言以蔽之,事件输出的字段原则就是:输出你关注的,方便检索的,方便后期聚合的字段。

一些建议

  1. 请求下游的请求体和返回体有固定格式, e.g. 输入:{ action: 'getUserInfo', payload: {} } 输出: { code: 0, msg: '', data: {}} 我们可以在事件输出 action,code 等,以便后期通过 action 检索某模块具体某个接口的各项指标和聚合。

一些原则

  1. 保证输出字段类型一致 由于所有事件都存储在同一个 ES 索引, 因此,相同字段不管是相同事件还是不同事件,都应该保持一致,例如:code不应该既是数字,又是字符串,这样可能会产生字段冲突,导致某些记录(document)无法被冲突字段检索到。

  2. ES 存储类型为 keyword, 不应该超过 ES mapping 设定的 ignore_above 中指定的字节数(默认4096个字节)。否则同样可能会产生无法被检索的情况

三、ES 索引模版定义

这里引入 ES 的两个概念,映射(Mapping)与模版(Template)。

首先,ES 基本的存储类型大概枚举下,有以下几种

  • String: keyword & text

  • Numeric: long, integer, double

  • Date: date

  • Boolean: boolean

一般的,我们不需要显示指定每个事件字段的在ES对应的存储类型,ES 会自动根据字段第一次出现的document中的值来决定这个字段在这个索引中的存储类型。但有时候,我们需要显示指定某些字段的存储类型,这个时候我们需要定义这个索引的 Mapping, 来告诉 ES 这此字段如何存储以及如何索引。

e.g.

还记得事件元字段中有一个字段为 timestamp ?实际上,我们输出的时候,timestamp 的值是一个数字,它表示跟距离 1970/01/01 00:00:00 的毫秒数,而我们期望它在ES的存储类型为 date 类型方便后期的检索和可视化, 那么我们创建索引的时候,指定我们的Mapping。

PUT my_logs
{
  "mappings": {
    "_doc": { 
      "properties": { 
        "title":    {
            "type": "date",
            "format": "epoch_millis"
         }, 
      }
    }
  }
}
登入後複製

但一般的,我们可能会按日期自动生成我们的日志索引,假定我们的索引名称格式为 my_logs_yyyyMMdd (e.g. my_logs_20181030)。那么我们需要定义一个模板(Template),这个模板会在(匹配的)索引创建时自动应用预设好的 Mapping。

PUT _template/my_logs_template
{
  "index_patterns": "my_logs*",
  "mappings": {
    "_doc": { 
      "properties": { 
        "title":    {
            "type": "date",
            "format": "epoch_millis"
         }, 
      }
    }
  }
}
登入後複製
提示:将所有日期产生的日志都存在一张索引中,不仅带来不必要的性能开销,也不利于定期删除比较久远的日志。

小结

至此,日志改造及接入的准备工作都已经完成了,我们只须在机器上安装 FileBeat -- 一个轻量级的文件日志Agent, 它负责将日志文件中的日志传输到 ELK。接下来,我们便可使用 Kibana 快速的检索我们的日志。

以上是Node框架接入ELK的過程小結的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
3 週前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
3 週前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您聽不到任何人,如何修復音頻
3 週前 By 尊渡假赌尊渡假赌尊渡假赌
WWE 2K25:如何解鎖Myrise中的所有內容
3 週前 By 尊渡假赌尊渡假赌尊渡假赌

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

PHP與Vue:完美搭檔的前端開發利器 PHP與Vue:完美搭檔的前端開發利器 Mar 16, 2024 pm 12:09 PM

PHP與Vue:完美搭檔的前端開發利器在當今網路快速發展的時代,前端開發變得愈發重要。隨著使用者對網站和應用的體驗要求越來越高,前端開發人員需要使用更有效率和靈活的工具來創建響應式和互動式的介面。 PHP和Vue.js作為前端開發領域的兩個重要技術,搭配起來可以稱得上是完美的利器。本文將探討PHP和Vue的結合,以及詳細的程式碼範例,幫助讀者更好地理解和應用這兩

如何使用 Go 語言進行前端開發? 如何使用 Go 語言進行前端開發? Jun 10, 2023 pm 05:00 PM

隨著網路技術的發展,前端開發變得日益重要。尤其是行動端設備的普及,更需要高效率、穩定、安全又易於維護的前端開發技術。而作為一門快速發展的程式語言,Go語言已經被越來越多的開發者所使用。那麼,使用Go語言進行前端開發行得通嗎?接下來,本文將為你詳細說明如何使用Go語言進行前端開發。先來看看為什麼要使用Go語言進行前端開發。很多人認為Go語言是一門

前端面試官常問的問題 前端面試官常問的問題 Mar 19, 2024 pm 02:24 PM

在前端開發面試中,常見問題涵蓋廣泛,包括HTML/CSS基礎、JavaScript基礎、框架和函式庫、專案經驗、演算法和資料結構、效能最佳化、跨域請求、前端工程化、設計模式以及新技術和趨勢。面試官的問題旨在評估候選人的技術技能、專案經驗以及對行業趨勢的理解。因此,應試者應充分準備這些方面,以展現自己的能力和專業知識。

Django是前端還是後端?一探究竟! Django是前端還是後端?一探究竟! Jan 19, 2024 am 08:37 AM

Django是一個由Python編寫的web應用框架,它強調快速開發和乾淨方法。儘管Django是web框架,但要回答Django是前端還是後端這個問題,需要深入理解前後端的概念。前端是指使用者直接和互動的介面,後端是指伺服器端的程序,他們透過HTTP協定進行資料的互動。在前端和後端分離的情況下,前後端程式可以獨立開發,分別實現業務邏輯和互動效果,資料的交

golang可以做前端嗎 golang可以做前端嗎 Jun 06, 2023 am 09:19 AM

golang可以做前端,Golang是一種通用性很強的程式語言,可以用於開發不同類型的應用程序,包括前端應用程序,透過使用Golang來編寫前端,可以擺脫JavaScript等語言引起的一系列問題,例如類型安全性差、效能低下,以及程式碼難以維護等問題。

C#開發經驗分享:前端與後端協同開發技巧 C#開發經驗分享:前端與後端協同開發技巧 Nov 23, 2023 am 10:13 AM

身為C#開發者,我們的開發工作通常包括前端和後端的開發,而隨著技術的發展和專案的複雜性提高,前端與後端協同開發也變得越來越重要和複雜。本文將分享一些前端與後端協同開發的技巧,以幫助C#開發者更有效率地完成開發工作。確定好介面規範前後端的協同開發離不開API介面的交互。要確保前後端協同開發順利進行,最重要的是定義好介面規格。接口規範涉及到接口的命

Go語言前端技術探秘:前端開發新視野 Go語言前端技術探秘:前端開發新視野 Mar 28, 2024 pm 01:06 PM

Go語言作為一種快速、高效的程式語言,在後端開發領域廣受歡迎。然而,很少有人將Go語言與前端開發聯繫起來。事實上,使用Go語言進行前端開發不僅可以提高效率,還能為開發者帶來全新的視野。本文將探討使用Go語言進行前端開發的可能性,並提供具體的程式碼範例,幫助讀者更了解這一領域。在傳統的前端開發中,通常會使用JavaScript、HTML和CSS來建立使用者介面

前端怎麼實現即時通訊 前端怎麼實現即時通訊 Oct 09, 2023 pm 02:47 PM

實作即時通訊的方法有WebSocket、Long Polling、Server-Sent Events、WebRTC等等。詳細介紹:1、WebSocket,它可以在客戶端和伺服器之間建立持久連接,實現即時的雙向通信,前端可以使用WebSocket API來創建WebSocket連接,並透過發送和接收訊息來實現即時通訊;2、Long Polling,是一種模擬即時通訊的技術等等

See all articles