このドキュメントでは、Simple Log Service のデータ変換機能を使用して複雑な JSON データを変換する方法について説明します。
配列として複数のサブキーを持つ複雑な JSON データを変換する
プログラムからのログは、多くの場合、統計的な JSON フォーマットで書き込まれます。これらのログには通常、基本情報と配列である複数のサブキーが含まれます。たとえば、サーバーは毎分ログを書き込みます。ログには、現在のステータスと、関連するサーバーおよびクライアントノードに関する統計情報が含まれています。
ログのサンプル
__source__: 192.0.2.1 __topic__: content:{ "service": "search_service", "overal_status": "yellow", "servers": [ { "host": "192.0.2.1", "status": "green" }, { "host": "192.0.2.2", "status": "green" } ], "clients": [ { "host": "192.0.2.3", "status": "green" }, { "host": "192.0.2.4", "status": "red" } ] }データ変換の要件
topicフィールドに基づいて、生のログをoverall_type、client_status、server_statusの 3 つのログに分割します。topicの値ごとに異なる情報を保存します。overall_type: サーバー数、クライアント数、overall_status の色、およびサービス情報を保持します。client_status: ホストアドレス、ステータス、およびサービス情報を保持します。server_status: ホストアドレス、ステータス、およびサービス情報を保持します。
期待される結果
__source__: 192.0.2.1 __topic__: overall_type client_count: 2 overal_status: yellow server_count: 2 service: search_service __source__: 192.0.2.1 __topic__: client_status host: 192.0.2.4 status: red service: search_service __source__: 192.0.2.1 __topic__: client_status host: 192.0.2.3 status: green service: search_service __source__: 192.0.2.1 __topic__: server_status host: 192.0.2.1 status: green service: search_service __source__: 192.0.2.1 __topic__: server_status host: 192.0.2.2 status: green service: search_serviceソリューション
ログを 3 つの個別のログに分割します。これを行うには、topic フィールドに 3 つの異なる値を割り当てます。分割後、
topicフィールド以外は同一の 3 つのログが作成されます。e_set("__topic__", "server_status,client_status,overall_type") e_split("__topic__")処理後のログフォーマットは次のとおりです。
__source__: 192.0.2.1 __topic__: server_status // 他の 2 つのログの Topic は `client_status` と `overall_type` です。残りのフィールドは同じです。 content: { ...As before... }contentフィールドの第 1 レイヤーの JSON コンテンツを展開し、contentフィールドを削除します。e_json('content',depth=1) e_drop_fields("content")処理後のログフォーマットは次のとおりです。
__source__: 192.0.2.1 __topic__: overall_type // 他の 2 つのログの Topic は `client_status` と `overall_type` です。残りのフィールドは同じです。 clients: [{"host": "192.0.2.3", "status": "green"}, {"host": "192.0.2.4", "status": "red"}] overal_status: yellow servers: [{"host": "192.0.2.1", "status": "green"}, {"host": "192.0.2.2", "status": "green"}] service: search_serviceTopic が
overall_typeのログについて、client_countとserver_countの値を計算します。e_if(e_search("__topic__==overall_type"), e_compose( e_set("client_count", json_select(v("clients"), "length([*])", default=0)), e_set("server_count", json_select(v("servers"), "length([*])", default=0)) ))処理されたログは次のとおりです。
__topic__: overall_type server_count: 2 client_count: 2不要なフィールドを削除します。
e_if(e_search("__topic__==overall_type"), e_drop_fields("clients", "servers"))Topic が
server_statusのログをさらに分割します。e_if(e_search("__topic__==server_status"), e_compose( e_split("servers"), e_json("servers", depth=1) ))ログは次の 2 つのログに分割されます。
__topic__: server_status servers: {"host": "192.0.2.1", "status": "green"} host: 192.0.2.1 status: green__topic__: server_status servers: {"host": "192.0.2.2", "status": "green"} host: 192.0.2.2 status: green保持する関連フィールド:
e_if(e_search("__topic__==overall_type"), e_drop_fields("servers"))Topic が
client_statusのログをさらに分割し、`clients` フィールドを削除できます。e_if(e_search("__topic__==client_status"), e_compose( e_split("clients"), e_json("clients", depth=1), e_drop_fields("clients") ))ログは次の 2 つのログに分割されます。
__topic__: client_status host: 192.0.2.3 status: green__topic__: clients host: 192.0.2.4 status: red完全な LOG ドメイン固有言語 (DSL) ルールは次のとおりです。
# ログを分割します。 e_set("__topic__", "server_status,client_status,overall_type") e_split("__topic__") e_json('content',depth=1) e_drop_fields("content") # overall_type ログを処理します。 e_if(e_search("__topic__==overall_type"), e_compose( e_set("client_count", json_select(v("clients"), "length([*])", default=0)), e_set("server_count", json_select(v("servers"), "length([*])", default=0)) )) # server_status ログを処理します。 e_if(e_search("__topic__==server_status"), e_compose( e_split("servers"), e_json("servers", depth=1) )) e_if(e_search("__topic__==overall_type"), e_drop_fields("servers")) # client_status ログを処理します。 e_if(e_search("__topic__==client_status"), e_compose( e_split("clients"), e_json("clients", depth=1), e_drop_fields("clients") ))
ソリューションの最適化
前述のソリューションには、content.servers と content.clients が空の場合に問題があります。たとえば、次の生ログを考えてみましょう。
__source__: 192.0.2.1
__topic__:
content:{
"service": "search_service",
"overal_status": "yellow",
"servers": [ ],
"clients": [ ]
}前述のソリューションを使用してこの生ログを 3 つのログに分割すると、Topic が client_status と server_status のログは空になります。
__source__: 192.0.2.1
__topic__: overall_type
client_count: 0
overal_status: yellow
server_count: 0
service: search_service
__source__: 192.0.2.1
__topic__: client_status
service: search_service
__source__: 192.0.2.1
__topic__: server_status
host: 192.0.2.1
status: green
service: search_serviceソリューション 1
最初の分割後、Topic が
server_statusとclient_statusのログが空であるかどうかを確認します。空の場合は、それらを破棄します。# server_status の場合: 空の場合は破棄し、そうでない場合は保持します。 e_keep(op_and(e_search("__topic__==server_status"), json_select(v("servers"), "length([*])"))) # client_status の場合: 空の場合は破棄し、そうでない場合は保持します。 e_keep(op_and(e_search("__topic__==client_status"), json_select(v("clients"), "length([*])")))完全な LOG DSL ルールは次のとおりです。
# ログを分割します。 e_set("__topic__", "server_status,client_status,overall_type") e_split("__topic__") e_json('content',depth=1) e_drop_fields("content") # overall_type ログを処理します。 e_if(e_search("__topic__==overall_type"), e_compose( e_set("client_count", json_select(v("clients"), "length([*])", default=0)), e_set("server_count", json_select(v("servers"), "length([*])", default=0)) )) # 新規: server_status の前処理: 空の場合は破棄し、そうでない場合は保持します。 e_keep(op_and(e_search("__topic__==server_status"), json_select(v("servers"), "length([*])"))) # server_status ログを処理します。 e_if(e_search("__topic__==server_status"), e_compose( e_split("servers"), e_json("servers", depth=1) )) e_if(e_search("__topic__==overall_type"), e_drop_fields("servers")) # 新規: client_status の前処理: 空の場合は破棄し、そうでない場合は保持します。 e_keep(op_and(e_search("__topic__==client_status"), json_select(v("clients"), "length([*])"))) # client_status ログを処理します。 e_if(e_search("__topic__==client_status"), e_compose( e_split("clients"), e_json("clients", depth=1), e_drop_fields("clients") ))ソリューション 2
ログを分割する前にフィールドが空かどうかを確認します。フィールドが空でない場合は、フィールドに基づいてログを分割します。
# 初期の Topic を設定します。 e_set("__topic__", "server_status") # content.servers フィールドが空でない場合、ログを分割して Topic が server_status のログを作成します。 e_if(json_select(v("content"), "length(servers[*])"), e_compose( e_set("__topic__", "server_status,overall_type"), e_split("__topic__") )) # content.clients フィールドが空でない場合、ログをさらに分割して Topic が client_status のログを作成します。 e_if(op_and(e_search("__topic__==overall_type"), json_select(v("content"), "length(clients[*])")), e_compose( e_set("__topic__", "client_status,overall_type"), e_split("__topic__") ))完全な LOG DSL ルールは次のとおりです。
# ログを分割します。 e_set("__topic__", "server_status") # content.servers フィールドが空でない場合、ログを分割して Topic が server_status のログを作成します。 e_if(json_select(v("content"), "length(servers[*])"), e_compose( e_set("__topic__", "server_status,overall_type"), e_split("__topic__") )) # content.clients フィールドが空でない場合、ログをさらに分割して Topic が client_status のログを作成します。 e_if(op_and(e_search("__topic__==overall_type"), json_select(v("content"), "length(clients[*])")), e_compose( e_set("__topic__", "client_status,overall_type"), e_split("__topic__") )) # overall_type ログを処理します。 e_if(e_search("__topic__==overall_type"), e_compose( e_set("client_count", json_select(v("clients"), "length([*])", default=0)), e_set("server_count", json_select(v("servers"), "length([*])", default=0)) )) # server_status ログを処理します。 e_if(e_search("__topic__==server_status"), e_compose( e_split("servers"), e_json("servers", depth=1) )) e_if(e_search("__topic__==overall_type"), e_drop_fields("servers")) # client_status ログを処理します。 e_if(e_search("__topic__==client_status"), e_compose( e_split("clients"), e_json("clients", depth=1), e_drop_fields("clients") ))
ソリューションの比較
ソリューション 1 は、生のログから空のログを作成してから削除するため、論理的に冗長です。ただし、ルールはシンプルで保守が容易です。このソリューションは、推奨されるデフォルトです。
ソリューション 2 は、分割前に空のフィールドをチェックするため、より効率的です。ただし、ルールはわずかに複雑です。このソリューションは、最初の分割で多くの余分なイベントが生成される可能性がある場合など、特定のシナリオでのみ推奨されます。
多層ネストされた配列オブジェクトを持つ複雑な JSON データを変換する
この例では、多層ネストされた配列を含む複雑なオブジェクトを処理する方法を示します。users 配列内の各オブジェクトの login_histories 配列内の各ログインイベントを、個別のログインイベントに分割することが目標です。
生ログ
__source__: 192.0.2.1 __topic__: content:{ "users": [ { "name": "user1", "login_histories": [ { "date": "2019-10-10 0:0:0", "login_ip": "192.0.2.6" }, { "date": "2019-10-10 1:0:0", "login_ip": "192.0.2.6" }, { ...その他のログイン情報... } ] }, { "name": "user2", "login_histories": [ { "date": "2019-10-11 0:0:0", "login_ip": "192.0.2.7" }, { "date": "2019-10-11 1:0:0", "login_ip": "192.0.2.9" }, { ...その他のログイン情報... } ] }, { ...その他のユーザー... } ] }分割後に期待されるログ
__source__: 192.0.2.1 name: user1 date: 2019-10-11 1:0:0 login_ip: 192.0.2.6 __source__: 192.0.2.1 name: user1 date: 2019-10-11 0:0:0 login_ip: 192.0.2.6 __source__: 192.0.2.1 name: user2 date: 2019-10-11 0:0:0 login_ip: 192.0.2.7 __source__: 192.0.2.1 name: user2 date: 2019-10-11 1:0:0 login_ip: 192.0.2.9 ...その他のログ...ソリューション
contentフィールドのusersに基づいてログを分割および展開します。e_split("content", jmes='users[*]', output='item') e_json("item",depth=1)処理されたログは次のとおりです。
__source__: 192.0.2.1 __topic__: content:{...Same as that in the raw log...} item: {"name": "user1", "login_histories": [{"date": "2019-10-10 0:0:0", "login_ip": "192.0.2.6"}, {"date": "2019-10-10 1:0:0", "login_ip": "192.0.2.6"}]} login_histories: [{"date": "2019-10-10 0:0:0", "login_ip": "192.0.2.6"}, {"date": "2019-10-10 1:0:0", "login_ip": "192.0.2.6"}] name: user1 __source__: 192.0.2.1 __topic__: content:{...Same as that in the raw log...} item: {"name": "user2", "login_histories": [{"date": "2019-10-11 0:0:0", "login_ip": "192.0.2.7"}, {"date": "2019-10-11 1:0:0", "login_ip": "192.0.2.9"}]} login_histories: [{"date": "2019-10-11 0:0:0", "login_ip": "192.0.2.7"}, {"date": "2019-10-11 1:0:0", "login_ip": "192.0.2.9"}] name: user2次に、
login_historiesに基づいてデータを分割し、展開します。e_split("login_histories") e_json("login_histories", depth=1)処理されたログは次のとおりです。
__source__: 192.0.2.1 __topic__: content: {...Same as that in the raw log...} date: 2019-10-11 0:0:0 item: {"name": "user2", "login_histories": [{"date": "2019-10-11 0:0:0", "login_ip": "192.0.2.7"}, {"date": "2019-10-11 1:0:0", "login_ip": "192.0.2.9"}]} login_histories: {"date": "2019-10-11 0:0:0", "login_ip": "192.0.2.7"} login_ip: 192.0.2.7 name: user2 __source__: 192.0.2.1 __topic__: content: {...Same as that in the raw log...} date: 2019-10-11 1:0:0 item: {"name": "user2", "login_histories": [{"date": "2019-10-11 0:0:0", "login_ip": "192.0.2.7"}, {"date": "2019-10-11 1:0:0", "login_ip": "192.0.2.9"}]} login_histories: {"date": "2019-10-11 1:0:0", "login_ip": "192.0.2.9"} login_ip: 192.0.2.9 name: user2 __source__: 192.0.2.1 __topic__: content: {...Same as that in the raw log...} date: 2019-10-10 1:0:0 item: {"name": "user1", "login_histories": [{"date": "2019-10-10 0:0:0", "login_ip": "192.0.2.6"}, {"date": "2019-10-10 1:0:0", "login_ip": "192.0.2.6"}]} login_histories: {"date": "2019-10-10 1:0:0", "login_ip": "192.0.2.6"} login_ip: 192.0.2.6 name: user1 __source__: 192.0.2.1 __topic__: content: {...Same as that in the raw log...} date: 2019-10-10 0:0:0 item: {"name": "user1", "login_histories": [{"date": "2019-10-10 0:0:0", "login_ip": "192.0.2.6"}, {"date": "2019-10-10 1:0:0", "login_ip": "192.0.2.6"}]} login_histories: {"date": "2019-10-10 0:0:0", "login_ip": "192.0.2.6"} login_ip: 192.0.2.6 name: user1最後に、無関係なフィールドを削除します。
e_drop_fields("content", "item", "login_histories")処理されたログは次のとおりです。
__source__: 192.0.2.1 __topic__: name: user1 date: 2019-10-11 1:0:0 login_ip: 192.0.2.6 __source__: 192.0.2.1 __topic__: name: user1 date: 2019-10-11 0:0:0 login_ip: 192.0.2.6 __source__: 192.0.2.1 __topic__: name: user2 date: 2019-10-11 0:0:0 login_ip: 192.0.2.7 __source__: 192.0.2.1 __topic__: name: user2 date: 2019-10-11 1:0:0 login_ip: 192.0.2.9完全な LOG DSL ルールは次のように記述できます。
e_split("content", jmes='users[*]', output='item') e_json("item",depth=1) e_split("login_histories") e_json("login_histories", depth=1) e_drop_fields("content", "item", "login_histories")
まとめ: 同様の要件については、まずログを分割し、次にデータを展開し、最後に無関係なフィールドを削除します。