ホームページ  >  記事  >  ウェブフロントエンド  >  NodeフレームワークをELKに接続するプロセスの概要

NodeフレームワークをELKに接続するプロセスの概要

不言
不言転載
2018-11-21 11:23:212442ブラウズ

この記事の内容は、Node フレームワークを ELK に接続するプロセスをまとめたものです。必要な方は参考にしていただければ幸いです。

クラスタの数が増えると、この原始的な操作によってもたらされる非効率性が、既存のネットワークの問題を特定する際に大きな課題をもたらすだけではありません。同時に、対象を絞った最適化や改善はおろか、サービスフレームワークのさまざまな指標について効果的な定量的診断を行うこともできません。この際、情報検索、サービス診断、データ分析などの機能を備えたリアルタイムログ監視システムの構築が特に重要です。

ELK (ELK Stack: ElasticSearch、LogStash、Kibana、Beats) は、オープンソースと高いパフォーマンスを備え、大手企業で広く使用されている成熟したログ ソリューションです。私たちのビジネスで使用されているサービス フレームワークはどのように ELK システムに接続されていますか?

ビジネスの背景

当社のビジネス フレームワークの背景:

  • ビジネス フレームワークは NodeJs をベースとした WebServer です

  • #このサービスは、winston ログ モジュールを使用してログをローカライズします

  • #サービスによって生成されたログは、それぞれのマシンのディスクに保存されます

  • #サービスは異なるリージョンにデプロイされます 複数のマシン
  • アクセス手順

フレームワーク全体を ELK に次の手順に簡単にまとめます:

    ログ構造設計: 従来のプレーン テキスト ログを構造化オブジェクトに変更し、JSON として出力します。
  • ログ収集: フレームワーク内のいくつかの主要なノードでログを出力します。リクエストのライフ サイクル
  • ES インデックス テンプレートの定義: JSON から ES の実際のストレージへのマッピングの確立
  • 1. ログ構造の設計

従来、ログ出力を行う際には、ログレベル(レベル)とログ内容文字列(メッセージ)を直接出力していました。ただし、いつ何が起こったかに注意を払うだけでなく、同様のログが何回発生したか、ログの詳細とコンテキスト、および関連するログにも注意を払う必要がある場合があります。したがって、単にログをオブジェクトに構造化するだけでなく、ログのキー フィールドも抽出します。

1. ログをイベントに抽象化します

各ログの発生をイベントとして抽象化します。イベントには以下が含まれます:

イベント メタフィールド

イベント発生時刻: 日付時刻、タイムスタンプ

イベント レベル: レベル (例: ERROR、INFO、WARNING、DEBUG##)

#イベント名:event、例: client-request

イベントが発生する相対時間 (単位: ナノ秒): reqLife、このフィールドはイベントが発生し始める時間 (間隔) です。リクエストに関連する

イベントが発生する場所: 行、コードの場所、サーバー、サーバーの場所

リクエスト メタ フィールド

リクエストの一意の ID: reqId、このフィールドはリクエスト全体を通して実行されますリンク上で発生するすべてのイベント

リクエスト ユーザー ID: reqUid、このフィールドはユーザー ID であり、ユーザーのアクセスまたはリンクのリクエストを追跡できます

データ フィールド

イベントのタイプが異なると、異なる出力詳細が必要になります。これらの詳細 (非メタ フィールド) を d -- data に入れます。これにより、イベント構造がより明確になり、同時にデータ フィールドがメタ フィールドを汚染するのを防ぎます。

例: 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"
}
ブラウザ、OS、エンジンなどのいくつかのフィールド 外層のログをできるだけ平らにしたい場合があるのはなぜですか (最大深さは 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 の log メソッド自体は文字列またはオブジェクトの受信メソッドをサポートしているため、 old 文字列が渡されると、フォーマッタが実際に受け取るのは { level: 'debug', message: 'checkLogin' } です。フォーマッタは、ログ出力の前に Winston のログ形式を調整するプロセスであり、この種の呼び出しメソッドによるログ出力を、ログ出力前に純粋な出力イベントに変換する機会を提供します。これを変更する必要はありません。呼び出しメソッド。

ログ出力形式を変換する

前述したように、winston はログを出力する前に、事前定義されたフォーマッタを通過するため、互換性のあるロジックの処理に加えて、いくつかの共通の機能を追加できます。ここでロジックを扱います。電話に関しては、私たちは現場そのものにのみ焦点を当てています。

    #メタ フィールドの抽出と処理
  • フィールド長の制御
  • 互換性のあるロジック処理
  • メタ フィールドを抽出する方法。これにはコンテキストの作成と使用が含まれます。ここでは、ドメインの作成と使用について簡単に説明します。
//--- 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

を出力することができ、イベントを相関させるという目的を達成できます。 2. ログの収集

イベントを出力する方法がわかったので、次のステップでは 2 つの質問について検討します。イベントをどこに出力すればよいでしょうか?

    イベントについてはどのような詳細を出力する必要がありますか?
  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 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はsegmentfault.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。