Elasticsearch — Segmentação de mapping por valor

João Neto
3 min readJan 28, 2023

--

ESTUDO DE CASO

Recentemente em um cliente surgiu o seguinte problema:

“Logs de aplicações diferentes sendo armazenadas no mesmo diretorio e consumido pelo mesmo Elastic Agent.”

Exemplo de Logs:

{"application":{"name":"APP01"},"message": """{"Message": {"body": {"chave":"texto"}}}"""}
{"application":{"name":"APP02"},"message": """{"Message": {"body": "Valor legal aqui"}}"""}}

Qual o problema de ingerir esses logs na mesma origem? Se você reparar bem todos os logs possuem um estrutura de JSON, porém os valores dentro do campo “Message” possuem um tipagem diferente. No primeiro log o campo “body” é do tipo object, pois ele tem um subaninhamento que é “chave: texto”, já no segundo log o “body” é string, pois já recebe direto a string “Valor legal aqui”.

Isso em um cenário de indexação ocasiona o erro conhecido como “Conflict Mapping” e acaba gerando o drop do documento que não é compatível.

Qual foi a ideia proposta para esse cenário? É isso que irei demonstrar na próxima sessão.

EXECUÇÃO

Como uma forma de contornar a limitação por tipo de mapeamento decidi então criar o mapeamento especifico para cada aplicação usando o campo “application.name” como um parâmetro. Loucura né? Mas funcionou, veja a seguir:

POST _ingest/pipeline/_simulate
{
"pipeline": {
"processors": [
{
"set": {
"field": "{{application.name}}",
"value": "{{message}}"
}
},
{
"set": {
"field": "tipo",
"value": "{{application.name}}"
}
},
{
"lowercase": {
"field": "tipo"
}
},
{
"script": {
"lang": "painless",
"source": """
def item_config = ctx.tipo;
Object json = Processors.json(ctx.message);
ctx[item_config] = json;
"""
}
}
]
},
"docs": [
{
"_source": {
"application": {
"name": "APP01"
},
"message": """{"Message": {"body": {"chave":"texto"}}}"""
}
},
{
"_source": {
"application": {
"name": "APP02"
},
"message": """{"Message": {"body": "Valor legal aqui"}}"""
}
}
]
}

Vamos entender melhor cada processor utilizado.


{
"set": {
"field": "{{application.name}}",
"value": "{{message}}"
}
}

No SET criamos um campo com o valor presente em “application.name” e nesse campo criado adicionamos o valor presente em “message”.

      {
"set": {
"field": "tipo",
"value": "{{application.name}}"
}
}

No SET acima criamos um campo chamado “tipo” e nesse campo criado adicionamos o valor presente em “application.name”.

      {
"lowercase": {
"field": "tipo"
}
}

Convertemos o campo “field” para lowercase.

      {
"script": {
"lang": "painless",
"source": """
def item_config = ctx.tipo;
Object json = Processors.json(ctx.message);
ctx[item_config] = json;
"""
}

Nesse script painless pegamos o valor do campo “tipo”, adicionamos na variável “item_config”, depois criamos uma variável json e aplicamos o processor de Json no campo “message” e por último falamos que será criado um campo com o valor presente em “item_config” com o valor convertido para json do “message”.

O resultado final é semelhate ao abaixo:

{
"docs": [
{
"doc": {
"_index": "_index",
"_id": "_id",
"_version": "-3",
"_source": {
"app01": {
"Message": {
"body": {
"chave": "texto"
}
}
},
"tipo": "app01",
"APP01": """{\"Message\": {\"body\": {\"chave\":\"texto\"}}}""",
"application": {
"name": "APP01"
},
"message": """{"Message": {"body": {"chave":"texto"}}}"""
},
"_ingest": {
"timestamp": "2023-01-19T02:30:15.40911674Z"
}
}
},
{
"doc": {
"_index": "_index",
"_id": "_id",
"_version": "-3",
"_source": {
"app02": {
"Message": {
"body": "Valor legal aqui"
}
},
"APP02": """{\"Message\": {\"body\": \"Valor legal aqui\"}}""",
"tipo": "app02",
"application": {
"name": "APP02"
},
"message": """{"Message": {"body": "Valor legal aqui"}}"""
},
"_ingest": {
"timestamp": "2023-01-19T02:30:15.409162877Z"
}
}
}
]
}

Veja o seguinte:

(...)
{
"app01": {
"Message": {
"body": {
"chave": "texto"
}
}
}
(...)
{
"app02": {
"Message": {
"body": "Valor legal aqui"
}
}
(...)

De forma resumida é possível notar melhor que o “app01” ficou com o tipo de aninhamento do “Message” diferente do “app02”, desta forma não teremos o Conflict Mapping.

Legal né?! Espero que seja útil para você como é útil para mim!!

FINE!

Essa era a dica que tinha para hoje, te vejo em breve!

Isso é tudo, pessoal ;)

--

--

João Neto

Especialista Elasticsearch, entusiasta na área de segurança da informação, cientista louco e acredita que melhor maneira de aprender é através do humor.