Ingeniería

Cómo usar Painless en campos con scripts de Kibana

Kibana proporciona formas poderosas de buscar y visualizar datos almacenados en Elasticsearch. A los fines de la visualización, Kibana busca campos definidos en mapeos de Elasticsearch y los presenta como opciones para el usuario creando un gráfico. ¿Pero qué sucede si olvidas definir un valor importante como un campo diferente en el esquema? ¿O si deseas combinar dos campos y tratarlos como uno? Aquí es donde entran en juego los campos con scripts de Kibana.

Los campos con scripts existen en realidad desde los inicios de Kibana 4. En el momento en que se presentaron, la única forma de definirlos dependía de Lucene Expressions, un lenguaje de scripting en Elasticsearch que se ocupa exclusivamente de valores numéricos. Como resultado, el poder de los campos con scripts se limitaba a un subconjunto de casos de uso. En la versión 5.0, Elasticsearch presentó Painless, un lenguaje de scripting seguro y poderoso que permite operar una variedad de tipos de datos y, como resultado, los campos con scripts en Kibana 5.0 son mucho más poderosos.

En el resto de este blog, te mostraremos cómo crear campos con script para casos de uso comunes. Para hacerlo, usaremos un set de datos del tutorial de primeros pasos con Kibana y usaremos una instancia de Elasticsearch y Kibana que se ejecutan en Elastic Cloud, lo cual puedes activar de forma gratuita.

En el video siguiente se muestra cómo activar una instancia personal de Elasticsearch y Kibana en Elastic Cloud y cargar un set de datos en ella. 

Cómo funcionan los campos con scripts

Elasticsearch te permite especificar campos con scripts en cada solicitud. Kibana mejora esto permitiéndote definir un campo con scripts una vez en la sección Management (Gestión), para poder usarlo en varios lugares de la UI en adelante. Ten en cuenta que si bien Kibana almacena los campos con scripts junto al resto de su configuración en el índice .kibana, esta configuración es específica de Kibana, y los campos con script de Kibana no se muestran a los usuarios de API de Elasticsearch.

Cuando vas a definir un campo con scripts en Kibana, tendrás la opción de elegir el lenguaje de scripting, lo que te permite seleccionar de una lista de todos los lenguajes instalados en los nodos de Elasticsearch que tengan habilitado el scripting dinámico. De forma predeterminada, las opciones son "expression" y "painless" en la versión 5.0, y solo "expression" en las versiones 2.x. Puedes instalar otros lenguajes de scripting y habilitar en ellos el scripting dinámico, pero no se recomienda debido a que no pueden evaluarse en el entorno de prueba de forma suficiente y han quedado obsoletos.

Los campos con scripts operan en un documento de Elasticsearch a la vez, pero pueden hacer referencia a varios campos en ese documento. Como resultado, es apropiado usar campos con scripts para combinar o transformar campos en un solo documento, pero no realizar cálculos basados en varios documentos (por ejemplo, cálculos de series temporales). Tanto las expresiones de Painless como de Lucene operan en los campos almacenados en doc_values. Por lo tanto, para los datos de cadena, deberás hacer que el texto se almacene en palabras clave de tipo dato. Los campos con scripts basados en Painless tampoco pueden operar directamente en _source.

Una vez que los campos con script se definen en "Management" (Gestión), el usuario puede interactuar con ellos de la misma manera que con otros campos en el resto de Kibana. Los campos con scripts se muestran automáticamente en la lista de campos de Discover y están disponibles en Visualize a los fines de la creación de visualizaciones. Kibana simplemente pasa las definiciones de campos con scripts a Elasticsearch en el momento de la búsqueda para la evaluación. El set de datos resultante se combina con otros resultados que devuelve Elasticsearch y se presenta al usuario como una tabla o un gráfico.

Existen algunas limitaciones conocidas al trabajar con campos con scripts al momento de redacción de este blog. Puedes aplicar la mayoría de las agregaciones de Elasticsearch disponibles en el generador visual de Kibana en los campos con scripts, con la excepción más notable que es la agregación de términos importantes. También puedes filtrar los campos con scripts a través de la barra de filtros en Discover, Visualize y Dashboard, aunque debes tener el recaudo de escribir scripts adecuados que devuelvan valores bien definidos, como mostramos a continuación. También es importante consultar la sección "Mejores prácticas" a continuación para asegurarte de no desestabilizar tu entorno, al usar campos con scripts.

En el video siguiente se muestra cómo usar Kibana para crear campos con scripts.

Ejemplos de campos con scripts

En esta sección se presentan algunos ejemplos de expresiones de Lucene y campos con scripts de Painless en Kibana en situaciones comunes. Como ya mencionamos, estos ejemplos se desarrollaron a partir de un set de datos del tutorial de primeros pasos con Kibana y suponen que estás usando Elasticsearch y Kibana 5.1.1, dado que hay algunos problemas conocidos relacionados con el filtrado y la clasificación en ciertos tipos de campos con scripts en versiones anteriores.

En su mayoría, los campos con scripts deberían funcionar tal como están, dado que Painless y las expresiones de Lucene están habilitados de forma predeterminada en Elasticsearch 5.0. La única excepción son los scripts que requieren parseo basado en regex de los campos, lo que requerirá que configures lo siguiente en elasticsearch.yml para activar la coincidencia de regex para Painless: script.painless.regex.enabled: true

Realizar un cálculo en un campo único

  • Ejemplo: calcular kilobytes a partir de bytes
  • Lenguaje: expressions
  • Tipo de devolución: número
 doc['bytes'].value / 1024

Nota: Ten en cuenta que los campos con scripts de Kibana funcionan en un solo documento a la vez, por lo que no hay forma de hacer un cálculo de serie temporal en un campo con scripts.

Cálculo de fecha que da como resultado un número

  • Ejemplo: parsear la fecha a hora del día
  • Lenguaje: expressions
  • Tipo de devolución: número

Las expresiones de Lucene proporcionan un host completo de funciones de manipulación de fecha listo para usar. Sin embargo, las expresiones de Lucene solo devuelven valores numéricos, tendremos que usar Painless para devolver un día de la semana basado en cadena (a continuación).

 doc['@timestamp'].date.hourOfDay

Nota: El script anterior devolverá 1-24.

doc['@timestamp'].date.dayOfWeek

Nota: El script anterior devolverá 1-7.

Combinar dos valores de cadena

  • Ejemplo: combinar origen y destino o primer nombre y apellido
  • Lenguaje: painless
  • Tipo de devolución: cadena
 doc['geo.dest.keyword'].value + ':' + doc['geo.src.keyword'].value

Nota: Dado que los campos con scripts deben operar en campos en doc_values, usamos versiones .keyword de las cadenas anteriores.

Presentación de la lógica

  • Ejemplo: devolver la etiqueta "big download" para cualquier documento con bytes que superen los 10 000.
  • Lenguaje: painless
  • Tipo de devolución: cadena
 if (doc['bytes'].value > 10000) { 
return "big download";
}
return "";

Nota: Al presentar la lógica, asegúrate de que cada ruta de ejecución tenga una instrucción return bien definida y un valor return (no nulo) bien definido. Por ejemplo, el campo con script anterior fallará con un error de compilación al usarse en filtros de Kibana sin la instrucción return al final o si la instrucción devuelve un valor nulo. Además, ten en cuenta que desglosar la lógica en funciones no es compatible en los campos con script de Kibana. 

Devolver subcadena

  • Ejemplo: devolver la parte posterior a la última barra diagonal en la URL
  • Lenguaje: painless
  • Tipo de devolución: cadena
 def path = doc['url.keyword'].value;
if (path != null) {
int lastSlashIndex = path.lastIndexOf('/');
if (lastSlashIndex > 0) {
return path.substring(lastSlashIndex+1);
}
}
return "";

Nota: Cuando sea posible, evita usar expresiones regex para extraer subcadenas, dado que las operaciones indexOf() usan menos recursos y tienen menor tendencia a errores. 

Buscar coincidencia de una cadena con regex y tomar medidas sobre la coincidencia

  • Ejemplo: devolver una cadena "error" si se encuentra una subcadena "error" en el campo "referer", de lo contrario devolver una cadena "no error".
  • Lenguaje: painless
  • Tipo de devolución: cadena
if (doc['referer.keyword'].value =~ /es/error/) { 
return "error"
} else {
return "no error"
}

Nota: La sintaxis de regex simplificada es útil para los condicionales basados en una coincidencia de regex. 

Buscar una coincidencia de cadena y devolver dicha coincidencia

  • Ejemplo: devolver un dominio, la cadena posterior al último punto en el campo "host"
  • Lenguaje: painless
  • Tipo de devolución: cadena
def m = /^.*\.([a-z]+)$/.matcher(doc['host.keyword'].value);
if ( m.matches() ) {
return m.group(1)
} else {
return "no match"
}

Nota: Definir un objeto a través de las funciones matcher() de regex te permite extraer grupos de caracteres que coincidieron con regex y devolverlos. 

Buscar una coincidencia de número y devolver dicha coincidencia

  • Ejemplo: devolver el primero octeto de la dirección IP (almacenada como cadena) y tratarlo como un número.
  • Lenguaje: painless
  • Tipo de devolución: número
 def m = /^([0-9]+)\..*$/.matcher(doc['clientip.keyword'].value);
if ( m.matches() ) {
return Integer.parseInt(m.group(1))
} else {
return 0
}

Nota: Es importante devolver el tipo de datos adecuado en un script. La coincidencia de regex devuelve una cadena, incluso si se encuentra una coincidencia con un número, por lo que debes convertirlo explícitamente en un entero en la devolución. 

Cálculo de fecha que da como resultado una cadena

  • Ejemplo: parsear la fecha a día de la semana en una cadena
  • Lenguaje: painless
  • Tipo de devolución: cadena
LocalDateTime.ofInstant(Instant.ofEpochMilli(doc['@timestamp'].value), ZoneId.of('Z')).getDayOfWeek().getDisplayName(TextStyle.FULL, Locale.getDefault())

Nota: Como Painless soporta todos los tipos nativos de Java, brinda acceso a las funciones nativas relacionadas con dichos tipos, como LocalDateTime(), lo que resulta útil para realizar cálculos de fecha más avanzados.

Mejores prácticas

Como puedes ver, el lenguaje con scripts Painless brinda formas poderosas de extraer información útil de campos arbitrarios almacenados en Elasticsearch a través de campos con scripts de Kibana. Sin embargo, este poder acarrea una gran responsabilidad. 

A continuación describimos algunas mejores prácticas relacionadas con los campos con scripts de Kibana.

  • Usa siempre un entorno de desarrollo para experimentar con campos con scripts. Como los campos con scripts estarán activos de inmediato una vez que los guardes en la sección Management (Gestión) de Kibana (por ejemplo, aparecerán en la pantalla Discover de dicho patrón de índice para todos los usuarios), no debes desarrollar campos con scripts directamente en producción. Te recomendamos probar primero la sintaxis en un entorno de desarrollo, evaluar el impacto de los campos con scripts en sets de datos realistas y volúmenes de datos en organización, y recién después promoverlos a producción. 
  • Una vez que estés seguro de que el campo con scripts proporciona valor para tus usuarios, considera modificar la ingesta para extraer el campo al momento de la indexación para datos nuevos. Esto le ahorrará procesamiento a Elasticsearch al momento de la búsqueda y brindará tiempos de respuesta más rápidos a los usuarios de Kibana. También puedes usar la API _reindex en Elasticsearch para volver a indexar los datos existentes.