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
Divida el registro sin procesar en tres registros según el campo
topic:overall_type,client_statusyserver_status.-
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
-
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... } -
Expanda el contenido JSON de primer nivel del campo
contenty luego elimine el campocontent.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 -
Para el registro con el tema
overall_type, calcule los valores declient_countyserver_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 -
Elimine los campos innecesarios.
e_if(e_search("__topic__==overall_type"), e_drop_fields("clients", "servers")) -
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 -
Campos relevantes a conservar:
e_if(e_search("__topic__==overall_type"), e_drop_fields("servers")) -
Divida aún más el registro con el tema
client_statusy luego elimine el campoclients.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 -
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_statusyclient_statusestá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
-
Divida y expanda el registro según
usersen el campocontent.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 -
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 -
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 -
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.