<?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/cn/search-labs/blog/category/ruby-programming</link>
    </image>
    <link>https://www.elastic.co/cn/search-labs/blog/category/ruby-programming</link>
    <atom:link href="https://www.elastic.co/cn/search-labs/rss/category/ruby-programming.xml" rel="self" type="application/rss+xml"/>
    <language><![CDATA[cn]]></language>
    <lastBuildDate>Mon, 28 Sep 2026 15:07:26 GMT</lastBuildDate>
  <item>
    <title><![CDATA[介绍用于 Elasticsearch Ruby 客户端的 ES|QL 查询生成器]]></title>
    <description><![CDATA[了解如何使用最近发布的用于 Elasticsearch Ruby 客户端的 ES|QL 查询生成器。这是一款使用 Ruby 代码更轻松地构建 ES|QL 查询的工具。]]></description>
    <content:encoded><![CDATA[<p>我们最近发布了<a href="https://github.com/elastic/esql-ruby/"><code>elastic-esql</code></a> ，这是一个根据 Apache 2 许可证发布的 Ruby gem。有了这个 gem，你就可以用惯用的 Ruby 语言创建 Elastic 的<a href="https://www.elastic.co/docs/explore-analyze/query-filter/languages/esql">ES|QL</a>查询，然后将其用于 ES|QL 查询 API。ES|QL 允许开发人员通过查询过滤、转换和分析存储在 Elasticsearch 中的数据。它使用"管道" (<code>|</code> ) 来逐步处理数据。该 gem 使用 Ruby 函数，你可以将这些函数链入原始对象，以建立更复杂的查询：</p><p><strong>ESQL：</strong></p><p><strong>鲁比</strong></p>Elastic::ESQL.from('sample_data').limit(2).sort('@timestamp').descending<h2>安装</h2><p>该 gem 可通过以下方式从 RubyGems 安装：</p>gem install elastic-esql<p>或者将其添加到项目的 Gemfile 中：</p>gem 'elastic-esql'<h2>使用方法</h2><p>您可以一次性建立一个完整的查询，也可以使用<code>from</code> 或<code>row</code> 等源命令创建一个查询对象，然后使用 ES|QL 方法链在其上建立查询。</p>query = Elastic::ESQL.from('sample_data')
query.limit(2).sort('@timestamp')<p>在<code>to_s</code> 方法中，gem 将代码转换为 ES|QL，因此在打印输出或转换为字符串时会返回 ES|QL 查询：</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>您可以使用<code>!</code> 中每个函数的对应函数来实例化查询对象并更改其初始状态：</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>该工具提供了将额外步骤链入 ES|QL 函数的便捷方法，如<code>enrich</code> 和<code>sort</code> 。一旦在<code>Elastic::ESQL</code> 对象上调用<code>enrich</code> ，就可以将<code>on</code> 和<code>with</code> 与之连锁：</p>esql.enrich!('policy').on('a').with({ name: 'language_name' })<p>在使用<code>sort</code> 之后，您还可以将<code>desc</code> 、<code>asc</code> 、<code>nulls_first</code> 和<code>nulls_last</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>它还支持自定义字符串，以备您自己编写 ES|QL 查询或使用尚未添加到库中的功能。<code>custom</code> 会在查询结束时连接字符串。它将在发送到函数时添加这些字符，而不会添加任何管道字符。它们将通过一个空格字符与查询的其余部分合并。</p>esql = Elastic::ESQL.from('sample_data')
esql.custom('| MY_VALUE = "test value"').to_s
# =&gt; 'FROM sample_data | MY_VALUE = "test value"'<p>您还可以将<code>custom</code> 功能串联起来：</p>esql.custom('| MY_VALUE = "test value"').custom('| ANOTHER, VALUE')
'FROM sample_data | MY_VALUE = "test value" | ANOTHER, VALUE'<h2>在 Ruby 客户端使用 ES|QL 查询生成器</h2><p>您可以通过发送查询对象，直接在<a href="https://github.com/elastic/elasticsearch-ruby">elasticsearch-ruby</a>和<code>esql.query</code> API 中使用查询生成器：</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>您还可以将其与 Elasticsearch Ruby 客户端中的 ES|QL Helper 结合使用，<a href="https://www.elastic.co/search-labs/blog/esql-ruby-helper-elasticsearch">了解更多详情</a>：</p>require 'elasticsearch/helpers/esql_helper'

Elasticsearch::Helpers::ESQLHelper.query(client, query)<h2>作为独立工具</h2><p>该 gem 被设计为一个独立工具，用于以惯用方式构建 ES|QL 查询。它没有运行时依赖性，可以与官方 Elasticsearch Ruby 客户端一起使用，也可以单独使用。</p><p>生成的查询可在<a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-esql-query"><code>esql.query</code></a> API 中使用，无论应用程序以何种方式与 Elasticsearch API 交互（无论是否使用 Ruby）。使用<code>elastic-esql</code> 创建查询后，生成的字符串就可以作为<code>query</code> 请求正文中的参数发送给应用程序接口。 </p><p>我曾写过一篇关于<a href="https://www.elastic.co/search-labs/blog/elasticsearch-ruby-tools">将 Elasticsearch 与流行的 Ruby 工具结合使用的</a>文章。该 gem 可与任何流行的 Ruby 工具一起使用，以通过 ES|QL 查询 Elasticsearch。</p><h2>结论</h2><p>该库正在积极开发中，最终的应用程序接口尚未完成。目前发布的是技术预览版。如果您对当前的应用程序接口或一般使用方法有任何反馈，请随时<a href="https://github.com/elastic/esql-ruby/issues">打开新问题</a>。有关 Ruby ES|QL 查询生成器的更多信息，请参阅<a href="https://github.com/elastic/esql-ruby/?tab=readme-ov-file#ruby-esql-query-builder">README</a>。</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[在 Logstash 中使用 Ruby 脚本]]></title>
    <description><![CDATA[了解 Logstash Ruby 过滤器插件，以便在 Logstash 管道中进行高级数据转换。]]></description>
    <content:encoded><![CDATA[<p>Logstash 是一个数据处理管道，可从多个来源摄取数据，对其进行转换，然后发送到您选择的目的地。过滤器插件是这一过程的关键；当数据通过管道时，它们会对数据执行特定操作。</p><p>Logstash 包含多个内置过滤器，可用于解析、丰富和修改数据等常见任务。但有时，您会遇到需要自定义逻辑的情况，这超出了这些标准过滤器所能提供的范围。这就是<a href="https://www.elastic.co/docs/reference/logstash/plugins/plugins-filters-ruby">Ruby 过滤器插件的</a>用武之地。</p><p><strong>Ruby 过滤器插件允许您在 Logstash 管道中直接执行自定义 Ruby 代码。</strong>如果标准过滤器还不够用，Ruby 过滤器还能让您处理复杂的数据转换、实施自定义业务逻辑或与外部系统集成。</p><p>在本博客中，我们将探讨如何从基础到高级使用 Ruby 过滤器。</p><h2>何时使用 Ruby 过滤器？</h2><p>作为 Elastic 的咨询架构师，我经常看到客户在数据处理管道中使用 Logstash，尽管现在它还不是最先进的数据处理引擎。当涉及到复杂的数据操作或自定义逻辑时，他们往往会因为标准过滤器的局限性而苦恼。在这种情况下，Ruby 过滤器可以帮助克服这些挑战。</p><p>当标准 Logstash 过滤器无法满足您的特定要求时，Ruby 过滤器就会派上用场。下面是一些常见的使用案例：</p><ul><li><p><strong>深度嵌套数据操作</strong>：修改复杂的 JSON 结构、数组中的数组或根据内容动态重组数据</p></li><li><p><strong>高级字符串处理</strong>：从非结构化文本中解析和提取结构化数据</p></li><li><p><strong>实施复杂的业务逻辑</strong>：创建需要条件逻辑、循环或复杂计算的自定义转换</p></li></ul><h2>基本用法</h2><p>让我们从一个简单的例子开始，了解 Ruby 过滤器是如何工作的。</p><h3>配置 Ruby 过滤器</h3><p>创建 Logstash 管道时，应将配置文件放在<code>/etc/logstash/conf.d</code> 目录中。或者，你也可以使用<code>-f</code> 选项，在手动启动 Logstash 时指定配置文件的路径，这样就可以轻松试验你的管道了。</p>$ ./bin/logstash -f /path/to/your_pipeline.conf<p>配置文件的扩展名应为<code>.conf</code> 。</p><p>要使用 Ruby 过滤器，请在 Logstash 管道配置 (*.conf) 文件的过滤器部分定义<code>ruby</code> 过滤器。下面是一个基本例子：</p>filter {
  ruby {
    code =&gt; "
      event.set('new_field', 'Hello from Ruby!')
    "
  }
}<p>此内联 Ruby 过滤器在 Logstash 配置中定义了一个 Ruby 过滤器实例。<code>code</code> 参数提供了 Logstash 将为该过滤器处理的每个事件执行的内联 Ruby 脚本。在该脚本中，有一个<code>event</code> 变量可用于表示事件本身。事件对象包含发送到 Logstash 的原始数据以及在 Logstash 过滤阶段创建的任何附加字段。您可以通过 Logstash 事件 API 访问这些字段，如<code>event.get()</code> 和<code>event.set()</code> 。在此示例代码中，<code>event.set('new_field', 'Hello from Ruby!')</code> 将名为<code>new_field</code> 的新字段设置为字符串值<code>Hello from Ruby!</code> 。您可以根据需要在<code>code</code> 块中添加任何其他代码。</p><p>请注意，这个<code>event</code> 对象虽然是键值类型的数据容器，但它并不是通常的 Ruby 哈希对象。查看<a href="https://www.elastic.co/docs/reference/logstash/event-api">此官方文档</a>，了解有关事件 API 的更多信息。</p><h3>外部化 Ruby 脚本</h3><p>对于简单的转换，内联 Ruby 代码非常方便。但是，对于复杂的逻辑或可重复使用的功能，建议将代码移到外部 Ruby 脚本中。这样可以提高可维护性，并保持 Logstash 管道配置的整洁。</p><p>首先，创建 Ruby 脚本并将其保存为<code>my_ruby_script.rb</code> 。脚本必须定义一个处理事件的<code>filter</code> 方法。它将一个事件对象作为参数，该对象代表正在处理的当前事件。<code>filter</code> 方法需要返回一个要发射的事件数组。要删除事件，返回空数组。</p><p>例如，下面的脚本读取<code>message</code> 字段，计算其长度，并将结果存储到名为<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>接下来，使用<code>path</code> 选项设置 Ruby 过滤器配置，以引用脚本。这会告诉 Logstash 加载并执行外部脚本。使用外部脚本时，请确保文件存在并具有正确的权限。</p>filter {
  ruby {
    path =&gt; "/path/to/my_ruby_script.rb"
  }
}<p>现在，每个事件都会被传递到<code>my_ruby_script.rb</code> 中的<code>filter</code> 方法，并由它进行处理。</p><p>这种方法能帮助你更有效地管理复杂的逻辑，使测试、调试和重用 Ruby 代码变得更容易。</p><h2>高级用法</h2><p>在本节中，我们将探讨在 Logstash 中使用 Ruby 过滤器的一些高级示例。这些示例将演示如何使用 Ruby 执行数据转换、丰富事件和实现自定义逻辑。</p><h3>操作嵌套数据结构</h3><p>Logstash 事件是 Logstash 处理的核心数据结构。它可以包含各种字段，包括嵌套数据结构，如数组和哈希值。通过 Ruby 过滤器，您可以轻松处理这些嵌套结构。</p><p>Ruby 过滤器可以处理嵌套数据结构，如哈希值和数组，允许你修改或添加这些结构中的字段。这在处理 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>此示例在输入数据中包含一个嵌套 JSON 对象。Ruby 过滤器通过添加新的键值对来修改嵌套数据。标准 Logstash 过滤器无法对嵌套数据进行此类操作，因此 Ruby 过滤器成为复杂数据结构的便捷选择。</p><h3>将单个事件拆分为多个事件</h3><p>Ruby 过滤器还可用于将单个事件分割成多个事件。当您有一个包含项目数组的单个事件，并希望为每个项目创建单独的事件时，这个功能就非常有用。</p><p>请注意，Elasticsearch 的摄取管道和 Beats/Elastic Agent 的处理器都不支持拆分事件。这是 Logstash 最强大的用例之一。</p><h4>带分体式过滤器</h4><p>您可以使用<code>split</code> 过滤器，根据指定字段将一个事件拆分成多个事件。不过，如果需要在分割过程中执行额外的转换或逻辑，可以将 Ruby 过滤器与分割过滤器结合使用。</p><p>在下面的示例中，我们将 RSS 订阅作为一行 XML 文本。它包含多个<code>&lt;item&gt;</code> 元素。Ruby 过滤器用于从 XML 中提取<code>&lt;item&gt;</code> 元素，并将其存储在名为<code>items</code> 的新字段中。然后，根据<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>输出结果如下</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>您可能已经注意到，在这种情况下，<code>ruby</code> 过滤器并不是必不可少的。<code>split</code> 过滤器可用于根据<code>items</code> 字段将事件拆分成多个事件，而<code>mutate</code> 过滤器可用于删除不必要的字段。不过，如果需要在分割过程中执行额外的转换或逻辑，可以使用 Ruby 过滤器。</p><h4>使用内联 Ruby 脚本</h4><p>您还可以使用内联 Ruby 脚本，通过<code>event.clone</code> 方法和<code>new_event_block variable</code> ，将单个事件拆分成多个事件，如<code>new_event_block.call(new_event)</code> 。这样，您就可以根据原始事件创建新事件，同时保留其数据。</p><p>下面是一个如何使用 Ruby 过滤器将单个事件分割成多个事件的示例。输入和输出与上一个示例相同。</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>使用外部 Ruby 脚本</h4><p>您还可以使用外部 Ruby 脚本将单个事件分割成多个事件。</p><p>配置文件：</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>Ruby 脚本需要外部化为<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>请记住，<code>filter</code> 方法必须返回一个事件数组。您可以通过克隆传入的事件对象并将其添加到数组中来返回多个事件，也可以将单个事件作为包含一个元素的数组来返回。</p>return events
# or
# return [event]<p>这样就可以将单个事件拆分成多个事件。</p><h3>执行外部命令并解析其输出结果</h3><p>Logstash exec 输入插件允许您执行外部命令，其输出将成为 Logstash 的事件。命令的输出将存储在事件的<code>message</code> 字段中。</p><p>通常情况下，系统命令的输出是人类可读的，但并不是 Logstash 可以轻松解析的 JSON 或其他格式的结构。要处理这种情况，可以使用 Ruby 过滤器解析输出并从中提取信息。</p><p>下面是一个使用<code>exec</code> 输入插件执行<code>ps -ef</code> 命令的示例，该命令可列出类 Unix 系统上所有正在运行的进程。Ruby 过滤器将对输出进行解析，以提取每个进程的相关信息。</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>本例使用<code>exec</code> 输入插件，每 60 秒运行一次<code>ps -ef</code> 命令。Ruby 过滤器处理输出，提取相关字段，如 UID、PID、PPID、CPU 使用率 (C)、开始时间 (STIME)、TTY、CPU 总时间 (TIME) 和执行的命令 (CMD)。它在我的 macOS 环境中运行良好，但你可能需要调整 regex 模式，以匹配你系统中<code>ps -ef</code> 命令的输出格式。</p><h3>使用内置库</h3><p>Ruby 过滤器插件允许您使用内置的 Ruby 库，这对各种任务都非常有用。例如，您可以使用<code>json</code> 库解析 JSON 字符串，或使用<code>date</code> 库处理日期。</p><p>下面是一个使用<code>json</code> 库解析存储在字段中的 JSON 字符串的示例：</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>为了避免每次都需要使用库，您应该将 Ruby 代码外部化，以便在 Ruby 过滤器脚本的开头使用<code>require</code> 语句。这将加载一次库，使其可在脚本中使用。</p><p>要检查环境中哪些库可用，可以在 Ruby 过滤器中运行以下代码，列出内置库：</p>Gem.loaded_specs.sort_by { |name, _| name }.each do |name, spec|
  puts "#{name}: #{spec.version}"
end<p><strong>注意： </strong>Logstash 官方不支持内置库，它们的行为可能会发生变化，或者在未来版本中不可用。使用风险自负。</p><h2>结论</h2><p>Logstash Ruby 过滤器允许您自定义和扩展 Logstash 管道的功能。在本篇文章中，我们介绍了使用 Ruby 过滤器的基础知识，并提供了高级用法示例。</p><p>利用 Ruby 过滤器，您可以处理需要自定义逻辑或高级操作的复杂数据处理任务。无论您是处理嵌套数据结构、拆分事件，还是解析复杂/非结构化文本并将其转换为结构化 JSON，Ruby 过滤器都能灵活满足您的特定需求。</p><p>希望本指南能为您提供探索 Logstash Ruby 过滤器全部潜力的知识和灵感。祝您编写脚本愉快！</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[索引数据]]></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[如何将 Ruby 应用程序从 OpenSearch 迁移到 Elasticsearch]]></title>
    <description><![CDATA[将 Ruby 代码库从 OpenSearch 客户端迁移到 Elasticsearch 客户端的指南。]]></description>
    <content:encoded><![CDATA[<p>OpenSearch Ruby 客户端是从版本<code>7.x</code> 的 Elasticsearch Ruby 客户端分叉而来，因此代码库相对相似。这意味着在将 Ruby 代码库从 OpenSearch 迁移到 Elasticsearch 时，各客户端库中的代码看起来会非常熟悉。在这篇博文中，我将展示一个使用 OpenSearch 的 Ruby 应用程序示例，以及将代码迁移到 Elasticsearch 的步骤。</p><p>这两个客户端都是根据流行的 Apache License 2.0 发布的，因此都是开源和免费软件。Elasticsearch 的许可证最近进行了更新，自 8.16 版起，Elasticsearch 和 Kibana 的核心内容均根据 OSI 批准的开源许可证 AGPL 发布。</p><h2>迁移 Ruby 应用程序时考虑 Elasticsearch 版本</h2><p>迁移时的一个考虑因素是将使用哪个版本的 Elasticsearch。我们建议使用最新的稳定版本，即<code>8.17.0</code> 。Elasticsearch Ruby 客户端的次版本与 Elasticsearch 的次版本一致。因此，对于 Elasticsearch<code>8.17.x</code> ，可以使用版本为<code>8.17.x</code> 的 Ruby gem。</p><p>OpenSearch 源自 Elasticsearch 7.10.2。因此，应用程序接口可能已经发生了变化，不同的功能可以在其中任何一个上使用。但这不在本篇文章的讨论范围内，我只想在一个示例应用程序中了解最常见的操作。</p><p>对于 Ruby on Rails，可以使用官方 Elasticsearch 客户端或<a href="https://github.com/elastic/elasticsearch-rails/">Rails 集成库</a>。我们建议分别迁移到 Elasticsearch 和客户端的最新稳定版本。<code>elasticsearch-rails</code> gem 版本<code>8.0.0</code> 支持 Rails<code>6.1</code> 、<code>7.0</code> 和<code>7.1</code> 以及 Elasticsearch<code>8.x</code> 。</p><h2>代码</h2><p>在本例中，我按照步骤<a href="https://opensearch.org/docs/latest/install-and-configure/install-opensearch/tar/">从 tar 包中安装 OpenSearch</a>。下载并解压 tar 包后，我需要设置一个初始管理员密码，稍后将使用该密码实例化客户端。</p><p>我创建了一个带有<code>Gemfile</code> 的目录，看起来像这样：</p>source 'https://rubygems.org'

gem 'opensearch-ruby'<p>运行<code>bundle install</code> 后，我的项目安装了 gem。这就安装了<code>3.4.0</code> 版本的 opensearch-ruby，而我运行的 OpenSearch 版本是<code>2.18.0</code> 。我在同一目录下的<code>example_code.rb</code> 文件中编写了代码。该文件中的初始代码是 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>传输选项<code>ssl: { verify: false}</code> 参数是根据用户指南传递的，以便于测试。在生产中，应根据 OpenSearch 的部署情况进行设置。</p><p>自 OpenSearch 2.12.0 版起，在运行安装脚本时，必须将<code>OPENSEARCH_INITIAL_ADMIN_PASSWORD</code> 环境变量设置为强密码。按照从压缩包中安装 OpenSearch 的步骤，我在控制台中导出了变量，现在我的 Ruby 脚本可以使用它了。</p><p>使用<code>cluster.health</code> API 是确保客户端连接 OpenSearch 的一个简单 API：</p>puts 'HEALTH:'
pp client.cluster.health<p>它确实有效：</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>我测试了 Elasticsearch Ruby 客户端文档中的一些常见示例，它们都能按预期运行：</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>将 Ruby 应用程序迁移到 Elasticsearch</h2><p>第一步是在 Gemfile 中添加<code>elasticsearch-ruby</code> 。运行<code>bundle install</code> 后，Elasticsearch Ruby 客户端 gem 将被安装。如果想在完全迁移前测试代码，可以先保留<code>opensearch-ruby</code> gem。</p><p>下一个重要步骤是客户端实例化。这取决于你运行 Elasticsearch 的方式。为了在这些示例中保持相似的方法，我按照<a href="https://www.elastic.co/downloads/elasticsearch">下载 Elasticsearch</a>并在本地运行的步骤进行操作。</p><p>运行<code>bin/elasticsearch</code> 时，Elasticsearch 会自动配置安全功能。确保复制了弹性用户的密码（但可以通过运行<code>bin/elasticsearch-reset-password -u elastic</code> 重置）。如果您遵循这个示例，请确保在启动 Elasticsearch 之前停止 OpenSearch，因为它们运行在同一个端口上。</p><p>在<code>example_code.rb</code> 的开头，我注释掉了 OpenSearch 客户端的实例化，并添加了 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>正如您所看到的，在这个测试场景中，代码几乎完全相同。它将根据 Elasticsearch 的部署以及您决定与之连接和验证的方式而有所不同。这里与 OpenSearch 的安全问题相同，不验证 ssl 的选项仅用于测试目的，不应在生产中使用。</p><p>设置好客户端后，我再次运行代码，使用：<code>bundle exec ruby example_code.rb</code>.一切都很顺利！</p><h2>调试迁移问题</h2><p>根据您的应用程序使用的 API，如果 OpenSearch 的 API 与 Elasticsearch 的不同，您在针对 Elasticsearch 运行代码时可能会收到错误信息。<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/rest-apis.html">REST API 文档</a>是了解如何使用 API 的详细信息的重要参考资料。请务必查看您正在使用的 Elasticsearch 版本的文档。您也可以参阅<a href="https://rubydoc.info/gems/elasticsearch-api"><code>Elasticsearch::API</code></a> 参考资料。</p><p>您可能会从 Elasticsearch 中遇到以下错误：</p><ul><li><p><code>ArgumentError: Required argument '&lt;ARGUMENT&gt;' missing</code> - 这是客户端错误，当请求缺少一个必填参数时就会出现。</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> 该错误来自 Elasticsearch，这意味着客户端代码使用了 Elasticsearch 无法识别的 API 参数。</p></li></ul><p>Elasticsearch 客户端会从 Elasticsearch 引发错误，并附带服务器发送的详细错误信息。因此，即使是不支持的参数或端点，错误也会告诉你有什么不同。</p><h2>结论</h2><p>正如我们在本示例代码中演示的那样，从 Ruby 端将 Ruby 应用程序从 OpenSearch 迁移到 Elasticsearch 并不复杂。您需要了解搜索引擎之间的版本和任何潜在的不同应用程序接口。但就最常见的操作而言，迁移客户端时的主要变化在于实例化。它们在这方面都很相似，但主机和凭证的定义方式因堆栈的部署方式而异。一旦设置好客户端，并验证它连接到 Elasticsearch，就可以用 Elasticsearch 客户端无缝替换 OpenSearch 客户端。
</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>