<?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/jp/search-labs/blog/category/ruby-programming</link>
    </image>
    <link>https://www.elastic.co/jp/search-labs/blog/category/ruby-programming</link>
    <atom:link href="https://www.elastic.co/jp/search-labs/rss/category/ruby-programming.xml" rel="self" type="application/rss+xml"/>
    <language><![CDATA[jp]]></language>
    <lastBuildDate>Mon, 28 Sep 2026 11:49:16 GMT</lastBuildDate>
  <item>
    <title><![CDATA[Elasticsearch Ruby クライアント向け ES|QL クエリビルダーのご紹介]]></title>
    <description><![CDATA[Elasticsearch Ruby クライアント用に最近リリースされた ES|QL クエリ ビルダーの使用方法を学習します。Ruby コードを使用して ES|QL クエリをより簡単に構築するためのツール。]]></description>
    <content:encoded><![CDATA[<p>最近、Apache 2 ライセンスに基づいて公開された Ruby gem、 <a href="https://github.com/elastic/esql-ruby/"><code>elastic-esql</code></a>をリリースしました。この gem を使用すると、Elastic の<a href="https://www.elastic.co/docs/explore-analyze/query-filter/languages/esql">ES|QL</a>クエリを慣用的な Ruby で構築し、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>gem は<code>to_s</code>メソッドでコードを 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>このツールは、 <code>enrich</code>や<code>sort</code>などの追加ステップを ES|QL 関数に連鎖させる便利な方法を提供します。<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>生成されたクエリは、アプリケーションが Elasticsearch API (Ruby かどうかに関係なく) と対話するあらゆる方法で<a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-esql-query"><code>esql.query</code></a> API で使用できます。<code>elastic-esql</code>を使用してクエリを構築すると、生成された文字列をリクエスト本文の<code>query</code>パラメータとして API に送信できます。 </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>このライブラリは現在開発中であり、最終的な API はまだ完成していません。現在はテクニカルプレビューとしてリリースされています。現在の API または一般的な使用方法に関してフィードバックがある場合は、遠慮なく<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 パイプラインでの高度なデータ変換を実現する Logstash Ruby フィルター プラグインについて学習します。]]></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 は現在最先端のデータ処理エンジンではないにもかかわらず、データ処理パイプラインに Logstash を使用している顧客をよく見かけます。複雑なデータ操作やカスタム ロジックに関しては、標準フィルターの制限に悩まされることがよくあります。このような場合、Ruby フィルターはこれらの課題を克服するのに役立ちます。</p><p>Ruby フィルターは、標準の Logstash フィルターが特定の要件を満たせない場合に役立ちます。一般的な使用例をいくつか紹介します。</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>ディレクトリに配置する必要があります。あるいは、Logstash を手動で起動するときに<code>-f</code>オプションを使用して構成ファイルへのパスを指定し、パイプラインを簡単に試すことができます。</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 のフィルター ステージ中に作成された追加フィールドが含まれます。これらのフィールドには、 <code>event.get()</code>や<code>event.set()</code>などの Logstash イベント API を介してアクセスできます。このサンプルコードでは、 <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 のハッシュオブジェクトではないことに注意してください。イベント API の詳細については、<a href="https://www.elastic.co/docs/reference/logstash/event-api">この公式ドキュメント</a>をご覧ください。</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>1つのイベントを複数のイベントに分割する</h3><p>Ruby フィルターを使用して、単一のイベントを複数のイベントに分割することもできます。これは、アイテムの配列を含む単一のイベントがあり、アイテムごとに個別のイベントを作成する場合に便利です。</p><p>Elasticsearch の取り込みパイプラインも Beats/Elastic Agent のプロセッサもイベントの分割をサポートしていないことに注意してください。これは Logstash の最も強力な使用例の 1 つです。</p><h4>分割フィルター付き</h4><p><code>split</code>フィルターを使用すると、指定されたフィールドに基づいてイベントを複数のイベントに分割できます。ただし、分割中に追加の変換やロジックを実行する必要がある場合は、分割フィルターと組み合わせて Ruby フィルターを使用できます。</p><p>次の例では、RSS フィードが 1 行の 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 フィルターを使用して 1 つのイベントを複数のイベントに分割する方法の例を次に示します。入力と出力は前の例と同じです。</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>メソッドはイベントの配列を返す必要があることに注意してください。受信したイベント オブジェクトを複製して配列に追加することで複数のイベントを返すことも、1 つの要素を持つ配列として 1 つのイベントを返すこともできます。</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>入力プラグインを使用して、Unix 系システムで実行中のすべてのプロセスを一覧表示する<code>ps -ef</code>コマンドを実行する例です。出力は 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 環境では問題なく動作しますが、システムの<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 フィルター スクリプトの先頭で<code>require</code>ステートメントを使用できるように Ruby コードを外部化する必要があります。これにより、ライブラリが一度読み込まれ、スクリプトで使用できるようになります。</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 のライセンスは最近更新され、Elasticsearch と Kibana のコアはバージョン 8.16 以降、OSI 承認のオープン ソース ライセンス AGPL に基づいて公開されています。</p><h2>Rubyアプリの移行時にElasticsearchのバージョンを考慮する</h2><p>移行時に考慮すべき点の 1 つは、どのバージョンの Elasticsearch を使用するかということです。最新の安定リリース (この記事の執筆時点では<code>8.17.0</code>を使用することをお勧めします。Elasticsearch Ruby クライアントのマイナー バージョンは、Elasticsearch のマイナー バージョンに従います。したがって、Elasticsearch <code>8.17.x</code>では、Ruby gem のバージョン<code>8.17.x</code>を使用できます。</p><p>OpenSearch は Elasticsearch 7.10.2 からフォークされました。そのため、API が変更され、どちらでも異なる機能が使用される可能性があります。しかし、それはこの記事の範囲外なので、サンプル アプリで最も一般的な操作についてのみ説明します。</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/">tarball から OpenSearch をインストールする</a>手順に従いました。tarball をダウンロードして解凍した後、後でクライアントをインスタンス化するために使用する初期管理者パスワードを設定する必要がありました。</p><p>次のような<code>Gemfile</code>を持つディレクトリを作成しました:</p>source 'https://rubygems.org'

gem 'opensearch-ruby'<p><code>bundle install</code>を実行すると、プロジェクトに gem がインストールされます。インストールされた opensearch-ruby バージョン<code>3.4.0</code>と、実行している 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>環境変数を強力なパスワードに設定する必要があります。tarball から OpenSearch をインストールする手順に従って、コンソールで変数をエクスポートし、Ruby スクリプトで使用できるようになりました。</p><p>クライアントが OpenSearch に接続していることを確認するための簡単な API は、 <code>cluster.health</code> 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 は自動的に構成されたセキュリティ機能で起動します。必ず elastic ユーザーのパスワードをコピーしてください (ただし、 <code>bin/elasticsearch-reset-password -u elastic</code>を実行してリセットできます)。この例に従う場合は、OpenSearch と Elasticsearch は同じポートで実行されるため、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 のデプロイメントと、Elasticsearch との接続および認証の方法によって異なります。セキュリティに関しては OpenSearch と同じことが当てはまります。SSL を検証しないオプションはテスト目的のみであり、本番環境では使用しないでください。</p><p>クライアントがセットアップされたら、 <code>bundle exec ruby example_code.rb</code>を使用してコードを再度実行します。そして、すべてがうまく機能します!</p><h2>移行問題のデバッグ</h2><p>アプリケーションが使用している API によっては、OpenSearch の API が異なる場合、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 から発生し、クライアント コードが、使用されている API に対して Elasticsearch が認識しないパラメータを使用していることを意味します。</p></li></ul><p>Elasticsearch クライアントは、サーバーから送信された詳細なエラー メッセージとともに、Elasticsearch からのエラーを発生させます。したがって、サポートされていないパラメータやエンドポイントの場合でも、エラーによって何が異なっているかが通知されるはずです。</p><h2>まとめ</h2><p>このサンプル コードで示したように、Ruby アプリを OpenSearch から Elasticsearch に移行することは、Ruby 側から見るとそれほど複雑ではありません。バージョン管理と、検索エンジン間で異なる可能性のある API に注意する必要があります。しかし、最も一般的なアクションでは、クライアントを移行する際の主な変更はインスタンス化にあります。その点ではどちらも似ていますが、ホストと資格情報の定義方法は、スタックの展開方法によって異なります。クライアントがセットアップされ、Elasticsearch に接続していることを確認したら、OpenSearch クライアントを 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>