Der Inhalt dieses Artikels ist eine Zusammenfassung des Prozesses der Verbindung des Node-Frameworks mit ELK. Ich hoffe, dass er für Freunde hilfreich ist.
Wir alle haben die Erfahrung, Protokolle auf Maschinen zu überprüfen. Wenn die Anzahl der Cluster zunimmt, stellt uns die Ineffizienz, die dieser primitive Vorgang mit sich bringt, nicht nur vor große Herausforderungen Gleichzeitig sind wir nicht in der Lage, eine effektive quantitative Diagnose verschiedener Indikatoren unseres Dienstleistungsrahmens durchzuführen, geschweige denn eine gezielte Optimierung und Verbesserung. Zu diesem Zeitpunkt ist es besonders wichtig, ein Echtzeit-Protokollüberwachungssystem mit Funktionen wie Informationssuche, Servicediagnose und Datenanalyse aufzubauen.
ELK (ELK Stack: ElasticSearch, LogStash, Kibana, Beats) ist eine ausgereifte Protokollierungslösung, die aufgrund ihrer Open Source und hohen Leistung in großen Unternehmen weit verbreitet ist. Wie verbindet sich das von unserem Unternehmen verwendete Service-Framework mit dem ELK-System?
Unser Business-Framework-Hintergrund:
Das Business-Framework ist WebServer basierend auf NodeJs
Der Dienst verwendet das Winston-Protokollmodul, um die Protokolle zu lokalisieren
Die vom Dienst generierten Protokolle werden auf den Festplatten der jeweiligen Maschinen gespeichert
Die Dienste werden in verschiedenen Regionen bereitgestellt. Mehrere Maschinen
Wir fassen das gesamte Framework zu ELK einfach in den folgenden Schritten zusammen:
Protokollstrukturdesign: Wandeln Sie herkömmliche Klartextprotokolle in strukturierte Objekte um und geben Sie sie als JSON aus.
Protokollsammlung: Protokolle an einigen Schlüsselknoten im Lebenszyklus der Framework-Anforderung ausgeben
Definition der ES-Indexvorlage: Erstellen einer Zuordnung von JSON zum tatsächlichen ES-Speicher
Traditionell , wir sind Bei der Protokollausgabe werden die Protokollebene (Ebene) und die Protokollinhaltszeichenfolge (Nachricht) direkt ausgegeben. Wir achten jedoch nicht nur darauf, wann und was passiert ist, sondern müssen möglicherweise auch darauf achten, wie oft ähnliche Protokolle aufgetreten sind, auf die Details und den Kontext der Protokolle sowie auf die zugehörigen Protokolle. Deshalb strukturieren wir unsere Protokolle nicht nur einfach in Objekte, sondern extrahieren auch die Schlüsselfelder der Protokolle.
Wir abstrahieren das Auftreten jedes Protokolls als Ereignis. Das Ereignis enthält:
Ereignisauftrittszeit: Datum/Uhrzeit, Zeitstempel
Ereignisebene: Ebene, zum Beispiel: ERROR, INFO, WARNING, DEBUG
Ereignisname: Ereignis, Beispiel: Kundenanfrage
Relative Zeit, zu der das Ereignis auftritt (Einheit: Nanosekunde): reqLife, dieses Feld ist die Zeit (Intervall), zu der das Ereignis auftritt relativ zur Anfrage
Der Ort, an dem das Ereignis auftritt: Zeile, Code-Server, der Standort des Servers
Eindeutige ID der Anforderung: reqId, dieses Feld durchläuft die gesamte Anforderung. Alle Ereignisse, die auf dem Link auftreten
Benutzer-ID der Anforderung: reqUid, dieses Feld ist die Benutzer-ID, die den Zugriff des Benutzers oder den Anforderungslink verfolgen kann
Verschiedene Arten von Ereignissen erfordern unterschiedliche Ausgabedetails. Wir fügen diese Details (Nicht-Meta-Felder) in d – Daten ein. Dies macht unsere Event-Struktur klarer und verhindert gleichzeitig, dass Datenfelder Metafelder verunreinigen.
z. B. ein Client-Init-Ereignis, dieses Ereignis wird jedes Mal gedruckt, wenn der Server eine Benutzeranfrage empfängt. Wir klassifizieren die IP-Adresse, die URL und andere Ereignisse des Benutzers eindeutig in Datenfelder und fügen sie in das d-Objekt ein
Geben Sie ein vollständiges Beispiel
{ "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" }
Einige Felder, wie zum Beispiel: Browser, Betriebssystem, Engine Warum möchten wir manchmal, dass das Protokoll in der äußeren Ebene so flach wie möglich ist (maximale Tiefe beträgt 2 ), um ES unnötigen Leistungsverlust durch die Indizierung zu vermeiden. In der tatsächlichen Ausgabe geben wir Werte mit einer Tiefe von mehr als 1 als Zeichenfolgen aus. Manchmal sind einige Objektfelder für uns von Belang, daher platzieren wir diese speziellen Felder in der äußeren Ebene, um sicherzustellen, dass die Ausgabetiefe nicht größer als 2 ist.
Im Allgemeinen müssen wir beim Ausdrucken des Protokolls nur auf 事件名称
und 数据字段
achten. Darüber hinaus können wir durch den Zugriff auf den Kontext in der Methode zum Drucken von Protokollen eine einheitliche Erfassung, Berechnung und Ausgabe durchführen.
Wir haben bereits erwähnt, wie ein Protokollereignis definiert wird. Wie können wir also ein Upgrade basierend auf der vorhandenen Protokolllösung durchführen und gleichzeitig mit der Protokollaufrufmethode des alten Codes kompatibel sein? .
Transformieren Sie das ProtokollausgabeformatWie bereits erwähnt, durchläuft Winston das Protokoll vor der Ausgabe durch unseren vordefinierten Formatierer, sodass wir zusätzlich zur Verarbeitung kompatibler Logik einige Gemeinsamkeiten angeben können Logik hier. Was den Anruf betrifft, konzentrieren wir uns nur auf das Feld selbst. Metafeldextraktion und -verarbeitung
// 改造前 logger.info('client-init => ' + JSON.stringfiy({ url, ip, browser, //... })); // 改造后 logger.info({ event: 'client-init', url, ip, browser, //... });
2. Protokollsammlung
Da wir nun wissen, wie ein Ereignis ausgegeben wird, sollten wir im nächsten Schritt zwei Fragen berücksichtigen: reqId
Dann können wir unser Ereignis wie folgt definieren:
Benutzeranforderung
client-init: Drucken Sie die vom Benutzer erhaltene Anforderung aus Frame (ungeparst), einschließlich: Anforderungsadresse, Anforderungsheader, HTTP-Version und -Methode, Benutzer-IP und Browser
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 快速的检索我们的日志。
Das obige ist der detaillierte Inhalt vonZusammenfassung des Prozesses zum Verbinden des Node-Frameworks mit ELK. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!