搜尋
首頁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中文網其他相關文章!

陳述
本文轉載於:segmentfault。如有侵權,請聯絡admin@php.cn刪除
Python vs. JavaScript:您應該學到哪種語言?Python vs. JavaScript:您應該學到哪種語言?May 03, 2025 am 12:10 AM

選擇Python還是JavaScript應基於職業發展、學習曲線和生態系統:1)職業發展:Python適合數據科學和後端開發,JavaScript適合前端和全棧開發。 2)學習曲線:Python語法簡潔,適合初學者;JavaScript語法靈活。 3)生態系統:Python有豐富的科學計算庫,JavaScript有強大的前端框架。

JavaScript框架:為現代網絡開發提供動力JavaScript框架:為現代網絡開發提供動力May 02, 2025 am 12:04 AM

JavaScript框架的強大之處在於簡化開發、提升用戶體驗和應用性能。選擇框架時應考慮:1.項目規模和復雜度,2.團隊經驗,3.生態系統和社區支持。

JavaScript,C和瀏覽器之間的關係JavaScript,C和瀏覽器之間的關係May 01, 2025 am 12:06 AM

引言我知道你可能會覺得奇怪,JavaScript、C 和瀏覽器之間到底有什麼關係?它們之間看似毫無關聯,但實際上,它們在現代網絡開發中扮演著非常重要的角色。今天我們就來深入探討一下這三者之間的緊密聯繫。通過這篇文章,你將了解到JavaScript如何在瀏覽器中運行,C 在瀏覽器引擎中的作用,以及它們如何共同推動網頁的渲染和交互。 JavaScript與瀏覽器的關係我們都知道,JavaScript是前端開發的核心語言,它直接在瀏覽器中運行,讓網頁變得生動有趣。你是否曾經想過,為什麼JavaScr

node.js流帶打字稿node.js流帶打字稿Apr 30, 2025 am 08:22 AM

Node.js擅長於高效I/O,這在很大程度上要歸功於流。 流媒體匯總處理數據,避免內存過載 - 大型文件,網絡任務和實時應用程序的理想。將流與打字稿的類型安全結合起來創建POWE

Python vs. JavaScript:性能和效率注意事項Python vs. JavaScript:性能和效率注意事項Apr 30, 2025 am 12:08 AM

Python和JavaScript在性能和效率方面的差異主要體現在:1)Python作為解釋型語言,運行速度較慢,但開發效率高,適合快速原型開發;2)JavaScript在瀏覽器中受限於單線程,但在Node.js中可利用多線程和異步I/O提升性能,兩者在實際項目中各有優勢。

JavaScript的起源:探索其實施語言JavaScript的起源:探索其實施語言Apr 29, 2025 am 12:51 AM

JavaScript起源於1995年,由布蘭登·艾克創造,實現語言為C語言。 1.C語言為JavaScript提供了高性能和系統級編程能力。 2.JavaScript的內存管理和性能優化依賴於C語言。 3.C語言的跨平台特性幫助JavaScript在不同操作系統上高效運行。

幕後:什麼語言能力JavaScript?幕後:什麼語言能力JavaScript?Apr 28, 2025 am 12:01 AM

JavaScript在瀏覽器和Node.js環境中運行,依賴JavaScript引擎解析和執行代碼。 1)解析階段生成抽象語法樹(AST);2)編譯階段將AST轉換為字節碼或機器碼;3)執行階段執行編譯後的代碼。

Python和JavaScript的未來:趨勢和預測Python和JavaScript的未來:趨勢和預測Apr 27, 2025 am 12:21 AM

Python和JavaScript的未來趨勢包括:1.Python將鞏固在科學計算和AI領域的地位,2.JavaScript將推動Web技術發展,3.跨平台開發將成為熱門,4.性能優化將是重點。兩者都將繼續在各自領域擴展應用場景,並在性能上有更多突破。

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

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

AI Clothes Remover

AI Clothes Remover

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

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

PhpStorm Mac 版本

PhpStorm Mac 版本

最新(2018.2.1 )專業的PHP整合開發工具

WebStorm Mac版

WebStorm Mac版

好用的JavaScript開發工具

記事本++7.3.1

記事本++7.3.1

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

Atom編輯器mac版下載

Atom編輯器mac版下載

最受歡迎的的開源編輯器