Todos los productos
Search
Centro de documentos

Simple Log Service:Transformar datos JSON complejos

Última actualización: Sep 02, 2026

Este documento describe cómo utilizar la función de transformación de datos de Simple Log Service (SLS) para transformar datos JSON complejos.

Transformar datos JSON complejos que tienen varias subclaves como matrices

Los registros de los programas suelen escribirse en un formato JSON estadístico. Estos registros generalmente incluyen información básica y varias subclaves que son matrices. Por ejemplo, un servidor escribe un registro cada minuto. El registro contiene el estado actual e información estadística sobre los nodos relacionados del servidor y del cliente.

  • Registro de ejemplo

    __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"
             }
         ]
    }
  • Requisitos de transformación de datos

    1. Divida el registro sin procesar en tres registros según el campo topic: overall_type, client_status y server_status.

    2. Almacene información diferente para distintos valores de topic.

      • overall_type: Conserve el recuento de servidores, el recuento de clientes, el color de overall_status y la información del servicio.

      • client_status: Conserve la dirección del host, el estado y la información del servicio.

      • server_status: Conserve la dirección del host, el estado y la información del servicio.

  • Resultado esperado

    __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
  • Solución

    1. Divida el registro en tres registros separados. Para ello, asigne tres valores diferentes al campo topic. Después de la división, tendrá tres registros idénticos excepto por el campo topic.

      e_set("__topic__", "server_status,client_status,overall_type")
      e_split("__topic__")

      El formato del registro después del procesamiento es el siguiente:

      __source__:  192.0.2.1
      __topic__:  server_status         // The other two logs have `client_status` and `overall_type` as topics. The rest of the fields are the same.
      content:  {
          ...As before...
      }
    2. Expanda el contenido JSON de primer nivel del campo content y luego elimine el campo content.

      e_json('content',depth=1)
      e_drop_fields("content")

      El formato del registro después del procesamiento es el siguiente:

      __source__:  192.0.2.1
      __topic__:  overall_type              // The other two logs have `client_status` and `overall_type` as topics. The rest of the fields are the same.
      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. Para el registro con el tema overall_type, calcule los valores de client_count y 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))
        ))

      El registro procesado es:

      __topic__:  overall_type
      server_count:  2
      client_count:  2
    4. Elimine los campos innecesarios.

      e_if(e_search("__topic__==overall_type"), e_drop_fields("clients", "servers"))
    5. Divida aún más el registro con el tema server_status.

      e_if(e_search("__topic__==server_status"), 
           e_compose(
              e_split("servers"), 
              e_json("servers", depth=1)
        ))

      El registro se divide en los siguientes dos registros:

      __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. Campos relevantes a conservar:

      e_if(e_search("__topic__==overall_type"), e_drop_fields("servers"))
    7. Divida aún más el registro con el tema client_status y luego elimine el campo clients.

      e_if(e_search("__topic__==client_status"), 
           e_compose(
              e_split("clients"), 
              e_json("clients", depth=1),
              e_drop_fields("clients")
        ))

      El registro se divide en los siguientes dos registros:

      __topic__:  client_status
      host: 192.0.2.3
      status: green
      __topic__:  clients
      host: 192.0.2.4
      status: red
    8. Las reglas completas del lenguaje específico de dominio (DSL) de LOG son las siguientes:

      # Split the log.
      e_set("__topic__", "server_status,client_status,overall_type")
      e_split("__topic__")
      e_json('content',depth=1)
      e_drop_fields("content")
      
      # Process the overall_type log.
      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))
      ))
      
      # Process the server_status log.
      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"))
      
      # Process the client_status log.
      e_if(e_search("__topic__==client_status"), 
           e_compose(
              e_split("clients"), 
              e_json("clients", depth=1),
              e_drop_fields("clients")
        ))

Optimización de la solución

La solución anterior presenta problemas cuando content.servers y content.servers están vacíos. Por ejemplo, considere el siguiente registro sin procesar:

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

Si utiliza la solución anterior para dividir este registro sin procesar en tres registros, los registros con los temas client_status y server_status estarán vacíos.

__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
  • Solución 1

    Después de la división inicial, compruebe si los registros con los temas server_status y client_status están vacíos. Si es así, descártelos.

    # For server_status: discard if empty, retain if not.
    e_keep(op_and(e_search("__topic__==server_status"), json_select(v("servers"), "length([*])")))
    
    # For client_status: discard if empty, retain if not.
    e_keep(op_and(e_search("__topic__==client_status"), json_select(v("clients"), "length([*])")))

    Las reglas completas del DSL de LOG son las siguientes:

    # Split the log.
    e_set("__topic__", "server_status,client_status,overall_type")
    e_split("__topic__")
    e_json('content',depth=1)
    e_drop_fields("content")
    
    # Process the overall_type log.
    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))
    ))
    
    # New: Pre-process server_status: discard if empty, retain if not.
    e_keep(op_and(e_search("__topic__==server_status"), json_select(v("servers"), "length([*])")))
    
    # Process the server_status log.
    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"))
    
    # New: Pre-process client_status: discard if empty, retain if not.
    e_keep(op_and(e_search("__topic__==client_status"), json_select(v("clients"), "length([*])")))
    
    # Process the client_status log.
    e_if(e_search("__topic__==client_status"), 
         e_compose(
            e_split("clients"), 
            e_json("clients", depth=1),
            e_drop_fields("clients")
      ))
  • Solución 2

    Compruebe si un campo está vacío antes de dividir el registro. Si el campo no está vacío, divida el registro según dicho campo.

    # Set the initial topic.
    e_set("__topic__", "server_status")
    
    # If the content.servers field is not empty, split the log to create a log with the topic server_status.
    e_if(json_select(v("content"), "length(servers[*])"),
       e_compose(
          e_set("__topic__", "server_status,overall_type"),
          e_split("__topic__")
       ))
    
    # If the content.clients field is not empty, further split the log to create a log with the 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__")
       ))

    Las reglas completas del DSL de LOG son las siguientes:

    # Split the log.
    e_set("__topic__", "server_status")
    
    # If the content.servers field is not empty, split the log to create a log with the topic server_status.
    e_if(json_select(v("content"), "length(servers[*])"),
       e_compose(
          e_set("__topic__", "server_status,overall_type"),
          e_split("__topic__")
       ))
    
    # If the content.clients field is not empty, further split the log to create a log with the 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__")
       ))
    
    # Process the overall_type log.
    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))
    ))
    
    # Process the server_status log.
    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"))
    
    # Process the client_status log.
    e_if(e_search("__topic__==client_status"), 
         e_compose(
            e_split("clients"), 
            e_json("clients", depth=1),
            e_drop_fields("clients")
      ))

Comparación de soluciones

  • La Solución 1 es lógicamente redundante porque crea registros vacíos a partir del registro sin procesar y luego los elimina. Sin embargo, las reglas son simples y fáciles de mantener. Esta solución es la predeterminada recomendada.

  • La Solución 2 es más eficiente porque comprueba si hay campos vacíos antes de realizar la división. No obstante, las reglas son ligeramente más complejas. Esta solución solo se recomienda para escenarios específicos, como cuando la división inicial podría generar muchos eventos adicionales.

Transformar datos JSON complejos con objetos de matriz anidados en varios niveles

Este ejemplo muestra cómo procesar un objeto complejo que contiene matrices anidadas en varios niveles. El objetivo es dividir cada evento de inicio de sesión en la matriz login_histories de cada objeto en la matriz users en un evento de inicio de sesión independiente.

  • Registro sin procesar

    __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"
              },
          {
          ...More logon information...
          }
            ]
        },
        {
            "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"
              },
          {
          ...More logon information...
          }     
            ]
        },
      {
        ...More users...
      }
      ]
    }
  • Registros esperados después de la división

    __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  
    
    ...More logs...
  • Solución

    1. Divida y expanda el registro según users en el campo content.

      e_split("content", jmes='users[*]', output='item')
      e_json("item",depth=1)

      Los registros procesados son:

      __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. A continuación, divida y luego expanda los datos según login_histories.

      e_split("login_histories")
      e_json("login_histories", depth=1)

      Los registros procesados son:

      __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. Finalmente, elimine los campos irrelevantes.

      e_drop_fields("content", "item", "login_histories")

      Los registros procesados son:

      __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. Las reglas completas del DSL de LOG se pueden escribir de la siguiente manera:

      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")

Resumen: Para requisitos similares, primero divida el registro, luego expanda los datos y finalmente elimine los campos irrelevantes.