<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
  <channel>
    <title><![CDATA[Ruby - Elasticsearch Labs]]></title>
    <description><![CDATA[Articles and tutorials from the Search team at Elastic]]></description>
    <copyright><![CDATA[© 2026. Elasticsearch B.V. All Rights Reserved]]></copyright>
    <image>
      <title><![CDATA[Ruby - Elasticsearch Labs]]></title>
      <url>https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1121c0bf0e8a6e65/6a88da6340a1841030ef456f/search-labs-thumbnail.png</url>
      <link>https://www.elastic.co/es/search-labs/blog/category/ruby-programming</link>
    </image>
    <link>https://www.elastic.co/es/search-labs/blog/category/ruby-programming</link>
    <atom:link href="https://www.elastic.co/es/search-labs/rss/category/ruby-programming.xml" rel="self" type="application/rss+xml"/>
    <language><![CDATA[es]]></language>
    <lastBuildDate>Mon, 28 Sep 2026 15:07:25 GMT</lastBuildDate>
  <item>
    <title><![CDATA[Presentamos el ES|Generador de consultas QL para el cliente Ruby de Elasticsearch]]></title>
    <description><![CDATA[Aprende a usar el recientemente lanzado ES|Generador de consultas QL para el cliente Ruby de Elasticsearch. Una herramienta para construir ES|QL consulta más fácilmente con código Ruby.]]></description>
    <content:encoded><![CDATA[<p>Recientemente lanzamos <a href="https://github.com/elastic/esql-ruby/"><code>elastic-esql</code></a>, una joya Ruby publicada bajo la licencia Apache 2. Esta gema te permite construir el <a href="https://www.elastic.co/docs/explore-analyze/query-filter/languages/esql">ES| de ElasticConsultas QL</a> en Ruby idiomático, que luego puedes usar con el ES|API de consulta QL. ES|QL permite a los desarrolladores filtrar, transformar y analizar los datos almacenados en Elasticsearch mediante consultas. Emplea "tuberías" ( <code>|</code> ) para trabajar paso a paso con los datos. La gema emplea funciones Ruby en su lugar, que puedes encadenar al objeto original para crear consultas más complejas:</p><p><strong>ESQL:</strong></p><p><strong>Rubí:</strong></p>Elastic::ESQL.from('sample_data').limit(2).sort('@timestamp').descending<h2>Instalación</h2><p>La gema puede instalar desde RubyGems con:</p>gem install elastic-esql<p>O puede agregar al archivo de gemas de un proyecto:</p>gem 'elastic-esql'<h2>Uso</h2><p>Puedes construir una consulta completa de una vez o crear un objeto de consulta con un comando fuente como <code>from</code> o <code>row</code> y luego encadenar ES|QL métodos para construir sobre ella.</p>query = Elastic::ESQL.from('sample_data')
query.limit(2).sort('@timestamp')<p>La gema traduce el código a ES|QL en el método <code>to_s</code> , así que devuelve el ES|Consulta QL cuando se imprime o se convierte en una cadena:</p>query = Elastic::ESQL.from('sample_data').limit(2).sort('@timestamp').descending
query.to_s
# =&gt; "FROM sample_data | LIMIT 2 | SORT @timestamp DESC"<p>Puedes instanciar un objeto de consulta y mutar su estado inicial usando los equivalentes <code>!</code> de cada función:</p>query = Elastic::ESQL.from('sample_data')
query.to_s
# =&gt; "FROM sample_data"
query.limit!(2).sort!('@timestamp')
query.to_s
# =&gt; "FROM sample_data | LIMIT 2 | SORT @timestamp"<p>La herramienta ofrece formas cómodas de encadenar pasos extra a un ES|Función QL, como <code>enrich</code> y <code>sort</code>. Una vez que llamas <code>enrich</code> a un objeto <code>Elastic::ESQL</code> , puedes encadenar <code>on</code> y <code>with</code> a él:</p>esql.enrich!('policy').on('a').with({ name: 'language_name' })<p>También puedes encadenar <code>desc</code>, <code>asc</code>, <code>nulls_first</code> y <code>nulls_last</code> a tu consulta tras usar <code>sort</code>:</p>Elastic::ESQL.from('sample_data').sort('@timestamp').asc.to_s
# =&gt; 'FROM sample_data | SORT @timestamp ASC'

Elastic::ESQL.from('sample_data').sort('@timestamp').desc.nulls_first.to_s
# =&gt; 'FROM sample_data | SORT @timestamp DESC NULLS FIRST'<p>También soporta cadenas personalizadas, por si quieres escribir el ES|Consulta QL tú mismo, o usa una función que aún no se agregó a la biblioteca. <code>custom</code> se unirán a las cadenas al final de la consulta. Los agregará a medida que se envían a la función, sin agregar ningún carácter de la tubería. Se combinarán con el resto de la consulta mediante un carácter espacio.</p>esql = Elastic::ESQL.from('sample_data')
esql.custom('| MY_VALUE = "test value"').to_s
# =&gt; 'FROM sample_data | MY_VALUE = "test value"'<p>También puedes encadenar <code>custom</code> funciones:</p>esql.custom('| MY_VALUE = "test value"').custom('| ANOTHER, VALUE')
'FROM sample_data | MY_VALUE = "test value" | ANOTHER, VALUE'<h2>Usando el ES|QL Query Builder con el cliente Ruby</h2><p>Puedes usar el constructor de consultas directamente con <a href="https://github.com/elastic/elasticsearch-ruby">elasticsearch-ruby</a> y la API <code>esql.query</code> enviando el objeto de consulta:</p>require 'elasticsearch'
require 'elastic/esql'

client = Elasticsearch::Client.new
index = 'sample_data'

query = Elastic::ESQL.from(index)
                     .sort('@timestamp')
                     .desc
                     .where('event_duration &gt; 5000000')
                     .limit(3)
                     .eval({ duration_ms: 'ROUND(event_duration/1000000.0, 1)' })
client.esql.query(body: { query: query })<p>También puedes usarlo con el ES|QL Helper del cliente Ruby de Elasticsearch, <a href="https://www.elastic.co/search-labs/blog/esql-ruby-helper-elasticsearch">para saber más</a>:</p>require 'elasticsearch/helpers/esql_helper'

Elasticsearch::Helpers::ESQLHelper.query(client, query)<h2>Como herramienta independiente</h2><p>La gema está diseñada como una herramienta independiente para construir ES|QL consulta de forma idiomática. No tiene dependencias en tiempo de ejecución; puedes usarlo con el cliente oficial de Elasticsearch Ruby, o por separado.</p><p>La consulta generada puede usar con la API <a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-esql-query"><code>esql.query</code></a> de cualquier forma que una aplicación interactúe con la API de Elasticsearch (Ruby o no). Una vez que una consulta se construye con <code>elastic-esql</code>, la cadena generada puede enviar a la API como el parámetro <code>query</code> en el cuerpo de la solicitud. </p><p>Anteriormente escribí sobre <a href="https://www.elastic.co/search-labs/blog/elasticsearch-ruby-tools">el uso de Elasticsearch con las herramientas Ruby populares</a>. Esta gema puede usar con cualquiera de las herramientas Ruby populares para consultar Elasticsearch con ES|QL.</p><h2>Conclusión</h2><p>Esta biblioteca está en desarrollo activo y la API final aún no se completó. Actualmente está lanzado como un avance técnico. Si tienes algún comentario sobre la API actual o su uso general, no dudes en <a href="https://github.com/elastic/esql-ruby/issues">abrir un nuevo número</a>. Por favor, consulta <a href="https://github.com/elastic/esql-ruby/?tab=readme-ov-file#ruby-esql-query-builder">el README</a> para saber más sobre el Ruby ES|Constructor de consultas QL.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/esql-query-builder-elasticsearch-ruby-client</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/esql-query-builder-elasticsearch-ruby-client</guid>
    <category><![CDATA[ES|QL]]></category>
    <category><![CDATA[Ruby]]></category>
    <dc:creator><![CDATA[Fernando Briano]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt85d112ccca541b9e/6a17dccb4b055d6bfd4320cc/f8e1263ab53d356824a4fc539084151be80899db-720x420.jpg" length="0" type="image/jpeg"/>
    <pubDate>Wed, 17 Sep 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Guion Ruby en Logstash]]></title>
    <description><![CDATA[Infórmate sobre el plugin de filtro Ruby Logstash para transformar datos avanzados en tu pipeline Logstash.]]></description>
    <content:encoded><![CDATA[<p>Logstash es una cadena de procesamiento de datos que ingiere datos de múltiples fuentes, los transforma y los envía a los destinos que elijas. Los plugins de filtro son clave para este proceso; Realizan operaciones específicas sobre tus datos a medida que avanzan en la pipeline.</p><p>Logstash incluye varios filtros integrados para tareas comunes como análisis sintáctico, enriquecimiento y modificación de datos. Pero a veces te encontrarás con escenarios que requieren una lógica personalizada que va más allá de lo que estos filtros estándar pueden ofrecer. Aquí es donde entra el <a href="https://www.elastic.co/docs/reference/logstash/plugins/plugins-filters-ruby">plugin de filtro Ruby</a> .</p><p><strong>El plugin de filtro Ruby te permite ejecutar código Ruby personalizado directamente dentro de tu pipeline de Logstash.</strong> Cuando los filtros estándar no son suficientes, el filtro Ruby te permite manejar transformaciones complejas de datos, implementar lógica de negocio personalizada o integrarte con sistemas externos.</p><p>En este blog, exploraremos cómo usar los filtros Ruby, desde el uso básico hasta el avanzado.</p><h2>¿Cuándo deberías usar el filtro Ruby?</h2><p>Como arquitecto consultor de Elastic, a menudo veo a clientes que emplean Logstash para la cadena de procesamiento de datos, aunque hoy en día no es un motor de procesamiento de datos de última generación. A menudo tienen dificultades con las limitaciones de los filtros estándar cuando se trata de manipulación compleja de datos o lógica personalizada. En estos casos, el filtro Ruby puede ayudar a superar esos desafíos.</p><p>El filtro Ruby es útil cuando los filtros Logstash estándar no pueden cumplir tus requisitos específicos. Aquí tienes algunos casos de uso comunes:</p><ul><li><p><strong>Manipulación profunda de datos anidados</strong>: Modificar estructuras JSON complejas, arrays dentro de arrays o reestructurar dinámicamente los datos en función del contenido</p></li><li><p><strong>Procesamiento avanzado de cadenas</strong>: Analizar y extraer datos estructurados de texto no estructurado</p></li><li><p><strong>Implementación de lógica de negocio compleja</strong>: Crear transformaciones personalizadas que requieran lógica condicional, bucles o cálculos complejos</p></li></ul><h2>Uso básico</h2><p>Empecemos con un ejemplo sencillo para entender cómo funciona el filtro Ruby.</p><h3>Configuración del filtro Ruby</h3><p>Cuando crees una pipeline de Logstash, deberías colocar el archivo de configuración en el directorio <code>/etc/logstash/conf.d</code> . Alternativamente, puedes usar <code>-f</code> opción para especificar la ruta al archivo de configuración cuando arranques Logstash manualmente, para que puedas experimentar fácilmente con tus pipelines.</p>$ ./bin/logstash -f /path/to/your_pipeline.conf<p>El archivo de configuración debería tener una extensión <code>.conf</code> .</p><p>Para usar el filtro Ruby, define un filtro <code>ruby</code> en la sección de filtros de tu archivo de configuración de la tubería Logstash (*.conf). Aquí tienes un ejemplo básico:</p>filter {
  ruby {
    code =&gt; "
      event.set('new_field', 'Hello from Ruby!')
    "
  }
}<p>Este filtro Ruby en línea define una instancia de filtro Ruby dentro de tu configuración de Logstash. El parámetro <code>code</code> proporciona el script Ruby en línea que Logstash ejecutará para cada evento procesado por este filtro. Dentro de ese script, hay una variable <code>event</code> disponible que representa el propio evento. El objeto evento contiene los datos originales enviados a Logstash y cualquier campo adicional creado durante las etapas de filtro de Logstash. Puedes acceder a esos campos a través de la API de eventos de Logstash como <code>event.get()</code> y <code>event.set()</code>. En este código de ejemplo, <code>event.set('new_field', 'Hello from Ruby!')</code> establecer un nuevo campo llamado <code>new_field</code> al valor de cadena <code>Hello from Ruby!</code>. Puedes agregar cualquier otro código en este bloque de <code>code</code> según lo necesites.</p><p>Ten en cuenta que este objeto <code>event</code> no es un objeto hash de Ruby habitual, aunque actúa como un contenedor de datos clave-valor. Consulta <a href="https://www.elastic.co/docs/reference/logstash/event-api">esta documentación oficial</a> para saber más sobre la API de eventos.</p><h3>Externalizar la escritura Ruby</h3><p>Para transformaciones simples, el código Ruby en línea es cómodo. Pero, para lógica compleja o funciones reutilizables, se recomienda mover el código a un script Ruby externo. Esto mejora la mantenibilidad y mantiene limpia la configuración de tu pipeline de Logstash.</p><p>Primero, crea un script Ruby y almacénalo como <code>my_ruby_script.rb</code>. El script debe definir un método <code>filter</code> que procese el evento. Toma un objeto evento como argumento, que representa el evento actual que se está procesando. El método <code>filter</code> necesita devolver un serial de eventos para emitir. Para eliminar el evento, devuelvo un array vacío.</p><p>Por ejemplo, el siguiente script lee el campo <code>message</code> , calcula su longitud y almacena el resultado en un nuevo campo llamado <code>message_length</code>.</p>def register(params)
  # This method is called when the plugin is loaded.
  # You can use it to initialize any instance variables or perform setup tasks.
end

def filter(event)
  message = event.get('message')

  if message
    event.set('message_length', message.length)
  end

  return [event]
end<p>A continuación, configura la configuración del filtro Ruby para que haga referencia al script usando la opción <code>path</code> . Esto indica a Logstash que cargue y ejecute el script externo. Al usar scripts externos, cerciórate de que el archivo existe y tiene las licencias correctas.</p>filter {
  ruby {
    path =&gt; "/path/to/my_ruby_script.rb"
  }
}<p>Ahora, cada evento se pasa al método <code>filter</code> en <code>my_ruby_script.rb</code> y es procesado por él.</p><p>Este enfoque te ayuda a gestionar la lógica compleja de forma más eficaz, facilitando probar, depurar y reutilizar tu código Ruby.</p><h2>Uso avanzado</h2><p>En esta sección, exploraremos algunos ejemplos avanzados de cómo usar el filtro Ruby en Logstash. Estos ejemplos demostrarán cómo realizar transformaciones de datos, enriquecer eventos e implementar lógica personalizada usando Ruby.</p><h3>Manipulación de estructuras de datos anidadas</h3><p>Un evento Logstash es la estructura de datos central que procesa Logstash. Puede contener varios campos, incluyendo estructuras de datos anidadas como arrays y hashes. El filtro Ruby te permite manipular fácilmente estas estructuras anidadas.</p><p>El filtro Ruby puede manejar estructuras de datos anidadas, como hashes y arrays, permitiéndote modificar o agregar campos dentro de estas estructuras. Esto es útil cuando se trata de formatos de datos complejos como JSON.</p>input {
  generator {
    lines =&gt; [
      '{"nested": {"key1": "value1", "key2": "value2"}}'
    ]
    count =&gt; 1
    codec =&gt; "json"
    ecs_compatibility =&gt; "disabled"
  }
}

filter {
  ruby {
    code =&gt; "
      nested_data = event.get('nested')

      if nested_data.is_a?(Hash)
        nested_data['key3'] = 'value3'
        event.set('nested', nested_data)
      end
    "
  }
}

output {
  stdout { codec =&gt; rubydebug }
}<p>Este ejemplo incluye un objeto JSON anidado en los datos de entrada. El filtro Ruby modifica los datos anidados agregando un nuevo par clave-valor. Este tipo de manipulación para datos anidados no es posible con los filtros Logstash estándar, lo que convierte al filtro Ruby en una opción útil para estructuras de datos complejas.</p><h3>Dividir un solo evento en varios eventos</h3><p>Los filtros Ruby también pueden usar para dividir un solo evento en varios eventos. Esto es útil cuando tienes un solo evento que contiene un array de objetos y quieres crear eventos separados para cada uno.</p><p>Ten en cuenta que ni la tubería de ingesta de Elasticsearch ni los procesadores de Beats/Elastic Agent soportan eventos de división. Este es uno de los casos de uso más estables de Logstash.</p><h4>Con filtro dividido</h4><p>Puedes usar el filtro <code>split</code> para dividir un evento en varios eventos según un campo especificado. Sin embargo, si necesitas realizar transformaciones adicionales o lógica durante la división, puedes usar el filtro Ruby en combinación con el filtro dividido.</p><p>En el siguiente ejemplo, tenemos un feed RSS como una sola línea de texto XML. Contiene múltiples elementos <code>&lt;item&gt;</code> . El filtro Ruby se emplea para extraer los <code>&lt;item&gt;</code> elementos del XML y almacenarlos en un nuevo campo llamado <code>items</code>. El filtro dividido se emplea entonces para dividir el evento en varios eventos según el campo <code>items</code> .</p>input {
  generator {
    lines =&gt; [
      '&lt;rss version="2.0"&gt;&lt;channel&gt;&lt;title&gt;Sample RSS&lt;/title&gt;&lt;item&gt;&lt;title&gt;Article 1&lt;/title&gt;&lt;link&gt;http://example.com/1&lt;/link&gt;&lt;description&gt;Desc 1&lt;/description&gt;&lt;/item&gt;&lt;item&gt;&lt;title&gt;Article 2&lt;/title&gt;&lt;link&gt;http://example.com/2&lt;/link&gt;&lt;description&gt;Desc 2&lt;/description&gt;&lt;/item&gt;&lt;/channel&gt;&lt;/rss&gt;'
    ]
    count =&gt; 1
    codec =&gt; "plain"
    ecs_compatibility =&gt; "disabled"
  }
}

filter {
  xml {
    source =&gt; "message"
    target =&gt; "rss"
    store_xml =&gt; true
    force_array =&gt; false
  }
  ruby {
    code =&gt; "event.set('items', event.get('[rss][channel][item]')) if event.get('[rss][channel][item]')"
  }
  split {
    field =&gt; "items"
  }
  ruby {
    code =&gt; "
      item = event.get('items')
      event.set('title', item['title']) if item['title']
      event.set('link', item['link']) if item['link']
      event.set('description', item['description']) if item['description']
    "
  }
  mutate {
    remove_field =&gt; ["@timestamp", "@version", "sequence", "host", "event", "message", "rss", "items"]
  }
}

output {
  stdout { codec =&gt; rubydebug }
}<p>Esto dará la siguiente manera:</p>{
          "title" =&gt; "Article 1",
           "link" =&gt; "http://example.com/1",
    "description" =&gt; "Desc 1"
}
{
          "title" =&gt; "Article 2",
           "link" =&gt; "http://example.com/2",
    "description" =&gt; "Desc 2"
}<p>Como notaste, el filtro <code>ruby</code> no es esencial en este caso. El filtro <code>split</code> puede usar para dividir el evento en varios eventos basados en el campo <code>items</code> , y el filtro <code>mutate</code> puede usar para eliminar campos innecesarios. Sin embargo, si necesitas realizar transformaciones o lógica adicional durante la división, puedes usar el filtro Ruby.</p><h4>Emplea escritura Ruby en línea</h4><p>También puedes usar un script Ruby en línea para dividir un solo evento en varios eventos usando el método <code>event.clone</code> y el <code>new_event_block variable</code>, como <code>new_event_block.call(new_event)</code>. Esto te permite crear nuevos eventos basados en el evento original mientras se conservan sus datos.</p><p>Aquí tienes un ejemplo de cómo usar el filtro Ruby para dividir un solo evento en varios eventos. La entrada y salida son las mismas que en el ejemplo anterior.</p>filter {
  xml {
    source =&gt; "message"
    target =&gt; "rss"
    store_xml =&gt; true
    force_array =&gt; false
  }
  ruby {
    code =&gt; "
      items = event.get('[rss][channel][item]')
      if items.is_a?(Array)
        items.each do |item|
          new_event = event.clone
          new_event.set('title', item['title'])
          new_event.set('link', item['link'])
          new_event.set('description', item['description'])
          new_event_block.call new_event
        end
        event.cancel
      elsif items.is_a?(Hash)
        event.set('title', items['title'])
        event.set('link', items['link'])
        event.set('description', items['description'])
      end
    "
  }
  mutate {
    remove_field =&gt; ["@timestamp", "@version", "sequence", "host", "event", "message", "rss", "items"]
  }
}<h4>Usa un script Ruby externo</h4><p>También puedes usar un script externo de Ruby para dividir un solo evento en varios eventos.</p><p>Archivo de configuración:</p>filter {
  xml {
    source =&gt; "message"
    target =&gt; "rss"
    store_xml =&gt; true
    force_array =&gt; false
  }
  ruby {
    path =&gt; "path/to/ruby/split_event.rb"
  }
  mutate {
    remove_field =&gt; ["@timestamp", "@version", "sequence", "host", "event", "message", "rss", "items"]
  }
}<p>El sistema Ruby debe externalizar como <code>split_event.rb</code>:</p>def filter(event)
  items = event.get('[rss][channel][item]')
  events = []
  if items.is_a?(Array)
    items.each do |item|
      new_event = event.clone
      new_event.set('title', item['title'])
      new_event.set('link', item['link'])
      new_event.set('description', item['description'])
      events &lt;&lt; new_event
    end
    return events
  elsif items.is_a?(Hash)
    event.set('title', items['title'])
    event.set('link', items['link'])
    event.set('description', items['description'])
    return [event]
  else
    return []
  end
end<p>Recuerda, el método <code>filter</code> debe devolver un serial de eventos. Puedes devolver varios eventos clonando un objeto de evento entrante y agregándolos al array, o puedes devolver un solo evento como un array con un solo elemento.</p>return events
# or
# return [event]<p>Esto te permite dividir un solo evento en varios eventos.</p><h3>Ejecuta comandos externos y analiza su salida</h3><p>El plugin de entrada ejecutiva de Logstash permite ejecutar comandos externos y su salida será un evento de Logstash. La salida del comando se almacenará en el campo <code>message</code> del evento.</p><p>Normalmente, la salida de los comandos del sistema es legible por humanos, pero no está estructurada como JSON u otros formatos que Logstash pueda analizar fácilmente. Para gestionarlo, puedes usar el filtro Ruby para analizar la salida y extraer la información de ella.</p><p>Aquí tienes un ejemplo de cómo se emplea el plugin de entrada <code>exec</code> para ejecutar el comando <code>ps -ef</code> , que lista todos los procesos en ejecución en un sistema tipo Unix. La salida será analizada por el filtro Ruby para extraer información relevante sobre cada proceso.</p>input {
  exec {
    command =&gt; "ps -ef"
    interval =&gt; 60
  }
}

filter {
  ruby {
    code =&gt; '
      processes = []
      lines = event.get("message").split("\n")  
      lines.each_with_index do |line, index|
        # Skip header line and empty lines
        next if index == 0 || line.strip.empty?
        entry = nil
        
        # Use regex to match the ps -ef output format more flexibly
        # This pattern accounts for variable spacing and different time formats
        if line =~ /^\s*(\S+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\S+)\s+(\S+)\s+([\d:]+\.?\d*)\s+(.+)$/
          uid, pid, ppid, c, stime, tty, time, cmd = $1, $2, $3, $4, $5, $6, $7, $8
          
          entry = {
            "UID" =&gt; uid,
            "PID" =&gt; pid,
            "PPID" =&gt; ppid,
            "C" =&gt; c,
            "STIME" =&gt; stime,
            "TTY" =&gt; tty,
            "TIME" =&gt; time,
            "CMD" =&gt; cmd.strip
          }
        elsif line =~ /^\s*(\S+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(.+)$/
          # Fallback pattern for lines that might not match the exact format
          # Split the remaining part more carefully
          uid, pid, ppid, c, remainder = $1, $2, $3, $4, $5
          
          # Split remainder into STIME, TTY, TIME, CMD
          parts = remainder.strip.split(/\s+/, 4)
          if parts.length &gt;= 4
            stime, tty, time, cmd = parts[0], parts[1], parts[2], parts[3]
            
            entry = {
              "UID" =&gt; uid,
              "PID" =&gt; pid,
              "PPID" =&gt; ppid,
              "C" =&gt; c,
              "STIME" =&gt; stime,
              "TTY" =&gt; tty,
              "TIME" =&gt; time,
              "CMD" =&gt; cmd
            }
          end
        end
        if entry &amp;&amp; entry["UID"] == "0"
          original_line = line.strip
          entry["original_line"] = original_line if original_line.length &gt; 0
          processes.push(entry)
        end
      end
      event.set("processes", processes)
      event.remove("message")
      event.remove("event")
    '
  }
}

output {
  stdout { codec =&gt; rubydebug }
}<p>Este ejemplo emplea el plugin de entrada <code>exec</code> para ejecutar el comando <code>ps -ef</code> cada 60 segundos. El filtro Ruby procesa la salida, extrayendo campos relevantes como UID, PID, PPID, uso de la CPU (C), hora de inicio (STIME), TTY, tiempo total de CPU (TIME) y el comando (CMD) ejecutado. Funciona bien en mi entorno macOS, pero puede que tengas que ajustar los patrones regex para que coincidan con el formato de salida del comando <code>ps -ef</code> en tu sistema.</p><h3>Emplea librerías integradas</h3><p>El plugin de filtro Ruby permite usar librerías Ruby integradas, que pueden ser muy útiles para diversas tareas. Por ejemplo, puedes usar la librería <code>json</code> para analizar cadenas JSON o la librería <code>date</code> para manipular fechas.</p><p>Aquí tienes un ejemplo de cómo usar la librería <code>json</code> para analizar una cadena JSON almacenada en un campo:</p>require 'json'

def filter(event)
  json_string = event.get('message')
  parsed_json = JSON.parse(json_string)
  event.set('parsed_json', parsed_json)
  return [event]
end<p>Para evitar tener que usar la librería cada vez, deberías externalizar tu código Ruby para poder usar la sentencia <code>require</code> al principio de tu script de filtro Ruby. Esto cargará la biblioteca una vez y la pondrá disponible para tu script.</p><p>Para comprobar qué librerías están disponibles en tu entorno, puedes listar las bibliotecas integradas ejecutando el siguiente código en el filtro Ruby:</p>Gem.loaded_specs.sort_by { |name, _| name }.each do |name, spec|
  puts "#{name}: #{spec.version}"
end<p><strong>Nota: </strong>Las bibliotecas integradas no son soportadas oficialmente por Logstash, y su comportamiento puede cambiar o puede que no estén disponibles en versiones futuras. Úsalos bajo tu propia responsabilidad.</p><h2>Conclusión</h2><p>El filtro Ruby de Logstash te permite personalizar y ampliar las capacidades de tus pipelines de Logstash. En esta publicación, cubrimos lo básico del uso del filtro Ruby y proporcionado ejemplos avanzados de uso.</p><p>Aprovechando el filtro Ruby, puedes manejar tareas complejas de procesamiento de datos que requieren lógica personalizada o manipulación avanzada. Ya sea que trabajes con estructuras de datos anidadas, divisiones de eventos o analizes y conviertas textos complejos/no estructurados en JSON estructurado, el filtro Ruby ofrece flexibilidad para satisfacer tus necesidades específicas.</p><p>Esperamos que esta guía te proporcionó el conocimiento e inspiración para explorar todo el potencial del filtro Ruby de Logstash. ¡Feliz guion!</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/ruby-scripting-logstash</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/ruby-scripting-logstash</guid>
    <category><![CDATA[Datos de índice]]></category>
    <category><![CDATA[Ruby]]></category>
    <dc:creator><![CDATA[Dai Sugimori]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt503d18396642e73e/6a17f62aaf47b68527cde121/b1bcd63c033ccbde102c20ba3085f165f9289a71-1600x1000.png" length="0" type="image/png"/>
    <pubDate>Tue, 24 Jun 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Cómo migrar tu aplicación Ruby de OpenSearch a Elasticsearch]]></title>
    <description><![CDATA[Una guía para migrar una base de código Ruby del cliente OpenSearch al cliente Elasticsearch.]]></description>
    <content:encoded><![CDATA[<p>El cliente Ruby de OpenSearch fue bifurcado desde el cliente Ruby de Elasticsearch en la versión <code>7.x</code>, por lo que las bases de código son relativamente similares. Esto significa que, al migrar una base de código Ruby de OpenSearch a Elasticsearch, el código de las respectivas bibliotecas cliente resultará muy familiar. En esta entrada del blog, voy a mostrar un ejemplo de aplicación Ruby que emplea OpenSearch y los pasos para migrar este código a Elasticsearch.</p><p>Ambos clientes están lanzados bajo la popular Apache License 2.0, por lo que son software de código abierto y libre. La licencia de Elasticsearch se actualizó recientemente y el núcleo de Elasticsearch y Kibana se publica bajo la licencia de código abierto aprobada por la OSI AGPL desde la versión 8.16.</p><h2>Considerando la versión de Elasticsearch al migrar la aplicación Ruby</h2><p>Una consideración al migrar es qué versión de Elasticsearch se va a emplear. Recomendamos usar la última versión estable, que en el momento de escribir esto es <code>8.17.0</code>. Las versiones menores del cliente Rubí de Elasticsearch siguen a las versiones menores de Elasticsearch. Así que para Elasticsearch <code>8.17.x</code>, puedes usar la versión <code>8.17.x</code> de la gema Rubí.</p><p>OpenSearch se bifurcó a partir de Elasticsearch 7.10.2. Así que las APIs pueden cambiar y se podrían usar diferentes funciones en cualquiera de las dos. Pero eso está fuera del alcance de esta publicación, y solo voy a analizar las operaciones más comunes en una aplicación de ejemplo.</p><p>Para Ruby on Rails, puedes usar el cliente oficial de Elasticsearch o las <a href="https://github.com/elastic/elasticsearch-rails/">bibliotecas de integración de Rails</a>. Recomendamos migrar a la última versión estable de Elasticsearch y del cliente, respectivamente. La versión <code>elasticsearch-rails</code> gema <code>8.0.0</code> soportar Rails <code>6.1</code>, <code>7.0</code> y <code>7.1</code> y Elasticsearch <code>8.x</code>.</p><h2>El código</h2><p>Para este ejemplo, seguí los pasos para <a href="https://opensearch.org/docs/latest/install-and-configure/install-opensearch/tar/">instalar OpenSearch desde un tarball</a>. Luego de descargar y extraer el tarball, necesitaba establecer una contraseña inicial de administrador que usaré más adelante para instanciar el cliente.</p><p>Creé un directorio con una <code>Gemfile</code> que se ve así:</p>source 'https://rubygems.org'

gem 'opensearch-ruby'<p>Luego de ejecutar <code>bundle install</code>, se instala la gema para mi proyecto. Esta versión de opensearch-ruby instalada <code>3.4.0</code> y la versión de OpenSearch que estoy usando es <code>2.18.0</code>. Escribí el código en un archivo <code>example_code.rb</code> en el mismo directorio. El código inicial en este archivo es la instancia de un cliente OpenSearch:</p>require 'opensearch'

client = OpenSearch::Client.new(
  host: 'https://localhost:9200',
  user: 'admin',
  password: ENV['OPENSEARCH_INITIAL_ADMIN_PASSWORD'],
  transport_options: { ssl: { verify: false } }
)<p>La opción de transporte <code>ssl: { verify: false}</code> el parámetro se está pasando según la guía de usuario para facilitar las pruebas. En producción, esto debería configurar dependiendo del despliegue de OpenSearch.</p><p>Desde la versión 2.12.0 de OpenSearch, la variable de entorno <code>OPENSEARCH_INITIAL_ADMIN_PASSWORD</code> debe establecer con una contraseña fuerte al ejecutar el script de instalación. Siguiendo los pasos para instalar OpenSearch desde un tarball, exporté la variable en mi consola y ahora está disponible para mi script Ruby.</p><p>Una API sencilla para cerciorar de que el cliente se conecta a OpenSearch es usar la API <code>cluster.health</code> :</p>puts 'HEALTH:'
pp client.cluster.health<p>Y efectivamente funciona:</p>$ be ruby example_code.rb
HEALTH:
{"cluster_name"=&gt;"opensearch",
"status"=&gt;"yellow",
 "timed_out"=&gt;false,
 "number_of_nodes"=&gt;1,
 "number_of_data_nodes"=&gt;1,<p>Probé algunos de los ejemplos comunes que tenemos en la documentación del cliente Ruby de Elasticsearch, y funcionan como se espera:</p>index = 'books'
puts 'Creating index'
response = client.indices.create(index: index)
puts response
# Creating index
# {"acknowledged"=&gt;true, "shards_acknowledged"=&gt;true, "index"=&gt;"books"}

puts 'Indexing a document'
document = { title: 'The Time Machine', author: 'H. G. Wells', year: 1895 }
response = client.index(index: index, body: document, refresh: true)
puts response
# Indexing document
# {"_index"=&gt;"books", "_id"=&gt;"esalT5MB4vnuJz5TtqOc", "_version"=&gt;1, "result"=&gt;"created", "forced_refresh"=&gt;true, "_shards"=&gt;{"total"=&gt;2, "successful"=&gt;1, "failed"=&gt;0}, "_seq_no"=&gt;0, "_primary_term"=&gt;1}

id = response['_id']
puts 'Getting document'
response = client.get(index: index, id: id)
puts response
# Getting document
# {"_index"=&gt;"books", "_id"=&gt;"esalT5MB4vnuJz5TtqOc", "_version"=&gt;1, "_seq_no"=&gt;0, "_primary_term"=&gt;1, "found"=&gt;true, "_source"=&gt;{"title"= &gt;"The Time Machine", "author"=&gt;"H. G. Wells", "year"=&gt;1895}}

puts "Does an index exist?"
puts client.indices.exists(index: 'imaginary_index')
# Does an index exist?
# false

puts 'Processing Bulk request'
body = [
  { index: { _index: 'books', data: { name: 'Leviathan Wakes', author: 'James S.A. Corey', release_date: '2011-06-02', page_count: 561 } } },
  { index: { _index: 'books', data: { name: 'Hyperion', author: 'Dan Simmons', release_date: '1989-05-26', page_count: 482 } } },
  { index: { _index: 'books', data: { name: 'Dune', author: 'Frank Herbert', release_date: '1965-06-01', page_count: 604 } } },
  { index: { _index: 'books', data: { name: 'Dune Messiah', author: 'Frank Herbert', release_date: '1969-10-15', page_count: 331 } } },
  { index: { _index: 'books', data: { name: 'Children of Dune', author: 'Frank Herbert', release_date: '1976-04-21', page_count: 408 } } },
  { index: { _index: 'books', data: { name: 'God Emperor of Dune', author: 'Frank Herbert', release_date: '1981-05-28', page_count: 454 } } },
  { index: { _index: 'books', data: { name: 'Consider Phlebas', author: 'Iain M. Banks', release_date: '1987-04-23', page_count: 471 } } },
  { index: { _index: 'books', data: { name: 'Pandora\'s Star', author: 'Peter F. Hamilton', release_date: '2004-03-02', page_count: 768 } } },
  { index: { _index: 'books', data: { name: 'Revelation Space', author: 'Alastair Reynolds', release_date: '2000-03-15', page_count: 585 } } },
  { index: { _index: 'books', data: { name: 'A Fire Upon the Deep', author: 'Vernor Vinge', release_date: '1992-06-01', page_count: 613 } } },
  { index: { _index: 'books', data: { name: 'Ender\'s Game', author: 'Orson Scott Card', release_date: '1985-06-01', page_count: 324 } } },
  { index: { _index: 'books', data: { name: '1984', author: 'George Orwell', release_date: '1985-06-01', page_count: 328 } } },
  { index: { _index: 'books', data: { name: 'Fahrenheit 451', author: 'Ray Bradbury', release_date: '1953-10-15', page_count: 227 } } },
  { index: { _index: 'books', data: { name: 'Brave New World', author: 'Aldous Huxley', release_date: '1932-06-01', page_count: 268 } } },
  { index: { _index: 'books', data: { name: 'Foundation', author: 'Isaac Asimov', release_date: '1951-06-01', page_count: 224 } } },
  { index: { _index: 'books', data: { name: 'The Giver', author: 'Lois Lowry', release_date: '1993-04-26', page_count: 208 } } },
  { index: { _index: 'books', data: { name: 'Slaughterhouse-Five', author: 'Kurt Vonnegut', release_date: '1969-06-01', page_count: 275 } } },
  { index: { _index: 'books', data: { name: 'The Hitchhiker\'s Guide to the Galaxy', author: 'Douglas Adams', release_date: '1979-10-12', page_count: 180 } } },
  { index: { _index: 'books', data: { name: 'Snow Crash', author: 'Neal Stephenson', release_date: '1992-06-01', page_count: 470 } } },
  { index: { _index: 'books', data: { name: 'Neuromancer', author: 'William Gibson', release_date: '1984-07-01', page_count: 271 } } },
  { index: { _index: 'books', data: { name: 'The Handmaid\'s Tale', author: 'Margaret Atwood', release_date: '1985-06-01', page_count: 311 } } },
  { index: { _index: 'books', data: { name: 'Starship Troopers', author: 'Robert A. Heinlein', release_date: '1959-12-01', page_count: 335 } } },
  { index: { _index: 'books', data: { name: 'The Left Hand of Darkness', author: 'Ursula K. Le Guin', release_date: '1969-06-01', page_count: 304 } } },
  { index: { _index: 'books', data: { name: 'The Moon is a Harsh Mistress', author: 'Robert A. Heinlein', release_date: '1966-04-01', page_count: 288 } } }
]
puts client.bulk(body: body, refresh: true)
# Processing Bulk request
# {"took"=&gt;38, "errors"=&gt;false, "items"=&gt;[{"index"=&gt;{"_index"=&gt;"books", "_id"=&gt;" ...

query = { query: { multi_match: { query: 'dune', fields: ['name'] } } }
puts 'Search results'
response = client.search(index: index, body: query)
puts response
# Search results
# {"_index"=&gt;"books", "_id"=&gt;"oEawT5MBOXHuGXdEu5Wu", "_score"=&gt;2.2886353, "_source"=&gt;{"name"=&gt;"Dune", "author"=&gt;"Frank Herbert", "release_date"=&gt;"1965-06-01", "page_count"=&gt;604}}
# {"_index"=&gt;"books", "_id"=&gt;"oUawT5MBOXHuGXdEu5Wu", "_score"=&gt;1.8893257, "_source"=&gt;{"name"=&gt;"Dune Messiah", "author"=&gt;"Frank Herbert", "release_date"=&gt;"1969-10-15", "page_count"=&gt;331}}
# {"_index"=&gt;"books", "_id"=&gt;"okawT5MBOXHuGXdEu5Wu", "_score"=&gt;1.6086557, "_source"=&gt;{"name"=&gt;"Children of Dune", "author"=&gt;"Frank Herbert", "release_date"=&gt;"1976-04-21", "page_count"=&gt;408}}
# {"_index"=&gt;"books", "_id"=&gt;"o0awT5MBOXHuGXdEu5Wu", "_score"=&gt;1.40059, "_source"=&gt;{"name"=&gt;"God Emperor of Dune", "author"=&gt;"Frank Herbert", "release_date"=&gt;"1981-05-28", "page_count"=&gt;454}}

puts 'Updating document'
document = { title: 'Walkaway', author: 'Cory Doctorow', release_date: '2017' }
response = client.index(index: index, body: document, refresh: true)
id = response['_id']
response = client.update(index: index, id: id, body: { doc: { release_date: '2017-04-26' } })
puts response
# Updating document
# {"_index"=&gt;"books", "_id"=&gt;"degnZJMBIGr4X0Yim55L", "_version"=&gt;2, "result"=&gt;"updated", "_shards"=&gt;{"total"=&gt;2, "successful"=&gt;1, "failed"=&gt;0}, "_seq_no"=&gt;26, "_primary_term"=&gt;1}

puts 'Retrieveing multiple documents'
response = client.search(index: index, body: { query: { match_all: {} }, size: 3, stored_fields: '_id' })
ids = response['hits']['hits']
ids.map { |a| a.delete('_score') }
response = client.mget(body: { docs: [{ _index: index, _id: ids }] })
puts response
# Retrieveing multiple documents
# {"docs"=&gt;[{"_index"=&gt;"books", "_id"=&gt;"qeg2ZJMBIGr4X0YiiqD2", "_version"=&gt;1, "_seq_no"=&gt;0, "_primary_term"=&gt;1, "found"=&gt;true, "_source"=&gt;{"title"=&gt;"The Time Machine", "author"=&gt;"H. G. Wells", "year"=&gt;1895}}, {"_index"=&gt;"books", "_id"=&gt;"q-g2ZJMBIGr4X0Yii6Ah", "_version"=&gt;1, "_seq_no"=&gt;1, "_primary_term"=&gt;1, "found"=&gt;true, "_source"=&gt;{"name"=&gt;"Leviathan Wakes", "author"=&gt;"James S.A. Corey", "release_date"=&gt;"2011-06-02", "page_count"=&gt;561}}, {"_index"=&gt;"books", "_id"=&gt;"rOg2ZJMBIGr4X0Yii6Ah", "_version"=&gt;1, "_seq_no"=&gt;2, "_primary_term"=&gt;1, "found"=&gt;true, "_source"=&gt;{"name"=&gt;"Hyperion", "author"=&gt;"Dan Simmons", "release_date"=&gt;"1989-05-26", "page_count"=&gt;482}}]}

puts "Count #{client.count(index: index)['count']}"
puts 'Deleting by query'
response = client.delete_by_query(index: index, body: { query: { match: { author: 'Robert A. Heinlein' } } }, refresh: true)
puts response
puts "Count #{client.count(index: index)['count']}"
# Count 26
# Deleting by query
# {"took"=&gt;16, "timed_out"=&gt;false, "total"=&gt;2, "deleted"=&gt;2, "batches"=&gt;1, "version_conflicts"=&gt;0, "noops"=&gt;0, "retries"=&gt;{"bulk"=&gt;0, "search"=&gt;0}, "throttled_millis"=&gt;0, "requests_per_second"=&gt;-1.0, "throttled_until_millis"=&gt;0, "failures"=&gt;[]}
# Count 24

puts 'Deleting document'
response = client.delete(index: index, id: id)
puts response
# Deleting document
# {"_index"=&gt;"books", "_id"=&gt;"nEawT5MBOXHuGXdEu5WA", "_version"=&gt;2, "result"=&gt;"deleted", "_shards"=&gt;{"total"=&gt;2, "successful"=&gt;1, "failed"=&gt;0}, "_seq_no"=&gt;25, "_primary_term"=&gt;1}

puts 'Deleting index'
response = client.indices.delete(index: index)
puts response
# Deleting index
# {"acknowledged"=&gt;true}<h2>Migración de la aplicación Ruby a Elasticsearch</h2><p>El primer paso es agregar <code>elasticsearch-ruby</code> en el archivo gema. Tras ejecutar <code>bundle install</code>, se instalará la gema cliente Ruby de Elasticsearch. Si quieres probar tu código antes de migrar completamente, puedes dejar la gema <code>opensearch-ruby</code> ahí inicialmente.</p><p>El siguiente paso importante será la instanciación del cliente. Esto va a depender de cómo ejecutes Elasticsearch. Para mantener un enfoque similar en estos ejemplos, sigo los pasos de <a href="https://www.elastic.co/downloads/elasticsearch">Download Elasticsearch</a> y lo ejecuto localmente.</p><p>Al ejecutar <code>bin/elasticsearch</code>, Elasticsearch se iniciará con las funciones de seguridad configuradas automáticamente. Cerciórate de copiar la contraseña del usuario elástico (pero puedes restablecerla ejecutando <code>bin/elasticsearch-reset-password -u elastic</code>). Si sigues este ejemplo, cerciórate de detener OpenSearch antes de iniciar Elasticsearch, ya que se ejecutan en el mismo puerto.</p><p>Al principio de <code>example_code.rb</code>, comenté la instancia del cliente OpenSearch y agregué la instancia para un cliente Elasticsearch:</p># require 'opensearch'

# client = OpenSearch::Client.new(
#   host: 'https://localhost:9200',
#   user: 'admin',
#   password: ENV['OPENSEARCH_INITIAL_ADMIN_PASSWORD']
#   transport_options: { ssl: { verify: false } }
# )

require 'elasticsearch'

client = Elasticsearch::Client.new(
  host: 'https://localhost:9200',
  user: ENV['ELASTICSEARCH_USER'],
  password: ENV['ELASTICSEARCH_PASSWORD'],
  transport_options: { ssl: { verify: false } }
)<p>Como puedes ver, el código es casi idéntico en este escenario de pruebas. Variará según el despliegue de Elasticsearch y cómo decidas conectarte y autenticarte con él. Lo mismo aplica aquí que en OpenSearch en cuanto a seguridad, la opción de no verificar SSL es solo para fines de prueba y no debe usar en producción.</p><p>Una vez configurado el cliente, vuelvo a ejecutar el código con: <code>bundle exec ruby example_code.rb</code>. ¡Y todo simplemente funciona!</p><h2>Problemas de depuración de migración</h2><p>Dependiendo de las APIs que use tu aplicación, existe la posibilidad de que recibas un error al ejecutar tu código contra Elasticsearch si las APIs de OpenSearch divergen. La <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/rest-apis.html">documentación de las APIs REST</a> es una referencia esencial para obtener información detallada sobre cómo emplear las APIs. Cerciórate de consultar la documentación de la versión de Elasticsearch que estás usando. También puedes consultar la <a href="https://rubydoc.info/gems/elasticsearch-api"><code>Elasticsearch::API</code></a> referencia.</p><p>Algunos errores que puedes encontrar en Elasticsearch podrían ser:</p><ul><li><p><code>ArgumentError: Required argument '&lt;ARGUMENT&gt;' missing</code> - Esto es un error de cliente y se generará cuando una solicitud tenga un parámetro requerido.</p></li><li><p><code>Elastic::Transport::Transport::Errors::BadRequest: [400] {"error":{"root_cause":[{"type":"illegal_argument_exception","reason":"request [/example/_doc] contains unrecognized parameter: [test]"}]...</code> Este error proviene de Elasticsearch y significa que el código del cliente está usando un parámetro que Elasticsearch no reconoce para la API que se está empleando.</p></li></ul><p>El cliente de Elasticsearch generará errores de Elasticsearch con el mensaje de error detallado enviado por el servidor. Así que, para parámetros o endpoints no soportados, el error debería informarte qué es diferente.</p><h2>Conclusión</h2><p>Como demostramos con este código de ejemplo, la migración de una aplicación Ruby de OpenSearch a Elasticsearch no es demasiado compleja desde el punto de vista de Ruby. Debes estar atento al versionado y a cualquier posible divergencia de APIs entre los motores de búsqueda. Pero para las acciones más comunes, el principal cambio al migrar clientes está en la instancia. Ambos son similares en ese aspecto, pero la forma en que se definen el host y las credenciales varía en función de cómo se despliega la pila. Una vez que el cliente está configurado y verifica que se conecta a Elasticsearch, puedes reemplazar el cliente OpenSearch sin problemas por el cliente Elasticsearch.
</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/ruby-opensearch-elasticsearch-migration</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/ruby-opensearch-elasticsearch-migration</guid>
    <category><![CDATA[Ruby]]></category>
    <dc:creator><![CDATA[Fernando Briano]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7f2ce1fb5cbf7d70/6a17e1dd148009bc02b486af/47756f629737d47c4430860ea23366a8d24c90d9-1280x720.jpg" length="0" type="image/jpeg"/>
    <pubDate>Fri, 13 Dec 2024 00:00:00 GMT</pubDate>
  </item>
  </channel>
</rss>