結構化日誌背後的想法很簡單:讓應用程序直接編寫 JSON 對象,而不是讓應用程序將需要通過正則表達式解析的日誌寫入到你索引到 Elasticsearch 的 JSON 對象中。
舉例來說,假設你正在編寫 Python Web 應用程序,並且正在使用標準庫進行記錄。 用戶登錄後,你可能會使用如下所示的日誌記錄語句:
createlogs.py
import logging
user = {
"name": "liuxg",
"id": "1"
}
session_id = "91e5b9d"
logging.basicConfig( filename="test.log", level=logging.DEBUG )
logging.debug( "User '{}' (id: {}) successfully logged in. Session id: {}"
.format(user["name"], user["id"], session_id) )
logging.debug("User '{}' (id: {}) changed state to verified."
.format(user["name"], user["id"]))
上面的 python 應用將生成如下的日誌信息:
DEBUG:root:User 'liuxg' (id: 1) successfully logged in. Session id: 91e5b9d
DEBUG:root:User 'liuxg' (id: 1) changed state to verified.
自從 printf 以來,這種日誌記錄方法就很流行。那時,你通常只有一臺服務器,並且會拖尾並 grep 日誌文件,一切都很好。
但是,時代已經變了,如今,你很有可能擁有數十,數百或數千個服務器/虛擬機/容器來創建大量日誌,因此你將它們集中在 Elasticsearch 中並使用其神奇的功能(這就是它的感受)搜索和彙總功能以在它們之間進行導航。
只有把上面的數據進行結構化處理,才能在 Elasticsearch 中發揮更好的效效果,因此你可以在各個字段上進行搜索和彙總。因此,在建立索引之前,你通常會使用 Logstash 出色的 Grok 過濾器將應用程序日誌解析爲JSON對象,但這意味着您必須編寫和維護Grok 模式並花費 CPU 週期來進行解析。當然你也可以使用 Filebeat 結合 pipeline processors 來完成這項工作。對於還不是很熟悉的開發者來說,請參閱我之前的文字:
現在,讓我們嘗試使用結構化日誌記錄的相同示例。儘管通常不在標準庫中,但是所有主要的編程語言都具有使結構化日誌記錄變得容易的庫。 James Turnbull在他的博客文章中創建了一個列表,其中還詳細介紹瞭如何爲Rails應用程序執行此操作。在Python中,有一個 structlog 庫,我們將在這裏使用它。
我們可以使用如下的方法來進行安裝:
pip2 install structlog
如果你想在開發環境中看到彩色的輸出,那麼你可以使用如下的方法進行安裝:
pip2 install structlog colorama
我們使用同樣分方法,來進行生產日誌:
createlogs_1.py
import logging
import structlog
user = {
"name": "liuxg",
"id": "1"
}
session_id = "91e5b9d"
log = structlog.get_logger()
log = log.bind(user='arthur', id=42, verified=False)
log.msg('logged_in')
log.msg('changed_state', verified=True)
上面的應用將生成如下的日誌:
2020-06-11 14:50.41 logged_in id=42 user=arthur verified=False
2020-06-11 14:50.41 changed_state id=42 user=arthur verified=True
需要注意的一件事是,代碼的重複性較低,它鼓勵開發人員包括所有數據,而不是僅在編寫代碼時才包含重要的數據。 還要注意,以這種格式,對於開發人員來說,日誌行仍然相當容易遵循。 但是,在投入生產時,使用 JSON 渲染器會更有意義:
createlogs_2.py
import logging
import structlog
from structlog import wrap_logger, PrintLogger, wrap_logger
from structlog.processors import JSONRenderer
file = open('json_logs', 'w')
log = wrap_logger(PrintLogger(file), processors=[JSONRenderer()])
log = log.bind(user_name='arthur', id=42, verified=False)
log.msg('logged_in')
log.msg('changed_state', verified=True)
file.close
上面的應用運行後,生產一個叫做 json_logs 的文件:
{"user_name": "arthur", "id": 42, "verified": false, "event": "logged_in"}
{"user_name": "arthur", "id": 42, "verified": true, "event": "changed_state"}
這中 JSON 格式的文件人眼難以理解,但是具有的優點是,數據已經按照 Elasticsearch 喜歡的格式進行了結構化。
Filebeat 是一個用Go語言編寫的開源日誌傳送器,可以將日誌行發送到Logstash和Elasticsearch。 它提供了“至少一次”保證的數據傳輸,因此你永遠不會丟失日誌行,並且它使用了背壓敏感協議,因此不會使你的管道過載。 還包括基本過濾和多行關聯。
如果你的日誌就像上面的示例一樣,Filebeat 每行存儲一個JSON對象,它還可以本地解碼 JSON 對象。
這是一個示例配置文件,該文件配置Filebeat來拾取文件並將 JSON 對象發送到 Elasticsearch:
filebeat_json.yml
filebeat.inputs:
- type: log
enabled: true
tags: ["i", "love", "json"]
json.message_key: event
json.keys_under_root: true
json.add_error_key: true
fields:
planet: liuxg
paths:
- /Users/liuxg/python/logs/json_logs
output.elasticsearch:
hosts: ["localhost:9200"]
index: "json_logs1"
setup.ilm.enabled: false
setup.template.name: json_logs1
setup.template.pattern: json_logs1
在運行 Filebeat 之前,我們可以在 Kibana 中執行如下的命令:
PUT json_logs1
{
"mappings": {
"properties": {
"event": {
"type": "keyword"
},
"id": {
"type": "long"
},
"user_name": {
"type": "keyword"
},
"verified": {
"type": "boolean"
}
}
}
}
在這裏,我們定義這個索引的 mapping。
我們接着執行運行 Filebeat:
./filebeat -e -c ~/python/logs/filebeat_json.yml
那麼在我們的 Kibana 中,我們可以查詢到最新生成的文檔:
GET json_logs1/_search
{
"took" : 0,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 2,
"relation" : "eq"
},
"max_score" : 1.0,
"hits" : [
{
"_index" : "json_logs1",
"_type" : "_doc",
"_id" : "uw-jonIBB4HethT_mOIZ",
"_score" : 1.0,
"_source" : {
"@timestamp" : "2020-06-11T09:08:48.550Z",
"user_name" : "arthur",
"verified" : false,
"tags" : [
"i",
"love",
"json"
],
"fields" : {
"planet" : "liuxg"
},
"ecs" : {
"version" : "1.5.0"
},
"agent" : {
"ephemeral_id" : "0c9b96dd-76c8-45c5-96ef-00859f9e12dc",
"hostname" : "liuxg",
"id" : "be15712c-94be-41f4-9974-0b049dc95750",
"version" : "7.7.0",
"type" : "filebeat"
},
"id" : 42,
"event" : "logged_in",
"log" : {
"offset" : 0,
"file" : {
"path" : "/Users/liuxg/python/logs/json_logs"
}
},
"input" : {
"type" : "log"
},
"host" : {
"name" : "liuxg"
}
}
},
{
"_index" : "json_logs1",
"_type" : "_doc",
"_id" : "vA-jonIBB4HethT_mOIZ",
"_score" : 1.0,
"_source" : {
"@timestamp" : "2020-06-11T09:08:48.551Z",
"event" : "changed_state",
"input" : {
"type" : "log"
},
"log" : {
"offset" : 75,
"file" : {
"path" : "/Users/liuxg/python/logs/json_logs"
}
},
"user_name" : "arthur",
"id" : 42,
"verified" : true,
"tags" : [
"i",
"love",
"json"
],
"fields" : {
"planet" : "liuxg"
},
"ecs" : {
"version" : "1.5.0"
},
"host" : {
"name" : "liuxg"
},
"agent" : {
"version" : "7.7.0",
"type" : "filebeat",
"ephemeral_id" : "0c9b96dd-76c8-45c5-96ef-00859f9e12dc",
"hostname" : "liuxg",
"id" : "be15712c-94be-41f4-9974-0b049dc95750"
}
}
}
]
}
}
如你所見,Filebeat 自動添加一個時間戳。 請注意,這是讀取日誌行的時間,可能與應用程序寫入日誌行的時間不同。 如果需要更好的準確性,可以設置 structlog 庫以生成時間戳。
Filebeat 還會自動添加一些元數據(例如主機名),並使其易於通過配置文件添加自定義字段和標籤。 這意味着應用程序不必擔心從環境中添加元數據。
這就是你所需要的。 簡單的事情應該很簡單:-)