sábado, 18 de mayo de 2019

ELK - Introducción a Logstah III

En el post anterior sobre Logstash, vimos que los dos filtros más utilizados para registros de tipo texto son dissect y grok. Usando syslog_generator, el cual os recuerdo que tenéis disponible aquí por si queréis usarlo, creamos entradas de tipo fijo para el syslog de una máquina donde habíamos instalado filebeat, las enviamos a Logstash y las procesamos con un filtro dissect para obtener los campos que nos interesaban.

En esta entrada usaremos grok sobre el mismo tipo de registros, enviados también desde un sistema con filebeat, para poder realizar la misma tarea.

Primero es importante recordar que grok es perfecto para procesar registros de tipo texto no estructurados, es decir, para aquellos casos en los que se generan registros con una estructura variable de línea a línea. Además, al basarse en expresiones regulares, es posible extender los patrones reconocidos creando patrones para poder usarlos en cualquier pipeline de Logstash que utilice grok.

La sintaxis básica para definir un filtro con grok se define del siguiente modo:
Sintaxis básica para un filtro grok.
Y esta definición, ¿que quiere decir exactamente? Como ya hemos visto, a diferencia de dissect, grok se basa en expresiones regulares ya existentes, identificadas con un nombre que se corresponde con el campo PATTERN, las cuales asignaremos al campo NAME que necesitemos cuando la coincidencia sea satisfactoria. Grok incluye un buen número de patrones ya listos para su uso, cuya lista puede consultarse aquí aunque pueden incluirse nuevos patrones, si es necesario, modificando la configuración de Logstash añadiendo un fichero con la definición de nuevos patrones.

Al definir filtros usando grok, este busca coincidencias desde el principio de cada línea de tecto hasta el final de la misma intentando mapear todo el evento, o hasta no encontrar una coincidencia. En función de los patrones, es posible que grok procese los datos varias veces, por lo que es más lento y requiere más recursos que filtros realizados con dissect.

Para nuestro ejemplo, vamos a seguir con líneas de registro que son perfectas para el uso de dissect, ya que no varían y presentan los mismos campos y delimitadores en todos los casos, pero así podremos comprobar que podemos usar indistintamente cualquiera de los dos tipos de filtro.

Las líneas a procesar presentan la siguiente estructura:
Líneas de registro a procesar y definición de campos.
Al igual que hicimos con el filtro dissect, vamos a empezar con un filtro simple y luego iremos complicándonos un poco más. 

Para empezar, vamos a hacer uso de una herramienta muy útil disponible en Kibana y que nos permite probar nuestros filtros grok, con lo que podemos analizar si el resultado obtenido es o no correcto antes de configurarlo en nuestro pipeline. Podemos acceder al Grok Debugger desde la sección Dev Tools de Kibana:

Grok Debugger disponible en Kibana.
Esta herramienta es de gran ayuda ya que, al tratarse de expresiones regulares, es fácil que tengamos que probar bastantes veces antes de dar con la configuración correcta del filtro. Por tanto, para empezar, vamos a probar a quedarnos solo con 4 campos de la línea message, en concreto con la fecha de syslog, el hostname, el nombre del programa que ha generado la línea y el mensaje generado por syslog_generator. Para esto, el filtro grok a aplicar sobre la línea message será:

Filtro grok inicial.
Como vemos en el filtro, puede que en algunos casos sea necesario definir los espacios que existan en el registro de texto, dados por el patrón SPACE. Teniendo esto en cuenta, el filtro está estableciendo lo siguiente:
  • Almacenar en el campo logger_date el texto encontrado que haga match con el patrón SYSLOGTIMESTAMP.
  • Almacenar en el campo logger_hostname la siguiente palabra encontrada.
  • Almacenar en el campo logger_program los siguientes datos encontrados.
  • Almacenar en el campo logger_message todo lo que queda hasta el final de la línea, lo cual especifcamos con el patrón GREEDYDATA.
Usando el grok debugger con este filtro, sobre una línea de ejemplo recibida en Logstash, podemos simular el resultado y comprobar que, al trabajar con expresiones regulares, los resultados que buscamos son un poco más complicados de obtener de lo que esperábamos:

Usando Grok Debugger con el filtro inicial.
Como vemos en la imagen anterior, el campo logger_program se queda vacio, es decir, que la definición %{DATA:logger_program} que hemos hecho para ese campo, no nos devuelve ningún contenido. Si vemos la definición del patrón DATA disponible, vemos que equivale a .*? lo cual establece que debe devolver la menor cantidad posible de caracteres (?) de cualquiera de los caracteres (.*) anteriores. Básicamente, al especificar que se devuelva la menor cantidad posible de caracteres con ?, no está devolviendo ningún valor en dicho campo.

La forma de corregirlo sería usar un filtro como el siguiente, en el cual fijásemos que haga match con cualquier palabra y todos aquellos caracteres que no sean un espacio:

Filtro grok modificado para incluir de forma correcta el cmapo logger_program.
Al probarlo en el depurador de grok de Kibana, vemos que ahora obtenemos los campos que queremos de forma correcta:

Comprobación de que el nuevo filtro es correcto.
La sintaxis empleada es la que podemos usar cuando la expresión regular que necesitamos, no se encuentra en los patrones disponibles incluidos con el plugin de filtro grok de Logstash. Podemos usarla directamente en la definición del filtro o, si queremos usarla de manera habitual, podemos extender la biblioteca de patterns incluyendo las nuestras y asignándoles un nombre para poder usarlas en múltiples pipelines.

Modificamos el fichero de configuración del filtro grok con esta definición de filtro y arrancamos Logstash. La salida que obtenemos en este caso es la siguiente:

Salida de Logstash con el filtro grok correcto.
Podemos ver como los cuatro campos que hemos definido están en la salida, además del resto generados por el plugin de entrada filebeat. Además, el campo message del registro ya no aparece al eliminarlo con la opción remove_field.

Ahora ya solo nos queda procesar el campo logger_message para obtener el resto de campos que necesitamos, para lo cual podríamos aplicar un filtro como el siguiente:

Filtro grok procesando todos los campos de message.

Cuando lo aplicamos, la salida de Logstash ya nos muestra los campos necesarios como podemos ver en la siguiente imagen:

Salida de Logstash con el filtro final grok.
En resumen, está claro que grok nos da mucha más potencia para procesar registros de tipo texto, pero con un coste de dificultad añadido debido al uso de expresiones regulares. En general, el uso de dissect o grok, dependerá de la estructura de los registros de texto que sea necesario procesar y, en algunos casos, será conveniente combinar ambos en el mismo pipeline.

En próximas entradas crearemos más de un pipeline, para ver como trabajan conjuntamente y enviaremos los datos a Elasticsearch para podeer realizar búsquedas sobre los datos recibidos.

Como referencia para el estudio de expresiones regulares, recomiendo usar la página https://regex101.com/ en la cual, además de poder probar expresiones regulares, nos explica cada una de las opciones y operadores que podemos usar para la construcción de expresiones regulares complejas.