ホームページ > 記事 > ウェブフロントエンド > NodeフレームワークをELKに接続するプロセスの概要
この記事の内容は、Node フレームワークを ELK に接続するプロセスをまとめたものです。必要な方は参考にしていただければ幸いです。
クラスタの数が増えると、この原始的な操作によってもたらされる非効率性が、既存のネットワークの問題を特定する際に大きな課題をもたらすだけではありません。同時に、対象を絞った最適化や改善はおろか、サービスフレームワークのさまざまな指標について効果的な定量的診断を行うこともできません。この際、情報検索、サービス診断、データ分析などの機能を備えたリアルタイムログ監視システムの構築が特に重要です。
ELK (ELK Stack: ElasticSearch、LogStash、Kibana、Beats) は、オープンソースと高いパフォーマンスを備え、大手企業で広く使用されている成熟したログ ソリューションです。私たちのビジネスで使用されているサービス フレームワークはどのように ELK システムに接続されていますか?
当社のビジネス フレームワークの背景:
ビジネス フレームワークは NodeJs をベースとした WebServer です
#このサービスは、winston ログ モジュールを使用してログをローカライズします
1. ログをイベントに抽象化します
イベント メタフィールド
イベント発生時刻: 日付時刻、タイムスタンプイベント レベル: レベル (例: ERROR、INFO、WARNING、DEBUG##)
#イベント名:event、例: client-requestイベントが発生する相対時間 (単位: ナノ秒): reqLife、このフィールドはイベントが発生し始める時間 (間隔) です。リクエストに関連するイベントが発生する場所: 行、コードの場所、サーバー、サーバーの場所
リクエスト メタ フィールド
イベントのタイプが異なると、異なる出力詳細が必要になります。これらの詳細 (非メタ フィールド) を 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 を超えないようにするために、これらの特別なフィールドを外層に配置します。 通常、ログを出力するときは、
イベント名と
データ フィールドに注意するだけで済みます。また、ログを出力する方法ではコンテキストにアクセスすることで一律に取得、計算、出力することができます。
// 改造前 logger.info('client-init => ' + JSON.stringfiy({ url, ip, browser, //... })); // 改造后 logger.info({ event: 'client-init', url, ip, browser, //... });
logger.debug('checkLogin');
//--- 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. ログの収集
以下のフローチャートに示すように、一般的なリクエスト リンク (ユーザー リクエスト、サービス側の受信リクエスト、下流サーバー/データベースへのサービス リクエスト (*複数回)、データ集約レンダリング、サービス レスポンス) と組み合わせる
http-start: 打印于请求下游起始:请求地址,请求包体,模块别名(方便基于名字聚合而且域名)
http-success: 打印于请求返回 200:请求地址,请求包体,响应包体(code & msg & data),耗时
http-error: 打印于请求返回非 200,亦即连接服务器失败:请求地址,请求包体,响应包体(code & message & stack),耗时。
http-timeout: 打印于请求连接超时:请求地址,请求包体,响应包体(code & msg & stack),耗时。
字段这么多,该怎么选择? 一言以蔽之,事件输出的字段原则就是:输出你关注的,方便检索的,方便后期聚合的字段。请求下游的请求体和返回体有固定格式, e.g. 输入:{ action: 'getUserInfo', payload: {} } 输出: { code: 0, msg: '', data: {}} 我们可以在事件输出 action,code 等,以便后期通过 action 检索某模块具体某个接口的各项指标和聚合。
保证输出字段类型一致 由于所有事件都存储在同一个 ES 索引, 因此,相同字段不管是相同事件还是不同事件,都应该保持一致,例如:code不应该既是数字,又是字符串,这样可能会产生字段冲突,导致某些记录(document)无法被冲突字段检索到。
ES 存储类型为 keyword, 不应该超过 ES mapping 设定的 ignore_above 中指定的字节数(默认4096个字节)。否则同样可能会产生无法被检索的情况
这里引入 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 サイトの他の関連記事を参照してください。