すべてのプロダクト
Search
ドキュメントセンター

Simple Log Service:複雑な JSON データを変換する

最終更新日:Nov 09, 2025

このドキュメントでは、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"
             }
         ]
    }
  • データ変換の要件

    1. topic フィールドに基づいて、生のログを overall_typeclient_statusserver_status の 3 つのログに分割します。

    2. 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
  • ソリューション

    1. ログを 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...
      }
    2. 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_service
    3. Topic が overall_type のログについて、client_countserver_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
    4. 不要なフィールドを削除します。

      e_if(e_search("__topic__==overall_type"), e_drop_fields("clients", "servers"))
    5. 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
    6. 保持する関連フィールド:

      e_if(e_search("__topic__==overall_type"), e_drop_fields("servers"))
    7. 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
    8. 完全な 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.serverscontent.clients が空の場合に問題があります。たとえば、次の生ログを考えてみましょう。

__source__:  192.0.2.1
__topic__:  
content:{
            "service": "search_service",
            "overal_status": "yellow",
            "servers": [ ],
            "clients": [ ]
}

前述のソリューションを使用してこの生ログを 3 つのログに分割すると、Topic が client_statusserver_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_statusclient_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  
    
    ...その他のログ...
  • ソリューション

    1. 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
    2. 次に、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
    3. 最後に、無関係なフィールドを削除します。

      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
    4. 完全な 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")

まとめ: 同様の要件については、まずログを分割し、次にデータを展開し、最後に無関係なフィールドを削除します。