<?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[Logs Analytics - Elastic Observability Labs]]></title>
    <description><![CDATA[Trusted security news & research from the team at Elastic.]]></description>
    <copyright><![CDATA[© 2026. Elasticsearch B.V. All Rights Reserved]]></copyright>
    <image>
      <title><![CDATA[Logs Analytics - Elastic Observability Labs]]></title>
      <url>https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltad972c1c27dbefc6/6a88d9782904ea5e8511d473/observability-labs-thumbnail.png</url>
      <link>https://www.elastic.co/observability-labs/blog/category/logs-analytics</link>
    </image>
    <link>https://www.elastic.co/observability-labs/blog/category/logs-analytics</link>
    <atom:link href="https://www.elastic.co/observability-labs/rss/category/logs-analytics.xml" rel="self" type="application/rss+xml"/>
    <language><![CDATA[en]]></language>
    <lastBuildDate>Sat, 24 Oct 2026 09:05:09 GMT</lastBuildDate>
  <item>
    <title><![CDATA[The Great Cat Escape: A hands-on guide to OpenTelemetry, Elastic APM, and Kibana alerting]]></title>
    <description><![CDATA[Six cats are plotting their escape and you get one alert to stop them. Instrument a Python game with OpenTelemetry, ship it to Elastic APM, and the ES|QL rule you write decides whether you catch the worst offender before it hits ten attempts.]]></description>
    <content:encoded><![CDATA[<p>In this article, you instrument a Python game, <em>The Great Cat Escape</em>, with OpenTelemetry (OTel) and then ship the telemetry to Elastic APM. Next, you write an Elasticsearch Query Language (ES|QL) rule in Kibana that fires before any cat reaches 10 escape attempts, as each cat picks up speed at its own pace. Swap <code>cat.name</code> for <code>user.id</code> and escape attempts for failed logins, and the same rule catches credential stuffing.</p><h2>How The Great Cat Escape game works</h2><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt41e16f2d9f236018/6ad5f6ec293d9ba55d66469f/image1.gif" alt="Great Cat Escape game lobby with three cats at 0/10 escape attempts in a geofenced yard" /><p>In this detective game, the rules that you eventually create in your Elastic cluster govern whether you win or lose against these cats. Each cat creeps toward the fence at its own accelerating pace. If any cat hits 10 escape attempts, that cat is free and you lose your detective license. Your job, gumshoe, should you choose to accept it, is to configure an Elastic Observability alert that fires before any cat hits 10. The earlier it fires, the higher your score.</p><h3> Requirements: Elastic Observability, Python, and a tunnel</h3><ul><li><p>Your own fork/branch of <a href="https://github.com/sophia-solo/game-escape-room"><em>The Great Cat Escape</em></a></p></li><li><p><a href="https://www.elastic.co/observability?utm_campaign=G-TXT-AMER-NA-Observability-EN-Lead_Gen-CloudTrials-BR&amp;utm_content=Brand-Observability&amp;utm_source=google&amp;utm_medium=cpc&amp;device=c&amp;utm_term=elastic%20observability&amp;utm_id=701610000005lJVAAY&amp;gad_source=1&amp;gad_campaignid=22944792373&amp;gbraid=0AAAAADrDgoIX_tJArIvcTI26Z_gBKsLDF&amp;gclid=Cj0KCQjwz9_QBhD_ARIsADnSCfArNrhUzwJcETVZ5ZToA3I4QN5Y98jhWuF_ArGlMVT0ZoaaFPjs1NIaAg_IEALw_wcB">Elastic Observability</a> v. 9.3.0 or higher (get a <a href="https://www.elastic.co/cloud/serverless">seven-day free trial of Elastic Cloud Serverless</a>), </p></li><li><p><a href="https://www.python.org/downloads/">Python</a> v. 3.11.0 or higher (latest version)</p></li><li><p><a href="https://ngrok.com/download/mac-os">ngrok</a> or a similar tunnel, since Kibana's webhook needs a way to reach your laptop</p></li></ul><p><em>The Great Cat Escape</em> game is governed through <code>python-dotenv</code> with credentials from an environment (<code>.env</code>) file. Change these to match your Elastic cluster, and make sure that the connection is through Elastic APM. You can input your <a href="https://www.elastic.co/docs/solutions/observability/apm/secret-token#apm-create-secret-token">secret token</a> through your agent by going to <strong>Fleet</strong> → <strong>Agent Policies</strong> → click on the policy you want to change → <strong>Elastic APM</strong> → <strong>Settings</strong> → and then scroll down until you see "Agent Authorization."</p><p>To populate your environment, take a copy of the <code>.env.example</code> and rename it <code>.env</code>. Your <code>.env</code> file needs two values:</p>ELASTIC_APM_HOST=your-host.apm.us-central1.gcp.cloud.es.io
ELASTIC_SECRET_TOKEN=your_token
NUM_CATS=3<p><code>NUM_CATS</code> is optional (default 1, max 6). More cats means more chaos, but more chaos means a potentially higher score for you, detective.</p><h2>Instrumenting a Python app with OpenTelemetry for Elastic APM</h2><p>Catching rogue cats is hard. What should we trigger on? What should be measured? Why? To answer these questions, we need to know what our cats are doing at any given time. That’s where instrumentation comes in. In observability, instrumentation is the process of adding code or configurations to your application that can generate and collect telemetry data, as well as export it. This telemetry data can include metrics and logs, along with traces. So let’s get into the code that will help your investigation.</p><p>To instrument our application, we’ll use OTel, which is a standardized framework for generating, collecting, and exporting telemetry data. It’s the industry standard for observability. Before we get started, we need to define a <code>resource</code> in our project. In OTel instrumentation, a <code>resource</code> represents the entity that’s producing telemetry data, which, in this case, is our “<code>escape-room-cat</code>” service.</p>SERVICE_NAME = "escape-room-cat"
resource = Resource.create({"service.name": SERVICE_NAME})<p>The “<code>service.name</code>” key is a standard semantic convention from OTel. <em>Semantic conventions</em> are standardized and vendor-neutral naming rules used throughout OTel to structure your data. You’ll see more of these conventions as we set up our signals in the sections that follow.</p><h3>Setting up traces, metrics, and logs with OTLP exporters</h3><p>We set up our three pillars and our logs, metrics, and traces through OTel in the following code. Let’s go through the code for each signal.</p>trace_provider = TracerProvider(resource=resource)
trace_provider.add_span_processor(
    BatchSpanProcessor(
        OTLPSpanExporter(endpoint=f"{OTLP_BASE_URL}/v1/traces", headers=_AUTH_HEADER)
    )
)
trace.set_tracer_provider(trace_provider)
tracer = trace.get_tracer(SERVICE_NAME)<p>With this code, we set up the <code>TracerProvider</code> to start collecting traces, which are then exported in batches through the <code>OTLPSpanExporter</code> via the OpenTelemetry Protocol (OTLP). A <em>trace</em> is a collection of spans, and spans can be enriched with span attributes. We go over the span "<code>cat.escape_attempt</code>" later in this article.</p>meter_provider = MeterProvider(
    resource=resource,
    metric_readers=[
        PeriodicExportingMetricReader(
            OTLPMetricExporter(endpoint=f"{OTLP_BASE_URL}/v1/metrics", headers=_AUTH_HEADER),
            export_interval_millis=5000,
        )
    ],
)
metrics.set_meter_provider(meter_provider)
meter = metrics.get_meter(SERVICE_NAME)<p>Next, we set up our metrics through a <code>MeterProvider</code> and a <code>PeriodicExportingMetricReader</code>. Metrics are polled on a timer according to the reader’s interval and then exported in OTLP format through <code>OTLPMetricExporter</code>, rather than being pushed when events arrive, like traces and logs. The <code>MeterProvider</code> is still necessary despite the <code>PeriodicExportingMetricReader</code> because it’s the configuration layer from the OTel SDK that actually processes and collects your telemetry and exports it to your Elastic Observability project. This is true for the <code>TracerProvider</code> and for the <code>LoggerProvider</code> (see below):</p>logger_provider = LoggerProvider(resource=resource)
logger_provider.add_log_record_processor(
    BatchLogRecordProcessor(
        OTLPLogExporter(endpoint=f"{OTLP_BASE_URL}/v1/logs", headers=_AUTH_HEADER)
    )
)
set_logger_provider(logger_provider)
logging.getLogger().addHandler(
    LoggingHandler(level=logging.INFO, logger_provider=logger_provider)
)
logging.getLogger().setLevel(logging.INFO)
logger = logging.getLogger(SERVICE_NAME)<p><code>LoggerProvider</code> works slightly differently than the rest of the signals. The code here bridges Python’s built-in <code>logging</code> module, <code>LoggingHandler</code>, with OTel, instead of creating a new logging system. This handler routes any logging.info call in the Python application into the OTel pipelines, which means that your structured log records get exported alongside the rest of your signals.</p><h3>Counters, gauges, and spans: The signals this game emits</h3><p>Now that we know how OTel works to instrument our game, let’s get into the details of the specific signal configurations that we’ll use in this application.</p><p>First, let’s go over the most important <a href="https://opentelemetry.io/docs/concepts/signals/metrics/">counter</a> (a data point that only goes up): </p>escape_attempts_counter = meter.create_counter(
    "cat.escape.attempts", description="Number of escape attempts by each cat", unit="1"
)<p>This metric is important to the game because it determines whether you win or lose. If the ”<code>cat.escape.attempts</code>” counter for any cat hits 10, you lose and the cat gets free. </p><p>The next metric is an observable gauge governed by a function from our game:</p>meter.create_observable_gauge(
    "cat.suspicion.level",
    callbacks=[_suspicion_callback],
    description="How suspicious each cat is acting (0-100)",
    unit="percent",
)
------------------------------------------------------------
cat.suspicion = min(100, cat.suspicion + random.uniform(8, 15))<p>This observable gauge meter indicates that, at each collection interval, a cat’s current suspicion level is recorded. “<code>cat.suspicion.level</code>” is governed by a function that picks a random floating number between 8 and 15 and appends that to the current suspicion level. An observable gauge represents a value that can go up and down, unlike a counter that only goes up. We’re using an observable gauge instead of a counter because we’re trying to determine <em>How suspicious is this cat right now?</em> as opposed to <em>How much suspicion has accumulated?</em> In this game, cat suspicion is another meter that you might be able to create a rule on.</p><p>Below is the central event, a span that fires every time a cat attempts to break out. This span identifies which cat it was, the escape attempt number, the cat’s position, whether the attempt was successful, and the cat’s suspicion level. The next span is also collected whenever the cat moves:</p>with tracer.start_as_current_span("cat.escape_attempt") as span:
    span.set_attribute("cat.name", cat.name)
    span.set_attribute("cat.position.x", cat.x)
    span.set_attribute("cat.position.y", cat.y)
    span.set_attribute("escape.attempt_number", attempt_num)
    span.set_attribute("escape.success", attempt_num &gt;= MAX_ATTEMPTS)
    span.set_attribute("cat.suspicion.level", cat.suspicion)
    span.set_attribute("game.phase", game.phase.value)
    escape_attempts_counter.add(1, {"cat.name": cat.name})
----------------------------------------------------------------------------------
with tracer.start_as_current_span("cat.move") as span:
        span.set_attribute("cat.name", cat.name)
        span.set_attribute("cat.position.x", cat.x)
        span.set_attribute("cat.position.y", cat.y)
        span.set_attribute("cat.distance.moved", dist)
        span.set_attribute("game.phase", game.phase.value)
        distance_traveled.add(dist, {"cat.name": cat.name})<p>When you view the traces data in Kibana, the trace and its span attributes appear as follows:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta4f8c0fff09923dc/6ad5f7ceca19d52bcbc58c1f/image3.png" alt="" /><p>The fields translated from our game are prepended with “numeric_labels” or “labels.” This is the result of our data being translated from OTel into the <a href="https://www.elastic.co/docs/reference/ecs">Elastic Common Schema</a> (ECS). However, you can rename these in Kibana, if you choose, to shorten the name or make searching easier for you. </p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt541da916a0a05576/6ad5f7e86f7ed1052dbf6207/image2.png" alt="Kibana document view of a cat.escape_attempt transaction with trace summary and correlated log" /><h3>Emitting structured logs with the Python logging module</h3><p>Logs are the most human-readable of the three signals, and this game emits them through semi-structured logging: </p>logger.warning(
f"🚨 {cat.name} escape attempt #{attempt_num}/{MAX_ATTEMPTS} "
f"at ({cat.x:.1f}, {cat.y:.1f}) near {edge_str} "
f"— suspicion={cat.suspicion:.0f}%"
       )


logger.info(f"🎮 {cat.name} begins escape plotting")


logger.error(f"🚪 {cat.name} ESCAPED! Game over.")


logger.info(
        f"✅ Alert fired! Worst cat at {worst}/{MAX_ATTEMPTS}. "
        f"Score: {game.score}. All cats caught!"
)<p>The game sends out several logs and communicates them via multiple methods. When it comes to logging, it’s generally good practice to keep your logs clear and concise so that they’re easily recognizable by anyone debugging, especially for errors. In this Flask application, we used the <code>logging</code> module by Python. The logs sent to Kibana are “<a href="http://logger.info">logger.info</a>”, “<code>logger.warning</code>”, and “<code>logger.error</code>”, and they indicate the log severity level. <code>logger.info</code> is used for generic information from your application, and <code>logger.warning</code> indicates unexpected events that the system has recovered from. <code>logger.error</code> notes a failure or critical error. In <em>The Great Cat Escape</em>, the only error is when you lose the game. These logs are emitted through the standard Python logger and exported via the OTLP log exporter, so they show structured <code>severity_text</code> and resource attributes, along with trace correlation in Kibana:</p>🚨 Mittens escape attempt #2/10 at (42.2, 62.1) near inner yard — suspicion=24%<p>The next logging system is <code>_log</code>, a custom function written for in-game use:</p>def _log(msg: str):
    ts = time.strftime("%H:%M:%S")
    with game_lock:
        game.log_lines.insert(0, f"[{ts}] {msg}")
        if len(game.log_lines) &gt; 60:
            game.log_lines.pop()
----------------------------------------------------------------------------------
_log(
f"🚨 {cat.emoji} {cat.name} attempt #{attempt_num}/{MAX_ATTEMPTS} "
f"near {edge_str} ({cat.suspicion:.0f}%)"
)
----------------------------------------------------------------------------------
_log(f"🎉 ALERT FIRED! Worst cat at {worst}/{MAX_ATTEMPTS}. Score: {game.score}")
    return jsonify({"ok": True, "score": game.score, "worst_count": worst})
----------------------------------------------------------------------------------
_log(f"🚪💨 {cat.emoji} {cat.name} ESCAPED! YOU LOSE!")
            return
----------------------------------------------------------------------------------
_log(f"{cat.emoji} {cat.name} starts pacing...")<p>This log function shows the player the same information as Kibana receives by grabbing the current time and by using <code>game_lock</code> through <code>game_lock = threading.Lock()</code>. In this game, since there are requests from multiple threads (cats), the game_lock helps by preventing data loss if two threads reach out to the same resource at the same time, which can lead to data corruption. </p><p>Now that you understand the instrumentation, the final step before you write your rules and play the game is to understand the <code>alert_fired</code> API and wire the needed webhook so that your alert can be received by your URL. </p><h2>Wiring a webhook connector for your alert</h2><p>To send out a webhook alert, you use ngrok or a similar tunnel to expose your port to Kibana, enabling it to POST to that port over the internet. You front your own port with a tunnel, and that public URL is the <code>&lt;your-tunnel&gt;</code> you point Kibana at. Add a connection of type “Webhook” in Kibana with these settings:</p>Method: POST
URL: http://&lt;your-ip&gt;:5003/api/alert_fired
Headers: Content-Type: application/json
----------------------------------------------------------------------------------
# You configure this with your rule under "Actions" later
Body:
{
  "cat_name": "{{context.group}}",
  "current_count": {{context.value}},
  "rule_name": "{{rule.name}}",
  "fired_at": "{{context.date}}"
}<p>In Kibana 9.4, the connector lives under <strong>Stack Management</strong> → <strong>Connectors</strong>. Save it once, and you can reuse it across all of your rules.</p><p>The <code>{{context.group}}</code> field shows which cat triggered the rule, and the fired_at <code>{{context date}}</code> field tells you when the alert happened. These are purely informational parameters, but the winning endpoint, <code>/api/alert_fired</code> takes more precaution with “<code>current_count</code>” and “<code>rule_name</code>.” The game's <code>/api/alert_fired</code> endpoint, shown in the following code, has several checkpoints based on the parameters above that help with specificity in this game. These checkpoints are also important to real alerting in systems to ensure that the correct source, system, or alert was sent via your alerting webhook.</p>@app.route("/api/alert_fired", methods=["POST"])
def api_alert_fired():
    # Checkpoint 1
    payload = request.get_json(silent=True) or {}


    # Checkpoint 2
    rule_name = payload.get("rule_name")
    if rule_name != EXPECTED_RULE_NAME:
        return jsonify({"ok": False, "msg": "Unexpected or missing rule_name."}), 400


    # Checkpoint 3
    count = payload.get("current_count")
    if not isinstance(count, (int, float)) or count &lt; MIN_COUNT:
        return jsonify({"ok": False, "msg": "Missing or invalid current_count."}), 400<p>The endpoint runs three checkpoints before it accepts a win, matching the numbered comments in the code above:</p><ol><li><p><strong>Parse the request body.</strong> <code>request.get_json</code> takes the body sent by the webhook and parses it into a Python dict. Passing <code>silent=True</code> means an empty or malformed body won't raise an error; the endpoint keeps working and fills in unknown values with <code>{}</code>.</p></li><li><p><strong>Check the rule name.</strong> "<code>Cat Escape Alert</code>" is the enforced <code>rule_name</code>, so a stray request from another source can't trigger a win. Use this name exactly, or change it in the code. (See <code>EXPECTED_RULE_NAME = "Cat Escape Alert"</code>.)</p></li><li><p><strong>Check the attempt count.</strong> This gate stops an alert from counting as a win when no cat has actually moved. It's set to one, so at least one escape attempt must have registered. If you want a higher bar, change it in the code. (See <code>MIN_COUNT = 1</code>.)</p></li></ol><h2>How to create a Kibana alert rule with ES|QL</h2><p>This game ends one of two ways. You win if your created rule alerts properly; and you lose if any cat reaches 10 escape attempts. </p><p>To create your rule and play the game, follow these instructions. Lettered subsections give you a full breakdown of how to create a winning rule.</p><p>1. Go to <strong>Kibana</strong> → <strong>Stack Management</strong> → <strong>Rules</strong>.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt61e52c2d888d731c/6ad5f88975da6119d73d5427/image8.png" alt="Kibana Stack Management Rules page with the Create rule button for a new Elastic APM alert" /><p>2. <strong>Create new rule</strong> → <strong>Elasticsearch query</strong>. </p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc49c341bf787cae0/6ad5f8b37963af25066ce802/image10.png" alt="Create new rule menu in Kibana with the Elasticsearch query rule type selected" /><p>3. Create a rule to alert on logs or metrics. You can use Kibana Query Language (KQL) or Lucene, Query DSL, or ES|QL.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf9cb1c29d735ce9b/6ad5f8ca7963affc756ce806/image11.png" alt="Elasticsearch query rule in Kibana showing KQL, Query DSL and ES|QL query type options" /><ul><li><p>To create a winning rule, choose ES|QL. Enter the code block below into the box for creating a query. The service for this game is “<code>escape-room-cat</code>,” and “<code>cat.escape.attempts” IS NOT NULL</code> discards every row that isn’t an <code>escape.attempts</code> measurement. Since the <code>cat.escape.attempts</code> is an OTel counter, it only climbs and doesn’t reset when you restart your game. To circumvent this counter, we take the change in the counter over the window (<code>EVAL attempts = hi - lo</code>) instead of its absolute cumulative value.</p></li><li><p>ES|QL query: </p></li></ul><ul><li><p>Change the rule schedule for every 30–45 seconds.</p></li></ul><p>4. Add an action with your configured webhook, and use the configuration from the ″A webhook connector for your alert″” section of this post.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta9c32fde9922dbfd/6ad5f90c64f7e77b95e95d67/image6.png" alt="Add an action step in a Kibana alert rule for selecting a webhook connector" /><p></p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt79cc729911461d25/6ad5f9246d19162a3115bd30/image12.png" alt="Kibana connector list with the cat-escape-game webhook connector selected for the alert action" /><p></p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3e62429db9f31f82/6ad5f93e9355c03cce7b1c74/image5.png" alt="Webhook action body in Kibana passing cat_name, current_count, rule_name and fired_at to the game" />{
"cat_name": "{{context.group}}",
"current_count": {{context.value}},
"rule_name": "{{rule.name}}",
"fired_at": "{{context.date}}"
}<p>5. Click <strong>Start Game</strong>, and wait. </p><p>6. If your alert signals in time, you’ve won, detective. You sleuthed out the right cat and caught it in time. If your rule doesn’t alert by the tenth escape attempt, you lose. Try configuring a new rule and checking that it matches the correct <code>rule_name</code> for the checkpoint (including whitespace and casing) or changing your rule’s threshold or timing.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5e553172de633b3a/6ad5f9838d329411df11ce30/image13.png" alt="Great Cat Escape win screen: all cats caught, score 2100, alert fired at 3/10 attempts" /><p></p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta60d4d6efed415ce/6ad5f991cb361982d1dbe897/image9.png" alt="Great Cat Escape game over screen after Shadow reached 10 escape attempts in 90.3 seconds" /><h2>From cat escapes to credential stuffing detection</h2><p>Beneath this fun, interactive cat-catching game is an application that demonstrates simple and correctly wired observability architecture. The pieces assembled from this application are the building blocks of a production observability stack. They include OTLP/HTTP exporters shipping to an Elastic Observability endpoint and signals stamped with a <code>service.name</code> by a <code>resource</code>, along with the separate <code>MeterProvider</code>, <code>LoggerProvider</code>, and <code>TracerProvider</code> pipelines. They also include a counter that’s emitted alongside the span which describes the same event, an observable gauge sample through a callback, ES|QL queries transformed for alerting on delta values, and more. And, since the <a href="https://www.elastic.co/blog/whats-new-elastic-9-4-0">Elastic 9.4.0</a> also allows you to natively use Prometheus Query Language (PromQL) to query time series data, you have even more query possibilities.</p><p>If you replace “escape attempt” with “failed authentication” and <code>cat.name</code> with <code>user.id</code>, the same rule that you created to catch a fence-jumping tabby can become a credential-stuffing detector. Try out different rules and types of alerting, along with other query rates, and see how creative you can be with Elastic Observability in this game. </p><p>If you need any help with <em>The Great Cat Escape</em>, feel free to contact the author.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2ec87c59a08e6319/6ad5f9af7963af07e16ce80b/image4.png" alt="Cat Escape Alert rule detail in Kibana showing 23 alerts and the cat-escape-game webhook action" /><p><em>Daylight streamed through the windows. With a robust rule in hand and an alert in the other, the detective sighed with relief. Nights of grepping had turned into quick decisions and high scores for the seasoned private eye. This case had proved difficult, but no cats had escaped the watchful observability back end. Although this investigation was successful, there would be more cases. And more cats. But now the detective was ready for them.</em></p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/esql-alert-rules-elastic-apm</link>
    <guid isPermaLink="false">esql-alert-rules-elastic-apm</guid>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[Logs Analytics]]></category>
    <dc:creator><![CDATA[Sophia Solomon]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltea410685c2335f63/6ad5f6c7bcf6e0145fe57ae3/image7.png" length="0" type="image/png"/>
    <pubDate>Sun, 18 Oct 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[One ES|QL query, 365 days of audit logs: Same search on hot data and searchable snapshots]]></title>
    <description><![CDATA[Collect audit logs with the EDOT Collector, then confirm the 365-day retention policy actually applied before an auditor asks you to.]]></description>
    <content:encoded><![CDATA[<p>The same Elasticsearch Query Language (ES|QL) query that reconstructs a user’s activity on live data runs unchanged against a mounted searchable snapshot, which is what makes a 365-day audit logging policy workable. You investigate a year-old event the way you investigate yesterday’s.</p><p>In this article, we follow six audit events from a company’s admin console through the Elastic Distribution of OpenTelemetry (EDOT) Collector into a dedicated data stream. One of them is a user named <code>alex</code>, who fails two login tries but gets in on the third and then attempts a role change. We reconstruct that timeline with one ES|QL query and read back the retention policy to confirm that it applied. Then we rerun the same query on a snapshot-backed index. The evidence comes back in a form that you can hand to an auditor and explain.</p><h2>Prerequisites</h2><ul><li><p>Elasticsearch and Kibana 9.5+</p></li><li><p>Docker, to run the <code>elastic-otel-collector:9.5</code> container</p></li><li><p>An API key created by a superuser</p></li></ul><p>You can find the companion notebook <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/observability-labs/audit-logging-elastic-stack/audit-logging-elastic-stack.ipynb">here</a>. It reproduces the whole lab from this article, including the collector configuration and the searchable snapshot, and the steps in between, and it cleans up at the end.</p><h2>What audit logs tell you that application logs don’t</h2><p>Application logs usually explain how software behaved, such as a timeout or an exception, or even a failed connection. Audit logs establish accountability by recording an actor, action, target, outcome, and time. They can help debugging, too, but their value is in reconstructing activity and demonstrating how controls operated.</p><h3>A Kubernetes RoleBinding change captured in full</h3><p>In Kubernetes, a Role defines permissions and a RoleBinding grants them to an identity within a namespace. Changes to both are auditable when the <a href="https://kubernetes.io/docs/tasks/debug/debug-cluster/audit/">audit policy</a> includes those resources. Here’s a real RoleBinding creation that was captured in an isolated Kubernetes 1.35.0 lab. It was collected through OpenTelemetry (OTel) and queried in Elasticsearch</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd6368a6e9f21787f/6acdf401cb361920b7dbcbba/image1.png" alt="Kubernetes audit log diagram: kubernetes-admin creates a RoleBinding granting deploy-bot the pod-reader role" /><p>The successful request grants the <code>deploy-bot</code> service account the existing <code>pod-reader</code> role in <code>payments</code>. The <a href="https://kubernetes.io/docs/reference/access-authn-authz/rbac/#rolebinding-and-clusterrolebinding">RoleBinding's subjects and role reference</a> come from its request body, captured by this lab's targeted <code>Request</code> audit level. <code>Metadata</code> logging alone would omit that body.</p><h3>Where audit logs come from</h3><p>Common sources cover different parts of the story:</p><p><strong>Source</strong></p><p><strong>Questions it can answer</strong></p><p>Kubernetes API audit logs</p><p>Who requested a resource change, and how did the API server respond?</p><p>Cloud provider trails</p><p>Which principal performed an API operation on which resource?</p><p>Authentication systems</p><p>Who signed in or failed authentication? Who changed privileges?</p><p>Database audit logs</p><p>Who accessed a sensitive object or changed database permissions?</p><p>Collection starts with enabling the right events at the source. Cloud data-access events may need separate configuration; database slow-query logs aren’t a complete access audit. For Kubernetes setup, use the existing <a href="https://www.elastic.co/observability-labs/blog/bringing-your-cloud-managed-kubernetes-audit-logs-into-elasticsearch">managed Kubernetes audit-log tutorial</a>.</p><h2>Four requirements for an audit logging pipeline</h2><p>Four requirements shape an audit pipeline:</p><p><strong>Requirement</strong></p><p><strong>Design decision</strong></p><p>Long retention</p><p>Keep audit data in streams with an explicit retention policy, separate from short-lived operational logs.</p><p>Immutability</p><p>Define who shouldn’t be able to alter or delete records, and for how long. A read-only index doesn’t prevent privileged administration.</p><p>Tamper-evidence</p><p>Preserve originals and independently protected verification material so changes or missing records can be detected.</p><p>Access control</p><p>Separate collection, investigation, and retention administration; record changes to those controls.</p><p>For <a href="https://www.aicpa-cima.com/resources/landing/system-and-organization-controls-soc-suite-of-services">System and Organization Controls 2 (SOC 2)</a> or <a href="https://www.iso.org/standard/27001">ISO/IEC 27001</a>, logs support the evidence behind your controls; storing them doesn’t establish compliance. Agree which activities to record, how long to retain each copy, who can retrieve it, and how exceptions or legal holds work. Neither framework gives this lab a universal 365-day retention requirement.</p><p>Search and preservation need complementary controls. For example, <a href="https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudtrail-log-file-validation-intro.html">CloudTrail integrity validation</a> uses signed digest files that must actually be validated. Where write-once retention is required, design a source archive using appropriate controls, such as <a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/object-lock.html">S3 Object Lock</a>. An indexed JSON string isn’t a substitute for that archive or its validation material.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt00cef31f4742cb6b/6acdf45d7cefb1fa34ce3582/image3.png" alt="Audit log pipeline: JSON events to EDOT Collector filelog, Elasticsearch data stream, ES|QL in Kibana" /><h2>How to collect audit logs with the EDOT Collector</h2><p>The retention walk-through uses six fictitious application-audit JSON events: four for the user <code>alex</code> and two for a second user, <code>priya</code>, so the actor filter has something to exclude. The <a href="https://www.elastic.co/docs/reference/edot-collector">EDOT Collector</a> runs as a container and ships to Elasticsearch and Kibana 9.5.3 on Elastic Cloud Hosted (ECH). The configuration is based on Elastic's documented <a href="https://www.elastic.co/docs/reference/edot-collector/config/configure-logs-collection">file log collection and JSON parsing procedure</a>: a <code>filelog</code> receiver with a <code>json_parser</code> operator and a resource processor that sets <code>data_stream.dataset</code> and <code>data_stream.namespace</code>, along with the <code>elasticsearch</code> exporter, in <code>otel</code> mapping mode. This article focuses on the decisions and results; the complete configuration and commands are in the companion notebook. </p><h3>Why the json_parser uses parse_to: attributes</h3><p>The collector parses the source timestamp and fields and then sends records to the dedicated <code>logs-audit_demo.otel-lab</code> data stream. The exporter builds that name from the dataset and namespace and appends .otel to the dataset.</p><p>Unlike the documented example, which uses <code>parse_to: body</code>, the lab's <code>json_parser</code> uses <code>parse_to: attributes</code>, so the parsed values go under <code>attributes</code> and the original JSON string stays in <code>body.text</code>. With <code>parse_to: body</code>, the fields would end up in <code>body.structured</code> and the original string would be lost. All six stored strings matched the fixture lines in this test. Elastic Common Schema–based (ECS-based) integrations can use root fields, such as <code>user.name</code>, so check the schema before reusing a query.</p><h3>Reconstruct an actor’s timeline with one ES|QL query</h3><p>In Kibana Discover, select <a href="https://www.elastic.co/docs/reference/query-languages/esql"><strong>ES|QL</strong></a>, Elastic's piped query language. The time picker uses the time zone of the <code>dateFormat:tz</code> advanced setting, which defaults to the browser's, so set it to UTC in <strong>Stack Management</strong> &gt;<strong> Advanced Settings</strong> before entering the absolute window <code>2026-09-07T04:59:00Z</code> through <code>2026-09-07T05:06:00Z</code>. Then run the following:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1d1b251b05fb2c0d/6acdf4ad2b0ddfd2d4870db3/image6.png" alt="ES|QL query returning four audit log events for user alex from the logs-audit_demo.otel-lab data stream" /><p>Notice that the last event is a failed role change. So far, we only know that alex tried to change a role and was denied, which isn’t the same as a permission change or a compromised account. To find out what happened, the next step is to look at identity-provider and resource activity within the same tenant and time window.</p><p>When comparing the screenshot with the query, keep in mind that the fixture uses UTC and Kibana displays timestamps in the browser's time zone (in this case, UTC−05:00). That’s why the rows show 00:00, while the time window is set in UTC.</p><p>Discover's time picker constrains this query; API clients need an explicit timestamp filter, and the notebook adds one. When handing evidence to a reviewer, record:</p><ul><li><p>The query.</p></li><li><p>The UTC window.</p></li><li><p>The source.</p></li><li><p>The retrieval time.</p></li><li><p>The result limit.</p></li></ul><p><code>LIMIT 100</code> is a bounded investigation view, not an exhaustive export.</p><h3>Confirm that every audit event arrived</h3><p>The data stream held six records with six distinct event IDs before and after a normal collector restart, because the <code>filelog</code> receiver keeps its file offsets and the exporter keeps its sending queue in the <code>file_storage</code> extension. At scale, also monitor source coverage, parse failures, queue saturation, and delivery lag. This six-event check is a functional test, not an ingestion benchmark or an exactly once guarantee.</p><h2>Set a log retention policy that you can verify</h2><p>A retention setting should be something that you can verify. On ECH, the built-in <code>logs-*-*</code> templates hand new data streams to the <code>logs</code> index lifecycle management (ILM) policy, so the lab gave its stream a dedicated index template, configured as follows: </p><ul><li><p>Priority 500, higher than the built-in logs templates that also match the stream name, so Elasticsearch applies this template instead of them.</p></li><li><p>Copies the built-in <code>logs-otel@template</code> component list.</p></li><li><p>Restricted to <code>logs-audit_demo.otel-*</code>.</p></li><li><p>Sets <code>index.lifecycle.prefer_ilm</code> to false.</p></li><li><p>Defines a 365-day <a href="https://www.elastic.co/docs/manage-data/lifecycle/data-stream">data stream lifecycle</a>.</p></li></ul><p>The data stream API then reported the backing index as managed by data stream lifecycle, and the lifecycle read-back reported <code>effective_retention: 365d</code>:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt84f935218424e641/6acdf4e2bcf6e02117e55c13/image7.png" alt="Kibana Dev Tools lifecycle API response showing 365d data retention on the audit log data stream" /><h3>Why your audit data stream ignores the retention policy you set</h3><p>Data stream lifecycle retention is a minimum retention mechanism based on backing-index lifecycle timing, not an exact per-event deletion deadline. A successful read-back proves configuration, not a year of availability. Rehearse retrieving the oldest required records, and review source coverage regularly. For the retention interface and ILM comparison, see <a href="https://www.elastic.co/observability-labs/blog/simplifying-retention-management-with-streams">retention management with Streams</a>.</p><h3>Capacity planning for long log retention</h3><p>Long retention also changes capacity planning. Daily ingest volume multiplied by retention days gives a starting raw volume; mappings, compression, replicas, query concurrency, and tier placement affect the actual resources needed. Size and test against representative investigations across realistic data ages.</p><h3>Query older audit logs with searchable snapshots</h3><p><a href="https://www.elastic.co/docs/deploy-manage/tools/snapshot-and-restore/searchable-snapshots">Searchable snapshots</a> keep snapshot-backed indices queryable through Elasticsearch. Fully mounted indices can serve older data from the cold tier; partially mounted indices use a shared local cache in the frozen tier. This gives recent and older evidence a common query surface, with storage and retrieval tradeoffs to test for your workload:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta2529daa1ead6e14/6acdf50b6ac96c019fa0d4e4/image5.png" alt="Searchable snapshots diagram: hot, cold and frozen tiers keeping older audit logs queryable with ES|QL" /><p>The audit lab took a snapshot of its six-event backing index and mounted it. It then reran the same actor query:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd5170c364d51f0be/6acdf5262b822ecbd992177c/image2.png" alt="The same ES|QL audit log query rerun on a mounted searchable snapshot, returning the same four events" /><p>The same four events were returned. This used full_copy on available hot nodes with an Enterprise license; it didn’t benchmark cold/frozen costs or latency. The backing snapshot must remain available while its mounted index uses it, and local cache isn’t a backup. The separate lab mount also needs its own retention and cleanup plan.</p><h2>Who can read and change your audit logs</h2><p>Use <a href="https://www.elastic.co/docs/deploy-manage/users-roles/cluster-or-deployment-auth/kibana-role-management">role-based access</a> to give investigators <code>read</code> and <code>view_index_metadata</code> on the required audit data, plus Discover read access in the intended Kibana space. Keep retention and snapshot administration in separate roles. Roles are additive, so a reader role doesn’t remove permissions granted elsewhere.</p><p>The collector's API key only carries <code>create_doc</code> and <code>auto_configure</code> on its stream, and the <a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-security-has-privileges">has privileges API</a> confirmed that it has no <code>index</code>, <code>write</code>, <code>delete</code>, or <code>manage</code> privilege there. That limits the collector's authority; it doesn’t make the records immutable against administrators. Also plan <a href="https://www.elastic.co/docs/deploy-manage/security/logging-configuration/enabling-audit-logs">Elasticsearch and Kibana audit logging</a> for the evidence platform itself, subject to deployment and subscription requirements.</p><h3>Connecting Kubernetes audit logs to container runtime data</h3><p>For the Security Labs crossover, <a href="https://www.elastic.co/security-labs/threat-command/kubernetes-audit-logs-container-escape">correlating Kubernetes audit logs with container runtime data</a> shows how API activity connects to what runs inside a workload. Stable identities and resource context make that connection possible. Our small application fixture demonstrates timeline reconstruction; it doesn’t validate those Kubernetes detection rules.</p><h2>Where to start with audit log management</h2><p>Start with one audit source and one question that you must answer months later. Verify delivery, preserve the required originals, rehearse searches across recent and retained data, and review who can change the evidence path. Elastic brings collection, structured investigation, searchable retention, and scoped access together; your policy defines the evidence that those capabilities must preserve.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/audit-logging-retention-elasticsearch</link>
    <guid isPermaLink="false">audit-logging-retention-elasticsearch</guid>
    <category><![CDATA[Logs Analytics]]></category>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[Data Management]]></category>
    <dc:creator><![CDATA[Jeffrey Rengifo]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte2fed541b53cb56d/6acdf37498f3c875c1b79c99/image4.png" length="0" type="image/png"/>
    <pubDate>Tue, 13 Oct 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[The service with the most errors was healthy: root cause analysis from logs with ES|QL]]></title>
    <description><![CDATA[Metrics ranked three services identically at 19.1%. Four ES|QL queries over the same 4,688 OpenTelemetry log records traced 955 of 956 failure chains back to one of them, and needed no service dependency model to do it.]]></description>
    <content:encoded><![CDATA[<p>Metrics ranked three services identically at 19.1%, so root cause analysis from logs had to do the ordering. Sorting error logs by volume put <code>catalog-api</code> on top with 1,820 errors, though it returned 200 to every request it received. Four <a href="https://www.elastic.co/docs/reference/query-languages/esql">ES|QL</a> queries over the same 4,688 log records found <code>ledger-service</code> instead, starting 955 of 956 failure chains with a connection pool of 8 and 0 connections available. Getting there took no service dependency model and nothing to maintain between incidents, only a <code>trace.id</code> on every record.</p>
<p>In this article we instrument four Python services with the <a href="https://www.elastic.co/docs/reference/opentelemetry/edot-sdks/python">Elastic Distribution of OpenTelemetry (EDOT)</a>, saturate one of them, and use ES|QL to recover which service failed first. Metrics tell you that something is wrong; logs tell you why, once you group them by request instead of by service.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt67a6e017c16ed9b2/6abb7a120c77f64d3feea202/02-architecture.png" alt="Four Python services under one OpenTelemetry pipeline. checkout-api calls payment-gateway, which calls ledger-service; catalog-api sits outside that path. EDOT Python exports to the Elastic Cloud Managed OTLP endpoint." /></p>
<h2 id="prerequisites">Prerequisites</h2>
<ul>
<li>Elasticsearch and Kibana <strong>9.1 or later</strong>. </li>
<li>Python 3.10 or later.</li>
<li>The <a href="https://www.elastic.co/docs/reference/opentelemetry/motlp/managed-otlp-endpoint">Elastic Cloud Managed OTLP endpoint</a> and an API key for it. Kibana shows both under Add data &gt; OpenTelemetry.</li>
</ul>
<p>A <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/observability-labs/logs-causal-graph-root-cause-esql/logs-causal-graph-root-cause-esql.ipynb">companion notebook</a> replays this incident and runs every query below, so you can follow along against your own cluster.</p>
<h2 id="rootcauseanalysisfromlogswhyerrorcountscannotbuildacausalgraph">Root cause analysis from logs: why error counts cannot build a causal graph</h2>
<p>A causal graph is a directed graph whose nodes are the failing components and whose edges record which failure produced which. Reading it is mechanical: walk the edges backwards from the service the user complained about, and the node you cannot walk back from is the root cause.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta399358c14d02ee8/6abb7a290f460d02f6d5a298/03-causal-graph-anatomy.png" alt="Anatomy of a causal graph. ledger-service has no incoming edge and is the root cause, payment-gateway propagates, checkout-api is the symptom the user sees." /></p>
<p>Causal edges can be declared in advance from a service dependency model, which covers every failure the model anticipates and has to be kept in step with the architecture as it changes. This article derives them from log fields after the fact instead. That covers one incident rather than every possible failure and needs no model to maintain.</p>
<p>Error counts alone give you nodes and no edges. Every service in a cascade logs one error per failed request, so their counts converge. Meanwhile, a healthy service that logs an error on a routine event outranks all of them as soon as that event is common enough. In this lab <code>catalog-api</code> serves less traffic than <code>checkout-api</code> and still logs almost twice as many errors, because it errors on more than half of its requests while the cascade services error on a fifth of theirs.</p>
<p>The <code>trace.id</code> field supplies the structure that counts cannot:</p>
<ul>
<li>Two errors with the same <code>trace.id</code> belong to the same request, so one may have caused the other.</li>
<li>Two errors with different <code>trace.id</code> values are unrelated, however close their timestamps.</li>
</ul>
<h2 id="fourpythonservicesinstrumentedwithedot">Four Python services instrumented with EDOT</h2>
<p><code>checkout-api</code> receives the order and calls <code>payment-gateway</code>, which calls <code>ledger-service</code>, which holds a fixed pool of 8 database connections. <code>catalog-api</code> sits outside that path and logs an ERROR on every image cache miss while still returning 200.</p>
<p>Install EDOT Python and the instrumentation for the libraries in use, following the <a href="https://www.elastic.co/docs/reference/opentelemetry/edot-sdks/python/setup">documented setup</a>:</p>
<pre><code>pip install elastic-opentelemetry flask requests
edot-bootstrap --action=install
</code></pre>
<p><code>edot-bootstrap</code> inspects the installed packages and adds the matching instrumentation, including <code>opentelemetry-instrumentation-logging</code>, which attaches trace context to log records.</p>
<p>Each service is ordinary Flask code using the standard library logger. This is <code>ledger-service</code>, the one that fails:</p>
<pre><code>POOL_SIZE = 8
ACQUIRE_TIMEOUT_S = 0.25

pool = threading.BoundedSemaphore(POOL_SIZE)
state = {"hold_ms": 15}

@app.post("/reserve")
def reserve():
    order_id = request.json.get("order_id")
    if not pool.acquire(timeout=ACQUIRE_TIMEOUT_S):
        logger.error(
            "connection pool exhausted, no connection available after %.0fms",
            ACQUIRE_TIMEOUT_S * 1000,
            extra={
                "db.connection_pool.size": POOL_SIZE,
                "db.connection_pool.available": 0,
                "error.kind": "pool_timeout",
                "order.id": order_id,
            },
        )
        return jsonify({"error": "pool_timeout"}), 503
    try:
        time.sleep(state["hold_ms"] / 1000.0)
        return jsonify({"reservation_id": f"res-{order_id}"}), 200
    finally:
        pool.release()
</code></pre>
<p>The ledger-service file contains no OpenTelemetry code. Every key in <code>extra</code> becomes a searchable field in Elasticsearch.</p>
<p>The two callers add one field; the others do not. When <code>payment-gateway</code> fails because its dependency failed, it records which dependency:</p>
<pre><code>logger.error(
    "ledger rejected reservation with status %s, cannot authorize payment",
    resp.status_code,
    extra={
        "error.kind": "authorization_failed",
        "upstream.service": "ledger-service",
        "upstream.status_code": resp.status_code,
        "order.id": order_id,
    },
)
</code></pre>
<p>Point the services at your OTLP endpoint and start them under <code>opentelemetry-instrument</code>:</p>
<pre><code>export OTEL_EXPORTER_OTLP_ENDPOINT="https://&lt;your-managed-otlp-endpoint&gt;"
export OTEL_EXPORTER_OTLP_HEADERS="Authorization=ApiKey%20&lt;your-api-key&gt;"
export OTEL_RESOURCE_ATTRIBUTES="service.name=ledger-service,deployment.environment=obs-labs-causal-graph"

opentelemetry-instrument python ledger_service.py
</code></pre>
<p><a href="https://opentelemetry.io/docs/specs/otel/protocol/exporter/">OTEL_EXPORTER_OTLP_HEADERS</a> is URL-encoded, so the space after <code>ApiKey</code> has to be written as <code>%20</code>.</p>
<p>The traffic generator drives roughly 12 checkouts and 8 product lookups per second, then raises the ledger connection hold time from 15ms to 900ms. At 8 connections and a 900ms hold, the service can serve about 8.9 requests per second, so the pool saturates and requests time out on acquire. No service returns a hardcoded error. The recorded run is 2 minutes of baseline, 4 minutes of saturation, and 1 minute of recovery, producing 4,053 successful checkouts and 956 failures.</p>
<h2 id="whatdoesatracecorrelatedlogrecordcontain">What does a trace-correlated log record contain?</h2>
<p>Sending OTLP to the Managed OTLP endpoint stores the logs in <code>logs-generic.otel-default</code> in their native OpenTelemetry shape, with no schema translation. This is what an ERROR record from the run looks like, with scope and host metadata trimmed:</p>
<pre><code>{
  "@timestamp": "2026-07-26T09:03:45.139Z",
  "resource": {
    "attributes": {
      "service.name": "ledger-service",
      "deployment.environment": "obs-labs-causal-graph"
    }
  },
  "trace_id": "f9b32dca62c1e142537413a047fb9b69",
  "span_id": "c4548cab42cbdd15",
  "severity_text": "ERROR",
  "body": { "text": "connection pool exhausted, no connection available after 250ms" },
  "attributes": {
    "error.kind": "pool_timeout",
    "order.id": "10c9e056ae8c",
    "db.connection_pool.size": 8,
    "db.connection_pool.available": 0
  }
}
</code></pre>
<p>Three details decide what the queries can do:</p>
<ul>
<li><code>trace_id</code> and <code>span_id</code> arrive without any application code setting them, because the logging instrumentation attaches the active span context to records emitted inside a request.</li>
<li>Every key in <code>extra</code> lands under <code>attributes</code> with its dots intact, and Elasticsearch exposes each one by its short name. The field the code wrote, <code>upstream.service</code>, is the field the queries use. Resource attributes work the same way, so <code>service.name</code> and <code>deployment.environment</code> are queryable as written.</li>
<li>The ECS names still resolve. <code>trace.id</code>, <code>span.id</code>, <code>log.level</code>, and <code>message</code> are built-in aliases for <code>trace_id</code>, <code>span_id</code>, <code>severity_text</code>, and <code>body.text</code>, so the queries below use whichever reads better.</li>
</ul>
<p>The four steps that follow add one piece of the graph each, starting from nodes with no edges:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc942745a92a937c1/6abb7a3cb2d1470ec27714b4/04-four-steps.png" alt="Building the graph, one query at a time. 4,688 error records split into 1,820 single-service traces, which are dropped, and 956 three-service cascades, whose upstream.service field yields two edges leading to ledger-service and its exhausted connection pool." /></p>
<h2 id="step1rankservicesbyfailurerateandbyerrorvolume">Step 1: rank services by failure rate and by error volume</h2>
<p>Start with the failure rate per service:</p>
<pre><code>FROM traces-*.otel-*
| WHERE @timestamp &gt;= "2026-07-26T08:59:00.000Z" AND @timestamp &lt; "2026-07-26T09:07:00.000Z"
    AND deployment.environment == "obs-labs-causal-graph" AND kind == "Server"
| STATS failed = COUNT(*) WHERE status.code == "Error", total = COUNT(*) BY service.name
| EVAL failed_pct = ROUND(100.0 * failed / total, 1)
| SORT failed_pct DESC
</code></pre>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8c7f79bb8e696138/6abb7a4bd1edc6031c5700a2/05-failure-rate.png" alt="ES|QL results ranking services by failure rate. checkout-api, ledger-service and payment-gateway are all at 19.1 percent with 956 failures each, catalog-api is at 0." /></p>
<p><code>checkout-api</code>, <code>ledger-service</code>, and <code>payment-gateway</code> all sit at <strong>19.1%</strong>, with 956 failures each. The rates match because in a synchronous call chain, every hop fails when the deepest hop fails. The metric names the affected services and gives no way to order them.</p>
<p>Now count ERROR records per service over the same window:</p>
<pre><code>FROM logs-*.otel-*
| WHERE @timestamp &gt;= "2026-07-26T08:59:00.000Z" AND @timestamp &lt; "2026-07-26T09:07:00.000Z"
    AND deployment.environment == "obs-labs-causal-graph" AND log.level == "ERROR"
| STATS errors = COUNT(*), traces = COUNT_DISTINCT(trace.id) BY service.name
| SORT errors DESC
</code></pre>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt68a73d7cd95b7077/6abb7a5ec6031564e215fa07/06-error-volume.png" alt="ES|QL results ranking services by error log volume, with catalog-api first at 1,820 errors and the three cascade services tied at 956." /></p>
<p><code>catalog-api</code> leads with <strong>1,820</strong> errors, almost double any other service, and it returned 200 to all 3,345 of its requests. Sorting by error count ranks services by log verbosity, which is independent of where the failure started.</p>
<h2 id="step2grouperrorlogsbytraceid">Step 2: group error logs by trace ID</h2>
<p>Group by <code>trace.id</code> and count how many distinct services logged an error inside each request:</p>
<pre><code>FROM logs-*.otel-*
| WHERE @timestamp &gt;= "2026-07-26T08:59:00.000Z" AND @timestamp &lt; "2026-07-26T09:07:00.000Z"
    AND deployment.environment == "obs-labs-causal-graph" AND log.level == "ERROR"
| STATS services = COUNT_DISTINCT(service.name) BY trace.id
| STATS traces = COUNT(*) BY services
| SORT services ASC
</code></pre>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltac874ecf5e94e954/6abb7a6ca48fccd9062f2e12/07-services-per-trace.png" alt="ES|QL results showing the trace population splitting in two, 1,820 traces with one erroring service and 956 traces with three." /></p>
<p>The population splits into two. Traces with one erroring service are local problems that never propagated; traces with three are cascades.</p>
<p>| Trace type | Traces | Services that logged an error |
| :---- | :---- | :---- |
| Single-service, never propagated | 1,820 | <code>catalog-api</code> |
| Three-service cascade | 956 | <code>checkout-api</code>, <code>ledger-service</code>, <code>payment-gateway</code> |</p>
<p><code>catalog-api</code> appears in no cascade, so the query removes the highest-volume service from the investigation without anyone reading a message.</p>
<h2 id="step3findwhichservicefailedfirstineachtrace">Step 3: find which service failed first in each trace</h2>
<p>Within a cascade, the service that logged the first error is the candidate cause. ES|QL has no window functions, so build a sortable string of timestamp plus service name, take the minimum per trace, and slice the service name back out:</p>
<pre><code>FROM logs-*.otel-*
| WHERE @timestamp &gt;= "2026-07-26T08:59:00.000Z" AND @timestamp &lt; "2026-07-26T09:07:00.000Z"
    AND deployment.environment == "obs-labs-causal-graph" AND log.level == "ERROR"
| EVAL marker = CONCAT(DATE_FORMAT("yyyy-MM-dd HH:mm:ss.SSS", @timestamp), "|", service.name)
| STATS origin_marker = MIN(marker), services = COUNT_DISTINCT(service.name) BY trace.id
| WHERE services &gt; 1
| EVAL origin_service = SUBSTRING(origin_marker, 25)
| STATS cascade_traces = COUNT(*) BY origin_service
| SORT cascade_traces DESC
</code></pre>
<p><code>MIN()</code> accepts keyword fields from 8.16.0 onward, and the formatted timestamp is fixed at 23 characters, so the earliest marker is also the lexicographically smallest one. <code>SUBSTRING(origin_marker, 25)</code> skips the timestamp and the separator.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc519f66562bf23bb/6abb7a7c6464888fb5ec8478/08-origin-service.png" alt="ES|QL results showing ledger-service as the origin in 955 cascades and checkout-api in one." /></p>
<p><code>ledger-service</code> is the origin in <strong>955 of 956</strong> cascades. <code>checkout-api</code> is the origin in one.</p>
<h3 id="howreliableistimestamporderingacrossservices">How reliable is timestamp ordering across services?</h3>
<p>Measure it rather than assuming it:</p>
<pre><code>FROM logs-*.otel-*
| WHERE @timestamp &gt;= "2026-07-26T08:59:00.000Z" AND @timestamp &lt; "2026-07-26T09:07:00.000Z"
    AND deployment.environment == "obs-labs-causal-graph" AND log.level == "ERROR"
| EVAL ms = DATE_FORMAT("yyyy-MM-dd HH:mm:ss.SSS", @timestamp)
| STATS spread_ms = DATE_DIFF("milliseconds", MIN(@timestamp), MAX(@timestamp)),
        distinct_ms = COUNT_DISTINCT(ms),
        services = COUNT_DISTINCT(service.name) BY trace.id
| WHERE services &gt; 1
| STATS cascade_traces = COUNT(*),
        median_spread_ms = MEDIAN(spread_ms),
        max_spread_ms = MAX(spread_ms),
        traces_with_a_collision = COUNT(*) WHERE distinct_ms &lt; services
</code></pre>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6b440d42bb5eef5c/6abb7a8e56f4a67a3294609f/09-timestamp-spread.png" alt="ES|QL results measuring timestamp spread across cascades: median 2ms, maximum 16ms, and 259 traces containing at least one millisecond collision." /></p>
<p>Across 956 cascades, the median spread between the first and last error is <strong>2ms</strong>, the maximum is 16ms, and <strong>259 traces</strong> (27% of the total) contain at least one millisecond collision. Collisions are this common because the hops are local HTTP calls, while <code>@timestamp</code> only has millisecond resolution.</p>
<p>A collision is not automatically a wrong answer. Alphabetically, <code>checkout-api</code> sorts before <code>ledger-service</code>, which sorts before <code>payment-gateway</code>. When <code>ledger-service</code> and <code>payment-gateway</code> land in the same millisecond, <code>MIN()</code> still returns <code>ledger-service</code>, which is the correct origin. Only a tie between <code>checkout-api</code> and <code>ledger-service</code> flips the result. That is why 259 traces contain a collision and just one of them reports the wrong origin.</p>
<p>Timestamp ordering degrades on fast hops and on hosts whose clocks disagree by more than the gap being measured. Treat it as a strong hint rather than proof.</p>
<h2 id="step4buildthecausaledgelistfromupstreamservice">Step 4: build the causal edge list from upstream.service</h2>
<p>The <code>upstream.service</code> field records which dependency the caller was waiting on, so every error carrying it is a directed edge. One <code>CONCAT</code> turns the set of them into an edge list:</p>
<pre><code>FROM logs-*.otel-*
| WHERE @timestamp &gt;= "2026-07-26T08:59:00.000Z" AND @timestamp &lt; "2026-07-26T09:07:00.000Z"
    AND deployment.environment == "obs-labs-causal-graph"
    AND log.level == "ERROR" AND upstream.service IS NOT NULL
| EVAL edge = CONCAT(upstream.service, " -&gt; ", service.name)
| STATS traces = COUNT_DISTINCT(trace.id), first_seen = MIN(@timestamp), last_seen = MAX(@timestamp) BY edge
| SORT traces DESC
</code></pre>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt724eb45ba6d84981/6abb7ab32693de58ea711dc0/10-edge-list.png" alt="ES|QL results showing the two causal edges, ledger-service to payment-gateway and payment-gateway to checkout-api, each observed in 956 traces." /></p>
<p>Two edges, each observed in all 956 cascades:</p>
<pre><code>ledger-service  -&gt; payment-gateway   956 traces
payment-gateway -&gt; checkout-api      956 traces
</code></pre>
<p>This edge list uses no timestamp comparison, so it needs no clock synchronization and no tie-breaking. It agrees with the timestamp method on all 956 traces, including the one the timestamp method got wrong.</p>
<p>|  | Timestamp ordering | Declared upstream |
| :---- | :---- | :---- |
| Code changes needed | none | one field per error log |
| Correct in this run | 955 of 956 | 956 of 956 |
| Fails when | hops are sub-millisecond, clocks drift | the field is missing |</p>
<h3 id="whichservicesfailwithoutnaminganupstream">Which services fail without naming an upstream?</h3>
<p>The root of the graph is the node that appears as a source but never as a target. Query for candidates by asking which services fail without naming an upstream:</p>
<pre><code>FROM logs-*.otel-*
| WHERE @timestamp &gt;= "2026-07-26T08:59:00.000Z" AND @timestamp &lt; "2026-07-26T09:07:00.000Z"
    AND deployment.environment == "obs-labs-causal-graph" AND log.level == "ERROR"
| STATS errors = COUNT(*), traces = COUNT_DISTINCT(trace.id),
        names_an_upstream = COUNT_DISTINCT(upstream.service) BY service.name
| WHERE names_an_upstream == 0
| SORT errors DESC
</code></pre>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt50b8af33b855e307/6abb7ac2c60315361c15fa0b/11-root-candidates.png" alt="ES|QL results listing the two services that fail without naming an upstream, catalog-api and ledger-service." /></p>
<p>This returns <code>catalog-api</code> and <code>ledger-service</code>. Intersect it with the cascade participants from step 2 and one service remains: <code>ledger-service</code>.</p>
<p>What these two queries produce is the propagation structure of one incident, not a reusable causal model. The edges carry no probabilities and say nothing about failures that did not occur during the window. They are observations, and they hold for the traces you queried.</p>
<h3 id="whattherootcauseservicereported">What the root cause service reported</h3>
<p>Now read what that service reported, keeping the pool numbers so the evidence travels with the failure mode:</p>
<pre><code>FROM logs-*.otel-*
| WHERE @timestamp &gt;= "2026-07-26T08:59:00.000Z" AND @timestamp &lt; "2026-07-26T09:07:00.000Z"
    AND deployment.environment == "obs-labs-causal-graph"
    AND log.level == "ERROR" AND service.name == "ledger-service"
| STATS events = COUNT(*), first_seen = MIN(@timestamp), last_seen = MAX(@timestamp)
        BY error.kind,
           db.connection_pool.size,
           db.connection_pool.available
| SORT events DESC
</code></pre>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta3393f108299efd1/6abb7ad45b49f84af1e3c694/12-ledger-message.png" alt="ES|QL results showing one distinct ledger-service message across 956 events, with pool size 8 and 0 connections available." /></p>
<p>One failure mode, <code>pool_timeout</code>, across all 956 events, spanning the four minutes of saturation, with <code>db.connection_pool.size</code> at 8 and <code>db.connection_pool.available</code> at 0. The message behind it, which the next query shows in full:</p>
<pre><code>connection pool exhausted, no connection available after 250ms
</code></pre>
<p>Expanding the trace where all three errors share a millisecond shows the chain intact without the timestamps:</p>
<pre><code>FROM logs-*.otel-*
| WHERE trace.id == "f9b32dca62c1e142537413a047fb9b69"
| KEEP @timestamp, service.name, log.level, message, upstream.service
| SORT @timestamp ASC
</code></pre>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta73c85202017d5b3/6abb7ae42693de5989711dc4/13-single-trace.png" alt="ES|QL results expanding one trace, showing three ERROR records sharing a millisecond and the upstream.service field naming the chain." /></p>
<p><code>upstream.service</code> reads <code>(null)</code> for <code>ledger-service</code>, <code>ledger-service</code> for <code>payment-gateway</code>, and <code>payment-gateway</code> for <code>checkout-api</code>.</p>
<h2 id="addtraceidsandstructuredfieldstoyourerrorlogs">Add trace IDs and structured fields to your error logs</h2>
<p>These queries need three things from your logging, and only one costs any effort:</p>
<ul>
<li><strong><code>trace.id</code> on every record.</strong> OpenTelemetry auto-instrumentation supplies it for logs emitted inside an active span. Access logs and background threads fall outside that scope. The Werkzeug access logs in this lab arrived with no trace context and were unusable for correlation.</li>
<li><strong>Structured fields instead of formatted strings.</strong> <code>error.kind</code> as a field lets you count failure modes. The same words inside a sentence do not.</li>
<li><strong>The upstream dependency named in the error.</strong> One key, <code>upstream.service</code>, on the errors your service logs when a dependency fails.</li>
</ul>
<p>The pattern this lab reproduces is common in production incidents: a component absorbing overflow from an upstream failure often logs more errors than the component that caused it. The same ranking problem applies to automated investigation, where trace-scoped evidence is what makes a root cause claim checkable against specific records and queries.</p>
<h2 id="conclusionwhatthelogsprovedandthemetricscouldnot">Conclusion: what the logs proved and the metrics could not</h2>
<p>Metrics reported three services broken at 19.1%. Error volume reported <code>catalog-api</code>, which was healthy. Grouping the same 4,688 log records by <code>trace.id</code> isolated 956 cascades, identified <code>ledger-service</code> as their origin, and returned the message that explained the failure.</p>
<p>Two techniques produced that result. Ordering by timestamp needs no code changes and was right 955 times out of 956, with the miss caused by millisecond ties. Reading a declared <code>upstream.service</code> field needs one extra key in your error logs and was right on every trace.</p>
<p>Neither technique needs a service dependency model, and neither one survives contact with logs that have no <code>trace.id</code>. That field is the whole investment.</p>
<h2 id="resources">Resources</h2>
<ul>
<li><a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/observability-labs/logs-causal-graph-root-cause-esql/logs-causal-graph-root-cause-esql.ipynb">Companion notebook</a> that replays the incident and runs every query in this article.</li>
<li><a href="https://www.elastic.co/observability-labs/blog/esql-llm-opentelemetry-debugging">ES|QL queries for debugging LLM latency, cost and GPU saturation</a> for more query patterns over OpenTelemetry data.</li>
<li><a href="https://www.elastic.co/docs/reference/opentelemetry/edot-sdks/python/configuration">EDOT Python configuration reference</a> to control which signals your services export.</li>
</ul>
<h2 id="tryrootcauseanalysisfromlogsonyourowndata">Try root cause analysis from logs on your own data</h2>
<ul>
<li>Run the step 2 query against your own <code>logs-*</code> data and check how many failing traces contain more than one erroring service.</li>
<li>Audit one service's error logs for whether they name the dependency that failed.</li>
</ul>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/root-cause-analysis-logs-esql</link>
    <guid isPermaLink="false">root-cause-analysis-logs-esql</guid>
    <category><![CDATA[Logs Analytics]]></category>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[APM]]></category>
    <dc:creator><![CDATA[Jeffrey Rengifo]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb64035d9fee35ef3/6abb797e4f25ca928696798c/01-header.png" length="0" type="image/png"/>
    <pubDate>Wed, 30 Sep 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[14 alerts, 1 incident: Measuring alerting rule noise with ES|QL in Elasticsearch]]></title>
    <description><![CDATA[Grouping keys decide how many alerts one incident produces, and because Kibana stamps every alert document with a rule revision, you can measure your alert noise reduction against the same incident that caused the alerts in the first place.]]></description>
    <content:encoded><![CDATA[<p>One bad deploy on a payments platform opened 18 alert episodes in seven minutes, all from one incident. A single miscalibrated rule produced 14 of those episodes across only 7 alert instances. The median episode stayed active for one minute, so most of them had recovered before anyone could open the page.</p>
<p>This is an alert noise reduction exercise done with queries. Kibana writes every alert into a hidden index and keeps it there after recovery, so we put three questions to that <a href="https://www.elastic.co/docs/explore-analyze/alerts-cases/alerts">alert history</a> with ES|QL. Was this one incident or many, where did it start, and which rule produced more noise than signal? Then we change the grouping key on the noisy rule and replay the same failure, and the same query comes back with one episode that lasts seven minutes.</p>
<p>Kibana stamps every alert document with a rule revision, so the before and after of that tuning change sit in one index. At the end, an <a href="https://www.elastic.co/docs/explore-analyze/ai-features/elastic-agent-builder">Agent Builder</a> agent answers the same questions over the same alert history.</p>
<p>Everything below was tested on Elasticsearch and Kibana 9.5.2 on Elastic Cloud, with OpenTelemetry Collector Contrib 0.157.0.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8859a2c225fa1e53/6ab8cbec4d574224fabc2701/01-architecture.png" alt="Three payments services emit logs through the OTel Collector into one Elasticsearch data stream, where three rules turn them into alert episodes stored in .alerts-*: fourteen short episodes at revision 0 against one long episode at revision 1, all queried back with ES|QL and joined against the service catalog" /></p>
<h2 id="prerequisites">Prerequisites</h2>
<ul>
<li>Elasticsearch and Kibana 9.5 or later, on <a href="https://www.elastic.co/cloud">Elastic Cloud</a> or <a href="https://www.elastic.co/docs/deploy-manage/deploy/self-managed">self-managed</a>.</li>
<li>Kibana privileges to create and edit <a href="https://www.elastic.co/docs/explore-analyze/alerts-cases/alerts/rule-type-es-query">Elasticsearch query rules</a> and <a href="https://www.elastic.co/docs/solutions/observability/incident-management/create-custom-threshold-rule">Observability custom threshold rules</a>, plus read access to the <code>.alerts-*</code> indices.</li>
<li><a href="https://github.com/open-telemetry/opentelemetry-collector-releases">OpenTelemetry Collector Contrib</a> 0.157.0 or later, and an Elasticsearch API key with ingest privileges for the OTLP endpoint.</li>
<li><a href="https://www.elastic.co/docs/explore-analyze/ai-features/elastic-agent-builder">Agent Builder</a> enabled, for the final section only. Everything before it is plain ES|QL and works without it.</li>
</ul>
<h2 id="whyalerthistorybeatsalertnotifications">Why alert history beats alert notifications</h2>
<p>A notification answers one short-lived question: who should look at this right now?</p>
<p>Most of the questions that make alerting better arrive later, and they are questions about data:</p>
<ul>
<li>Did 18 alert episodes represent 18 problems, or one incident evaluated from 18 angles?  </li>
<li>Which service showed the first symptom, and did the failure follow the dependency chain?  </li>
<li>Which rules produce short, repetitive episodes that nobody can act on?  </li>
<li>Did last month's threshold change actually reduce noise?</li>
</ul>
<p>Inside Elastic, this framing has a name: alerts as data.</p>
<p>The idea is that every alert and every state transition is a fact in time, stored with explicit semantics, so correlation becomes a query across streams rather than a bespoke feature.</p>
<p>It also matches what shows up in real operations channels: a single infrastructure failure can open dozens of alerts within seconds, and flapping alerts with miscalibrated thresholds are a familiar source of page fatigue.</p>
<p>Both problems are hard to solve from notifications and straightforward to study from preserved alert history.</p>
<h2 id="whatkibanaalertingstoresineveryalertdocument">What Kibana alerting stores in every alert document</h2>
<p>Kibana <a href="https://www.elastic.co/docs/explore-analyze/alerts-cases/alerts">alerting rules</a> write alerts-as-data documents into hidden indices that you can query like any other index.</p>
<p>The two aliases used in this article are <code>.alerts-stack.alerts-default</code> for Elasticsearch query rules and <code>.alerts-observability.threshold.alerts-default</code> for the Observability custom threshold rule.</p>
<p>The unit of storage is what we will call an episode: one document that covers one alert instance from the moment it becomes active until it recovers.</p>
<p>The fields that matter for investigation:</p>
<p>| Field | What it tells you |
| :---- | :---- |
| <code>kibana.alert.instance.id</code> | The grouping-key value this episode belongs to |
| <code>kibana.alert.start</code>, <code>kibana.alert.end</code> | When the episode opened and recovered |
| <code>kibana.alert.duration.us</code> | How long it stayed active |
| <code>kibana.alert.status</code> | <code>active</code> or <code>recovered</code> |
| <code>kibana.alert.flapping</code>, <code>kibana.alert.flapping_history</code> | Whether Kibana marked it as rapidly changing state |
| <code>kibana.alert.rule.name</code>, <code>kibana.alert.rule.revision</code> | Which rule, and which version of it |
| <code>kibana.alert.grouping</code> | The structured grouping values, for example <code>service.name</code> |
| <code>kibana.alert.reason</code> | The human-readable condition at trigger time |
| <code>kibana.alert.url</code> | A deep link back to the rule's data view |</p>
<p>Two properties make the rest of the article possible.</p>
<p>First, a new episode for the same instance creates a new document, so counting documents counts episodes.</p>
<p>Alert documents and notifications are decoupled: whether an episode also pages anyone depends on the actions attached to the rule and on their frequency, which can be every check, only on status changes, or a summary on a custom interval.</p>
<p>Our three rules have no actions attached, so nobody was paged during the lab; everything we count from here on is an alert document.</p>
<p>Second, grouping fields with ECS names such as <code>service.name</code> are copied to the top level of the alert document, which means alert history can join against other indices.</p>
<p>One caveat before you query: alert indices are wide.</p>
<p>The alerts index on this deployment maps roughly 1,400 fields, so every ES|QL query in this article projects a narrow set of columns with <code>KEEP</code>, and you should do the same, especially in tools that feed an LLM.</p>
<p>Because these are hidden system indices, the Discover editor cannot always introspect their fields, so it may flag the query with a warning or even an error badge while still running it and returning rows. Trust the result count in the toolbar, not the badge.</p>
<p>One more orientation note: Kibana screenshots below render timestamps in the browser's local time (UTC-5 in this lab), while the query result tables in the text use UTC.</p>
<h2 id="thelabhowonebaddeployproducesanalertstorm">The lab: how one bad deploy produces an alert storm</h2>
<p>To generate an honest storm, we need services that fail in a chain, not a single threshold breach.</p>
<p>The scenario: <code>fraud-scorer</code> 2.3.0 ships with slow model loading, the payments queue backs up, <code>payment-gateway</code> requests start timing out, and customers see failed checkouts on <code>payments-web</code>.</p>
<p>Every service writes structured JSON logs, and an <a href="https://github.com/open-telemetry/opentelemetry-collector-releases">OpenTelemetry Collector</a> tails them with a <code>filelog</code> receiver and ships them over OTLP/HTTP to the deployment's <a href="https://www.elastic.co/docs/reference/opentelemetry">Elastic OpenTelemetry endpoint</a> using an API key.</p>
<pre><code>receivers:
  filelog/payment_gateway:
    include:
      - ${env:LAB_DIR}/events/payment-gateway.jsonl
    start_at: end
    resource:
      service.name: payment-gateway
      service.namespace: payments
      service.version: 5.1.2
      cloud.region: us-central1
    operators:
      - id: parse_json
        type: json_parser
        parse_from: body
      - id: parse_severity
        type: severity_parser
        parse_from: attributes.severity_text
      - id: use_message_as_body
        type: move
        from: attributes.message
        to: body

exporters:
  otlphttp/elasticsearch:
    endpoint: ${env:OTEL_EXPORTER_OTLP_ENDPOINT}
    headers:
      Authorization: ApiKey ${env:ES_API_KEY}
</code></pre>
<p>A transform processor routes everything into one data stream, <code>logs-payments.stormlab-default</code>, and the records land with <code>service.name</code>, <code>log.level</code>, custom string attributes under <code>labels.*</code>, and numeric attributes under <code>numeric_labels.*</code>.</p>
<p>The full collector configuration for all three services, and a notebook that reproduces the whole lab, are in the <a href="https://github.com/elastic/elasticsearch-labs/tree/main/supporting-blog-content/observability-labs/alerts-as-data-log-derived-signals-v2">companion repository</a>.</p>
<p>We also load one small index that will do a lot of work later: a service catalog.</p>
<pre><code>PUT payments_service_catalog
{
  "settings": { "index.mode": "lookup" },
  "mappings": {
    "properties": {
      "service.name": { "type": "keyword" },
      "team":         { "type": "keyword" },
      "tier":         { "type": "keyword" },
      "depends_on":   { "type": "keyword" }
    }
  }
}
</code></pre>
<p>Three documents map each service to its owning team, tier, and upstream dependency: <code>fraud-scorer</code> depends on <code>payments-queue</code>, <code>payment-gateway</code> on <code>fraud-scorer</code>, and <code>payments-web</code> on <code>payment-gateway</code>.</p>
<pre><code>POST payments_service_catalog/_bulk
{ "index": {} }
{ "service.name": "fraud-scorer",    "team": "risk-ml",       "tier": "backend",  "depends_on": "payments-queue" }
{ "index": {} }
{ "service.name": "payment-gateway", "team": "payments-core", "tier": "edge",     "depends_on": "fraud-scorer" }
{ "index": {} }
{ "service.name": "payments-web",    "team": "storefront",    "tier": "frontend", "depends_on": "payment-gateway" }
</code></pre>
<p><code>index.mode: lookup</code> is what later allows <a href="https://www.elastic.co/docs/reference/query-languages/esql/esql-lookup-join"><code>LOOKUP JOIN</code></a> from ES|QL.</p>
<h3 id="howgroupingkeysdecidehowmanyalertsyouget">How grouping keys decide how many alerts you get</h3>
<p>The storm is observed by three alerting rules, and their grouping keys are the real subject of this article.</p>
<p>| Rule | Rule type | Grouping key | Window | Threshold |
| :---- | :---- | :---- | :---- | :---- |
| <code>gateway-5xx-per-endpoint-status</code> | Elasticsearch query rule (ES|QL mode) | <code>labels.endpoint</code>, <code>labels.status_code</code>, <code>cloud.region</code> | 1 minute | 3 or more errors |
| <code>fraud-scorer-queue-lag</code> | Elasticsearch query rule (ES|QL mode) | <code>service.name</code>, <code>cloud.region</code> | 1 minute | Max lag of 30 seconds or more |
| <code>payments-error-rate-per-service</code> | Observability custom threshold rule | <code>service.name</code> | 2 minutes | More than 5 <code>ERROR</code> documents |</p>
<p>The first rule is deliberately miscalibrated in a way we have all shipped at some point: it groups gateway errors by endpoint and status code with a one-minute window and a low threshold.</p>
<pre><code>FROM logs-payments.stormlab*
| WHERE service.name == "payment-gateway" AND log.level == "ERROR"
| STATS error_count = COUNT(*) BY labels.endpoint, labels.status_code, cloud.region
| WHERE error_count &gt;= 3
</code></pre>
<p>It runs every minute as an Elasticsearch query rule in ES|QL mode, and each returned row becomes one alert instance, so <code>/api/payments,503,us-central1</code> and <code>/api/payments/confirm,504,us-central1</code> alert separately.</p>
<p>The second rule watches the first domino directly: consumer lag on the fraud queue, reported by the service in <code>numeric_labels.queue_lag_seconds</code>.</p>
<pre><code>FROM logs-payments.stormlab*
| WHERE service.name == "fraud-scorer" AND numeric_labels.queue_lag_seconds IS NOT NULL
| STATS max_lag_seconds = MAX(numeric_labels.queue_lag_seconds) BY service.name, cloud.region
| WHERE max_lag_seconds &gt;= 30
</code></pre>
<p>The third rule is an Observability <a href="https://www.elastic.co/docs/solutions/observability/incident-management/create-custom-threshold-rule">custom threshold rule</a>: more than five <code>ERROR</code> documents in two minutes, grouped by <code>service.name</code>.</p>
<p>It represents the sane default: one stable alert instance per affected service. It also demonstrates that different rule types feed the same investigation because all of them write alerts-as-data documents.</p>
<p>All three rules carry the tag <code>alerts-as-data-v2</code>, which is how every query below isolates them.</p>
<p>Tags on rules are not cosmetic: <code>kibana.alert.rule.tags</code> is stored on every alert document, so a tag is effectively a queryable dataset label for your alert history.</p>
<h3 id="replayingtheincident18alertepisodesinsevenminutes">Replaying the incident: 18 alert episodes in seven minutes</h3>
<p>We replay the incident: three minutes of healthy baseline, the 2.3.0 deploy, queue lag climbing past the threshold, six minutes of rotating gateway 5xx bursts, customer-visible errors, then a rollback and recovery.</p>
<p>The Alerts page during the storm looks like the pager felt.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltef758fb7ee9a57f4/6ab8cbff25e2f26bda5b00c3/02-alerts-storm.png" alt="Kibana Alerts page during the storm: 12 episodes already open or recovered, six active at once, and the same endpoint and status instances flipping between Active and Recovered" /></p>
<p>Notice the table, not just the count.</p>
<p>The same instance IDs appear both as <code>Active</code> and as <code>Recovered</code> within minutes of each other, which is churn you can see but not yet measure.</p>
<p>When the dust settles, the preserved history can be queried in Discover's ES|QL mode.</p>
<p>The raw material of the storm, the error logs themselves, confirms the cascade shape:</p>
<pre><code>FROM logs-payments.stormlab*
| WHERE labels.incident_id == "payments-brownout-20260722" AND log.level == "ERROR"
| STATS errors = COUNT(*) BY minute = DATE_TRUNC(1 minutes, @timestamp), service.name
| SORT minute
</code></pre>
<p>![Discover ES|QL view of error counts per minute and service: fraud-scorer errors first, then payment-gateway at seven per minute, then payments-web]]<a href="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1ced7aef9d407283/6ab8cc24688f6518c589ec0b/04-first-symptoms.png">3</a></p>
<p>Now we leave the logs behind and interrogate only the alert history.</p>
<h2 id="howtotellifanalertstormwasoneincidentormany">How to tell if an alert storm was one incident or many</h2>
<pre><code>FROM .alerts-*
| WHERE kibana.alert.rule.tags == "alerts-as-data-v2"
| STATS episodes = COUNT(*),
        alert_instances = COUNT_DISTINCT(kibana.alert.instance.id),
        first_episode = MIN(kibana.alert.start),
        last_episode = MAX(kibana.alert.start)
  BY rule = kibana.alert.rule.name
| SORT episodes DESC
</code></pre>
<p>| rule | episodes | alert_instances | first_episode | last_episode |
| :---- | ----: | ----: | :---- | :---- |
| gateway-5xx-per-endpoint-status | 14 | 7 | 16:36:38 | 16:42:38 |
| payments-error-rate-per-service | 3 | 3 | 16:36:41 | 16:37:41 |
| fraud-scorer-queue-lag | 1 | 1 | 16:35:35 | 16:35:35 |</p>
<p>Eighteen episodes, one incident, and every one of them opened inside a seven-minute window.</p>
<p>The gateway rule alone produced 14 of the 18 episodes while tracking only 7 distinct instances, which means half of its episodes were re-opens of something that had just recovered.</p>
<p>That single row is the quantified version of a complaint that usually stays anecdotal: this rule fires more than it helps.</p>
<h2 id="howtofindwhichservicefailedfirstwithesql">How to find which service failed first with ES|QL</h2>
<p>Because the grouping fields were ECS names, each episode carries <code>service.name</code> at the top level, and we can join the alert history against the service catalog.</p>
<pre><code>FROM .alerts-*
| WHERE kibana.alert.rule.tags == "alerts-as-data-v2" AND service.name IS NOT NULL
| STATS first_symptom = MIN(kibana.alert.start) BY service.name
| LOOKUP JOIN payments_service_catalog ON service.name
| KEEP first_symptom, service.name, team, tier, depends_on
| SORT first_symptom
</code></pre>
<p>| first_symptom | service.name | team | tier | depends_on |
| :---- | :---- | :---- | :---- | :---- |
| 16:35:35 | fraud-scorer | risk-ml | backend | payments-queue |
| 16:36:41 | payment-gateway | payments-core | edge | fraud-scorer |
| 16:37:41 | payments-web | storefront | frontend | payment-gateway |</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1ced7aef9d407283/6ab8cc24688f6518c589ec0b/04-first-symptoms.png" alt="Discover ES|QL view of the first-symptom query joining alert history with the payments service catalog" /></p>
<p>The alert order matches the dependency chain exactly: the first symptom belongs to team risk-ml, the edge degraded 66 seconds later, and customers noticed a minute after that.</p>
<p>There is a subtler deduction available in this table.</p>
<p>The earliest alerting service depends on <code>payments-queue</code>, which never alerted at all, so the most likely true origin sits one hop upstream of the first visible symptom, and that is where a post-incident review should start reading logs.</p>
<p>Also note which rule is absent from this analysis: the noisy gateway rule grouped by endpoint and status code, so its episodes carry no <code>service.name</code> and cannot participate in the topology join.</p>
<p>A grouping key is the data contract for your future investigations.</p>
<h2 id="howtoscorewhichalertingruleisnoisiest">How to score which alerting rule is noisiest</h2>
<p>A noise scorecard query condenses rule quality into one table.</p>
<pre><code>FROM .alerts-*
| WHERE kibana.alert.rule.tags == "alerts-as-data-v2"
| EVAL minutes_active = COALESCE(kibana.alert.duration.us, 0) / 60000000.0
| STATS episodes = COUNT(*),
        alert_instances = COUNT_DISTINCT(kibana.alert.instance.id),
        flapping_episodes = SUM(CASE(kibana.alert.flapping == true, 1, 0)),
        median_minutes_active = MEDIAN(minutes_active)
  BY rule = kibana.alert.rule.name, revision = kibana.alert.rule.revision
| SORT episodes DESC
</code></pre>
<p>| rule | revision | episodes | instances | flapping | median minutes active |
| :---- | ----: | ----: | ----: | ----: | ----: |
| gateway-5xx-per-endpoint-status | 0 | 14 | 7 | 0 | 1.0 |
| payments-error-rate-per-service | 0 | 3 | 3 | 0 | 7.0 |
| fraud-scorer-queue-lag | 0 | 1 | 1 | 0 | 9.0 |</p>
<p>Read the last column first.</p>
<p>A median active time of one minute means the typical episode from the gateway rule was already resolving itself before the on-call could even sign in, while the two well-grouped rules produced episodes long enough to represent the actual incident.</p>
<p>The <code>flapping</code> column is Kibana's own attempt at the same problem, and it is damage control.</p>
<p>The real fix is upstream, in the rule definition, and the alert history has just told us exactly what to change: the grouping key manufactures instances, and the one-minute window manufactures re-opens.</p>
<h2 id="alertnoisereductionfixingtheruleandprovingitworked">Alert noise reduction: fixing the rule and proving it worked</h2>
<p>The rule's own detail page already summarizes the problem: 14 alerts in 24 hours, every one of them from a single incident, produced by the definition on the right.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte457ea7bdb8c61f6/6ab8cc35f9d7ed34d45e8cc3/05-rule-detail-rev0.png" alt="Rule detail page of the noisy gateway rule: 14 alerts, zero active, with the endpoint and status grouping visible in the ES|QL definition" /></p>
<p>We update the rule rather than replace it, changing the grouping to <code>service.name</code>, widening the window to five minutes, and raising the threshold.</p>
<pre><code>FROM logs-payments.stormlab*
| WHERE service.name == "payment-gateway" AND log.level == "ERROR"
| STATS error_count = COUNT(*) BY service.name, cloud.region
| WHERE error_count &gt;= 10
</code></pre>
<p>Updating a rule in place matters for one specific reason: Kibana stamps every alert document with <code>kibana.alert.rule.revision</code>, so the before and after of a tuning change are separable in the same index with no bookkeeping on our side.</p>
<p>Then we replay a compressed version of the same failure: same deploy, same lag ramp, same rotating gateway bursts, same customer impact, rollback after seven minutes.</p>
<p>The scorecard query runs unchanged, and now the revision column earns its place:</p>
<p>| rule | revision | episodes | instances | flapping | median minutes active |
| :---- | ----: | ----: | ----: | ----: | ----: |
| gateway-5xx-per-endpoint-status | 0 | 14 | 7 | 0 | 1.0 |
| payments-error-rate-per-service | 0 | 6 | 3 | 0 | 6.0 |
| fraud-scorer-queue-lag | 0 | 2 | 1 | 0 | 8.0 |
| gateway-5xx-per-endpoint-status | 1 | 1 | 1 | 0 | 7.0 |</p>
<p>The unchanged rules now show both incidents in their rows, six and two episodes, which is exactly what stable rules should do.</p>
<p>The gateway rule splits into two rows because the revision changed, and the contrast is the entire argument of this article in four numbers.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb8f816570cfca616/6ab8cc45fb64e075972dd74e/06-noise-scorecard.png" alt="Discover ES|QL view of the noise scorecard grouped by rule and revision, showing revision 1 of the gateway rule producing a single stable episode" /></p>
<p>Same failure shape, one episode instead of fourteen, and the median episode now lasts seven minutes instead of one, so it spans the incident instead of a fraction of it.</p>
<p>This is the part of alerting work that usually relies on faith, and here it is a query result: the tuning change is verified against the same incident pattern, in the same index, by the same ES|QL.</p>
<p>Nothing about this loop is specific to our lab rule.</p>
<p>Any rule that tags its alerts can be scored, tuned, and re-scored this way, which turns alert tuning from an opinion into a measurement.</p>
<h2 id="queryingalerthistorywithanagentbuilderagent">Querying alert history with an Agent Builder agent</h2>
<p>Every query above is deterministic, which makes them ideal tools for an AI agent.</p>
<p>We register three ES|QL tools in <a href="https://www.elastic.co/docs/explore-analyze/ai-features/elastic-agent-builder">Agent Builder</a>, each one a parameterized version of a query from this article with an explicit <code>KEEP</code> projection, and wire them into an agent called Alert Historian.</p>
<pre><code>POST kbn:/api/agent_builder/tools
{
  "id": "alert_noise_scorecard",
  "type": "esql",
  "description": "Noise scorecard per alerting rule and revision: episodes, distinct instances, flapping episodes, median minutes active.",
  "configuration": {
    "query": "FROM .alerts-* | WHERE kibana.alert.rule.tags == \"alerts-as-data-v2\" AND DATE_DIFF(\"hours\", kibana.alert.start, NOW()) &lt;= ?lookback_hours | EVAL minutes_active = COALESCE(kibana.alert.duration.us, 0) / 60000000.0 | STATS episodes = COUNT(*), alert_instances = COUNT_DISTINCT(kibana.alert.instance.id), flapping_episodes = SUM(CASE(kibana.alert.flapping == true, 1, 0)), median_minutes_active = MEDIAN(minutes_active) BY rule = kibana.alert.rule.name, revision = kibana.alert.rule.revision | KEEP rule, revision, episodes, alert_instances, flapping_episodes, median_minutes_active | SORT episodes DESC",
    "params": {
      "lookback_hours": {
        "type": "integer",
        "description": "How many hours of alert history to analyze, for example 6"
      }
    }
  }
}
</code></pre>
<p>The agent's instructions define the vocabulary (an episode is one alert document), the method (timeline first, then propagation, then scorecard), and the honesty rules (report numbers and timestamps from tool output, never invent alerts).</p>
<p>We ask it one question: what happened in the last three hours, was it one incident or many, which team owns the first symptom, and did the gateway rule tuning reduce noise?</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4ef21427e82f02b3/6ab8cc572f8bb23e405e8070/07-agent-two-storms.png" alt="Agent Builder conversation where Alert Historian identifies two storms separated by a quiet gap, with 23 episodes across 3 rules" /></p>
<p>The agent called all three tools and got the structure right: it separated the history into two storms with a quiet gap, one per failure wave, rather than blending them into one event.</p>
<p>It attributed the first symptom to team risk-ml, reproduced the upstream deduction about the queue that never alerted, and then answered the tuning question with the revision table.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8ebe9a3561cff889/6ab8cc696ad09533cb4259df/08-agent-summary.png" alt="End of the Alert Historian conversation: team risk-ml named as the owner of the first symptom, the deduction about the upstream queue that never alerted, and revision 0 compared against revision 1" /></p>
<p>Its verdict on the fix reads like a review comment backed by data: fourteen episodes at revision 0 against one stable episode at revision 1, with the median episode going from one minute to seven.</p>
<p>The agent is not doing anything we could not do by hand.</p>
<p>That is precisely the point: because alerts are durable, structured data with explicit semantics, a language model can reason over patterns and histories instead of reacting to a single notification, and every number it states is traceable to a tool call you can rerun.</p>
<h2 id="runningalerthistoryqueriesinproduction">Running alert history queries in production</h2>
<p>A few things to decide before you rely on alert history in production.</p>
<h3 id="retentionisalearningwindowdecision">Retention is a learning-window decision</h3>
<p>Alert indices follow an ILM policy, and the default keeps far more than the few hours this lab needed, but if you want quarter-over-quarter rule quality trends, align the policy with that ambition deliberately.</p>
<h3 id="scopealertindexreadaccessnarrowly">Scope alert index read access narrowly</h3>
<p>Alert documents embed rule parameters, reasons, and deep links, so treat <code>.alerts-*</code> read privileges like any other operational dataset and grant the narrowest index patterns that work, for example only the observability alert aliases.</p>
<h3 id="groupingkeysareadatacontract">Grouping keys are a data contract</h3>
<p>Use ECS field names in <code>BY</code> clauses when they exist, keep cardinality bounded, and never put secrets or personal data into grouping values, because they become <code>kibana.alert.instance.id</code> and live for the retention window.</p>
<h3 id="projectcolumnsaggressivelywithkeep">Project columns aggressively with <code>KEEP</code></h3>
<p>With around 1,400 mapped fields in the alert indices, an unprojected query is unpleasant for humans and actively harmful inside LLM tools, so end every reusable query with <code>KEEP</code>.</p>
<h3 id="alertstormsalsostressyourautomation">Alert storms also stress your automation</h3>
<p>If a rule triggers a workflow per alert, fourteen episodes mean fourteen executions doing the same work, which is another argument for pointing automation at the aggregated alert dataset rather than at each notification.</p>
<h2 id="wheretostartwithyourownnoisiestrule">Where to start with your own noisiest rule</h2>
<p>The lab in this article is intentionally small, and every piece of it generalizes.</p>
<p>Score your noisiest real rule with the scorecard query, fix its grouping or window, and let <code>kibana.alert.rule.revision</code> tell you whether the fix held.</p>
<p>Join your alert history against whatever catalog you already have; a CMDB export in a lookup index is enough, and the propagation order becomes a query.</p>
<p>If you use Agent Builder, wrap your two or three most-used investigation queries as tools, and the next storm review starts with a conversation instead of a wall of notifications.</p>
<p>The <a href="https://github.com/elastic/elasticsearch-labs/tree/main/supporting-blog-content/observability-labs/alerts-as-data-log-derived-signals-v2">companion notebook</a> builds everything used here: the catalog index, the log data stream, the three rules, both incident replays, the queries, and the Agent Builder tools and agent. It runs for about 25 minutes because the rules evaluate once a minute and the episode durations are the data.</p>
<p>A notification tells you something broke.</p>
<p>The alert history tells you how your detection is performing, and it is already sitting in an index waiting to be queried.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/esql-alert-noise-reduction</link>
    <guid isPermaLink="false">esql-alert-noise-reduction</guid>
    <category><![CDATA[Incident Management]]></category>
    <category><![CDATA[Logs Analytics]]></category>
    <category><![CDATA[Data Management]]></category>
    <dc:creator><![CDATA[Jeffrey Rengifo]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2306ef56ba70a634/6ab8cbb44d5742d665bc26fd/header.png" length="0" type="image/png"/>
    <pubDate>Tue, 29 Sep 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Cut log storage costs with two Elasticsearch data tiers instead of four]]></title>
    <description><![CDATA[Elastic benchmarked one day of logs on SSD with everything older moved to frozen searchable snapshots, and it came out 16x cheaper than keeping all of it hot. The ILM policy and the cost model are both in the post.]]></description>
    <content:encoded><![CDATA[<p>For years, the standard Elastic storage story moved data through four tiers: hot, warm, cold, and frozen. Each step traded performance for cost. That model works, but at petabyte scale it adds operational overhead you do not need.</p>
<p>The simpler path is hot → frozen: one day on SSD for live troubleshooting, then <a href="https://www.elastic.co/docs/manage-data/lifecycle/index-lifecycle-management">Index Lifecycle Management (ILM)</a> moves everything else to the frozen tier as <a href="https://www.elastic.co/docs/deploy-manage/tools/snapshot-and-restore/searchable-snapshots">searchable snapshots</a> on blob storage. No warm tier. No cold tier. Frozen indices are partially mounted and read-only. You do not keep indexing into them. You do search them in Kibana and ES|QL, without a restore-then-query workflow.</p>
<p>Elasticsearch maps fields dynamically at ingest, so you do not need a rigid schema before you send logs, metrics, traces, or security logs. The same two-tier ILM policy can cover those data streams. Layer <a href="https://www.elastic.co/observability-labs/blog/elastic-streams-processing">Elastic Streams</a> on top when you need per-stream processing, drops, aggregation, downsampling, or <a href="https://www.elastic.co/observability-labs/blog/simplifying-retention-management-with-streams">retention</a>. For the companion cost walkthrough, see <a href="https://www.elastic.co/observability-labs/blog/observability-data-retention-elastic-streams">Not every log deserves 90 days: per-stream retention in Elastic Streams</a>.</p>
<h2 id="whenfourdatatiersismorethanlogstorageneeds">When four data tiers is more than log storage needs</h2>
<p>Traditional Elastic Cloud Hosted deployments often follow a ladder like this:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8677545f66b3e8cb/6ab38c2f97bf871d82b23a63/hot-to-frozen-flow.png" alt="Increased telemetry retention with hot, warm, cold, and frozen data tiers and a snapshot repositor" /></p>
<p>Each transition requires ILM policy tuning, node sizing for warm and cold hardware profiles, and replica management across tiers. Frozen still depends on a snapshot repository, but you pass through warm and cold first.</p>
<p>At hundreds of terabytes or multiple petabytes per day, those middle tiers become expensive to operate and hard to explain to finance teams. You pay for spinning disks and replica shards on data that nobody queries at second-level latency anymore.</p>
<h2 id="howhottofrozenlogstorageworks">How hot-to-frozen log storage works</h2>
<p>A two-tier <a href="https://www.elastic.co/docs/deploy-manage/reference-architectures/hotfrozen-high-availability">hot/frozen architecture</a> collapses the ladder:</p>
<p>| Phase | Storage | Purpose |
| --- | --- | --- |
| <strong>Hot (1 day)</strong> | SSD, high IO | Active incidents, alerting, SLO burn rates, ongoing ingest |
| <strong>Frozen</strong> | Blob plus cache nodes | Audit, compliance, historical search (read-only searchable snapshots) |</p>
<p>After the first 24 hours, ILM converts indices to searchable snapshots on the <code>found-snapshots</code> repository that Elastic Cloud Hosted provisions against cloud object storage. Data stays queryable. You omit warm and cold entirely.</p>
<p>The hot-to-frozen pattern matches how Elastic documents large time-series Observability and Security deployments that do not require updates after ingest. New events continue to land on hot. Aged backing indices move to frozen.</p>
<p>Elastic benchmarked a related hot-frozen pattern on ECH with 105 TB of logs over 90 days. Frozen-tier Discover queries stayed within single-digit seconds at p99.9 for most tasks, and holding one day in hot plus the rest in frozen cost roughly 16x less than keeping the full dataset on hot nodes alone. See <a href="https://www.elastic.co/search-labs/blog/searchable-snapshots-benchmark">Measuring searchable snapshots performance</a>.</p>
<h2 id="whatpetabytescalelogstoragecostsonelasticcloudhosted">What petabyte-scale log storage costs on Elastic Cloud Hosted</h2>
<p>The figures below are an illustrative example based on a telco-scale workload model, not a quote or pricing commitment from Elastic. Your costs will vary with ingest volume, retention, query patterns, cloud region, hardware profile, and licensing tier. Use <a href="https://www.elastic.co/pricing/cloud-hosted">Elastic Cloud Hosted pricing</a> and your account team for project-specific estimates.</p>
<h3 id="howdoeselasticcloudhostedpricingdifferfromserverless">How does Elastic Cloud Hosted pricing differ from Serverless?</h3>
<p>Elastic Cloud Hosted (ECH) bills primarily on the memory footprint of the resources you run: hot and frozen tier node capacity (GB RAM-hour), plus snapshot storage and data transfer. Even with a two-tier hot-to-frozen ILM policy, you still size and pay for the Elasticsearch nodes and frozen cache tier that execute queries against blob-backed data.</p>
<p>Elastic Cloud Serverless bills on ingest volume and stored data, not provisioned cluster RAM. That model removes node sizing, capacity planning, and most lifecycle administration. You pay for what you send and what you keep.</p>
<p>This example models ECH only. Serverless is not included in the tables below because Observability Serverless does not yet support writing aged data out to S3 or blob storage for long-term frozen retention. When that capability ships, the same hot-to-blob pattern on Serverless should reduce administration further while keeping ingest-plus-storage pricing. Until then, ECH with hot-to-frozen ILM is the path for petabyte-scale audit and compliance on blob.</p>
<p>The example assumes 1-day hot, then ILM to blob (frozen searchable snapshots) for long-term retention. Treat the per-PB figures as a planning block for mixed telemetry, not as a claim that 1 PB of raw logs occupies the same disk as 1 PB of raw metrics. <a href="https://www.elastic.co/docs/manage-data/data-store/data-streams/time-series-data-stream-tsds">Time series data streams</a> store metrics far more efficiently than a regular data stream. Calculations use published ECH capacity, snapshot storage, and data transfer rates with Enterprise licensing.</p>
<h3 id="logstoragecostat1pbperdaywithoutstreams">Log storage cost at 1 PB per day without Streams</h3>
<p>At 1 PB/day ingest in this example, ECH alone is modeled at $178K/month ($2.14M/year) using published rates from <a href="https://www.elastic.co/pricing/cloud-hosted">elastic.co/pricing/cloud-hosted</a>.</p>
<p>Scaling linearly:</p>
<p>| Daily ingest | ECH monthly | ECH annual |
| --- | --- | --- |
| 1 PB/day | $178K | $2.14M |
| 2 PB/day | $357K | $4.28M |
| 3 PB/day | $535K | $6.42M |
| 4 PB/day | $713K | $8.56M |
| 5 PB/day | $891K | $10.70M |
| 6.5 PB/day | $1.16M | $13.90M |
| 8.45 PB/day | $1.51M | $18.07M |</p>
<p>Even without Streams optimization, the two-tier model is built for petabyte-per-day scale: short hot retention, blob-backed frozen search, and no warm or cold hardware profiles. Competitor TCO depends on ingest mix, retention length, and whether other vendors bill long-term searchability as premium hot storage. Compare published ECH rates to your current quotes rather than treating any single percentage as a universal saving.</p>
<h3 id="howelasticstreamscutslogretentioncostsby28">How Elastic Streams cuts log retention costs by 28%</h3>
<p>Adding <a href="https://www.elastic.co/observability-labs/blog/observability-data-retention-elastic-streams">Elastic Streams</a> controls (drop, aggregate, downsample, retention) on top of the same hot-to-frozen ILM foundation reduces the example run rate by roughly 28%:</p>
<p>| Daily ingest | Streams optimized monthly | Streams optimized annual |
| --- | --- | --- |
| 1 PB/day | $127K | $1.52M |
| 2 PB/day | $254K | $3.04M |
| 3 PB/day | $380K | $4.56M |
| 4 PB/day | $507K | $6.08M |
| 5 PB/day | $634K | $7.60M |
| 6.5 PB/day | $824K | $9.88M |
| 8.45 PB/day | $1.07M | $12.85M |</p>
<p>At <strong>5 PB/day</strong> with <strong>30% year-over-year growth</strong>, the three-year comparison looked like this:</p>
<p>| Year | Daily volume | ECH annual | Streams optimized annual |
| --- | --- | --- | --- |
| Year 1 | 5 PB/day | $10.70M | $7.60M |
| Year 2 | 6.5 PB/day | $13.90M | $9.88M |
| Year 3 | 8.45 PB/day | $18.07M | $12.85M |</p>
<p>In this example, that is <strong>$12.3M lower over three years</strong> with Streams optimization compared to ECH alone. Use that delta as a sensitivity check, then adjust for telemetry mix. Metrics in a TSDS, downsampled series, and verbose logs do not share one storage multiplier.</p>
<p>When Serverless adds blob-tier retention for observability data, rerun this comparison on <a href="https://www.elastic.co/pricing/serverless-observability">Serverless pricing</a>. Ingest-plus-storage billing plus zero node administration should make Serverless the simpler operational choice for long-retention workloads at scale.</p>
<h2 id="ilmpolicyexampleonedayhotfrozenretentionforcompliance">ILM policy example: one day hot, frozen retention for compliance</h2>
<p>On ECH, attach a policy that rolls daily on hot and transitions to frozen searchable snapshots. Omit warm, cold, and delete phases when audit requirements call for indefinite retention on blob.</p>
<pre><code>PUT _ilm/policy/obs-hot-frozen
{
  "policy": {
    "phases": {
      "hot": {
        "min_age": "0ms",
        "actions": {
          "rollover": {
            "max_age": "1d",
            "max_primary_shard_size": "50gb"
          }
        }
      },
      "frozen": {
        "min_age": "1d",
        "actions": {
          "searchable_snapshot": {
            "snapshot_repository": "found-snapshots"
          }
        }
      }
    }
  }
}
</code></pre>
<p>The <code>searchable_snapshot</code> action in the frozen phase mounts a read-only, partially mounted snapshot (<code>partial-*</code>). Keep ingest on hot. Point historical Discover and ES|QL at frozen.</p>
<p>Apply the policy through index templates, Kibana Index Management, or the <a href="https://www.elastic.co/observability-labs/blog/simplifying-retention-management-with-streams">Streams Retention tab</a>. Frozen tier nodes cache frequently queried blob regions locally. First access to a new time range may be slower; repeat queries hit cache and perform closer to hot-tier latency.</p>
<p>For regulated workloads, pair indefinite frozen retention with Streams AI Partitioning so compliance-heavy streams (payments audit, security events) inherit longer lifetimes while verbose debug streams get shorter DSL delete policies on child streams.</p>
<h2 id="whyelasticsearchdatatiersworkforpetabytescalelogstorage">Why Elasticsearch data tiers work for petabyte-scale log storage</h2>
<p>Among managed observability platforms, Elastic Cloud Hosted is the only option we are aware of that combines:</p>
<ul>
<li><strong>Petabyte-scale telemetry ingest</strong> on one platform for logs, metrics, traces, security logs, and more.</li>
<li><strong>Two-tier ILM</strong> from hot to frozen without warm or cold hardware profiles.</li>
<li><strong>Searchable snapshots</strong> that query blob storage directly, with no rehydration step.</li>
<li><strong>Published-rate TCO modeling</strong> you can run against <a href="https://www.elastic.co/pricing/cloud-hosted">ECH pricing</a> before you talk to finance.</li>
</ul>
<p>Competing observability SaaS tools often charge premium rates for long hot retention, bill per telemetry type, or move aged data to archives that are not searchable with the same query tools you use for live incidents. At petabyte-per-day ingest, those pricing models widen the gap. Elastic built tiering into the search engine itself, which is why Discover, ES|QL, and APM views work across hot and frozen data transparently.</p>
<h2 id="howtosizehotandfrozennodesforpetabytescalelogstorage">How to size hot and frozen nodes for petabyte-scale log storage</h2>
<p>Exact node counts depend on daily ingest, query concurrency, and hardware profile. A practical starting ratio from Elastic benchmarks:</p>
<ul>
<li>Hot nodes sized for one day of ingest plus indexing headroom.</li>
<li>Frozen cache nodes sized for concurrent query patterns, not total blob volume.</li>
</ul>
<p>Before production rollout:</p>
<ol>
<li>Measure daily telemetry ingest (logs, metrics, traces, security logs).</li>
<li>Pilot the one-day hot policy on a representative data stream.</li>
<li>Run Discover and ES|QL queries against frozen data to validate cache sizing.</li>
<li>Apply Streams drops and partitioning to reduce volume entering hot.</li>
</ol>
<p>Keep alert evaluation and SLO burn-rate queries on hot. Plan incident workflows that start on the last 24 hours, then expand into frozen for historical context. Frozen queries can show higher latency on first access after cache eviction.</p>
<p>Searchable snapshots and the frozen tier require appropriate Elastic Cloud capabilities. Review <a href="https://www.elastic.co/pricing/">Elastic Cloud pricing</a> and your deployment tier before enabling frozen at scale.</p>
<h2 id="howtomoveyourlogretentionpolicytohottofrozen">How to move your log retention policy to hot-to-frozen</h2>
<ul>
<li>Replace warm and cold phases with a hot-to-frozen ILM policy on observability data streams.</li>
<li>Model TCO at your ingest rate using published ECH pricing, then add Streams optimization scenarios.</li>
<li>Send OpenTelemetry telemetry to wired streams and apply <a href="https://www.elastic.co/observability-labs/blog/observability-data-retention-elastic-streams">Streams TCO controls</a>.</li>
<li>Read the <a href="https://www.elastic.co/docs/deploy-manage/tools/snapshot-and-restore/searchable-snapshots">searchable snapshots documentation</a> and <a href="https://www.elastic.co/docs/manage-data/lifecycle/data-tiers">data tiers guide</a>.</li>
</ul>
<p>Petabyte observability does not require four storage tiers or choosing between cost and searchability. One day on hot, everything else in frozen searchable snapshots on blob, and Streams dials on top: that is how Elastic Cloud Hosted keeps full history queryable at a TCO finance teams can approve.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/log-storage-petabyte-scale-hot-frozen-ilm</link>
    <guid isPermaLink="false">log-storage-petabyte-scale-hot-frozen-ilm</guid>
    <category><![CDATA[Logs Analytics]]></category>
    <dc:creator><![CDATA[Peter Simkins]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3aad0dcdb96aff20/6ab38abfa8762889f85d6d6d/header.jpg" length="0" type="image/jpeg"/>
    <pubDate>Thu, 24 Sep 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Not every log deserves 90 days: per-stream retention in Elastic Streams]]></title>
    <description><![CDATA[AI Partitioning reads your data and proposes child streams. Retention, drops and downsampling then become per-stream settings, so the noisy ones expire on their own schedule.]]></description>
    <content:encoded><![CDATA[<p><a href="https://www.elastic.co/docs/solutions/observability/streams/streams">Elastic Streams</a> lets you set observability data retention per stream, so a payments API stream can keep 90 days while a notifications worker keeps 7. AI Partitioning reads the incoming firehose and proposes the child streams. Drops, retention and downsampling are then settings in the Streams UI, not routing rules you maintain somewhere else. Teams moving off a single 30-day policy typically cut 20–40% of log volume at ingest and downsample metrics after the first few days.</p>
<p>The controls work the same on Elastic Cloud Serverless, Elastic Cloud Hosted (ECH), and self-managed clusters. Elasticsearch uses <a href="https://www.elastic.co/docs/manage-data/data-store/mapping">dynamic mapping</a> (schema-on-write with automatic type detection), so the same Streams controls apply to logs, metrics, traces, and any other signal you ingest over OpenTelemetry or native shippers, with no fixed schema required up front.</p>
<blockquote>
  <p><strong>UI note (Stack 9.5+):</strong> the Streams <strong>Retention</strong> tab was renamed <strong>Data lifecycle</strong>. This article uses <strong>Data lifecycle</strong> for current GA. Screenshots captured on earlier builds may still show the old label; the controls themselves are unchanged. See <a href="https://www.elastic.co/docs/solutions/observability/streams/configure-retention">Configure data lifecycle with Streams</a>.</p>
</blockquote>
<h2 id="whyobservabilitydatastoragecostsgetoutofhand">Why observability data storage costs get out of hand</h2>
<p>Most teams start with one retention policy for everything. That is simple until:</p>
<ul>
<li>Debug logs from a load generator sit next to payment audit events in the same data stream.</li>
<li>High-cardinality metrics keep growing while only a handful of dimensions matter after a week.</li>
<li>Trace volume spikes during deploys, but you still pay to store every span at full resolution for 90 days.</li>
</ul>
<p>The usual fixes (sampling everything globally, turning off logging, or maintaining brittle routing rules in ingest pipelines) trade away visibility for cost. Streams takes a different path: identify patterns first, then apply the right storage policy to each pattern.</p>
<h2 id="observabilitydataretentioncontrolsinelasticstreams">Observability data retention controls in Elastic Streams</h2>
<p>Streams sits on top of Elasticsearch data streams. Each stream gets its own Data lifecycle tab, processing pipeline, and (for wired streams) a place in a parent/child tree. That structure is what turns generic "reduce observability spend" goals into concrete settings you can audit.</p>
<p><strong>Streams-native controls</strong> (configure these in the Streams UI):</p>
<p>| Control | What it does | Typical signal |
| --- | --- | --- |
| <strong>Drop</strong> | Stop indexing matched documents before they hit storage | Noisy debug logs, health checks |
| <strong>Retention (DSL or ILM)</strong> | Delete or tier data after a defined age via the Data lifecycle tab | Short-lived app logs vs compliance logs |
| <strong>Downsample</strong> | Replace high-resolution metrics with fixed intervals (from Elastic 9.4) | Infrastructure metrics after 7 days |</p>
<p><strong>Adjacent Elasticsearch capabilities</strong> that compound the savings (not separate Streams actions, but important for TCO):</p>
<p>| Capability | What it does | Typical signal |
| --- | --- | --- |
| <strong>TSDS storage efficiency</strong> | Dimension-based routing, <code>_tsid</code>/<code>@timestamp</code> sorting, and synthetic <code>_source</code> improve compression; identical <code>_tsid</code> + timestamp data points are rejected | OTel or Prometheus metrics |
| <strong>Ingest processors / transforms</strong> | Processors reshape or drop individual documents before indexing; <a href="https://www.elastic.co/docs/explore-analyze/transforms/transform-overview">transforms</a> pivot already-indexed data into summary destinations on a schedule | High-volume counters, enrichment |
| <strong>ILM tiering</strong> | Move older data to warm, cold, or frozen storage (exposed in Streams Data lifecycle on ECH/on-prem) | Long-lived logs and metrics |</p>
<p>On Elastic Cloud Serverless, you also benefit from storage optimizations built into the platform (for example, <a href="https://www.elastic.co/observability-labs/blog/elasticsearch-logsdb-index-mode-storage-savings">LogsDB index mode for logs</a> and efficient TSDB storage for metrics). Streams helps you reduce what you store by routing and trimming data before Serverless bills reflect the volume.</p>
<p>On ECH and self-managed deployments, you get the full Elasticsearch lifecycle toolkit: ILM phases, data stream lifecycle (DSL), searchable snapshots, and downsampling. Streams exposes those policies in the UI so you are not hunting through JSON policy files.</p>
<h3 id="whichcontroltouse">Which control to use</h3>
<p>Drop, retention and downsampling solve different problems, and the cost of each is paid at a different point.</p>
<p>| Control | What you still pay for | What you give up | Best fit |
| --- | --- | --- | --- |
| <strong>Drop</strong> | Nothing. Matched documents are never indexed. | The data, permanently. No reindex, no recovery. | Health checks, load generator output, debug namespaces |
| <strong>Retention (DSL)</strong> | Full storage until the age limit, then nothing. | Any query beyond the retention window. | Short-lived application logs, anything with a compliance floor |
| <strong>Retention (ILM tiering)</strong> | A lower cost per GB as data moves to warm, cold, or frozen. | Query speed on older data. Frozen needs searchable snapshots. | Long-lived logs and metrics on ECH or self-managed |
| <strong>Downsample</strong> | One data point per interval instead of every sample. | Resolution. Original samples are replaced, not kept alongside. | Infrastructure metrics and other high cardinality metrics after the first week |</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta6cdaac2a6941ffd/6ab2b4e2a8762834575d6980/retention-view.png" alt="Data lifecycle tab in Elastic Streams showing storage size, ingestion rate, and lifecycle phases" /></p>
<h2 id="step1findlogpatternswithaipartitioning">Step 1: Find log patterns with AI Partitioning</h2>
<p>You cannot right-size retention until you know what is in the firehose. <a href="https://www.elastic.co/docs/solutions/observability/streams/management/partitioning">Streams AI Partitioning</a> analyzes incoming documents and proposes child streams based on field patterns, service names, log shapes, or plain-English instructions.</p>
<p>A common starting point is the wired <code>logs.otel</code> stream created when you send OpenTelemetry logs to the <a href="https://www.elastic.co/observability-labs/blog/elastic-managed-otlp-endpoint-for-opentelemetry">Managed OTLP endpoint</a>. The same idea applies to metrics and traces wired streams as your deployment ingests them.</p>
<h3 id="howtorunaipartitioning">How to run AI Partitioning</h3>
<ol>
<li>Open <strong>Observability &gt; Streams</strong> and select a parent wired stream.</li>
<li>Run <strong>AI Partitioning</strong> on a representative time range.</li>
<li>Review suggested child streams (for example per team, service, or log level).</li>
<li>Refine partitions in natural language if the first pass merges things you want separated.</li>
<li>Apply partitions so each child stream inherits processing and retention from the parent, with per-child overrides.</li>
</ol>
<p>Once partitioned, a payments API stream can keep 90 days while a notifications worker keeps 7. You set that in the UI instead of maintaining reroute processors in Logstash or the OTel Collector.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4b0954b0b672c3a5/6ab2b4faa8762824225d6984/ai-partitioning-retention.png" alt="Per-team retention after Streams AI Partitioning replaces a single shared retention policy" /></p>
<p>For a full walkthrough, see <a href="https://www.elastic.co/observability-labs/blog/elasticsearch-streams-ai-partitioning-log-routing">One OTLP endpoint, three teams, zero routing rules</a>.</p>
<h2 id="step2logfilteringinstreamsinsteadofyourtelemetrypipeline">Step 2: Log filtering in Streams instead of your telemetry pipeline</h2>
<p>Partitioning tells you <em>who</em> the data belongs to. Processing tells you <em>what to keep</em>.</p>
<p>In the Streams <strong>Processing</strong> tab you can:</p>
<ul>
<li>Add <strong>conditional blocks</strong> scoped to one child stream or log shape.</li>
<li>Use <strong>AI-generated Grok patterns</strong> to parse samples instead of hand-writing regex.</li>
<li>Add a <strong>drop processor</strong> for documents that should never be indexed.</li>
</ul>
<p>This preview-first workflow matters for TCO. You inspect matched, parsed, and dropped samples before anything goes live. That is safer than a pipeline change that silently discards the wrong field.</p>
<p>Example: drop load-generator logs inside a Kubernetes containers stream while keeping application errors. The <a href="https://www.elastic.co/observability-labs/blog/kubernetes-logs-elastic-streams-processing">Kubernetes logs processing walkthrough</a> shows the full flow with the Dropped tab preview.</p>
<p>Drops are the most direct control when the data has zero long-term value. You pay nothing to store what never gets indexed.</p>
<h2 id="step3setlogretentionperstreaminthedatalifecycletab">Step 3: Set log retention per stream in the Data lifecycle tab</h2>
<p>After partitioning, open the <strong>Data lifecycle</strong> tab (formerly <strong>Retention</strong> prior to Stack 9.5) on each child stream.</p>
<p>You will see:</p>
<ul>
<li>Current <strong>storage size</strong> and projected ingestion rates.</li>
<li><strong>Lifecycle phase breakdown</strong> when ILM is active (hot, warm, cold, frozen).</li>
<li>Options to switch between <strong>DSL</strong> (simple age-based retention) and <strong>ILM</strong> (tiered storage).</li>
</ul>
<p>Wired streams support <strong>inheritance</strong>: set retention on a parent stream and let children inherit it, or override specific children that need longer or shorter lifetimes. Classic streams can follow existing index template policies while still giving you Streams visibility.</p>
<p>For DSL and ILM editing in the UI, see <a href="https://www.elastic.co/observability-labs/blog/simplifying-retention-management-with-streams">How Streams simplifies retention management</a> and <a href="https://www.elastic.co/docs/solutions/observability/streams/configure-retention">Configure data lifecycle with Streams</a>.</p>
<h2 id="step4downsamplehighcardinalitymetricsandrolluptimeseries">Step 4: Downsample high cardinality metrics and roll up time series</h2>
<p>Logs are often about retention length and drops. Metrics are often about resolution over time.</p>
<p>Starting in Elastic 9.4, you can configure downsampling directly in the Streams Data lifecycle tab for both ILM-backed and DSL-managed streams. Pick a phase, set a <code>fixed_interval</code>, and preview the timeline before you save.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt588176e9c1003450/6ab2b5127e66d4d5420163e1/downsampling-flyout.png" alt="ILM phase flyout with downsample interval and live timeline preview in Elastic Streams" /></p>
<h3 id="exampleilmpolicywithhotandcolddownsampling">Example ILM policy with hot and cold downsampling</h3>
<pre><code>{
  "phases": {
    "hot": {
      "min_age": "0ms",
      "actions": {
        "rollover": { "max_age": "1d" },
        "downsample": {
          "fixed_interval": "5m",
          "wait_timeout": "1d"
        }
      }
    },
    "cold": {
      "min_age": "4d",
      "actions": {
        "downsample": {
          "fixed_interval": "10m",
          "wait_timeout": "1d"
        }
      }
    },
    "delete": {
      "min_age": "30d",
      "actions": { "delete": {} }
    }
  }
}
</code></pre>
<h3 id="howtsdsreducesmetricsstorage">How TSDS reduces metrics storage</h3>
<p>Elasticsearch <a href="https://www.elastic.co/docs/manage-data/data-store/data-streams/time-series-data-stream-tsds">Time Series Data Streams (TSDS)</a> cuts storage for OTel and Prometheus metrics through dimension-based routing (each time series lands on one shard), index sorting by <code>_tsid</code> and <code>@timestamp</code> that co-locates repeated values for better compression, and synthetic <code>_source</code>. TSDS also rejects duplicate data points with an identical <code>_tsid</code> and timestamp. Pair TSDS with downsampling for metrics that only need minute-level granularity after the first week.</p>
<p>See <a href="https://www.elastic.co/observability-labs/blog/configure-downsampling-elastic-streams">Configure downsampling directly in Elastic Streams</a> for UI steps and shared-policy warnings.</p>
<h2 id="storagetiersbydeploymenttype">Storage tiers by deployment type</h2>
<h3 id="observabilitydataretentiononelasticcloudserverless">Observability data retention on Elastic Cloud Serverless</h3>
<p>Serverless removes cluster sizing work, but you still control what lands in storage. Streams partitioning, drops, and per-stream retention directly affect ingest volume and retention duration, which drives Serverless observability costs.</p>
<p>Platform-level optimizations (LogsDB for logs, columnar TSDB improvements for metrics) stack on top of the policies you set in Streams. You get efficient storage and intentional data lifecycle management.</p>
<p>If you only need log analytics at a lower price point, <a href="https://www.elastic.co/observability-labs/blog/log-analytics-elastic-serverless-logs-essentials">Logs Essentials on Serverless</a> is another option. Streams TCO controls still apply when you need per-team retention on Complete.</p>
<h3 id="storagetiersonelasticcloudhostedandselfmanaged">Storage tiers on Elastic Cloud Hosted and self-managed</h3>
<p>ECH and on-prem deployments add storage tiering: hot SSD, warm, cold, frozen, and searchable snapshots. ILM moves streams across tiers automatically. DSL offers a simpler delete-after-N-days model when you do not need tiering.</p>
<p>The same Streams UI and APIs apply. The difference is you also choose hardware profiles and snapshot repositories behind the policies.</p>
<h2 id="howretentiondiffersforlogsmetricsandtraces">How retention differs for logs, metrics and traces</h2>
<p>Elasticsearch does not require a fixed schema at ingest. OpenTelemetry logs, metrics, and traces can land in wired streams, get partitioned by AI on whatever fields appear in your data, and pick up stream-specific retention.</p>
<p>That matters for TCO because observability signals are not isolated cost lines:</p>
<ul>
<li><strong>Logs</strong>: drop noise, shorten retention on verbose services, keep audit trails longer.</li>
<li><strong>Metrics</strong>: use TSDS storage efficiency, downsample after incident windows pass.</li>
<li><strong>Traces</strong>: partition by service or environment, align retention with sampling policies.</li>
</ul>
<p>You are not maintaining three different cost systems. You are applying the same Streams primitives to each signal type.</p>
<h2 id="suggestedobservabilitydataretentionworkflow">Suggested observability data retention workflow</h2>
<ol>
<li><strong>Ingest to wired streams</strong> (for example <code>logs.otel</code>, metrics, or traces OTel endpoints).</li>
<li><strong>Partition with AI</strong> so each team or service gets its own stream.</li>
<li><strong>Drop</strong> known noise (health checks, load tests, debug namespaces).</li>
<li><strong>Set retention</strong> per stream based on compliance and debugging needs.</li>
<li><strong>Downsample or tier</strong> metrics and high-volume logs on ECH/on-prem.</li>
<li><strong>Review the Data lifecycle tab monthly</strong> using ingestion graphs to catch drift.</li>
</ol>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1017908443d6f216/6ab2b915f3b126049a6506b0/tco-control-flow.png" alt="Elastic Streams TCO control flow from ingest to AI partition to per-stream drop, retention, and downsample, then to storage tier or delete" /></p>
<h2 id="howmuchstoragecanyouactuallysave">How much storage can you actually save?</h2>
<p>Exact savings depend on your workload. Teams that previously kept everything for 30 days often see the largest wins from:</p>
<ul>
<li>Dropping 20–40% of debug or duplicate logs at ingest.</li>
<li>Shortening retention on high-volume, low-value streams.</li>
<li>Downsampling metrics after the first few days.</li>
</ul>
<p>Combine those controls with Elasticsearch storage modes (LogsDB, TSDS) for compounding effects. Measure before and after using the storage size metric on each stream's Data lifecycle tab and cluster-level <code>_stats</code> where you have access.</p>
<h2 id="tryperstreamretentionononenoisystream">Try per-stream retention on one noisy stream</h2>
<ul>
<li>Enable wired streams and send sample OTel data to <code>logs.otel</code>.</li>
<li>Run <a href="https://www.elastic.co/docs/solutions/observability/streams/management/partitioning">AI Partitioning</a> on a busy parent stream.</li>
<li>Set one aggressive retention policy on a noisy child stream and compare storage after 48 hours.</li>
<li>Read the <a href="https://www.elastic.co/docs/solutions/observability/streams/streams">Streams documentation</a> for API options (<code>Upsert stream</code>, ingest settings) when you want to automate policies in GitOps.</li>
</ul>
<p>Observability TCO is not about collecting less data by default. It is about storing the right data for the right duration. Streams gives you the map, the scissors, and the dial.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/observability-data-retention-elastic-streams</link>
    <guid isPermaLink="false">observability-data-retention-elastic-streams</guid>
    <category><![CDATA[Logs Analytics]]></category>
    <dc:creator><![CDATA[Peter Simkins]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltbe7a23471d082529/6ab2b49c7c5f02bd966f3147/header.jpg" length="0" type="image/jpeg"/>
    <pubDate>Tue, 22 Sep 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Cross-project search for Elastic Observability: one query across every linked project]]></title>
    <description><![CDATA[Keep your observability data where it lives, and still search, alert, and monitor across every linked Serverless project, easily!]]></description>
    <content:encoded><![CDATA[<p>A single query can now reach every Serverless project you own. Link your projects once and every search, dashboard and alerting rule after that can span all of them. Discover, dashboards and ES|QL all operate with no duplication of data between projects and no remote index prefixes in your queries. Cross-project search (CPS) is <a href="https://www.elastic.co/blog/cross-project-search-elastic-serverless-ga">generally available</a> on <a href="https://www.elastic.co/cloud/serverless">Elastic Cloud Serverless</a>.</p><p>If you run Observability on Elastic Cloud Hosted (ECH), this will feel familiar. <a href="https://www.elastic.co/docs/explore-analyze/cross-cluster-search">Cross-cluster search</a> (CCS) has long enabled teams to query across remote clusters, and it's a mainstay in large scale ECH environments. CPS takes the same idea to Serverless, and makes linking even simpler: no topology mapping, certificate configuration or per connection credentials. You link projects in the Cloud console and Elastic manages the rest.</p><h2>What is cross-project search?</h2><p>Serverless organizes your data into <strong>projects</strong>: Elasticsearch, Observability, or Security, each with its own scaling, permissions, and lifecycle. That isolation is a feature: it preserves data locality where needed, simplifies cost attribution, and lets teams own their own data. The tradeoff, historically, has been visibility. If your logs live in one project and your APM data lives in another, or if every business unit runs its own Observability project, getting a unified view has typically meant duplicating data into a shared store.</p><p>CPS removes that tradeoff. You designate an <strong>origin project</strong> (the project you search from) and link it to other projects you want included in that search. Once linked, an unqualified query like <code>FROM logs*</code> or a Discover session automatically spans the origin plus every linked project; no special syntax, no remote index prefixes. Kibana's project picker lets anyone narrow a query to just the current project or expand it to everything in scope, and administrators can set a default scope per Kibana space so a local incident-response space stays local by default while a global operations space starts broad. Access is enforced per project using your existing Elastic Cloud roles, so linking a project for search doesn't quietly grant broader permissions than a user already has.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2cee1f522a706b1c/6ab29c62a8762835045d68c9/Screenshot_2026-09-22_at_6.18.44_p.m..png" alt="Diagram showing an origin Observability project linking to multiple linked projects across regions and project types, with queries fanning out and results merging back automatically" /><p></p><h3>What's the difference between cross-project search and cross-cluster search?</h3><h3></h3><p></p><p>Cross-cluster search (ECH, self-managed)</p><p>Cross-project search (Serverless)</p><p>Where you link</p><p>Remote cluster connections, configured per cluster</p><p>Link projects in the Elastic Cloud console</p><p>Credentials</p><p>Certificates or API keys you issue and rotate</p><p>Managed by Elastic</p><p>Network setup</p><p>Network topology you map and maintain</p><p>None to configure</p><p>Query syntax</p><p>Remote cluster name referenced in the query</p><p>Unqualified query, no remote index prefix</p><p>Scope control</p><p>Set per query</p><p>Project picker, per-space defaults, and tag-based routing</p><p>Where CCS asks administrators to configure remote cluster connections, exchange certificates or API keys, and reference remote cluster names in every query, CPS asks you to link projects in the UI and query the way you already do. It's the same underlying idea: remote search without moving data, adapted to a control plane where infrastructure is meant to disappear.</p><h2>Why cross-project search matters for Observability teams</h2><p>Cross-project search matters most the moment your observability data stops living in one place. Observability organizations typically split their data by team, environment, region, or customer, usually for good reasons: isolating noisy neighbors, controlling blast radius, meeting data residency requirements, or giving each team ownership over its own ingest and retention. The problem shows up during an incident or a cross-cutting investigation, when someone needs to see across those boundaries without waiting for a data migration or standing up a consolidated cluster.</p><p>A few examples of where CPS changes the equation:</p><h3>Keep observability data in its region and still search globally</h3><p>Teams operating in multiple regions or clouds often need to keep observability data in the region it was generated, whether for regulatory reasons or to limit cross-region egress. CPS lets each region keep its own Observability project while a central project links to all of them, so an SRE team can run one query to check whether an anomaly in one region is also showing up elsewhere, without pulling all that data into one place first.</p><h3>Isolate environments without losing the incident view</h3><p>Splitting production, staging, and per-service or per-team projects makes scaling and cost predictable, and it limits how far a bad deploy or a runaway job can spread. It also means an on-call engineer used to have to check several places during an incident. With CPS, on-call can work from a single origin project and pull in whichever linked environments matter to the investigation, then narrow back down once things are resolved.</p><h3>Central teams monitoring many business units or customers</h3><p>Platform or Observability Center of Excellence (CoE) teams that operate on behalf of many business units, or Managed Service Providers (MSPs) monitoring several customer environments, can give each unit or customer its own project, with its own retention, access, and cost, while the central team links across all of them for cross-cutting reporting and anomaly detection. Each project owner keeps control of their own data; the center gets the aggregate view it needs.</p><h3>Avoid ingesting the same data twice</h3><p>When two teams need the same data for different purposes, the old options were duplicate ingestion pipelines or a shared, less isolated deployment. With linked projects, data is ingested once and queried from wherever it's needed, which keeps both storage costs and pipeline maintenance down.</p><p>This applies across solution types too: CPS links Elasticsearch, Observability, and Security projects interchangeably, so a Security team and an Observability team can each keep their own project, with their own retention and access controls, while querying the same underlying logs. A security analyst investigating an alert can query infrastructure or service context from a linked Observability project in Discover, dashboards, or ES|QL, and an SRE debugging an incident can query related signals from a linked Security project the same way, all without either team re-ingesting the other's data.</p><h2>What does cross-project search support in Elastic Observability?</h2><p>At GA, CPS is available across Elasticsearch, Observability, and Security project types (Observability and Security projects require the Complete tier). For Elastic Observability specifically, application and infrastructure monitoring, alerting rules, SLOs, machine learning anomaly detection (including APM anomaly detection), and Synthetics work across linked projects, alongside the platform-level capabilities every project gets: Discover, dashboards, and ES|QL. Project routing lets you target a query to a subset of linked projects using predefined tags (alias, cloud provider, region) or your own custom tags, combined with boolean logic, so "all EU projects" or "every environment except staging" can be expressed once and reused.</p><p>This release also raises the default number of projects you can link to a single origin, well beyond the tech preview limit, with room to go higher for larger deployments. Any existing project can now act as an origin rather than only newly created ones. For programmatic access, you only need a Cloud API key to call CPS from your own tooling. A new dedicated cloud role can now grant those keys for programmatic access.</p><p>A few nuances are worth knowing before you design around CPS:</p><ul><li><strong>Alerting</strong>: Alerting rules defined in the origin project can evaluate data across linked projects. The alerts they generate stay local to the project where the alerting rule lives.</li><li><strong>SLOs</strong>: SLOs don't follow the project picker in the Kibana header. Instead, you set scope in the SLO create and edit form, choosing this project, all linked projects, or a specific subset, since changing scope rebuilds the SLO's underlying computation. You can't view SLOs created in linked projects from the current project, but you can create an SLO here based on data that lives in those projects.</li><li><strong>Synthetics</strong>: Overview, monitor details, certificates, errors, and journeys honor the Kibana header project picker and show linked-project monitors as read-only. Creating, editing, and managing monitors stay in the origin project, and Synthetics status and TLS rules still evaluate origin monitors only.</li><li><strong>Streams</strong>: Not CPS-aware yet; Streams stay scoped to the project where they were created.</li></ul><h2>How do you set up cross-project search?</h2><p>Setting up cross-project search takes a few minutes:</p><ol><li>From your origin project's settings in the Elastic Cloud console, open <strong>Link projects</strong>.</li><li>Select the projects you want to link, individually or in bulk.</li><li>Save.</li><li>Open Discover, run an ES|QL query, or build a dashboard as you normally would: linked project data is included automatically, and the project picker lets you narrow or widen the scope at any time.</li></ol><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd604c155e0ef0f17/6aad2c627e66d4c520015488/cps-link-projects-screenshot-embedded.svg" alt="The Link projects screen in the Elastic Cloud console, showing project search and filters (project type, cloud provider, region, tags) and a list of candidate projects to select for linking" /><p>For the full configuration walkthrough, including linking topologies (hub-and-spoke, shared data project, or full mesh) and how to scope project routing with tags and expressions, see the <a href="https://www.elastic.co/docs/deploy-manage/cross-project-search-config">cross-project search documentation</a> and the <a href="https://www.elastic.co/docs/solutions/observability/cross-project-search">Observability-specific guide</a>.</p><h2>Cross-project search pricing</h2><p>CPS pricing has two components: a charge for data retained in each linked project you query, and a charge for data egress generated by cross-project query traffic, tracked as its own line item so you can see what CPS is adding to your transfer costs. There's no separate per-link fee. Because actual cost depends on your data volumes, retention settings, and how many projects you link, use the <a href="https://cloud.elastic.co/pricing/serverless">Elastic Cloud Serverless pricing estimator</a> to model costs for your own environment against your own ingest and retention numbers.</p><h2>Start using cross-project search</h2><p>If you're already running Elastic Observability on Serverless, cross-project search is available in your project settings today: link your projects and run your first cross-project query in Discover. If you haven't tried Elastic Cloud Serverless yet, <a href="https://cloud.elastic.co/serverless-registration">start a free trial</a> and see how far a single query can reach.</p><p><em>The release and timing of any features or functionality described in this post remain at Elastic's sole discretion. Any features or functionality not currently available may not be delivered on time or at all.</em></p><p><em>Elastic, Elasticsearch, and associated marks are trademarks, logos, or registered trademarks of Elasticsearch B.V. in the United States and other countries. All other company and product names are trademarks, logos, or registered trademarks of their respective owners.</em></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/cross-project-search-serverless-observability</link>
    <guid isPermaLink="false">cross-project-search-serverless-observability</guid>
    <category><![CDATA[Data Management]]></category>
    <category><![CDATA[Logs Analytics]]></category>
    <category><![CDATA[Incident Management]]></category>
    <dc:creator><![CDATA[Vinay Chandrasekhar]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt871cded13b7f8cb8/6aad22506ea6417ea141fe46/header.jpg" length="0" type="image/jpeg"/>
    <pubDate>Mon, 21 Sep 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[No log file too small: How Elastic Agent tracks files below the 1 KiB threshold]]></title>
    <description><![CDATA[Elastic Agent 9.5 builds a small log file's identity out of the bytes it already has, then re-links it as it grows and crosses 1024 bytes, so it is never re-ingested.]]></description>
    <content:encoded><![CDATA[<p>Elastic Agent 9.5 reads log files smaller than the 1024-byte fingerprint threshold.
Growing fingerprint identifies each one by hashing the bytes it currently has, and the read offset carries over when the file crosses the threshold, so nothing is re-ingested.
Until 9.5, a file that small had no stable identity, so it was skipped until it grew.</p>
<h2 id="whysmalllogfileswereskipped">Why small log files were skipped</h2>
<p>Filestream needs a stable identifier for every file it reads so that it can track how far it has read from each file.
In earlier versions, we used filesystem metadata such as inode numbers.
This was not enough.
For example, filesystems such as ext4 can reuse an inode number after a file is deleted, leading to a new file being mistaken for the old one.
This is why we <a href="https://www.elastic.co/blog/introducing-filestream-fingerprint-mode">introduced</a> the fingerprint identity and made it the default in 9.0.</p>
<p>Fingerprinting means files are identified by their content, not their metadata.
The first 1024 bytes (by default) are hashed and used as the unique identity of the file.
A registry keeps track of all IDs we encounter beyond the agent's process lifetime.
Restart your machine, change the underlying device, host the files over the network, it doesn't matter; Elastic Agent will remember.</p>
<p>The major downside is that we can't hash a file's first 1024 bytes if it doesn't have this many bytes to begin with.
Imagine a critical Kubernetes pod that panics on startup and its small log output is the reason the panic never reaches your Kibana dashboard.</p>
<p>The main way we've dealt with this until now is through configuration: instead of 1024 bytes, we could hash 512.
Or 256.
Or even less.
However, each step towards reducing the threshold leaves less file content available to distinguish one file from another.
Distinct files are therefore more likely to share the same fingerprint material, making the resulting file identity less stable.
This is where the new growing fingerprint can shine instead.</p>
<h2 id="howgrowingfingerprintsplitsfileidentityintotwophases">How growing fingerprint splits file identity into two phases</h2>
<p>With the introduction of the growing fingerprint mode, filestream splits a file's identity into two phases based on its size.
While a file is smaller than the 1024-byte fingerprint threshold, it's in "growing" mode.
Filestream identifies it with a hash of everything the file contains so far.
When the file grows past the threshold, it gets promoted to a static fingerprint, the one it would have had pre-9.5.</p>
<p>What about file-identity collisions?
Two files with identical contents do share a growing fingerprint, but only for as long as they stay identical: the moment their contents diverge, so do their fingerprints.</p>
<p>Let's take a closer look inside the internals.
Let's start with a simple 13-byte text file:</p>
<pre><code>mkdir -p /tmp/demo
echo 'Hello world!' &gt; /tmp/demo/file.txt
</code></pre>
<p>and start Filebeat, the component inside Elastic Agent that collects log files, with a minimal configuration:</p>
<pre><code>cat &gt;/tmp/demo/filebeat.yml &lt;&lt;YAML
path.home: /tmp/demo

filebeat.inputs:
  - type: filestream
    id: growing-demo
    paths: [ "/tmp/demo/file.txt" ]

output.file:
  path: /tmp/demo/out
logging.level: debug
YAML

filebeat -c /tmp/demo/filebeat.yml
</code></pre>
<h3 id="phase1thegrowingfingerprintofa13bytefile">Phase 1: the growing fingerprint of a 13-byte file</h3>
<p>After a few moments, filestream will pick up our new file.
Digging through the debug logs (vastly reduced here for simplicity), we see:</p>
<pre><code>{"log.level":"debug","log.logger":"input.filestream.prospector","message":"A new file /tmp/demo/file.txt has been found"}
</code></pre>
<p>We can also directly check the registry write-ahead log (WAL) filestream writes to <code>data/registry/filebeat/log.json</code>:</p>
<pre><code>{
  "k": "filestream::growing-demo::fingerprint::f04f9d5ad8c21a115cd207488c6aaadeb4d7db581ea675f48c7f5d23e038c805",
  "v": {
    "cursor": {
      "eof": false,
      "offset": 13
    },
    "meta": {
      "fingerprint_len": 13,
      "identifier_name": "fingerprint",
      "source": "/tmp/demo/file.txt"
    }
  }
}
</code></pre>
<p>The <code>k</code> field is the registry key filestream uses to identify this file state.
Its final segment is the fingerprint hash, which we can reproduce from the file contents:</p>
<pre><code>$ xxd -p /tmp/demo/file.txt | tr -d '\n' | sha256sum
f04f9d5ad8c21a115cd207488c6aaadeb4d7db581ea675f48c7f5d23e038c805  -
</code></pre>
<h3 id="stillinphase1thefingerprintchangesasthefilegrows">Still in phase 1: the fingerprint changes as the file grows</h3>
<p>Let's write more lines to the file:</p>
<pre><code>for i in {1..70}; do echo "Hello $i" &gt;&gt; /tmp/demo/file.txt; done
</code></pre>
<p>Filestream detects the write while the file remains below the threshold:</p>
<pre><code>{"log.logger":"input.filestream.prospector","message":"File /tmp/demo/file.txt has been updated","operation":"write"}
</code></pre>
<p>In the registry, the larger file now has a new fingerprint and a 634-byte <code>fingerprint_len</code>:</p>
<pre><code>{
  "k": "filestream::growing-demo::fingerprint::cb8e361971e7ba91e343d1b5bd8e6560be0bdadbbed2b2f89a6c50099e972d63",
  "v": {
    "cursor": {
      "eof": false,
      "offset": 634
    },
    "meta": {
      "fingerprint_len": 634,
      "identifier_name": "fingerprint",
      "source": "/tmp/demo/file.txt"
    }
  }
}
</code></pre>
<p>The updated key ends in the SHA-256 hash of the hexadecimal fingerprint material:</p>
<pre><code>$ xxd -p /tmp/demo/file.txt | tr -d '\n' | sha256sum
cb8e361971e7ba91e343d1b5bd8e6560be0bdadbbed2b2f89a6c50099e972d63  -
</code></pre>
<h3 id="phase2promotiontoastatic1024bytefingerprint">Phase 2: promotion to a static 1024-byte fingerprint</h3>
<p>Let's now grow the file beyond the 1024-byte threshold:</p>
<pre><code>for i in {1..500}; do echo "More messages $i" &gt;&gt; /tmp/demo/file.txt; done
</code></pre>
<p>Once the file crosses the threshold, filestream reports another write:</p>
<pre><code>{"log.logger":"input.filestream.prospector","message":"File /tmp/demo/file.txt has been updated","operation":"write"}
</code></pre>
<p>The registry key changes one final time as the growing fingerprint is promoted to its static form:</p>
<pre><code>{
  "k": "filestream::growing-demo::fingerprint::c8e252d76d621a02161f92e7a59d1617e3079db666cefd6cfcab106329082d1e",
  "v": {
    "cursor": {
      "eof": false,
      "offset": 9526
    },
    "meta": {
      "identifier_name": "fingerprint",
      "source": "/tmp/demo/file.txt"
    }
  }
}
</code></pre>
<p>The promoted fingerprint hashes the first 1024 bytes.
It is exactly what a pre-9.5 Filebeat would have computed for this file:</p>
<pre><code>$ head -c 1024 /tmp/demo/file.txt | sha256sum
c8e252d76d621a02161f92e7a59d1617e3079db666cefd6cfcab106329082d1e
</code></pre>
<h2 id="howfilestreammatchesagrowingfingerprinttoanexistingfile">How filestream matches a growing fingerprint to an existing file</h2>
<p>During each scan, filestream resolves a file's identity in this order:</p>
<ol>
<li>Look for an exact registry key matching the file's current fingerprint. This part has not changed.</li>
<li>If no key is found, search the growing entries for a shorter fingerprint, trying the same path first. A prefix match means filestream saw the same file earlier, when it contained fewer bytes.</li>
<li>Move the existing registry state to the new key, preserving the read offset.</li>
</ol>
<p>You might have noticed from the registry entries above that we don't store the raw file contents.
That was our initial approach, but it quickly exploded the size of the registry.
Instead, we use the SHA-256 hash as the registry key and store <code>fingerprint_len</code>, which records the number of raw bytes the fingerprint represents.
This reduces the storage required per entry by nearly 100×.</p>
<p>When filestream looks for a prefix match, it first groups the growing fingerprints by length.
It then streams the scanned file's hexadecimal fingerprint material through SHA-256, snapshotting the digest at each group length.
Filestream compares each snapshot only with stored hashes for the same length.
A match means that the stored fingerprint is a prefix of the scanned file.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdf5e8f36fc898105/6aabc22f92a58722afee1031/how-it-works.svg" alt="Diagram of a 700-byte scanned file whose hexadecimal fingerprint material is hashed in one SHA-256 pass. Digest snapshots are compared with registry entries of the same length; the 634-byte entry matches, so its state moves to the new key" /></p>
<h2 id="growingfingerprintbenchmarksmemoryandcpuat10000logfiles">Growing fingerprint benchmarks: memory and CPU at 10,000 log files</h2>
<p>So what does all this extra work cost?
We benchmark Elastic Agent 9.5 ingesting thousands of above-threshold and sub-threshold files.
To make the benchmark fair, we compare the growing fingerprint against the workaround of reducing the fingerprint size to 128 bytes.
Otherwise, we would be comparing doing something against doing nothing and nothing would always win.</p>
<p>The benchmarks run on an r8id.2xlarge EC2 instance (8 vCPUs, Ubuntu 24.04) and report the median of five runs.
Memory numbers are the Elastic Agent's anonymous memory as reported by the kernel via cgroups.</p>
<p>At 10,000 files, the two modes compare as follows.</p>
<p>| Metric | Condition | 128-byte static | Growing | Difference |
|---|---|---|---|---|
| Peak memory | Files above 1 KiB | 1,057 MiB | 1,099 MiB | +4% |
| Peak memory | Files below 1 KiB | 941 MiB | 1,012 MiB | +8% |
| Peak memory | Files crossing the threshold | 968 MiB | 1,016 MiB | +5% |
| Steady-state memory | Files above 1 KiB | 291 MiB | 290 MiB | none |
| Steady-state memory | Files below 1 KiB | 270 MiB | 322 MiB | +52 MiB |
| CPU, one core | Files above 1 KiB, idle | 4.1% | 4.1% | none |
| CPU, one core | Files below 1 KiB, idle | 3.9% | 4.9% | +1.0pp |
| CPU, one core | Files crossing the threshold | 16.8% | 25.5% | +8.7pp |
| Registry size at rest | Files above and below 1 KiB | 8.6 MB | 8.6 MB | none |</p>
<h3 id="memoryuseat1001000and10000logfiles">Memory use at 100, 1,000 and 10,000 log files</h3>
<p>At 100 and 1,000 files, 128-byte static and growing fingerprints differ by only a few MiB.
At 10,000 files the peaks separate by a few percent, but the only lasting difference is for sub-threshold files: above the threshold both modes stabilize within a MiB of each other.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltfb3f4bc0bc224b00/6aabc25719b2267b105a8882/benchmark-peak-memory.svg" alt="Grouped bar chart of peak memory at 100, 1,000 and 10,000 log files. At 10,000 files, growing uses 1,099 vs 1,057 MiB above the 1 KiB threshold and 1,012 vs 941 MiB below it" /></p>
<p>The most demanding case is when the fingerprints are still changing.
We created 10,000 sub-threshold files and appended 2,000 lines/s across them until every file crossed the threshold.
That repeatedly exercises the prefix-lookup path and moves registry state to new keys.
The extra memory this costs is released once the files are past the threshold and their harvesters close: both modes settle under 300 MiB, within a few MiB of each other:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltede5d60e559fad77/6aabc26a14e38491ae4eaa40/benchmark-promotion-timeline.svg" alt="Line chart of memory over thirteen minutes while 10,000 files grow past the 1 KiB threshold under sustained writes and then idle. After the harvesters close, the 128-byte static and growing fingerprint modes settle at about 282 and 291 MiB" /></p>
<h3 id="cpuusewithandwithoutwrites">CPU use with and without writes</h3>
<p>The CPU impact is larger: every scan re-hashes a sub-threshold file's full contents instead of a fixed 128-byte prefix.
That costs one extra percentage point of a core with no writes coming in, and almost nine under the changing-fingerprint workload above:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt218b4365aaf1002f/6aabc27e8f2cd72150e5380e/benchmark-cpu.svg" alt="Bar chart of CPU as percent of one core at 10,000 files. Idle scans cost 4.1% for 128-byte static and growing fingerprints on above-threshold files and 3.9% vs 4.9% on sub-threshold files; while files grow past the threshold under sustained writes, the 128-byte static fingerprint uses 16.8% and growing 25.5%" /></p>
<h3 id="whatchangesin96">What changes in 9.6</h3>
<p>We are already working on improving these numbers.
On current main (future 9.6 release), the same benchmarks peak at less than half the memory, and the growing fingerprint's steady-state overhead, in both memory and CPU, is roughly halved.</p>
<h2 id="upgradingandrollingback">Upgrading and rolling back</h2>
<p>Growing mode ships enabled by default in 9.5 through <code>file_identity.fingerprint.growing</code>.
Set it to <code>false</code> to restore the pre-9.5 behavior.
There is no need for a migration on upgrade; existing registry entries keep working and files that were previously too small get picked up on the next scan.</p>
<p>Rolling back is also possible, but you go back to pre-9.5 behaviour: small files are ignored and when they cross the threshold, they get re-ingested.</p>
<h2 id="whatgrowingfingerprintmeansforyourlogfiles">What growing fingerprint means for your log files</h2>
<p>Elastic Agent 9.5 ingests log files smaller than 1024 bytes by default, with no configuration and no waiting for the file to grow.
Shared identities between identical small files resolve themselves as soon as their contents diverge, which a static fingerprint cannot do.
The cost stays small: at realistic node scales the growing fingerprint is indistinguishable from the old workarounds, and at a 10,000-file workload it costs a few percent of memory and single-digit percent of one CPU core.
Remember when upgrading that small files that were invisible before count as new, however long they have been sitting in your paths.
If the trade doesn't work for you, <code>file_identity.fingerprint.growing: false</code> brings back the old behavior.</p>
<h2 id="references">References</h2>
<ul>
<li><a href="https://www.elastic.co/docs/reference/beats/filebeat/file-identity">How to choose file identity for filestream</a> covers all identity options and their trade-offs.</li>
<li><a href="https://www.elastic.co/docs/reference/beats/filebeat/file-identity#file-identity-fingerprint-growing">Growing fingerprint reference</a> documents the setting and the rollback caveat.</li>
<li><a href="https://github.com/elastic/beats/pull/50566">PR #50566</a> is the implementation and <a href="https://github.com/elastic/beats/pull/51784">PR #51784</a> the registry optimization; <a href="https://github.com/elastic/beats/pull/51863">PR #51863</a> and <a href="https://github.com/elastic/beats/pull/51914">PR #51914</a> trimmed per-scan allocations and fingerprint retention in 9.5.2.</li>
<li>Optimizations coming up in 9.6: <a href="https://github.com/elastic/beats/pull/51278">PR #51278</a> parks idle harvesters instead of keeping one goroutine per open file, <a href="https://github.com/elastic/beats/pull/51694">PR #51694</a> scans each file in a single pass, and <a href="https://github.com/elastic/beats/pull/52476">PR #52476</a> normalizes published events in place. <a href="https://github.com/elastic/beats/pull/52103">PR #52103</a>, which replaces the multiline timeout goroutine with a read deadline, is not exercised by these benchmarks.</li>
</ul>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/growing-fingerprint-filestream-file-identity</link>
    <guid isPermaLink="false">growing-fingerprint-filestream-file-identity</guid>
    <category><![CDATA[Logs Analytics]]></category>
    <category><![CDATA[Infrastructure Monitoring]]></category>
    <category><![CDATA[What's New]]></category>
    <dc:creator><![CDATA[Orestis Floros]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd520b0634f15c227/6aabc2b1a4ecdf0c30ca91a0/header.jpg" length="0" type="image/jpeg"/>
    <pubDate>Fri, 18 Sep 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Monitor Supabase in Elastic: dashboards, alert templates, SLO templates, and zero agents]]></title>
    <description><![CDATA[When your Supabase API goes slow, it could be the node, Postgres, the pooler or PostgREST. Elastic tells you which one and shows you the logs from whichever it was.]]></description>
    <content:encoded><![CDATA[<p>Your Supabase project is doing more than you think. Underneath the clean dashboard and the auto-generated APIs, there's a PostgreSQL database handling transactions, a connection pooler that manages competing clients, an Auth service for every login, a PostgREST layer turning HTTP into SQL, and a node underneath that you never touch. When something slows down, any one of them could be the cause.</p>
<p>The Elastic <a href="https://www.elastic.co/docs/reference/integrations/supabase">Supabase Integration</a> handles Supabase monitoring for every one of them, with nothing to deploy. No agent and no collector. Metrics come from Supabase's Prometheus-compatible Metrics API, logs arrive through Supabase Log Drains, and both sit in Elasticsearch where you can query them together. Four dashboards, a full set of alert rules, and four SLO templates install once data arrives. Elastic is a listed <a href="https://supabase.com/docs/guides/observability/metrics#choose-your-monitoring-stack">metrics monitoring partner</a> on Supabase's observability docs.</p>
<h2 id="whatsupabasemonitoringcoverslayerbylayer">What Supabase monitoring covers, layer by layer</h2>
<p>The <a href="https://www.elastic.co/docs/reference/integrations/supabase">Supabase Integration</a> covers the full stack of what Supabase actually is: a database plus a connection pooler plus an auth service plus a REST API layer plus real-time subscriptions plus file storage, all running on a node you don't directly manage.</p>
<p>The integration collects two signal types: metrics scraped from Supabase's Prometheus-compatible <a href="https://supabase.com/docs/guides/telemetry/metrics">Metrics API</a>, and logs forwarded through <a href="https://supabase.com/docs/guides/monitoring-and-debugging/log-drains">Supabase Log Drains</a>. But what's interesting is the depth of each; this isn't "CPU and error rate." It goes all the way from the node your project runs on down to individual PostgreSQL statements, connection pool wait times, and per-service request counts.</p>
<p><strong>Infrastructure and node metrics</strong> cover the compute layer Supabase runs on: CPU utilisation, memory availability, disk I/O, filesystem usage, and network traffic. On Supabase's smaller compute tiers, CPU and disk I/O are the most commonly exhausted resources. Their exhaustion cascades quietly into slower queries and connection pool pressure before anything explicitly fails.</p>
<p><strong>PostgreSQL internals</strong> go deep: database size, active connections versus the <code>max_connections</code> ceiling, transaction commit and rollback counts, cache hit ratios, WAL volume, replication lag, bgwriter activity, and statement-level statistics from <a href="https://www.postgresql.org/docs/current/pgstatstatements.html"><code>pg_stat_statements</code></a>. This is the data you need to understand whether slowdowns are a schema problem, a connection problem, or a capacity problem.</p>
<p><strong>Connection pooling</strong> covers both <a href="https://supabase.com/docs/guides/database/connecting-to-postgres#supavisor">Supavisor</a> (Supabase's shared pooler) and <a href="https://supabase.com/docs/guides/database/connecting-to-postgres#dedicated-pooler">PgBouncer</a> (the dedicated pooler). Pool occupancy, idle connections, and client wait times are the signals that show you whether connection exhaustion is approaching before clients start getting refused.</p>
<p><strong>Application services:</strong>  <a href="https://supabase.com/docs/guides/auth">Auth (GoTrue)</a>, <a href="https://supabase.com/docs/guides/api">PostgREST</a>, and <a href="https://supabase.com/docs/guides/realtime">Realtime</a> emits their own metrics. GoTrue tracks running status and authentication request rates. PostgREST exposes database pool availability and timeout counts. Realtime tracks active client subscriptions. These are the layers users actually interact with, and their health is distinct from PostgreSQL's health.</p>
<p><strong>Logs</strong> from every Supabase service land in a single data stream (<code>logs-supabase.otel-*</code>), with a service filter on the dashboard to focus on any individual component. When a PostgREST error rate spikes, the logs tell you exactly what requests failed and why.</p>
<p>All metrics land in <code>metrics-supabase.metrics.otel-*</code> with their original Prometheus names preserved.</p>
<h2 id="howtoconnectsupabasemetricsandlogstoelastic">How to connect Supabase metrics and logs to Elastic</h2>
<p>The integration has two setup paths that run in parallel: metrics and logs.</p>
<p><strong>For metrics</strong>:</p>
<ol>
<li>In Kibana, go to <strong>Management → Integrations → Supabase</strong> and install the <a href="https://www.elastic.co/docs/reference/integrations/supabase">Supabase integration</a>.</li>
<li>From your Supabase project, copy the Project URL under <strong>Settings → API</strong>.</li>
<li>Copy your <code>service_role</code> key from the same page.</li>
</ol>
<p>The integration uses those to authenticate against Supabase's <a href="https://supabase.com/docs/guides/telemetry/metrics">Prometheus endpoint</a> and scrape metrics on a schedule.</p>
<p><strong>For logs</strong>, Elastic operates a <a href="https://www.elastic.co/docs/reference/opentelemetry/managed-inputs">managed endpoint</a> that receives log drain payloads. In Kibana, go to <strong>Add data → More → Supabase</strong> and copy the Supabase logs endpoint URL. It follows this pattern:</p>
<pre><code>https://&lt;managed-endpoint&gt;/inputs/supabase/_default_/v1/logs
</code></pre>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt68309515cd517d21/6aa7ec595ef72d31f3c93e9f/elastic-supabase-endpoint.png" alt="The Supabase logs endpoint URL and Create key option in Elastic Add data" /></p>
<ol>
<li>Create an API key there.</li>
<li>Go to your Supabase project's <strong>Settings → <a href="https://supabase.com/docs/guides/monitoring-and-debugging/log-drains">Log Drains</a></strong> and create a drain.</li>
<li>Choose <strong>OpenTelemetry (OTLP)</strong> as the destination.</li>
<li>Set the endpoint URL.</li>
<li>Choose <code>http/protobuf</code> as the protocol.</li>
<li>Enable Gzip.</li>
<li>Add the header:</li>
</ol>
<pre><code>Authorization: ApiKey &lt;your-key&gt;
</code></pre>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta54badf95fc2b543/6aa7ec6ef08d252a9f9921a3/supabase_log_drain.png" alt="Supabase Log Drains configured with an OpenTelemetry destination, HTTP/Protobuf, Gzip, and an Authorization ApiKey header" /></p>
<p>For the full setup walkthrough, refer to the <a href="https://www.elastic.co/docs/reference/integrations/supabase">Supabase (OpenTelemetry) Integration docs</a>.</p>
<h2 id="whatshipswithsupabasemonitoringinelastic">What ships with Supabase monitoring in Elastic</h2>
<p>Once data is flowing, dashboards, alert rule templates, and SLO templates install automatically via the <a href="https://www.elastic.co/docs/reference/integrations/supabase_otel">Supabase OpenTelemetry Assets</a> package.</p>
<h3 id="foursupabasedashboardsinkibana">Four Supabase dashboards in Kibana</h3>
<p>The <strong>Node &amp; Infrastructure dashboard</strong> is where resource exhaustion shows up first. CPU saturation, memory pressure, disk I/O utilisation, filesystem usage, and network throughput – all at the node level. On smaller compute tiers, this dashboard often tells the story before PostgreSQL metrics do.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltbc10375fb9bcef24/6aa7ec84e96fa6d5fd7859fd/node_infrastructure_1.png" alt="Supabase Node and Infrastructure dashboard in Kibana showing memory and network metrics" /></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltbe21c62b7f01ba8f/6aa7ec961a380ed1b5845eff/node_infra_2.png" alt="Supabase Node and Infrastructure dashboard in Kibana showing CPU and disk I/O" /></p>
<p>The <strong>Databases &amp; PostgreSQL dashboard</strong> covers the database layer in depth: connection counts versus the <code>max_connections</code> limit, transaction throughput, cache hit ratios, WAL volume, replication lag, PgBouncer pool status, and bgwriter activity. This is where you diagnose whether a slowdown is a schema problem, a connection problem, or a capacity problem.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8d6ce14e5022dfde/6aa7ecaf5ceda9fc89dae3cf/databases_postgresql.png" alt="Supabase Databases and PostgreSQL dashboard in Kibana showing connections, WAL, query time, and database size" /></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt424dc59e80f6e728/6aa7eccaa812984078e3b868/databases_postgresql_1.png" alt="Supabase Databases and PostgreSQL dashboard in Kibana showing query throughput, bgwriter, cache hits, and connection pooling" /></p>
<p>The <strong>Services &amp; API dashboard</strong> covers the application layer: Auth (GoTrue) running status and request rates, PostgREST database pool availability and timeouts, Realtime subscription counts, and Go runtime metrics. A PostgREST pool timeout spike or a GoTrue outage shows up here before users start filing support tickets.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8fe3a3ae223d21b9/6aa7ecdff08d25ff1a9921a7/services_api.png" alt="Supabase Services and API dashboard in Kibana showing Auth, PostgREST, Realtime, and Go runtime metrics" /></p>
<p>The <strong>Service Logs dashboard</strong> brings together log streams from every Supabase service with a service filter to focus on any single component. When an error rate spikes on the Services dashboard, this is where you read what actually happened.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc7d7a719bee1a82a/6aa7ecf05ef72d8cbcc93ea3/logs1.png" alt="Supabase Service Logs dashboard in Kibana showing log volume, errors by service, Postgres logs, and API gateway traffic" /></p>
<h3 id="alertruletemplatesforsupabasefailuremodes">Alert rule templates for Supabase failure modes</h3>
<p>Pre-built alert rules ship with the Assets package, covering Supabase's specific failure modes in a way generic infrastructure alerts don't. They cover:</p>
<ul>
<li><strong>Service availability</strong> – PostgreSQL, Auth (GoTrue), and PgBouncer each have dedicated rules, because each failure has a different blast radius. PostgreSQL down means everything fails. Auth down means no new logins. PgBouncer down means clients fall back to direct connections and can exhaust the connection budget within minutes.</li>
<li><strong>Connection budget exhaustion</strong> – the most common way a Supabase project fails. Once <code>max_connections</code> is hit, new clients are refused outright.</li>
<li><strong>Long running transactions</strong> – which hold locks, block vacuum, and pin WAL simultaneously.</li>
<li><strong>PostgreSQL deadlocks</strong> – any deadlock in the window is worth surfacing. These are application bugs, not capacity problems, and they show up as <code>pg_stat_database_deadlocks_total</code> increasing.</li>
<li><strong>Replication slot lag</strong> – building quietly toward disk exhaustion.</li>
<li><strong>Disk I/O saturation and memory pressure</strong> – infrastructure signals that precede failures at the database and service layer.</li>
<li><strong>PostgREST pool timeouts</strong> – each one a user visible 504 error.</li>
<li><strong>Metrics collection health</strong> – fires when the scraper itself starts failing, because a silent scraper means every other alert goes quiet.</li>
</ul>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1d859014634d7443/6aa7ed0866ee2e04873766f8/postgresql-deadlocks-alert.png" alt="Kibana alert rule for Supabase OTel PostgreSQL deadlocks detected, querying metrics-supabase.metrics.otel with ES|QL" /></p>
<h3 id="slotemplatesforsupabase">SLO templates for Supabase</h3>
<p>Four SLO templates ship with the Assets package.</p>
<p>| SLO template | Target | Window | What it catches |
|---|---|---|---|
| Database availability | 99.5% | Rolling 30 days | PostgreSQL down, which takes everything else with it |
| Mean query latency | 99.5% | Rolling 30 days | Average execution time above 50ms, the closest project-wide signal for a slow API |
| REST API connection pool timeouts | 99.5% | Rolling 30 days | PostgREST pool exhaustion, where every timeout is a user visible HTTP 504 |
| Transaction commit ratio | 99.5% | Rolling 30 days | Fewer than 95% of transactions committing |</p>
<h2 id="startmonitoringyoursupabaseproject">Start monitoring your Supabase project</h2>
<p>If you're on Supabase <a href="https://supabase.com/pricing">Pro, Team, or Enterprise</a> and already using <a href="https://cloud.elastic.co">Elastic Cloud</a>, both signal types are available to you today. The <a href="https://www.elastic.co/docs/reference/integrations/supabase">integration</a> covers the full stack (infrastructure, database internals, application services, and logs), so you can start with whatever is most pressing and layer in the rest from there.</p>
<p>The integration is available on <a href="https://www.elastic.co/docs/deploy-manage/deploy/elastic-cloud/serverless">Elastic Cloud Serverless</a> and <a href="https://www.elastic.co/docs/deploy-manage/deploy/elastic-cloud/cloud-hosted">Elastic Cloud Hosted</a>. Head to <strong>Management → Integrations → Supabase</strong> in Kibana to get started, or follow the <a href="https://www.elastic.co/docs/reference/integrations/supabase">Supabase (OpenTelemetry) Integration docs</a>.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/supabase-on-elastic-observability</link>
    <guid isPermaLink="false">supabase-on-elastic-observability</guid>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[Metrics]]></category>
    <category><![CDATA[Logs Analytics]]></category>
    <dc:creator><![CDATA[Ishleen Kaur]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt024bc7f60cae219d/6aa7eb960cac0d1715315de7/title_supabase.png" length="0" type="image/png"/>
    <pubDate>Wed, 16 Sep 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Collecting rootless Podman logs with Elastic Agent: the CRI parser, user-scoped paths, and the Podman socket]]></title>
    <description><![CDATA[Rootless Podman containers write their logs in CRI format. This Fleet policy reads them and attaches container.* fields, with the match_source_index value that rootless paths need.]]></description>
    <content:encoded><![CDATA[<p>Rootless Podman exposes a Docker-compatible API, but its logs are a different matter. They use CRI format, they live under the user's home directory, and the API socket is per-user. The Elastic Docker integration is built for Docker's JSON logs and its <code>/var/lib</code> paths, so a Custom Logs integration is the right input here. Collecting rootless Podman logs with Elastic Agent needs three settings: <code>format: cri</code> on the container parser, a file path into the user's <code>overlay-containers</code> storage, and <code>add_docker_metadata</code> pointed at the rootless Podman socket with <code>match_source_index: 7</code>. The default of <code>4</code> is correct for Docker's shorter path.</p><p>This post builds that pipeline from a real debugging session and ends with Agent Builder diagnosing a Jellyfin playback failure from the enriched logs. It also covers the alternatives, <code>journald</code> and the OpenTelemetry Collector, and when to prefer them.</p><h2>How rootless Podman logs differ from Docker logs</h2><p>The Docker integration reads Docker's format, paths and socket, and rootless Podman uses different ones for all three. Each produced a distinct symptom during our debugging session.</p><h3>The log format is CRI, not Docker JSON</h3><p>Podman's default file-based log driver is <code>k8s-file</code>, and even <code>--log-driver=json-file</code> is just an alias for it.</p><p>Instead of Docker's one-JSON-object-per-line format, Podman writes the format used by Kubernetes container runtimes (CRI): a timestamp, the stream name, a partial/full flag, and then the message.</p><p>An example follows:</p>2026-08-31T09:15:04.518084921+02:00 stdout F 10.89.0.2 - - [31/Aug/2026:07:15:04 +0000] "GET / HTTP/1.1" 200 615<p>A parser expecting Docker JSON breaks on this immediately.</p><h3>Where rootless Podman stores container logs</h3><p>Rootful Docker writes logs under <code>/var/lib/docker/containers/&lt;container-id&gt;/</code>. Rootless Podman stores everything under the user's home instead:</p>/home/&lt;user&gt;/.local/share/containers/storage/overlay-containers/&lt;container-id&gt;/userdata/ctr.log<p>Any integration with a hardcoded <code>/var/lib/...</code> path finds nothing.</p><h3>The Podman API socket is per-user</h3><p>In rootless Podman there is no <code>/var/run/docker.sock</code>. Instead, it exposes a Docker-compatible API on a user-scoped socket, typically <code>/run/user/&lt;uid&gt;/podman/podman.sock</code>.</p><h2>Where Elastic Agent runs: host install vs containerized</h2><p>This walkthrough assumes Elastic Agent is installed directly on the host, not in a container. In our case, the Agent is Fleet-managed and installed directly on a Ubuntu host. It is <em>not</em> running in a container. That matters because the paths in the Agent Policy are resolved by the Agent process itself: a containerized Agent only sees its own mount namespace, so the host directories above would have to be volume-mounted into the Agent container and referenced by their in-container paths. Running as root with default privileges is also what lets the Agent read files under another user's home directory.</p><h2>How the rootless Podman log pipeline fits together</h2><p>The setup we ended up with is simple once all the pieces are known:</p><ol><li>Elastic Agent tails the raw CRI log files from the rootless user's container storage.</li><li>A <code>container</code> parser with <code>format: cri</code> strips the timestamp, stream, and flag prefix from each line.</li><li>A processor extracts the container ID from a fixed position in the log file path.</li><li>The <code>add_docker_metadata</code> processor resolves that ID against the rootless Podman socket and attaches container name, image, and labels.</li><li>Enriched events are shipped to Elasticsearch.</li></ol><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt417ffefa1a64204c/6aa4376bf548681006812d7e/architecture.png" alt="Diagram showing Elastic Agent on the host reading CRI log files from the rootless user's container storage, enriching events with metadata from the Podman user socket, and shipping them to Elasticsearch" /><p>Let's now build the log ingestion pipeline step by step.</p><h2>Step 1: Confirm the Podman log driver</h2><p>First, check which log driver Podman uses on your host, because the default varies by distribution and <code>containers.conf</code>:</p>podman info --format '{{ .Host.LogDriver }}'<p>If it prints <code>journald</code>, either switch to the file-based driver or jump to the journald alternative at the end of this post. To make <code>k8s-file</code> the default for all containers of this user, set it in <code>~/.config/containers/containers.conf</code>:</p>[containers]
log_driver = "k8s-file"<p>You can also set it per container with <code>podman run --log-driver=k8s-file</code>. Remember that <code>json-file</code> is accepted but silently behaves as <code>k8s-file</code>: do not expect Docker-formatted JSON from it. Containers pick up the driver at creation time, so you will need to recreate any containers that were started with a different driver.</p><h2>Step 2: Enable the rootless Podman API socket</h2><p>The metadata enrichment in step 4 needs the Podman API. Enable the user-scoped socket as the user that runs the containers:</p>systemctl --user enable --now podman.socket  # without sudo!
loginctl enable-linger $USER<p>The <code>enable-linger</code> call keeps the user's systemd instance, and with it the socket, alive when the user is not logged in. Verify the socket answers Docker-compatible API calls:</p>curl --unix-socket /run/user/$(id -u)/podman/podman.sock \
  http://d/v1.41/containers/json<p>You should get a JSON array describing the running containers. This compatibility layer is exactly why a processor named <code>add_docker_metadata</code> will work against Podman later.</p><h2>Step 3: Collect rootless Podman logs with a Custom Logs integration</h2><p>In Fleet, add the <strong>Custom Logs (Filestream)</strong> integration to your agent policy instead of the Docker integration.</p><p>Set the file path pattern to the rootless storage location:</p>/home/&lt;user&gt;/.local/share/containers/storage/overlay-containers/*/userdata/ctr.log<p>Then, in the advanced options, configure the parser so the agent understands the CRI format:</p>- container:
    format: cri<p>The <code>container</code> parser removes the <code>&lt;timestamp&gt; &lt;stream&gt; &lt;flag&gt;</code> prefix, reassembles partial lines (the <code>P</code> flag marks a message that was split), and stores the original timestamp and stream in the event.</p><p>For standalone agents, the equivalent input configuration looks like this:</p>- type: filestream
  id: rootless-podman-logs
  data_stream:
    dataset: podman.container_logs
  paths:
    - /home/&lt;user&gt;/.local/share/containers/storage/overlay-containers/*/userdata/ctr.log
  parsers:
    - container:
        format: cri<p>At this point you should see clean log messages in Discover, but without any container context: no name, no image, no labels. A raw container ID buried in <code>log.file.path</code> is all you have, which makes the data hard to filter and nearly impossible to correlate.</p><h2>Step 4: Enrich with container metadata using add_docker_metadata</h2><p>The <code>add_docker_metadata</code> processor enriches our logs with information such as the container name. It extracts a container ID from the log file path, queries the Docker-compatible API for that container, and attaches the container's metadata to each event.</p><p>Add it to the integration's processors field (or under <code>processors:</code> in a standalone input):</p>- add_docker_metadata:
    # Replace `1000` with the UID of the user running the containers (`id -u &lt;user&gt;`).
    host: "unix:///run/user/1000/podman/podman.sock"
    match_source: true
    match_source_index: 7<p>The <code>match_source_index</code> value deserves an explanation, as it is not a commonly used one. The processor splits the log file path on <code>/,</code> discards the empty leading element, and picks the component at the given index as the container ID.</p><p>For the rootless Podman path, the indices work out like this:</p><p>Index</p><p>Component</p><p>0</p><p><code>home</code></p><p>1</p><p><code>&lt;user-id&gt;</code></p><p>2</p><p><code>.local</code></p><p>3</p><p><code>share</code></p><p>4</p><p><code>containers</code></p><p>5</p><p><code>storage</code></p><p>6</p><p><code>overlay-containers</code></p><p>7</p><p><code>&lt;container-id&gt;</code></p><p>8</p><p><code>userdata</code></p><p></p><p>The default value of <code>4</code> exists because Docker's path is <code>/var/lib/docker/containers/&lt;container-id&gt;/...</code>, where index 4 lands on the ID. However, as you can see from the table above, in our case the proper value is <code>7</code>.</p><p>Note that the correct index depends on the depth of the home directory. <code>/home/&lt;user-id&gt;/...</code> puts the ID at index 7, but a nonstandard home location shifts it. Count the components of your actual path to verify whether <code>7</code> is the right value for you.</p><p>Once the ID resolves correctly, the processor calls the Podman socket and each event gains the familiar <code>container.*</code> fields. Here is a real document from a Jellyfin container managed with podman-compose:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1eeffc8b04e32828/6aa43d0048a68a62da1d8e1d/discover-enriched.png" alt="Kibana Discover document flyout showing a rootless Podman log event enriched with container.id, container.image.name and container labels, including podman-compose and OCI image labels" /><p>The metadata is more complete than it first appears: alongside <code>container.id</code> and <code>container.image.name</code>, every label on the container arrives too, including the <code>com_docker_compose_*</code> and <code>io_podman_compose_*</code> labels set by <code>podman-compose</code> and the standard <code>org_opencontainers_image_*</code> labels baked into the image. Filtering all logs of a compose project is now one query on <code>container.labels.com_docker_compose_project</code>.</p><h2>Step 5: Validate the rootless Podman log pipeline</h2><p>Generate some traffic and check Discover:</p>podman run -d --name nginx-demo -p 8080:80 nginx
curl localhost:8080<p>A healthy pipeline produces events where:</p><ul><li><code>message</code> contains only the application log line, with no CRI prefix.</li><li><code>@timestamp</code> matches the timestamp Podman wrote.</li><li><code>log.file.path</code> points into <code>overlay-containers</code>.</li><li><code>container.id</code> is the full 64-character ID, and <code>container.name</code> and <code>container.image.name</code> are populated.</li></ul><p>If metadata is missing but messages parse fine, test the socket with the <code>curl</code> command from step 2 and re-check <code>match_source_index</code>. If messages still carry a timestamp prefix, the CRI parser is not applied; verify the parser YAML made it into the integration policy.</p><h2>Alternatives for collecting rootless Podman logs</h2><p>The file-based approach above yields the same ECS <code>container.*</code> fields as the Docker integration, but three alternatives are worth weighing first.</p><p></p><p>Approach</p><p>Metadata</p><p>ECS <code>container.*</code></p><p>Throughput</p><p>Best when</p><p>Custom Logs + <code>add_docker_metadata</code></p><p>Name, image, and all labels from the Podman socket</p><p>Native</p><p>High, plain file tailing</p><p>You want the same fields the Docker integration produces</p><p><code>journald</code> log driver</p><p>Container ID, name, and image as journal fields</p><p>Needs a rename step</p><p>Lower, rate limited by <code>RateLimitBurst</code></p><p>Containers run as systemd services via Quadlet</p><p>Docker integration on the compat socket</p><p>None for logs</p><p>Metrics datasets only</p><p>Not applicable to logs</p><p>You only need metrics, validated per dataset</p><p>OpenTelemetry Collector (<code>filelog</code>)</p><p><code>container.id</code> via a regex operator, names and images need custom work</p><p>Partial</p><p>High, plain file tailing</p><p>You are standardizing on EDOT</p><h3>
Collect Podman logs with the journald log driver</h3><p>Podman integrates with systemd natively, and on many distributions <code>journald</code> is already the default log driver. With <code>--log-driver=journald</code>, Podman writes each log line to the journal and attaches <code>CONTAINER_ID_FULL</code>, <code>CONTAINER_NAME</code>, and the image name as structured journal fields.</p><p>You can collect these with the <a href="https://www.elastic.co/docs/current/integrations/journald"><strong>Custom Journald logs</strong></a> integration. The metadata comes for free, with no socket, no path counting, and no <code>match_source_index</code>.</p><p>However, journald applies rate limiting that can drop bursts from chatty containers unless you raise <code>RateLimitBurst</code>. Throughput is lower than plain file tailing. The container fields also arrive with journald's naming, so you need a rename step (an ingest pipeline or processors) to get ECS-style <code>container.*</code> fields. Rootless containers also write to the per-user journal rather than the system one. An agent running as root still sees those entries, because reading the default journal as root includes user journals, but enable journald persistence (<code>Storage=persistent</code> in <code>journald.conf</code>) if you want container logs to survive a reboot.</p><p>If your containers are managed as systemd services via Quadlet, this is a natural fit.</p><h3>Point the Docker integration at the Podman compat socket (metrics only)</h3><p>Since Podman exposes a Docker-compatible API, it is tempting to point the Docker integration's <code>host</code> at <code>unix:///run/user/&lt;uid&gt;/podman/podman.sock</code>.</p><ul><li>Metrics datasets can work, because they only talk to the API.</li><li>Logs do not, because the integration reads Docker's JSON log files from Docker's paths, and rootless Podman uses neither. Treat this as a partial option for metrics, and validate each dataset you enable.</li></ul><h3>Collect Podman logs with the OpenTelemetry Collector</h3><p>If you are standardizing on OpenTelemetry, for instance by using EDOT (Elastic Distributions of OpenTelemetry) the Collector's <code>filelog</code> receiver reads the same files with its <code>container</code> operator, which auto-detects CRI-style formats:</p>receivers:
  filelog:
    include:
      - /home/&lt;user&gt;/.local/share/containers/storage/overlay-containers/*/userdata/ctr.log
    operators:
      - type: container
        add_metadata_from_filepath: false<p>Set <code>add_metadata_from_filepath: false</code> because that option expects Kubernetes pod log paths, which don't match Podman's layout.</p><p>Here you will miss out on some data enrichment: there is no Podman equivalent of the <code>k8sattributes</code> processor, so container names and labels are not attached automatically.. You can extract <code>container.id</code> from the file path with a regex operator, but resolving it to names and images requires custom work. The contrib Collector does include a <code>podman_stats</code> receiver for container metrics over the same socket, so a Collector-based setup covers metrics well and logs with reduced metadata.</p><h2>Parsing application logs from Podman containers</h2><p>The Custom Logs pipeline above ships raw log lines without application-level parsing. For instance, Nginx logs will end up in your cluster plainly, without proper processing. If you'd like to enable proper processing, you can use Ingest Pipelines to "redirect" specific containers (e.g. <code>nginx</code>) to the Ingest Pipeline defined by the Elastic Integration, and get full parsing of that data "for free".</p><h2>Root cause analysis on Podman logs with Agent Builder</h2><p>Was it worth the effort? Absolutely! Getting clean, metadata-rich container logs into Elasticsearch lets an LLM investigate issues against the data directly, and <a href="https://www.elastic.co/docs/solutions/observability/ai/agent-builder-observability">Elastic Agent Builder</a> is the quickest way to see that in action.</p><p>Agent Builder, available on Elastic Cloud Serverless and the Enterprise tier for Elastic Cloud Hosted and self-managed, provides a chat interface in Kibana, backed by an LLM, with built-in tools to explore your indices and run ES|QL queries against them.</p><p>Here is a real example from the host we just configured. Among other containers, it runs Jellyfin, a media server, as a rootless Podman container. One day, playback of a title fails instantly on an Android TV client: the screen goes black and drops back to the menu, with no error apart from a cryptic "Playback error".</p><p>In Discover, filtering on <code>container.name: "jellyfin"</code> narrows the view to a few hundred verbose .NET log lines around the playback attempts. The histogram shows a burst of activity at each failed attempt, but the actual cause is buried somewhere in the noise.</p><p>Instead of scanning the messages manually, open the Agent Builder panel directly from Discover and ask it to investigate the issue. The agent queries the log data stream on its own, reconstructs the timeline, and returns a diagnosis:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltbc2f9f7746f35d25/6aa45ce1afd9a87b6b120b81/agent-builder-jellyfin-podman.jpg" alt="AI Agent panel in Kibana Discover diagnosing a Jellyfin playback failure from rootless Podman container logs, identifying a failing FFmpeg hardware-transcoding pipeline and suggesting verification steps" /><p>In this run it worked out that the client requested playback six times. Jellyfin tried to convert a 4K Dolby Vision/HEVC video to H.264 through VA-API hardware transcoding while burning in a subtitle stream, and FFmpeg exited with code 187 each time before producing the first HLS segment. That is why playback stopped at zero milliseconds. It also stated its uncertainty honestly: the available logs don't contain FFmpeg's stderr, so it proposed concrete steps to discriminate between a broken transcoding combination and missing GPU access, including checking whether the container can reach the render device <code>/dev/dri/renderD128</code>.</p><p>The pipeline we just built made each step of this investigation possible. The agent can slice by <code>container.name</code> only because <code>add_docker_metadata</code> resolved it from the Podman socket, the <code>message</code> field is queryable because the CRI parser stripped the prefixes, and the timeline is trustworthy because <code>@timestamp</code> comes from Podman, not from ingestion time. Without that work, the LLM would face a pile of prefixed raw lines, likely resulting in worse performance and higher token usage.</p><h2>Conclusion: three settings for rootless Podman logs</h2><p>Rootless Podman is Docker-compatible where it matters the most: the API. For log collection it uses its own format, file paths, and socket location, which is what the Elastic Docker integration reads.</p><p>Once you know that, the fix is three configuration decisions: parse <code>cri</code> instead of Docker JSON, tail the user's <code>overlay-containers</code> storage, and point <code>add_docker_metadata</code> at the per-user Podman socket with a corrected <code>match_source_index</code>.</p><p>The same recipe applies to any rootless user on the host; only the home path and UID in the socket change.</p><p>If you want to try this workflow end to end to collect your Podman logs, spin up an <a href="https://cloud.elastic.co/registration">Elastic Cloud trial</a>, install the Elastic Agent on your host, and you can go from raw CRI files to fully enriched container logs in one integration policy. If you want to push this further, from a one-off chat to automated investigations that open cases with evidence attached, see <a href="https://www.elastic.co/observability-labs/blog/automated-root-cause-analysis-agent-builder">automated root cause analysis with Elastic Agent Builder</a>.</p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/rootless-podman-logs-elastic-agent</link>
    <guid isPermaLink="false">rootless-podman-logs-elastic-agent</guid>
    <category><![CDATA[Logs Analytics]]></category>
    <category><![CDATA[Infrastructure Monitoring]]></category>
    <category><![CDATA[Agentic Observability]]></category>
    <dc:creator><![CDATA[Lorenzo Soligo]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt087aeec3734bc804/6aa42c849936f588158b85c8/header_(2).jpg" length="0" type="image/jpeg"/>
    <pubDate>Mon, 14 Sep 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[AI root cause analysis in Elastic Agent Builder that cites its evidence]]></title>
    <description><![CDATA[The new release failed at 27.2%, the old one at 28.2%, so the deploy was never the cause; the agent worked that out in 72 seconds and handed back a trace ID for the failure that was.]]></description>
    <content:encoded><![CDATA[<p>Root cause analysis is the work of separating the failure that started an incident from everything that broke because of it, or merely alongside it. A cascading failure makes that hard: a deploy that shipped the same minute and a second service failing at the same time both look like causes.</p>
<p>Three ES|QL tools and about two dozen lines of query text turn AI root cause analysis into something you can check. This article builds them in <a href="https://www.elastic.co/docs/solutions/observability/ai/agent-builder-observability">Elastic Agent Builder</a> and wires them to an agent whose report names the tool behind every number, and a <code>trace.id</code> and document <code>_id</code> behind every root cause claim.</p>
<h2 id="whatyouneedtoreproducethisrootcauseanalysis">What you need to reproduce this root cause analysis</h2>
<ul>
<li>Elasticsearch and Kibana 9.5, self-managed or on Elastic Cloud.</li>
<li>A generative AI connector for Agent Builder. Ours is Anthropic Claude Sonnet 4.6.</li>
<li>Python 3.12 with <code>opentelemetry-distro[otlp]</code> and the Flask and requests instrumentations.</li>
<li>An Elasticsearch API key with write access for OTLP ingest and Kibana access for the Agent Builder APIs.</li>
</ul>
<p>Use the <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/observability-labs/ai-root-cause-analysis-agent-builder/evidence-first-rca-agent-builder.ipynb">companion notebook</a> if you want to reproduce the use case in this article.</p>
<h2 id="thedemoenvironmentfourpythonservicesonopentelemetry">The demo environment: four Python services on OpenTelemetry</h2>
<p>Four Python services run on one host. <code>checkout-api</code> handles the customer request and calls <code>pricing-api</code>, which calls <code>fx-rates</code> for a currency quote. A fourth service, <code>search-api</code>, serves product search and sits outside that call path.</p>
<p>Each service exports OTLP straight to the Elasticsearch native OTLP endpoint, with no collector in between. Logs land in <code>logs-generic.otel-default</code> and spans in <code>traces-generic.otel-default</code>. Two <code>checkout-api</code> processes run on different versions at once, which one of the tools below depends on.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta41d352de970c26a/6aa13544e1500a5ccf018ff5/02-demo-environment.png" alt="checkout-api calls pricing-api, which calls fx-rates. search-api sits outside that path. All four export OTLP to logs-generic.otel-default and traces-generic.otel-default, which the three tools read with ES|QL" /></p>
<p>Starting a service is one command, with the OTLP endpoint and the service identity in the environment:</p>
<pre><code>export OTEL_EXPORTER_OTLP_ENDPOINT="${ES_URL}/_otlp"
export OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
export OTEL_EXPORTER_OTLP_HEADERS="Authorization=ApiKey ${ES_API_KEY}"
export OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED=true

OTEL_SERVICE_NAME=checkout-api \
OTEL_RESOURCE_ATTRIBUTES="service.version=2026.07.26.2,deployment.environment=production" \
  opentelemetry-instrument python services/checkout_api.py
</code></pre>
<h2 id="whytheloudestserviceisnottherootcause">Why the loudest service is not the root cause</h2>
<p>At 09:55:33Z, <code>fx-rates</code> starts refusing about a third of its requests because its cached FX snapshot is older than the <code>max_age</code> it enforces. <code>pricing-api</code> turns each refusal into a 500, and <code>checkout-api</code> returns a 500 to the customer.</p>
<p>Two unrelated things happen in the same window. Release 2026.07.26.2 of <code>checkout-api</code> rolls out at 09:55, the minute the errors start, and 120 milliseconds after <code>fx-rates</code> breaks, <code>search-api</code> starts timing out during a scheduled reindex of its catalog.</p>
<p>One failure propagates, and the two other signals share only the clock:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd86d1d0df5c606bb/6aa1355b5f9db7554a56331f/03-cascade-vs-coincidence.png" alt="fx-rates returns a 503 at 09:55:33.991 that pricing-api turns into a 500 and checkout-api passes to the customer. The deploy at 09:55:00.000 and the search-api timeouts at 09:55:34.111 hang off that chain, sharing no traces with it" /></p>
<p>The on-call view is four unhealthy services and a fresh deploy. This query gives the failure rate per service over the window:</p>
<pre><code>FROM traces-generic.otel-default
| WHERE kind == "Server"
| EVAL failed = CASE(attributes.http.status_code &gt;= 500, 1, 0)
| STATS failures = SUM(failed), requests = COUNT(*)
    BY service = resource.attributes.service.name
| EVAL failure_rate_pct = ROUND(100.0 * failures / requests, 1)
| SORT failure_rate_pct DESC
</code></pre>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt271a014535cce4bb/6aa1356b5c31268ee14413e6/04-failure-rate-query.png" alt="The failure rate query in the ES|QL editor" /></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc67e72759b5b3863/6aa1357bde23953f28e86152/05-failure-rate-results.png" alt="Failure rate per service: search-api 36.4%, and 27.7% each for checkout-api, pricing-api and fx-rates" /></p>
<p>Nothing in the result ranks the candidates. <code>search-api</code> has the highest failure rate at 36.4%, and it is the one service with no involvement in the failing checkouts.</p>
<h2 id="automatedrootcauseanalysiswiththedefaultelasticaiagent">Automated root cause analysis with the default Elastic AI Agent</h2>
<p>Before writing any tools, we gave the incident to the default Elastic AI Agent to see how far the built-in observability skills get on their own:</p>
<pre><code>checkout-api started returning HTTP 500s to customers today. Investigate the
window 2026-07-26T09:50:00.000Z to 2026-07-26T10:02:00.000Z and tell me the root
cause. Context you have from the deploy log: checkout-api release 2026.07.26.2
rolled out at 09:55 UTC, and the on-call channel also reported search-api
timeouts starting at 09:55 UTC.
</code></pre>
<p>It got the cascade right. It named <code>fx-rates</code> as the origin, quoted the stale-snapshot message, and separated the <code>search-api</code> timeouts as a different problem. Then it ranked its hypotheses:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd06ff05a0e9fa650/6aa1358e346a4b0d74409087/06-default-agent-hypotheses.png" alt="The default agent's ranked hypotheses, with the deploy second, and its impact summary reporting ~38% over 5,331 requests" /></p>
<p>Hypothesis 2 claims the release restarted <code>fx-rates</code> without refreshing its snapshot, or lowered its <code>max_age</code>. Neither appears in the telemetry: the release was on <code>checkout-api</code>, and <code>fx-rates</code> reports <code>service.version</code> 2026.07.19.1 across all 7,374 of its spans.</p>
<p>The agent had a timestamp: the deploy and the first error share a minute. Across three runs, it connected them with a different invented mechanism each time:</p>
<ul>
<li>a restart that dropped the snapshot</li>
<li>a new call path that was not exercised before the release</li>
<li>a previously tolerant code path that stopped tolerating stale data</li>
</ul>
<p>The numbers have the same problem. The impact summary in that same answer reports the failure rate as "~38%", dividing the 2,043 failures by the 5,331 successes instead of the 7,374 requests, with no query next to the number for a reader to check.</p>
<p>The rest of this article closes that gap with tools.</p>
<h2 id="the16builtinobservabilitytoolsinagentbuilder">The 16 built-in observability tools in Agent Builder</h2>
<p>Agent Builder ships with tools that cover most of the exploration work, so check the <a href="https://www.elastic.co/docs/explore-analyze/ai-features/agent-builder/tools/builtin-tools-reference">built-in tools reference</a> before writing anything. The catalog is under <strong>Agent Builder &gt; Manage components &gt; Tools</strong>:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt941df10ff2d942b0/6aa1359e80575524cc70670a/07-tools-catalog.png" alt="The Agent Builder tools catalog" /></p>
<p>Sixteen of the built-in tools are observability tools, organized around investigation steps instead of index operations:</p>
<p>| Tool | What it answers |
| :---- | :---- |
| <code>observability.get_logs</code> | What is the log volume and shape for this filter, with samples and message categories |
| <code>observability.get_traces</code> | What documents belong to these traces, grouped by <code>trace.id</code> |
| <code>observability.get_service_topology</code> | Which dependencies does this service have, with error rate per connection |
| <code>observability.get_apm_correlations</code> | Which attributes are over-represented in the slow or failing transactions |
| <code>observability.run_log_rate_analysis</code> | Which fields or patterns correlate with a change in log throughput |
| <code>observability.get_log_change_points</code> | Which message categories spiked, dipped or shifted, and when |</p>
<p>One platform tool matters here too. <code>platform.streams.investigation_progress_report</code> is what an agent calls to publish its hypothesis list while it works, with a status per hypothesis, a conclusion, and an explicit list of what the data could not settle:</p>
<pre><code>{
  "summary": "string",
  "hypotheses": [
    {
      "candidate": "string",
      "confidence": 0.0,
      "status": "investigating | dismissed | confirmed",
      "reason": "string"
    }
  ],
  "conclusion": "string",
  "gaps_found": ["string"]
}
</code></pre>
<h2 id="threeesqltoolsforairootcauseanalysis">Three ES|QL tools for AI root cause analysis</h2>
<p>Agent Builder's built-in observability tools are shaped for showing the landscape. Ruling a candidate out needs one number that answers one question, and two questions in this incident have no built-in tool. A third returns the document IDs that make a claim checkable.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt95268108854607c7/6aa135b032b5309b366d32f9/08-rca-tools.png" alt="The three registered rca tools in the Agent Builder tools library" /></p>
<h3 id="howtotelloneincidentfromtwocoincidentfailuresrca_failure_shapes">How to tell one incident from two coincident failures (rca_failure_shapes)</h3>
<p>Two services failing in the same minute belong to the same incident only if they appear in the same requests. This tool groups error logs by trace, collapses each trace to the set of services that erred inside it, then counts each shape:</p>
<pre><code>FROM logs-generic.otel-default
| WHERE @timestamp &gt;= TO_DATETIME(?start) AND @timestamp &lt;= TO_DATETIME(?end)
  AND severity_text == "ERROR" AND trace_id IS NOT NULL
| STATS services = VALUES(resource.attributes.service.name) BY trace_id
| EVAL failure_shape = MV_CONCAT(MV_SORT(services), " + ")
| STATS traces = COUNT(*) BY failure_shape
| SORT traces DESC
| LIMIT 20
</code></pre>
<p><code>MV_CONCAT(MV_SORT(...))</code> matters here. Grouping directly by a multivalue field makes ES|QL expand it, one row per service, which loses the combination. Collapsing the set to a single string first keeps the shape intact.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt02d3435111f1cc61/6aa135c5ee57e5b4c90525b1/09-failure-shapes.png" alt="Two failure shapes with no overlap: 2,043 traces for the three checkout services and 1,324 for search-api alone" /></p>
<p>Two clusters come back, and they share no traces. The three checkout services appear together in 2,043 traces, <code>search-api</code> errs alone in 1,324, and that zero overlap settles the coincidence in one row.</p>
<h3 id="howtoruleoutadeployastherootcauserca_version_split">How to rule out a deploy as the root cause (rca_version_split)</h3>
<p>A release that caused the failures makes the version carrying it fail at a materially higher rate than the version it replaced. Server spans carry both <code>service.version</code> and the response status, so one query decides it:</p>
<pre><code>FROM traces-generic.otel-default
| WHERE @timestamp &gt;= TO_DATETIME(?start) AND @timestamp &lt;= TO_DATETIME(?end)
  AND resource.attributes.service.name == ?service AND kind == "Server"
| EVAL failed = CASE(attributes.http.status_code &gt;= 500, 1, 0)
| STATS failures = SUM(failed), requests = COUNT(*)
    BY version = resource.attributes.service.version
| EVAL failure_rate_pct = ROUND(100.0 * failures / requests, 1)
| SORT version
</code></pre>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0297cfe3b7a1122a/6aa135d55c3126a1c04413ea/10-version-split.png" alt="The two checkout-api versions fail at 27.2% and 28.2% on roughly 3,700 requests each" /></p>
<p>27.2% against 28.2%, on roughly 3,700 requests per version, is a difference within noise. The version that was already running fails as often as the one that shipped, which rules the release out.</p>
<p>The tool depends on both versions serving at once. With an instantaneous and total rollout, no query separates a broken new version from something else breaking at the same moment.</p>
<h3 id="howtoreturndocumentidsanagentcanciterca_evidence_sample">How to return document IDs an agent can cite (rca_evidence_sample)</h3>
<p><code>METADATA _id</code> on an ES|QL source command returns the Elasticsearch document ID, so a claim can point at a specific record:</p>
<pre><code>FROM logs-generic.otel-default METADATA _id, _index
| WHERE @timestamp &gt;= TO_DATETIME(?start) AND @timestamp &lt;= TO_DATETIME(?end)
  AND severity_text == "ERROR" AND resource.attributes.service.name == ?service
| KEEP @timestamp, _id, _index, trace_id,
       attributes.error.kind, attributes.upstream.service, body.text
| SORT @timestamp DESC
| LIMIT 5
</code></pre>
<p>Each tool is registered with one <code>POST kbn:/api/agent_builder/tools</code> call carrying the query and its typed parameters. Registered, <code>rca_failure_shapes</code> looks like this; the agent fills <code>start</code> and <code>end</code> at call time:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltea8e8815b60d1329/6aa135e33eabd0004c442e8c/11-registered-tool.png" alt="The registered rca_failure_shapes tool, with its query and its two typed parameters" /></p>
<h2 id="writethetooldescriptionasthedecisionitsupports">Write the tool description as the decision it supports</h2>
<p>The model reads the tool description to decide when to call the tool, so the description does more work than the name.</p>
<p>We describe <code>rca_failure_shapes</code> as the way to test whether two services that broke together are one failure or two. That phrasing got it called whenever a question mentioned a second failing service.</p>
<p>The description also outweighs the agent instructions. An agent with these three tools and one line of instruction, "You are an SRE assistant, help the user find the root cause of production incidents", still ruled out the deploy and the coincidence every time.</p>
<h2 id="wiringthetoolsintoanincidentrootcauseanalysisagent">Wiring the tools into an incident root cause analysis agent</h2>
<p>The agent gets the three custom tools, four built-in observability tools, and <code>platform.streams.investigation_progress_report</code>. Its instructions are five numbered steps:</p>
<pre><code>1. Scope. Establish the affected service, the failure window, and the size of the
   symptom before you name any cause. State the window as an explicit ISO 8601
   range and reuse that same range in every tool call.

2. Enumerate. Write down at least three candidate causes before you test any of
   them. Include the candidate a human on call would reach for first, such as a
   recent deploy or another service that started failing at the same minute.

3. Refute. For each candidate, state the observation that would prove it wrong,
   then run the query that produces that observation. A candidate is dismissed
   when the refuting observation is present, not when a different candidate
   looks better.

4. Cite. Every number you report must name the tool that returned it. Every claim
   about a root cause must carry at least one trace_id and at least one document
   _id. A claim with no citation is not a finding, it is a guess.

5. Report. Put anything the available data cannot settle in gaps_found rather
   than resolving it with reasoning.
</code></pre>
<p>Two rules follow the steps. Each one blocks a mistake we saw in the default agent's answers:</p>
<ul>
<li>Do not rank services by error volume and call the loudest one the cause.</li>
<li>Do not treat time correlation as causation. Two services that start failing in the same minute belong to the same incident only if they appear in the same traces.</li>
</ul>
<p>The finished agent, with Elastic capabilities switched off so everything it produces comes from these eight tools:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt77e4032c3e72acd0/6aa135f15f9db7ba68563323/12-agent.png" alt="The agent in Agent Builder with its eight tools" /></p>
<h2 id="runningtheinvestigation11toolcalls72seconds">Running the investigation: 11 tool calls, 72 seconds</h2>
<p>We gave the new agent the same question, word for word. It opened with a progress report listing its candidates, then ran the refuting queries in parallel batches:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt65b8f00289526519/6aa135fe32b53003116d32fd/13-progress-report.png" alt="The agent's progress report and its parallel tool calls" /></p>
<p>Eleven tool calls and 72 seconds later, the report opens with a citation table:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7c0281d13b148e27/6aa1362832b5305c286d3301/14-citation-table.png" alt="The report's citation table, with three services, three messages, three document IDs and one shared trace ID" /></p>
<p>Three layers, three verbatim messages, three document IDs, and one trace ID shared by all of them. The origin message states the mechanism: <code>StaleQuoteError: fx snapshot age 1215s exceeds max_age 900s (provider=ecb-eod)</code>.</p>
<p>The dismissals come next, each with the observation that ruled it out:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt25e57cb0f0d9b5d1/6aa1363a3481c252e28b0a1b/15-dismissals.png" alt="The dismissed hypotheses, each with its refuting observation" /></p>
<p>| Candidate | Refuting observation | Tool |
| :---- | :---- | :---- |
| <code>checkout-api</code> release 2026.07.26.2 | Both versions fail at the same rate, 27.2% against 28.2%, on roughly 3,700 requests each | <code>rca_version_split</code> |
| <code>search-api</code> timeouts | Zero shared traces. 2,043 traces contain the three checkout services, 1,324 contain <code>search-api</code> alone | <code>rca_failure_shapes</code> |
| Database failure | No database errors in the <code>checkout-api</code> logs | <code>rca_evidence_sample</code> |</p>
<p>The report closes with a gaps section:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltaf924586ef3174a4/6aa1364c346a4b5cba40908f/16-gaps.png" alt="The gaps_found section of the report" /></p>
<p>The first gap covers the question the default agent answered with an invented mechanism. The snapshot was already 1,127 seconds old at the first error, so the feed stopped refreshing before the window opened, and no log in this system records when. The next place to look is the feed ingestion job.</p>
<h2 id="verifyingtheagentscitationbyhandinesql">Verifying the agent's citation by hand in ES|QL</h2>
<p>The report gives a trace ID, so open it:</p>
<pre><code>FROM logs-generic.otel-default
| WHERE trace_id == "a131d0cf7343195c0a7a14f6a98da6b7"
| KEEP @timestamp, resource.attributes.service.name,
       attributes.upstream.service, body.text
| SORT @timestamp ASC
</code></pre>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd4f02f8e160e9420/6aa1365cf08ee1abfc855b36/17-trace-query.png" alt="The trace query in the ES|QL editor" /></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt725ef26468021fc2/6aa13676508cca15a8a169a3/18-trace-results.png" alt="The three error records of that trace, fx-rates with a null upstream.service, then pricing-api naming fx-rates, then checkout-api naming pricing-api" /></p>
<p>Three records, one millisecond apart, in the order the report claimed. <code>fx-rates</code> errs first and names no upstream, because it is the origin. <code>pricing-api</code> names <code>fx-rates</code>, and <code>checkout-api</code> names <code>pricing-api</code>.</p>
<h2 id="addupstreamservicetoyourerrorlogs">Add upstream.service to your error logs</h2>
<p><code>upstream.service</code> is a logging convention that OpenTelemetry does not provide: each service writes it on the error it emits when a dependency fails. With that one key, error records order themselves into a call chain, and the ordering survives the clock skew and sub-millisecond hops that break timestamp sorting.</p>
<p>Two other fields carry the rest of the weight: <code>trace.id</code> arrives free from auto-instrumentation as long as the log is emitted inside an active span, and a structured <code>error.kind</code> lets you count failure modes without matching on message text.</p>
<h2 id="extractingknowledgeindicatorsfromyourstreams">Extracting knowledge indicators from your streams</h2>
<p>Knowledge indicators are facts Elastic extracts from your raw data with LLM models. It picks out things like the underlying infrastructure and the dependencies between services.</p>
<p>You find this option under <code>Streams &gt; {your_stream} &gt; Significant events</code>:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5f3923d8527d0113/6aa13518a1633692653742c7/19-significant-events.png" alt="Significant events in the stream view" /></p>
<p>Click <strong>Generate</strong> and the indicators are created for you:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltfea38d81276e8307/6aa1352b8057551fde706706/20-knowledge-indicators.png" alt="The generated knowledge indicators" /></p>
<h2 id="whatthreeesqltoolschangedabouttheagentsanswer">What three ES|QL tools changed about the agent's answer</h2>
<p>Three ES|QL tools and about two dozen lines of query text turned an agent's answer into a report that names its candidates, shows the observation that ruled each one out, and carries a record ID behind every number.</p>
<h2 id="nextsteps">Next steps</h2>
<ul>
<li>Run the <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/observability-labs/ai-root-cause-analysis-agent-builder/evidence-first-rca-agent-builder.ipynb">companion notebook</a> to reproduce the incident, the tools, and the agent in your own cluster.</li>
<li>Write the query that rules out your team's most common wrong answer, the recent deploy or the loudest service, and register it as a tool whose description states the decision it supports.</li>
<li>Audit one service's error logs for <code>trace.id</code>, a structured <code>error.kind</code>, and a declared <code>upstream.service</code>. The last two are a one-line change in your logging.</li>
<li>Register the failure modes your team already knows about as significant events, so the next investigation starts with a history instead of a blank window.</li>
<li>For a different shape of Agent Builder investigation, read <a href="https://www.elastic.co/observability-labs/blog/apm-health-check-elastic-agent-builder">From five dashboards to one prompt</a>, which scores APM service health with five ES|QL tools.</li>
</ul>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/ai-root-cause-analysis-agent-builder</link>
    <guid isPermaLink="false">ai-root-cause-analysis-agent-builder</guid>
    <category><![CDATA[Agentic AI]]></category>
    <category><![CDATA[Logs Analytics]]></category>
    <category><![CDATA[OpenTelemetry]]></category>
    <dc:creator><![CDATA[Jeffrey Rengifo]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte526f338f22edd08/6aa25191346a4ba897409639/01-header.png" length="0" type="image/png"/>
    <pubDate>Wed, 09 Sep 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[One edit, every dashboard updated: managing Kibana observability at scale with Terraform]]></title>
    <description><![CDATA[Define your golden-signals panels once in a shared HCL library and use for_each to generate every team's dashboard, with drift detection and git rollback built in.]]></description>
    <content:encoded><![CDATA[<p>Elastic ships a Kibana Dashboards API and a native Terraform resource for managing dashboards as code. This capability was introduced as a technical preview in Elastic 9.4 and was made generally available in Elastic 9.5. You define a golden signals panel library once in HCL, and <code>for_each</code> generates a dashboard for every team from it. When you need to change an error threshold, a panel layout or a query, one pull request updates every team at once. If something drifts or breaks, you roll back with git.</p>
<h2 id="whymanagingobservabilitydashboardsbyhandbreaksdownatscale">Why managing observability dashboards by hand breaks down at scale</h2>
<p>Large organizations often end up with hundreds of dashboards. Teams build similar panels and maintain them using the Kibana UI.</p>
<p>When a small change comes in (a panel rename, a field fix, a new error threshold), there is no easy way to apply it across all of them. You either open each dashboard and edit it in the UI one by one, or you export the NDJSON, run a string replace, and re-import it.</p>
<h2 id="dashboardsarecodenow">Dashboards are code now</h2>
<p>Elastic ships a <a href="https://www.elastic.co/search-labs/blog/kibana-dashboards-as-code-terraform-api">typed Kibana Dashboards API</a> and a native <a href="https://registry.terraform.io/providers/elastic/elasticstack/latest/docs/resources/kibana_dashboard"><code>elasticstack_kibana_dashboard</code></a> Terraform resource. You define a dashboard in an HCL file and then manage versions and changes as if it was regular code.</p>
<h2 id="goldensignalsdashboardonedefinitionforeveryteam">Golden signals dashboard: one definition for every team</h2>
<p>The platform team owns a standard dashboard built on the four <a href="https://sre.google/sre-book/monitoring-distributed-systems/#xref_monitoring_golden-signals">golden signals</a>: latency, traffic, errors, and saturation. Every team should get that standard, and some teams add a panel or two of their own.</p>
<p>We want one definition of the standard, each team's dashboard generated from it, and a single change that reaches every team.</p>
<h2 id="prerequisites">Prerequisites</h2>
<ul>
<li>An Elastic Cloud deployment or self-managed cluster running <strong>Elastic 9.4</strong> or newer, or an <strong>Elastic Cloud Serverless</strong> project</li>
<li><strong>Terraform</strong> installed</li>
<li>An Elasticsearch API key</li>
</ul>
<p>The full Terraform configuration, the seed script, and the captured <code>terraform plan</code> outputs used in this article are available in the <a href="https://github.com/Delacrobix/Observability-dashboards-as-code-one-standard-across-every-team-with-Terraform">companion repo</a>.</p>
<h2 id="configuretheelasticterraformprovider">Configure the Elastic Terraform provider</h2>
<p>Create a <a href="https://github.com/Delacrobix/Observability-dashboards-as-code-one-standard-across-every-team-with-Terraform/blob/main/provider.tf"><code>provider.tf</code></a> next to the rest of your Terraform files:</p>
<pre><code>terraform {
  required_providers {
    elasticstack = {
      source  = "elastic/elasticstack"
      version = "~&gt; 0.11"
    }
  }
}

variable "elasticsearch_endpoint" {
  type = string
}

variable "elasticsearch_api_key" {
  type      = string
  sensitive = true
}

variable "kibana_endpoint" {
  type = string
}

variable "kibana_api_key" {
  type      = string
  sensitive = true
}

provider "elasticstack" {
  elasticsearch {
    endpoints = [var.elasticsearch_endpoint]
    api_key   = var.elasticsearch_api_key
  }
  kibana {
    endpoints = [var.kibana_endpoint]
    api_key   = var.kibana_api_key
  }
}
</code></pre>
<p>Provide your credentials through a local <code>terraform.tfvars</code> file (and add it to <code>.gitignore</code> so the keys never reach the repo):</p>
<pre><code>elasticsearch_endpoint = "https://...es.region.cloud.es.io"
elasticsearch_api_key  = "..."
kibana_endpoint        = "https://...kb.region.cloud.es.io"
kibana_api_key         = "..."
</code></pre>
<p>You can use the same API key for both <code>elasticsearch_api_key</code> and <code>kibana_api_key</code> as long as it has dashboard write privileges in the target space.</p>
<p>Then initialize the working directory:</p>
<pre><code>terraform init
</code></pre>
<h2 id="defineasingleteamkibanadashboardinhcl">Define a single-team Kibana dashboard in HCL</h2>
<p>Start with a baseline dashboard for a single team. Panels sit on a 48-column grid, and each one is a <a href="https://www.elastic.co/docs/explore-analyze/visualize/lens">Lens</a> visualization configured inline. Use <code>config_json</code> for KPI tiles (it exposes secondary metrics and value coloring) and <code>xy_chart_config</code> for time-series charts.</p>
<p>Add the baseline resource to a new <a href="https://github.com/Delacrobix/Observability-dashboards-as-code-one-standard-across-every-team-with-Terraform/blob/main/dashboards.tf"><code>dashboards.tf</code></a>:</p>
<pre><code>resource "elasticstack_kibana_dashboard" "golden_signals" {
  title            = "Golden Signals - payments"
  description      = "Latency, traffic, errors"
  query            = { language = "kql", text = "" }
  refresh_interval = { pause = false, value = 60000 }
  time_range       = { from = "now-15m", to = "now" }

  panels = [
    {
      type = "vis"
      grid = { x = 0, y = 0, w = 12, h = 5 }
      config_json = jsonencode({
        type        = "metric"
        data_source = {
          type  = "esql"
          query = "FROM logs-payments-* | STATS `5xx errors` = COUNT(CASE(status &gt;= 500, 1, null))"
        }
        metrics = [{ type = "primary", column = "5xx errors" }]
      })
    },
    # More panels follow the same shape: other metric tiles, xy_chart_config line charts, and a breakdown datatable. See the companion repo for the full file.
  ]
}
</code></pre>
<p>Each panel sets a <code>type</code> and <code>grid</code> position, then picks one chart kind. KPI tiles serialize the whole Lens config into <code>config_json</code>; the <a href="https://www.elastic.co/docs/reference/query-languages/esql">ES|QL</a> query lives under <code>data_source</code> and the metric column is referenced by name in <code>metrics[*].column</code>. The dashboard time picker already scopes ES|QL panels, so the query needs no explicit <code>@timestamp</code> range filter.</p>
<h3 id="previewkibanadashboardchangeswithterraformplan">Preview Kibana dashboard changes with terraform plan</h3>
<p>Run <code>terraform plan</code> to see what Terraform will create:</p>
<pre><code>terraform plan
</code></pre>
<p>The plan output lists the new <code>elasticstack_kibana_dashboard.golden_signals</code> resource and every attribute it will set: the top-level dashboard fields and one entry per panel with its grid position, chart kind, and data source.</p>
<pre><code>Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # elasticstack_kibana_dashboard.golden_signals will be created
  + resource "elasticstack_kibana_dashboard" "golden_signals" {
      + description      = "Latency, traffic, errors"
      + title            = "Golden Signals - payments"
      + query            = { language = "kql", text = "" }
      + refresh_interval = { pause = false, value = 60000 }
      + time_range       = { from = "now-15m", to = "now" }
      + panels           = [
          # Every panel described in full: KPI tiles (config_json),
          # line charts (xy_chart_config), and the breakdown datatable.
        ]
    }

Plan: 1 to add, 0 to change, 0 to destroy.
</code></pre>
<p>Reviewing the plan is your last check before anything ships to Kibana.</p>
<p>Don't apply yet. The next section extends the file with per-team dashboards, and then a single <code>terraform apply</code> ships everything.</p>
<h2 id="generateperteamobservabilitydashboardsfromasharedpanellibrary">Generate per-team observability dashboards from a shared panel library</h2>
<p>On top of the baseline, each team gets the standard set of panels, with the option to add a few of their own. Hardcoding one resource per team does not scale. Instead, define a panel library and a teams map as <code>locals</code>, then build the dashboards with <code>for_each</code>. Each library entry describes a chart kind, a title, and the data it needs; the resource emits the right Lens block (<code>config_json</code> for metric tiles, <code>xy_chart_config</code> for line charts) based on <code>chart_type</code>.</p>
<p>Replace the contents of <a href="https://github.com/Delacrobix/Observability-dashboards-as-code-one-standard-across-every-team-with-Terraform/blob/main/dashboards.tf"><code>dashboards.tf</code></a> with:</p>
<pre><code>locals {
  panel_library = {
    errors = {
      chart_type     = "metric"
      title          = "Error rate"
      esql_query_tpl = "FROM {idx} | STATS `5xx errors` = COUNT(CASE(status &gt;= 500, 1, null))"
      esql_column    = "5xx errors"
    }
    saturation = {
      chart_type     = "metric"
      title          = "Saturation (CPU)"
      # Saturation reads from the metrics TSDB, so this query is not parameterized by {idx}.
      esql_query_tpl = "TS metrics-payments-* | STATS avg_cpu = AVG(cpu.pct)"
      esql_column    = "avg_cpu"
    }
    latency = {
      chart_type = "xy"
      title      = "Latency p95"
      x_json     = jsonencode({
        operation          = "date_histogram"
        field              = "@timestamp"
        suggested_interval = "auto"
      })
      y_json = jsonencode({
        operation  = "percentile"
        field      = "duration_ms"
        percentile = 95
      })
    }
    # ... more entries (traffic, cart_value) in the companion repo.
  }

  teams = {
    payments = {
      index  = "logs-payments-*"
      panels = ["errors", "saturation", "latency", "traffic"]
    }
    checkout = {
      index  = "logs-checkout-*"
      panels = ["errors", "cart_value", "latency", "traffic"]
    }
  }
}

resource "elasticstack_kibana_dashboard" "golden_signals" {
  for_each         = local.teams
  title            = "Golden Signals - ${each.key}"
  description      = "Latency, traffic, and errors for the ${each.key} service"
  query            = { language = "kql", text = "" }
  refresh_interval = { pause = false, value = 60000 }
  time_range       = { from = "now-15m", to = "now" }

  sections = [
    {
      title     = "KPIs"
      grid      = { y = 0 }
      collapsed = false
      panels = [
        for i, p in [for q in each.value.panels : q if local.panel_library[q].chart_type == "metric"] : {
          type        = "vis"
          grid        = { x = (i % 4) * 12, y = 0, w = 12, h = 5 }
          config_json = jsonencode({ ... }) # one metric tile per panel; see the companion repo for the full config
        }
      ]
    },
    {
      title     = "Trends"
      grid      = { y = 1 }
      collapsed = false
      panels = [
        for i, p in [for q in each.value.panels : q if local.panel_library[q].chart_type == "xy"] : {
          type       = "vis"
          grid       = { x = (i % 3) * 16, y = 0, w = 16, h = 10 }
          vis_config = { by_value = { xy_chart_config = { ... } } }
        }
      ]
    },
    # A third "Breakdown" section holds the request-by-status datatable. See the companion repo.
  ]
}
</code></pre>
<p>Adding a team is one entry in <code>teams</code>. Adding a panel to every team is one entry in <code>panel_library</code> and one reference per team. The full config (data source ES|QL queries, metrics, layers, axis defaults, and legend placement) lives in <a href="https://github.com/Delacrobix/Observability-dashboards-as-code-one-standard-across-every-team-with-Terraform/blob/main/dashboards.tf"><code>dashboards.tf</code></a>.</p>
<p>The saturation panel queries the metrics data stream with the ES|QL <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/ts"><code>TS</code></a> command, which is designed for TSDB. For the query to work, data streams matching <code>metrics-payments-*</code> must use <code>time_series</code> mode, so the configuration also ships an index template (<a href="https://github.com/Delacrobix/Observability-dashboards-as-code-one-standard-across-every-team-with-Terraform/blob/main/metrics_tsdb.tf"><code>metrics_tsdb.tf</code></a>) that enables that.</p>
<h3 id="applydashboardsascodetokibanawithterraformapply">Apply dashboards as code to Kibana with terraform apply</h3>
<p>Run <code>terraform plan</code> to confirm both team dashboards (payments and checkout) will be created then apply:</p>
<pre><code>terraform apply
</code></pre>
<p>Open Kibana and you'll see one <strong>Golden Signals</strong> dashboard per team, each backed by its own index pattern.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd3d461fb8f841d32/6a85cdb4331d7a7965c3180b/image2.jpg" alt="" /></p>
<h2 id="dashboardsascodeinthegitopsloopreviewchangesinpullrequests">Dashboards as code in the GitOps loop: review changes in pull requests</h2>
<p>Dashboards are now an artifact in version control, like the rest of your infrastructure.</p>
<p>You edit the library or a team's selection, open a pull request, your reviewer reads the <code>terraform plan</code> diff and sees which dashboards change.</p>
<p>For example, say you tighten the "critical error" threshold from <code>status &gt;= 500</code> to <code>status &gt;= 503</code> in <code>panel_library.errors.esql_query_tpl</code>. Running <code>terraform plan</code> shows the change reaching both teams at once:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6a891e92c1b74fba/6a85cdb79a32f15bbda7e038/image3.jpg" alt="" /></p>
<p><em>Note: Full output in <a href="https://github.com/Delacrobix/Observability-dashboards-as-code-one-standard-across-every-team-with-Terraform/blob/main/outputs/terraform-plan-update.txt"><code>terraform-plan-update.txt</code></a>.</em></p>
<p>A single edit to <code>panel_library.errors</code> propagates to every team that references it. After the PR merges, it's time to run <code>terraform apply</code>.</p>
<p>After the apply finishes, refresh the dashboards in Kibana and the new threshold is in effect:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4a1490023fd81294/6a85cdba0782902a5a3217c4/image4.jpg" alt="" /></p>
<h2 id="detectdashboarddriftandrollbackwithgit">Detect dashboard drift and roll back with git</h2>
<p>If someone edits a dashboard using the UI, the next <code>terraform plan</code> shows the difference, because the code and the live state no longer match.</p>
<p>To see this in action, open <code>Golden Signals - payments</code> in Kibana, rename the <strong>Latency p95</strong> panel to <code>Latency p95 (EDITED)</code>, and save the dashboard.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf100d616be5f89de/6a85cdbc9bf99401610a05c1/image5-small.jpg" alt="" /></p>
<p>Then run <code>terraform plan</code>:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt55cc1662818757b8/6a85cdbfeaf245b4d9a49fa5/image6-small.jpg" alt="" /></p>
<p>Terraform reads the panel title from the live dashboard, compares it against the code, and proposes reverting the UI rename. You decide whether to keep the change (update the code to match) or revert it by running <code>terraform apply</code>.</p>
<p>You can commit the new version, or rollback one or many versions using git.</p>
<p>Replaying the earlier example: if you reopen the PR that changed <code>panel_library.errors</code> to broaden the error threshold and add a clearer title, <code>git diff dashboards.tf</code> shows the entire intent in two lines:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6c138d8078b2634c/6a85cdc2bc5bb33835f81b45/image7.jpg" alt="" /></p>
<p>Every team that references <code>errors</code> picks up the new threshold on the next <code>terraform apply</code>, and reverting that commit rolls the change back across all of them at once.</p>
<h2 id="wrapup">Wrap up</h2>
<p>Managing Kibana observability dashboards by hand does not scale past a few teams. With the Kibana Dashboards API and Terraform, you define a standard once, compose each team's dashboard from a shared library, and review every change in a pull request. One edit reaches every team, and you can roll back by reverting a commit.</p>
<p>The proposed file structure is only one of many ways you can organize your dashboards depending on how much information they share.</p>
<h2 id="nextsteps">Next steps</h2>
<ul>
<li><a href="https://www.elastic.co/search-labs/blog/kibana-dashboards-as-code-terraform-api">Kibana Dashboards as code with Terraform</a></li>
<li><a href="https://registry.terraform.io/providers/elastic/elasticstack/latest/docs/resources/kibana_dashboard"><code>elasticstack_kibana_dashboard</code> resource reference</a></li>
<li><a href="https://registry.terraform.io/providers/elastic/elasticstack/latest/docs">Elastic Stack Terraform provider documentation</a></li>
<li><a href="https://www.elastic.co/docs/api/doc/kibana/group/endpoint-dashboards">Kibana Dashboards API documentation</a></li>
<li><a href="https://www.elastic.co/docs/reference/query-languages/esql">ES|QL reference</a></li>
</ul>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/kibana-observability-dashboards-terraform</link>
    <guid isPermaLink="false">kibana-observability-dashboards-terraform</guid>
    <category><![CDATA[Metrics]]></category>
    <category><![CDATA[Logs Analytics]]></category>
    <dc:creator><![CDATA[Jeffrey Rengifo]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdbc974846f9b410e/6a85cdc4342d69301d21b147/image1.jpg" length="0" type="image/jpeg"/>
    <pubDate>Mon, 03 Aug 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[One OTLP endpoint, three teams, zero routing rules: Elasticsearch Streams AI Partitioning]]></title>
    <description><![CDATA[Stop writing log routing rules upfront. See how Streams AI Partitioning reads your data, proposes child streams, and lets you set per-team retention in minutes.]]></description>
    <content:encoded><![CDATA[<p>Ship logs from three teams into one Elastic OTLP endpoint, and <a href="https://www.elastic.co/docs/solutions/observability/streams/management/partitioning">Streams AI Partitioning</a> routes them into per-team child streams, with no routing rules written upfront. In this post, you generate 115 multi-team log records, let the AI analyze what arrived and propose partitions, refine the suggestions in plain English, then set retention independently per team: 90 days for payments, 30 for checkout, 7 for notifications. The entire workflow runs inside Elastic Observability without touching index templates or ILM policies.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc0aa396438b856c5/6a85ce06342d693b6421b14f/image1.png" alt="logs.otel parent stream partitioned into per-team child streams" /></p>
<h2 id="whymultiteamlogroutingneedsstructure">Why multi-team log routing needs structure</h2>
<p>Multi-team Elasticsearch deployments typically converge on a single shared index, which works until teams need different retention periods, sharding settings or processor pipelines.</p>
<p>Before <a href="https://www.elastic.co/docs/solutions/observability/streams/streams">Streams</a>, you had to set up your ingestion scripts to send data to different indices, or data streams, or use the <a href="https://www.elastic.co/docs/reference/enrich-processor/reroute-processor">reroute</a> processor to define the data destination based on some field name.</p>
<p>With AI Partitioning, you let the data arrive first. Then the AI analyzes what showed up, reviews the suggestions it proposes, refines them as needed, and applies them. The result is a set of <a href="https://www.elastic.co/docs/solutions/observability/streams/wired-streams">wired child streams</a> that inherit retention, processors, and schema from the parent, while still allowing you to override any of those per child.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7f1ab44aa0327f17/6a85ce0999083f683140fa31/image2.png" alt="Before and after diagram: single retention for all teams becomes per-team retention with AI Partitioning" /></p>
<h2 id="whatyouneedbeforeusingstreamsaipartitioning">What you need before using Streams AI Partitioning</h2>
<p>Before running the example, three things need to be in place:</p>
<ol>
<li><strong>Wired Streams enabled.</strong> On Elastic Cloud Serverless and Elastic Cloud Hosted 9.4+, wired streams are on by default. If you upgraded from an earlier version, open the Streams app and confirm the toggle is on under Settings.</li>
<li><strong>The Elastic Managed LLM connector.</strong> Go to <strong>Stack Management &gt; Connectors &gt; Create connector &gt; <a href="https://www.elastic.co/docs/reference/kibana/connectors-kibana/elastic-managed-llm">Elastic Managed LLM</a></strong>. Keep in mind the following considerations:</li>
<li>This connector ships preconfigured and does not require an external account or API key.</li>
<li>Any <a href="https://www.elastic.co/docs/reference/kibana/connectors-kibana/gen-ai-connectors">generative AI connector</a> works with the feature.</li>
<li>Note that Elastic Managed LLMs <a href="https://www.elastic.co/pricing/serverless-search">incur a cost per million tokens</a> for input and output.</li>
<li>The account you use needs the <code>manage_inference</code> cluster privilege (the built-in <code>inference_admin</code> role grants it).</li>
<li><strong>The Managed OTLP endpoint URL and an API key.</strong> Open <strong>Cloud Console &gt; Manage &gt; Application endpoints &gt; Ingest</strong>. Copy the <a href="https://www.elastic.co/docs/reference/opentelemetry/motlp">Managed OTLP endpoint URL</a> and generate an API key from that same panel.</li>
</ol>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4fa44d988e9a0819/6a85ce0b342d690b0521b153/image3.png" alt="Managed OTLP endpoint URL and Component ID in Elastic Cloud Console" /></p>
<p>Once these are ready, open <strong>Observability &gt; Streams</strong> and confirm a <code>logs.otel</code> wired stream is listed. That stream is the parent we will partition.</p>
<p><em>If <strong>Streams</strong> does not appear in the sidebar, your Kibana space may be using a solution view other than Observability. You can change it in Stack Management &gt; <a href="https://www.elastic.co/docs/deploy-manage/manage-spaces">Spaces</a> &gt; edit your space &gt; set Solution view to <strong>Observability</strong>.</em></p>
<h2 id="generatingmultiteamlogdata">Generating multi-team log data</h2>
<p>Our example uses three apps produced by teams from the same fictional company:</p>
<ul>
<li><code>payments-api</code>: structured JSON with <code>transaction_id</code> and <code>amount_cents</code>. Sensitive data, long retention needs.</li>
<li><code>checkout-web</code>: JSON with <code>cart_id</code> and <code>customer_id</code>. Mostly INFO and ERROR.</li>
<li><code>notifications-worker</code>: less structured, with <code>recipient</code> and <code>channel</code>. High volume.</li>
</ul>
<p>We use a Python script with the <a href="https://opentelemetry.io/docs/languages/python/">OpenTelemetry Python SDK</a> to emit logs for all three teams over OTLP directly to the <a href="https://www.elastic.co/observability-labs/blog/elastic-managed-otlp-endpoint-for-opentelemetry">Managed OTLP endpoint</a>. The full code, including setup and execution, is in the <a href="https://github.com/Delacrobix/Taming-the-Log-Chaos-with-Streams-AI-Partitioning/blob/main/notebook.ipynb">companion notebook</a>.</p>
<p>Each team is defined with a service name, a set of message templates, and a function that generates team-specific attributes:</p>
<pre><code>TEAMS = {
    "payments": {
        "service": "payments-api",
        "messages": [
            ("INFO", "charge captured tx={tx} amount_cents={amt}"),
            ("ERROR", "charge declined tx={tx} reason=insufficient_funds"),
            ("INFO", "refund issued tx={tx} amount_cents={amt}"),
        ],
        "extra": lambda: {
            "transaction_id": f"tx_{random.randint(10000, 99999)}",
            "amount_cents": random.randint(100, 50000),
        },
    },
    "checkout": {
        "service": "checkout-web",
        "messages": [
            ("INFO", "cart updated cart={cart} customer={cust}"),
            ("INFO", "checkout started cart={cart} customer={cust}"),
            ("ERROR", "checkout failed cart={cart} stage=address_validation"),
        ],
        "extra": lambda: {
            "cart_id": f"c_{random.randint(1000, 9999)}",
            "customer_id": f"u_{random.randint(100, 999)}",
        },
    },
    "notifications": {
        "service": "notifications-worker",
        "messages": [
            ("INFO", "email queued recipient={rcp} channel=email"),
            ("INFO", "sms queued recipient={rcp} channel=sms"),
            ("ERROR", "webhook failed recipient={rcp} channel=webhook status=503"),
        ],
        "extra": lambda: {
            "recipient": f"+1555{random.randint(1000000, 9999999)}",
            "channel": random.choice(["email", "sms", "webhook"]),
        },
    },
}
</code></pre>
<p>Setting <code>elasticsearch.index</code> to <code>logs.otel</code> as a resource attribute routes the data into the wired streams root instead of the default OTLP data stream.</p>
<pre><code>def setup_provider():
    resource = Resource.create({"elasticsearch.index": "logs.otel"})
    provider = LoggerProvider(resource=resource)
    provider.add_log_record_processor(BatchLogRecordProcessor(OTLPLogExporter()))
    set_logger_provider(provider)
    handler = LoggingHandler(level=logging.INFO, logger_provider=provider)
    root = logging.getLogger()
    root.setLevel(logging.INFO)
    root.addHandler(handler)
    return provider
</code></pre>
<p>Run the notebook to emit 115 records with an uneven split across teams.</p>
<p>Open <strong>Observability &gt; Streams &gt; <code>logs.otel</code></strong> and switch to the <strong>Partitioning</strong> tab. You should see the ingested data in the preview panel, with attributes like <code>team</code>, <code>service.name</code>, and the team-specific fields visible in the columns.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltae43e2a923d42095/6a85ce0f5c2790885df59b6f/image4.png" alt="Partitioning tab on logs.otel with the Get partitions suggestions button and data preview" /></p>
<h2 id="howstreamsaipartitioningproposeschildstreams">How Streams AI Partitioning proposes child streams</h2>
<p>Streams AI Partitioning analyzes up to 1,000 documents from the parent stream, identifies attribute clustering and cardinality distribution, then proposes child streams, keyed on whichever field best separates the data logically (the ML approach behind this analysis is detailed in <a href="https://www.elastic.co/observability-labs/blog/automated-log-parsing-ml-streams">automated log parsing in Streams</a>).</p>
<p>For the data we just emitted, the AI proposed three child streams keyed on <code>attributes.service.name</code>:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt54fb99bf7450bb1a/6a85ce11d6cf29559dbb093b/image5.png" alt="Review partitioning suggestions keyed on attributes.service.name" /></p>
<p>Each suggestion shows a <a href="https://www.elastic.co/docs/solutions/observability/streams/management/streamlang">Streamlang</a> condition and the percentage of sampled documents that would match. The AI picked <code>service.name</code> because it is a standard OpenTelemetry attribute and a natural identifier for any single workload.</p>
<p>This is a reasonable first proposal, but it is worth thinking about what happens as the deployment grows. Right now there are three services because there are three teams. Tomorrow, Payments might add a <code>refunds-api</code> and a <code>fraud-detector</code>. Each new service would mechanically create another child stream, and over time you would end up with dozens of partitions for what is really just three organizational boundaries.</p>
<p>Elastic's <a href="https://www.elastic.co/docs/solutions/observability/streams/management/partitioning#streams-partitioning-recommendations">partitioning recommendations</a> prefer logical groupings like team or technology type, and aim for tens of partitions rather than hundreds. A <code>team</code>-keyed partitioning is more stable because Payments stays one child stream no matter how many services that team operates.</p>
<h2 id="refiningstreamsaipartitioningsuggestionsinnaturallanguage">Refining Streams AI Partitioning suggestions in natural language</h2>
<p>After reviewing the AI's initial suggestions in Streams AI Partitioning, click <strong>Modify suggestions</strong> to open a free-text prompt.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4c4c5bfe6a47ec17/6a85ce14342d6955af21b159/image6.png" alt="Modify suggestions modal with a plain-English prompt to partition by attributes.team" /></p>
<p>After submitting, the AI regenerates the suggestions. Now the three cards are keyed on <code>attributes.team</code> instead of <code>service.name</code>:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt64002e7c2459f0b9/6a85ce17f5f1a028cc2ec969/image7.png" alt="Regenerated suggestions keyed on attributes.team, all three selected" /></p>
<p>Select all three and click <strong>Accept selected</strong>. A confirmation dialog shows the streams that will be created, each with its <code>WHERE attributes.team equals &lt;team&gt;</code> condition.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt67702e77c93a7775/6a85ce1a9a32f1e5eda7e042/image8.png" alt="Create 3 streams confirmation dialog listing payments, checkout, and notifications" /></p>
<p>Click <strong>Create all streams</strong>. The Partitioning tab now shows the three child streams as part of the <code>logs.otel</code> parent:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc0aa396438b856c5/6a85ce06342d693b6421b14f/image1.png" alt="Partitioning tab showing logs.otel.payments, logs.otel.checkout, and logs.otel.notifications" /></p>
<p>Every new document arriving at the OTLP endpoint will be routed into the correct child according to these conditions. You can open any child stream to verify its data. For example, <code>logs.otel.checkout</code> shows only checkout logs:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd89be7d1789e0361/6a85ce1dba7acc0afb992192/image9.png" alt="logs.otel.checkout child stream data preview showing only checkout-web events" /></p>
<h2 id="howdoyousetperteamlogretentioninelasticsearchstreams">How do you set per-team log retention in Elasticsearch Streams?</h2>
<p>After Streams AI Partitioning creates child streams, each one can have its own lifecycle configuration independently of the parent. Because wired streams use a parent-child hierarchy, every child inherits retention, processors, and schema from the parent by default. You only need to override the partitions you need to change.</p>
<p>Open the child stream <code>logs.otel.payments</code> and go to the <a href="https://www.elastic.co/docs/solutions/observability/streams/management/retention">Retention</a> tab. Click <strong>Edit retention method</strong>, select <strong>Custom period</strong>, and set it to 90 days.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd4181096276f4ba5/6a85ce2098292622ff583944/image10.png" alt="Edit data retention dialog with Custom period set to 90 days" /></p>
<p>Do the same for the other teams with the retention that fits their needs:</p>
<p>| Stream | Retention | Rationale |
| :---- | :---- | :---- |
| <code>logs.otel.payments</code> | 90 days | Sensitive financial data, compliance requirements |
| <code>logs.otel.checkout</code> | 30 days | Useful for debugging, no long-term need |
| <code>logs.otel.notifications</code> | 7 days | High volume, low value after delivery confirmation |</p>
<h2 id="conclusionfromsharedindextoperteamstreamswithoutroutingrules">Conclusion: from shared index to per-team streams, without routing rules</h2>
<p>A shared Elastic deployment with several teams shipping logs is the normal starting point. Organizing it used to mean writing routing rules upfront or maintaining separate index templates and ILM policies by hand.</p>
<p>With Streams AI Partitioning, the workflow is different: you let the data arrive, let the AI read what showed up, refine the suggestions in natural language when they need adjusting, and accept.</p>
<p>The result is a set of child streams that inherit everything from the parent while giving each team its own retention and processing, without any manual template management.</p>
<h2 id="nextsteps">Next steps</h2>
<ul>
<li>Try the <a href="https://github.com/Delacrobix/Taming-the-Log-Chaos-with-Streams-AI-Partitioning/blob/main/notebook.ipynb">companion notebook</a> to generate your own multi-team data.</li>
<li>Read <a href="https://www.elastic.co/observability-labs/blog/simplifying-retention-management-with-streams">How Streams in Elastic Observability Simplifies Retention Management</a> for a deeper look at the retention model.</li>
<li>Read <a href="https://www.elastic.co/observability-labs/blog/elastic-streams-processing">Streams Processing: Stop Fighting with Grok</a> to explore the parsing side of Streams when teams need different processors.</li>
<li>Read <a href="https://www.elastic.co/observability-labs/blog/elastic-observability-streams-ai-logs-investigations">Introducing Streams for Observability</a> for the broader investigation story Streams is part of.</li>
</ul>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/elasticsearch-streams-ai-partitioning-log-routing</link>
    <guid isPermaLink="false">elasticsearch-streams-ai-partitioning-log-routing</guid>
    <category><![CDATA[Logs Analytics]]></category>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[AI]]></category>
    <dc:creator><![CDATA[Aleksandar Panov]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltaf5bc8b7f4699ea7/6a85ce2393ffb97251b9148d/header.png" length="0" type="image/png"/>
    <pubDate>Wed, 01 Jul 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[SNMP Topology Data in Kibana: Collection to Canvas]]></title>
    <description><![CDATA[The Network Topology plugin for Kibana provides a ready-to-deploy Logstash pipeline, a structured schema, and a topology view that shows what's connected to what.]]></description>
    <content:encoded><![CDATA[<h2 id="snmpcollectionshouldntrequireasidequest">SNMP collection shouldn't require a side quest.</h2>
<p>Getting SNMP data into Elasticsearch unlocks rich visibility into your network — interface utilization, routing health, L2 forwarding, and more. The path there involves a few familiar steps: choosing which MIBs to walk, mapping OIDs to human-readable field names, configuring SNMP v2c or v3 authentication, accommodating vendor-specific MIB extensions, and tuning the pipeline to gracefully handle device timeouts across large inventories. With a solid template in place, what used to be a bespoke Logstash project becomes a repeatable, shareable setup that any engineer on the team can pick up and extend.</p>
<p><a href="https://github.com/elastic/kibana-network-topology-plugin">The plugin</a> includes a Logstash pipeline <a href="https://github.com/elastic/kibana-network-topology-plugin/blob/main/docs/collectors/logstash.conf">template</a> that handles the common cases out of the box. It walks IF-MIB (interface counters and status), IP-MIB (ARP tables and IP address assignments), BRIDGE-MIB (MAC address forwarding tables), BGP4-MIB (BGP peer sessions), and OSPF-MIB (OSPF neighbor adjacencies) per target device on a configurable poll interval. You add your device list and authentication details, start Logstash, and data begins flowing into Elasticsearch.</p>
<p>The template also handles the operational annoyances that trip people up: poll timeouts, missing OID branches on devices that don't support a given MIB, and batching walks across large device inventories.</p>
<h2 id="structuringsnmpdatainelasticsearchschemadesign">Structuring SNMP data in Elasticsearch: schema design</h2>
<p>Once SNMP data lands in Elasticsearch, the next problem is structure. Interface counters like <code>ifInOctets</code> and <code>ifOperStatus</code> map to ECS concepts reasonably well. They're host-level metrics with direct equivalents in <code>host.network.*</code> fields. But the data network engineers actually need for troubleshooting is relational, and this plugin offers a way to view those relationships.</p>
<p>A BGP peer session has a state, a remote AS number, an uptime, and an update count. An OSPF adjacency has a neighbor router ID, an area, a priority, and a state machine position. A MAC table entry records which physical switch port a given MAC address was learned on. None of these have ECS field definitions, and stuffing them into generic <code>event.*</code> or <code>observer.*</code> fields loses the semantic meaning that makes the data useful.</p>
<p>The plugin takes an opinionated approach: use ECS where it fits, extend with clear namespaces where it doesn't. Interface data maps to ECS-aligned fields. Routing protocol and L2 forwarding data goes into purpose-built namespaces (<code>bgp_peer.*</code>, <code>ospf_neighbor.*</code>, <code>arp.*</code>, <code>mac_table.*</code>) with field names that match the concepts operators already think in. If you know what <code>bgpPeerState</code> means on a router CLI, <code>bgp_peer.state</code> in Elasticsearch is immediately familiar. If you already collect SNMP data in a homegrown schema, the plugin's templates and ingest pipeline will complement it rather than replace it. The new fields are additive, so you can adopt them at your own pace!</p>
<p>| Data Type | Key Fields | ECS Namespace |
| --- | --- | --- |
| BGP Peer Session | State, Remote AS, Uptime, Update Count | <code>bgp_peer.*</code> |
| OSPF Adjacency | Neighbor Router ID, Area, Priority, State | <code>ospf_neighbor.*</code> |
| MAC Table Entry | Switch Port, Learned MAC Address | <code>mac_table.*</code> |
| ARP Entry | IP-to-MAC Mapping | <code>arp_table.*</code> |</p>
<p>An ingest pipeline (<code>snmp-device-enrichment</code>) handles classification at index time. It parses each device's <code>sysDescr</code> string to assign a normalized <code>device.type</code> (router, switch, firewall, access point) and <code>device.vendor</code>, so every downstream consumer (dashboards, ES|QL queries, alerting rules, the topology view) gets consistent device metadata without custom parsing. The pipeline recognizes common vendors out of the box and is extensible for environments with less common hardware.</p>
<p>The result is SNMP data you can query like any other structured data in Elasticsearch. "Show me every BGP session not in Established state" is a filter, not a regex exercise. "Which Cisco switches have interfaces that are admin-up but oper-down" is a KQL query, not a script.</p>
<h2 id="visualisingsnmpnetworktopologyinkibana">Visualising SNMP network topology in Kibana</h2>
<p>Dashboards excel at answering "what are the numbers?" A topological view answers a complementary question: "what's connected to what?" Network engineers think in topology: upstream and downstream relationships, path diversity, and blast radius of a link failure. A spatial, graph-based view brings that mental model directly into Kibana, sitting alongside the charts and data tables operators already rely on.</p>
<p>The plugin adds an interactive topology graph to Kibana's Observability navigation. It reads the structured SNMP data from Elasticsearch, builds an adjacency graph from ARP, MAC table, BGP, and OSPF relationships, and renders it as a force-directed layout you can zoom, pan, and rearrange. Nodes are devices, edges are discovered relationships, and clicking any device opens a flyout with its interface table, ARP neighbors, and routing protocol sessions.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt80b0174a4703b359/6a7f1afd63e959967973e279/topo-diagram.png" alt="Network Topology Diagram" /></p>
<h2 id="howdoyousetupsnmpnetworktopologymonitoringinkibana">How do you set up SNMP network topology monitoring in Kibana?</h2>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte26fc1b65d134bd5/6a7f1b006693f80125664391/setup-tab.png" alt="Setup Overview" /></p>
<p>The plugin is nearly ready to go out of the box, <a href="https://www.elastic.co/docs/solutions/observability/infra-and-hosts/network-topology/monitor-network-devices">only a few assets need installation</a>. Here's what the setup looks like:</p>
<ol>
<li><p><strong>Install the plugin zip</strong> on a self-managed Kibana instance (<code>bin/kibana-plugin install file:///path/to/networkTopology-&lt;version&gt;.zip</code>).</p></li>
<li><p><strong>Apply the index templates and ingest pipeline</strong>. Click through the template installation in the plugin's Setup tab. A few button clicks and the schema is in place.</p></li>
<li><p><strong>Deploy the Logstash pipeline.</strong> Add your device targets, authentication details, or other configuration to the included template and start it. If you're using <a href="https://www.elastic.co/docs/reference/logstash/logstash-centralized-pipeline-management">Logstash Centralized Pipeline Management</a>, push it from Kibana, no SSH required.</p></li>
</ol>
<p>Data hits Elasticsearch on the next poll cycle, the ingest pipeline classifies and enriches them, and the topology view populates. Start to finish, you're looking at minutes, not hours or days of trial and error.</p>
<p>A <a href="https://github.com/elastic/kibana-network-topology-plugin/blob/main/scripts/generate_sample_data.mjs">sample data generator</a> is included for teams that want to evaluate the plugin before connecting to live infrastructure; spin up a <a href="https://www.elastic.co/docs/deploy-manage/deploy/self-managed/install-elasticsearch-docker-basic">Docker development environment</a> and explore the full feature set with a realistic multi-site network.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/snmp-topology-data-kibana-collection-canvas</link>
    <guid isPermaLink="false">snmp-topology-data-kibana-collection-canvas</guid>
    <category><![CDATA[Metrics]]></category>
    <category><![CDATA[Logs Analytics]]></category>
    <dc:creator><![CDATA[C. Pierce]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc2ce47ec9d31d82c/6a7f1b0342a117161f95c31f/cover.png" length="0" type="image/png"/>
    <pubDate>Wed, 03 Jun 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[From raw logs to system knowledge: the AI context layer observability is missing]]></title>
    <description><![CDATA[A self-updating knowledge base built from your logs: services, dependencies, and failure modes, so your AI agents always know what they are looking at.]]></description>
    <content:encoded><![CDATA[<p>Your monitoring system sees everything, but understands almost nothing.</p>
<p>Before you can rely on most tools to trigger a meaningful alert, you have to do the heavy lifting of telling them exactly what to watch. You have to write the rules, specify what a "normal" baseline looks like, and manually define your service catalog. We're working to change that dynamic at Elastic, and the first major building block is now in place: a system designed to simply read your logs and figure out what's inside them on its own.</p>
<p>Consider what happens when an alert fires today. Your on-call engineer opens an investigation, and the first few minutes are inevitably burned reconstructing basic facts. They have to figure out which services are involved, how those services connect to one another, what error patterns are typical, and which queries they actually need to run to dig deeper. An AI agent faces this exact same cold start problem. Without prior knowledge of your system's architecture, an agent has to read through hundreds of log lines just to establish baseline context that really should already be available.</p>
<p>This blank slate is the default state of most observability setups. You only know what you've explicitly configured. When new services spin up and start writing logs, they sit there without rules until someone takes the time to write them. When architectural dependencies shift, your topology map quietly goes stale unless you've done an exceptional job instrumenting all your services. If an error pattern fires every day but nobody wrote a specific rule to catch it, it remains invisible.</p>
<p>Knowledge Indicators (KI) are our way of closing this gap. When you run extraction against a log stream, Elastic analyzes the raw data and returns structured facts about your environment. It identifies which services are running, the underlying infrastructure they rely on, how they depend on each other, and the log schemas they're using. It even generates a set of ES|QL queries for conditions that might be worth alerting on. Rather than a static configuration, this knowledge accumulates over time, automatically expires when a service disappears, and feeds directly into downstream capabilities like Rules, topology maps, AI agent investigations, and dashboards.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltff3305086ef2e39b/6a7f07c3bdcff054f3c42c2f/topology-graph@2x.png" alt="Knowledge Indicators graph: service nodes (claim-intake, fraud-check, policy-lookup, payment-processor, kafka, notification-dispatch, kubernetes) connected by labeled dependency edges (connection refused, ECONNREFUSED, gRPC UNAVAILABLE, pool exhausted, pod sync)" />
<em>Topology graph generated from dependency KIs: service nodes, dependency edges, and detected error conditions.</em></p>
<h2 id="theextractionpipeline">The Extraction Pipeline</h2>
<p>When designing this system, our primary goal was to eliminate the need for prior context. There should be no mandatory schemas, no service catalogs tied to specific properties, and no predefined static assets that would need to be maintained. We asked ourselves a simple question: if you handed a sample of raw logs to an engineer who had never seen the system before, what could they deduce just by looking?</p>
<p>That thought experiment became our core approach. The system samples a small batch of logs from a stream, processes them through a combination of LLM analysis and deterministic code generators, and accumulates its findings across multiple rounds, entirely configuration-free.</p>
<p>Imagine hiring a room full of junior SREs with one specific job: read these log lines and report their observations, not to fix anything or trigger alarms, just to notice things. "This looks like an nginx server," or "This database is PostgreSQL," or "Service A is calling Service B over HTTP." That's essentially what our extraction job is doing continuously across your streams.</p>
<p>To see how this works in practice, take a look at this single line from an nginx access log:</p>
<pre><code>192.168.1.45 - - [31/Mar/2026:14:23:01 +0000] "POST /api/v2/claims HTTP/1.1" 200 1247 "-" "claim-intake/1.4.2"
</code></pre>
<p>From just this string, the pipeline extracts three distinct facts:</p>
<ul>
<li><strong>Entity</strong>: <code>claim-intake</code> (identifiable as a service from the User-Agent)</li>
<li><strong>Version</strong>: <code>1.4.2</code> (extracted from the User-Agent string)</li>
<li><strong>Technology</strong>: nginx (the web server fielding the request)</li>
<li><strong>Schema</strong>: Combined Log Format</li>
</ul>
<p>Similarly, consider this Java service log:</p>
<pre><code>2026-03-31T14:23:03.412Z INFO fraud-check --- [nio-8080-exec-3] c.e.FraudCheckService : Calling upstream POST http://policy-lookup:8081/v1/policy latency=142ms status=200
</code></pre>
<p>Here, the extraction identifies:</p>
<ul>
<li><strong>Entity</strong>: <code>fraud-check</code> (a Spring Boot service)</li>
<li><strong>Dependency</strong>: <code>fraud-check</code> → <code>policy-lookup</code> (via an outbound HTTP call)</li>
<li><strong>Technology</strong>: Java, Spring Boot</li>
</ul>
<p>Pull twenty lines like these from across your stream, and you quickly build a working, accurate picture of your system architecture.</p>
<p>To ensure this process never blocks ingestion, extraction runs entirely as a background task. You can trigger it on demand from the stream detail view or the Significant Events Discovery UI, but the goal is to have it running by default without requiring attention.</p>
<p>The pipeline itself runs multiple iterations, each time fetching a small sample of documents. We use a mix of random and already-excluded documents to ensure we discover the full scope of the system. KIs found in one iteration are fed back as exclusions into the next, so each round focuses on what the previous one missed—ensuring quieter, less-represented services aren't crowded out by noisier ones.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdfa9e57d3cb5b3bc/6a7f07c6b43770fb324d6a9d/ki-extraction-pipeline@2x.png" alt="KI extraction pipeline: raw logs → three-pool biased sampling (entity-filtered / diverse / random) → LLM finalize_features and 4 computed generators in parallel → merge and dedup → 84 KIs stored" />
<em>Extraction pipeline: biased document sampling feeds a parallel LLM pass and four deterministic generators. Results are merged and deduplicated before storage.</em></p>
<p>Once sampled, the documents are sent to an LLM. We use a system prompt that instructs the model to identify a few specific types of features, which we plan to extend over time:</p>
<p>| Type | What it captures |
|------|------------------|
| Entity | Distinct system components: services, applications, jobs |
| Infrastructure | Environment context: Kubernetes, cloud provider, OS |
| Technology | Languages, frameworks, libraries, databases |
| Dependency | Relationships between components |
| Schema | Log format conventions: ECS, OTel, custom |</p>
<p>The LLM returns its findings, delivering newly identified traits alongside any intentionally ignored ones (like user-excluded false positives). To be accepted, every feature must include stable identifying properties and cite direct evidence from the sampled logs. The LLM also assigns a confidence score from 0–100 for each KI, so any downstream use of that KI knows how much to trust it.</p>
<p>In parallel, a set of deterministic code-based generators independently analyze the data to produce statistical summaries, log samples, pattern clusters, and error-specific features. Because these are computed rather than inferred, they always receive a confidence score of 100.</p>
<p>Finally, the LLM results and computed features are merged and deduplicated. Known KIs reuse their existing UUIDs, new discoveries get fresh ones, and any user-excluded features are quietly dropped server-side. Surviving KIs are saved with an active status and an expiration date set for seven days out.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1aefe63628f536ce/6a7f07c9fc63ab916364ca61/kis.png" alt="Knowledge Indicators tab showing 84 KIs across streams, with type, confidence (1–5 stars), and stream columns visible" />
<em>Knowledge Indicators tab showing 84 KIs across streams, with type, confidence (1–5 stars), and stream columns.</em></p>
<h2 id="whataknowledgeindicatorcontains">What a Knowledge Indicator Contains</h2>
<p>Knowledge Indicators fall into two categories: Feature KIs and Query KIs.</p>
<p>Feature KIs are descriptive. They explain the contents of the stream: what services are running, the infrastructure housing them, their dependencies, and the active tech stack.</p>
<p>Query KIs are actionable. They are ready-to-run ES|QL queries targeting notable conditions like connection exhaustion, out-of-memory errors, or fatal exceptions. Each comes with a severity score from 0 to 100, and when promoted to Rules, they fire Events.</p>
<p>Feature KIs carry a full data model:</p>
<ul>
<li><strong><code>type</code> / <code>subtype</code></strong>: the category of the fact (Entity, Infrastructure, Technology, Dependency, Schema)</li>
<li><strong><code>title</code> / <code>description</code></strong>: a human-readable summary</li>
<li><strong><code>properties</code></strong>: stable key-value pairs used to deduplicate findings across multiple runs</li>
<li><strong><code>confidence</code></strong>: 0–100. LLM-identified KIs score based on evidence quality. Deterministic KIs always score 100.</li>
<li><strong><code>evidence</code></strong>: 2–5 supporting log excerpts that justify the KI's existence</li>
<li><strong><code>filter</code></strong>: an optional StreamLang condition scoping the KI to specific documents</li>
</ul>
<p>A dependency KI looks like this:</p>
<pre><code>{
  "type": "dependency",
  "subtype": "service_dependency",
  "title": "api_gateway → inference_service",
  "description": "Service-to-service HTTP dependency from api_gateway to inference_service, observed in request logs",
  "properties": {
    "source": "api_gateway",
    "target": "inference_service",
    "protocol": "http"
  },
  "confidence": 85,
  "evidence": [
    "service.name=api_gateway http.url=/v1/inference peer.service=inference_service",
    "upstream=inference_service:8080 request=POST /v1/inference 200"
  ],
  "filter": { "field": "service.name", "eq": "api_gateway" },
  "status": "active",
  "expires_at": "2026-04-09T00:00:00Z"
}
</code></pre>
<p>Query KIs take a simpler shape, focusing solely on the title, severity score, and the executable query:</p>
<pre><code>{
  "kind": "query",
  "title": "PostgreSQL connection slot exhaustion",
  "description": "Fires when Postgres runs out of available connection slots",
  "severity_score": 90,
  "esql": {
    "query": "FROM logs-* | WHERE service.name == \"postgres\" AND message : \"remaining connection slots\""
  }
}
</code></pre>
<p>The <code>properties</code> field is what keeps Feature KIs stable across multiple pipeline runs. The dependency KI for <code>api_gateway → inference_service</code> records the source, target, and protocol as fixed pairs. The next time extraction runs, Elastic recognizes this existing relationship and updates the KI's <code>last_seen</code> timestamp rather than creating a duplicate.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltcf99c981e914aefc/6a7f07cc6693f89c62663d5b/ki-detail.png" alt="KI detail panel for api_gateway → inference_service showing type, subtype, properties, confidence, evidence, and expiry date" />
<em>KI detail panel showing type, subtype, properties, confidence, evidence, and expiry date for a service dependency.</em></p>
<h2 id="thefoundationforintelligentobservability">The Foundation for Intelligent Observability</h2>
<p>So what can we do with all of this? These KIs serve as the contextual foundation for Elastic's more advanced capabilities. From just these extracted KIs, we can automatically generate active Rules to surface interesting signals, without a human engineer writing a single line of configuration. More on this particular capability in the next post in this series.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2ab47e653ddc6683/6a7f07d01967eada7c330527/queries.png" alt="85 auto-generated Rules from KIs with impact ratings (Critical/High) and event occurrence sparklines" />
<em>85 auto-generated Rules from 84 KIs, with impact ratings and event occurrence sparklines.</em></p>
<p>As a user or an agent, the dependency KIs automatically construct an infrastructure graph—inferred entirely from log data, not from distributed tracing or any manual configuration. During an incident, this graph is invaluable for assessing blast radius. If a specific database goes down, the topology map immediately shows you exactly which upstream services are about to fail, without maintaining a manual service catalog.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb87332c1b105dd39/6a7f07d373d9bd5d0b29d946/topology-dependencies@2x.png" alt="Topology graph showing service dependencies: claim-intake connects to claim-intake-db (PostgreSQL), fraud-check, policy-lookup; fraud-check connects to fraud-check-db (MongoDB); policy-lookup connects to policy-lookup-db (PostgreSQL); payment-processor and kafka grouped separately; notification-dispatch and kubernetes at bottom" />
<em>Service dependency graph extracted from KIs, showing services, databases, and infrastructure components.</em></p>
<p>This context changes how an AI agent handles an incident. Instead of starting from scratch, the agent initiates its investigation using your system's actual topology and known failure modes. Based on the KIs, it identifies the relevant streams, runs the applicable queries, and formulates a specific hypothesis. In our example, it already knows that <code>api_gateway</code> relies on <code>inference_service</code>, and it knows that connection slot exhaustion is a high-severity failure mode for your Postgres instance.</p>
<p>This extracted knowledge doesn't have to be perfect to be useful. Because LLMs are inherently non-deterministic, a KI might occasionally be slightly off, but it still gives the agent a significant head start. The agent can cross-reference the KI against live logs and self-correct on the fly. The real benefit is simply not having to reconstruct basic facts during a critical outage. KIs also drive AI-generated dashboard suggestions and inform Grok pattern generation whenever you introduce new streams.</p>
<h2 id="selfcleaningandscalable">Self-Cleaning and Scalable</h2>
<p>Maintaining this knowledge base is entirely hands-off. KIs auto-expire after 7 days if they aren't observed in subsequent extraction runs. If you decommission a service, its associated KIs simply fade away without any manual cleanup. If the service comes back online later, the KIs are re-extracted. Users can also mark individual feature KIs as false positives, and the system carries those exclusions forward into future runs to prevent re-identification.</p>
<p>Because we scoped KI extraction as a specific classification task, looking at around 20 log samples to identify services, infrastructure, and dependencies, it doesn't require a large frontier model to run. A fast, cost-effective model handles this without multi-step reasoning.</p>
<h2 id="youshouldnthavetotellyourtoolswhattowatch">You Shouldn't Have to Tell Your Tools What to Watch</h2>
<p>The fundamental promise of observability is to help you understand your systems. For far too long, the burden of teaching the tool how those systems actually work has fallen on the engineers operating them.</p>
<p>The next post in this series looks at what agents do with that context: why every agent that investigates your system without KIs re-learns the same things from scratch on every incident, and what changes when it doesn't have to.</p>
<p><strong>NOTE:</strong> These capabilities are available behind a feature flag in Serverless Observability projects. Turn on <code>observability:streamsEnableSignificantEvents</code> by searching for it in the Kibana advanced settings page. </p>
<h2 id="frequentlyaskedquestions">Frequently asked questions</h2>
<p><strong>What are Knowledge Indicators in Elasticsearch Streams?</strong>
Knowledge Indicators (KIs) are structured facts extracted from raw log streams: service names, infrastructure components, service-to-service dependencies, and tech stack details. Elastic extracts them automatically by sampling log lines, without requiring schemas, service catalogs, or manual configuration.</p>
<p><strong>How does Elastic build a service topology map from logs alone?</strong>
The extraction pipeline samples log lines and identifies dependency relationships, such as an outbound HTTP call from one service to another. These dependency KIs are used to construct a topology graph that shows which services depend on which, entirely inferred from log data, without distributed tracing or any manual input.</p>
<p><strong>Why does an AI agent need Knowledge Indicators before investigating an incident?</strong>
Without KIs, an AI agent starts every investigation from scratch: it has to read hundreds of log lines just to establish which services exist and how they relate. KIs give the agent a pre-built map of your system, including services, known failure modes, and relevant queries, so it can begin reasoning about the actual incident immediately.</p>
<p><strong>Do I need to configure anything for Knowledge Indicator extraction to work?</strong>
No. The pipeline requires no schema definitions, no service catalog, and no predefined rules. It samples a small set of log lines from a stream, analyzes them through a combination of LLM inference and deterministic generators, and accumulates findings automatically.</p>
<p><strong>How accurate are LLM-extracted KIs compared to computed ones?</strong>
Computed (deterministic) KIs always receive a confidence score of 100 because they are derived from statistical analysis rather than inference. LLM-extracted KIs receive scores from 0 to 100 based on the quality of evidence found in the sampled logs. Rules, agent investigations, and topology maps can all use this score to weight their decisions.</p>
<p><strong>What happens when a service is decommissioned?</strong>
KIs carry a 7-day expiration. If a service stops appearing in subsequent extraction runs, its KIs expire and are removed automatically. No manual cleanup required. If the service comes back, the KIs are re-extracted on the next run.</p>
<p><strong>How does this compare to service discovery via distributed tracing?</strong>
Distributed tracing requires instrumented services and a trace collector. Knowledge Indicator extraction requires nothing beyond existing log streams: no SDK, no agent, no schema. For environments with partial or no tracing coverage, KI extraction provides topology and dependency information that tracing would otherwise miss.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/elastic-knowledge-indicators-log-extraction</link>
    <guid isPermaLink="false">elastic-knowledge-indicators-log-extraction</guid>
    <category><![CDATA[Machine Learning]]></category>
    <category><![CDATA[Logs Analytics]]></category>
    <dc:creator><![CDATA[Luca Wintergerst]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4563c156c7dfcd41/6a7f07d66c6eac3466f13f0b/cover.png" length="0" type="image/png"/>
    <pubDate>Tue, 05 May 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Fixing Elastic Streams processing failures without dropping data]]></title>
    <description><![CDATA[When your Streams ingest pipeline breaks, failed documents land in the failure store, not the floor. Here's how to use those exact failures to fix your pipeline without re-ingesting from the source.]]></description>
    <content:encoded><![CDATA[<p>If you've run a Streams pipeline for more than a week, you've probably hit a processing failure. Before Streams, that often meant dropped data or a dead letter queue at the shipper layer: extra infrastructure you had to operate separately. Here's the recovery loop today.</p>
<h2 id="whenprocessingfailsdatalandsinthefailurestore">When processing fails, data lands in the failure store</h2>
<p>When a Streams pipeline fails (a Grok pattern doesn't match, a field type conflicts with the mapping), the documents that caused the failure are written to the <a href="https://www.elastic.co/docs/manage-data/data-store/data-streams/failure-store">failure store</a>. The failure store is a set of backing indices attached to your data stream. It scales the same way as any other data stream, so it can absorb everything that fails. It's enabled by default for logs as of Elasticsearch 9.2.</p>
<p>The <strong>Data quality</strong> tab gives you insights into the quality of your stream and into documents in the failure store. When failures are accumulating, you'll see a rising count of failed documents along with the error type and a sample of the messages that triggered it.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4686e6817c90d63e/6a7f09635967e508545dd162/processing-failures-in-failure-store.png" alt="Processing failures accumulating in the failure store" />
<em>The Data quality tab showing a rising failure count, error type, and a sample of the documents that triggered it.</em></p>
<p>A Grok expression mismatch (<code>illegal_argument_exception</code>) is sending documents to the failure store. The raw log line doesn't match the expected pattern. The documents aren't dropped. They're in the failure store, ready to debug against.</p>
<h2 id="processingswitchthesamplesourcetothefailurestore">Processing: Switch the sample source to the failure store</h2>
<p>Start by navigating to the <strong>Processing</strong> tab.</p>
<p>By default, the editor samples from recent live documents. Switch the sample source to <strong>Failure store</strong> instead: it loads the exact documents that failed, the unmodified originals before any Streams processing ran. You're iterating against the actual failures.</p>
<p>Change the sample source dropdown from the default to Failure store.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta92cf204670630eb/6a7f09666693f82c81663def/sample-source-dropdown.png" alt="Sample source dropdown showing Latest samples and Failure store options" />
<em>The sample source dropdown with the Failure store option selected.</em></p>
<p>The editor loads up to 100 documents from the failure store and runs them through the current pipeline. You can see exactly where parsing breaks down.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt528d61760756ab0f/6a7f09694c4bfb9ca2ccd3d9/failure-store-samples-processing.png" alt="Pipeline editor with failure store selected as the sample source" />
<em>The pipeline editor loaded with documents from the failure store instead of recent live samples.</em></p>
<h2 id="fixtheprocessoragainsttheactualfailures">Fix the processor against the actual failures</h2>
<p>With the failure store documents loaded as samples, iterate on the processor. The editor shows you the result against the actual failed documents in real time.</p>
<p>In this example, the pipeline was originally built to parse HTTP access logs:</p>
<pre><code>DELETE /api/v1/auth/logout from 26.72.241.177 - Status: 200 - Response time: 38ms - Request ID: req_24363339 - Location: São Paulo, BR - Device: desktop
HEAD /api/v1/notifications from 20.94.145.254 - Status: 202 - Response time: 60ms - Request ID: req_74513322 - Location: Tokyo, JP - Device: mobile
</code></pre>
<p>The original Grok pattern matched those:</p>
<pre><code>%{WORD:http.method} %{URIPATH:uri.path}
</code></pre>
<p>A second log type started flowing in. Cache hits and external API calls arrived in a different format:</p>
<pre><code>cache_hit: Cache hit for key: config
external_api_call: External API call completed - latency: 1695ms - Duration: 598ms
</code></pre>
<p>The original pattern doesn't match these at all. Every one goes straight to the failure store. With the failure store loaded as the sample source, the problem is immediately obvious: the editor shows the parse failing on lines that start with a word followed by a colon, not an HTTP method followed by a path.</p>
<p>The fix is a second pattern to handle the new format:</p>
<pre><code>%{WORD:event.type}: %{GREEDYDATA:message}
</code></pre>
<p>Add it to the processor, and the editor immediately shows both log types parsing correctly against the failure store samples.</p>
<p>When the sample view shows all fields extracting correctly and the parse rate hits 100%, the fix is ready.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1ffb5b202ba966a8/6a7f096d63e959962d73dc6a/successful-parsing.png" alt="Pipeline editor showing successful parsing against failure store samples" />
<em>Both log types parsing correctly after adding the second Grok pattern. Parse rate at 100%.</em></p>
<p>No guessing — the editor confirms the fix before you save.</p>
<h2 id="watchthefailurecountdrop">Watch the failure count drop</h2>
<p>Save the updated pipeline. New documents are now processed with the corrected pipeline. Switch back to the Data quality tab and watch the failure count.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6fd207484c0a8170/6a7f096f73d9bdcc0129d9c9/resolved.png" alt="Failure store count dropping after pipeline fix" />
<em>The failure count dropping as new documents are processed by the corrected pipeline.</em></p>
<p>The count drops as the fixed pipeline handles new incoming data correctly. The remaining documents in the failure store are the pre-fix failures. They'll clear out as retention ages them off.</p>
<p>The fix applies to new documents only. Documents already in the failure store aren't automatically reprocessed; each was processed by the pipeline version active when it arrived. If you need them in your main stream, that's a separate step.</p>
<h2 id="therecoveryloop">The recovery loop</h2>
<p>Open Data quality, switch to the failure store, fix the processor, save. The whole thing takes a few minutes at most.</p>
<p>No re-ingestion from source. No shipper-level dead letter queue to operate. If you haven't checked the Data quality tab for your streams recently, it's worth a look. There might be failures sitting there that a one-line fix would clear.</p>
<p>For a deeper look at what the Data quality tab shows and how to configure the failure store, see <a href="https://www.elastic.co/observability-labs/blog/data-quality-and-failure-store-in-streams">Elastic Observability: Streams Data Quality and Failure Store Insights</a>.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/elastic-streams-failure-store-processing</link>
    <guid isPermaLink="false">elastic-streams-failure-store-processing</guid>
    <category><![CDATA[Logs Analytics]]></category>
    <dc:creator><![CDATA[Luca Wintergerst]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4686e6817c90d63e/6a7f09635967e508545dd162/processing-failures-in-failure-store.png" length="0" type="image/png"/>
    <pubDate>Thu, 30 Apr 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Connecting Cursor to Production Logs via the Elastic MCP Server]]></title>
    <description><![CDATA[Learn how to connect Cursor to your Elastic APM data using the Elastic Agent Builder MCP server, so you can debug production errors and make UI decisions backed by real usage data without leaving your editor.]]></description>
    <content:encoded><![CDATA[<h2 id="prerequisites">Prerequisites</h2>
<ul>
<li><p>Elasticsearch 9.3+ (or Elastic Cloud Serverless)</p></li>
<li><p>Elasticsearch API KEY and Kibana URL</p></li>
<li><p>An application instrumented with Elastic APM: the <a href="https://www.elastic.co/guide/en/apm/agent/rum-js/current/index.html">RUM agent</a> for frontend interactions (populates <code>traces-apm-*</code>) and the <a href="https://www.elastic.co/docs/reference/apm-agents">APM agent</a> for backend errors (populates <code>logs-apm.error-*</code></p></li>
<li><p><a href="https://cursor.com/home">Cursor</a> (version 2.6+) installed</p></li>
</ul>
<h2 id="theproblemwithtwoworlds">The problem with two worlds</h2>
<p>Application logs and code are two separate worlds that don't talk to each other. If you want to apply log insights into the application you have to analyze the logs, and then come back to the editor and apply your findings.</p>
<p>The <a href="https://modelcontextprotocol.io/">Model Context Protocol (MCP)</a> changes this. MCP is an open standard that lets AI clients like Cursor connect to external tools and data sources through a standardized interface. Instead of your IDE only knowing about your local code, it can also talk to your Elasticsearch cluster, query your APM data, and reason about production behavior alongside your source files.</p>
<p>Elastic ships a <a href="https://www.elastic.co/docs/explore-analyze/ai-features/agent-builder/mcp-server">built-in MCP server</a> as part of <a href="https://www.elastic.co/docs/explore-analyze/ai-features/elastic-agent-builder">Agent Builder</a>. You define tools in Kibana, expose them via the MCP endpoint, and any MCP-compatible client can call them. Cursor supports MCP natively, which means you can set this up in minutes.</p>
<h2 id="whatwerebuilding">What we're building</h2>
<p>We're working with an eCommerce search app instrumented with Elastic APM. The RUM JS agent tracks filter click interactions from the browser, stored in <code>traces-apm-default</code>. The Node.js APM agent captures backend errors, stored in <code>logs-apm.error-default</code>.</p>
<p>Two situations come up during development:</p>
<ul>
<li><p><strong>Use case 1</strong>: The product team wants to simplify the search page. There are six filters but we don't know which ones users actually click. We need usage data to decide which to keep.</p></li>
<li><p><strong>Use case 2</strong>: Users report intermittent 500 errors on search. The errors are not constant and started two days ago. We need the error details to find the root cause.</p></li>
</ul>
<p>To bring that data into Cursor, we'll build two Agent Builder tools in Kibana and connect them via the <a href="https://www.elastic.co/docs/explore-analyze/ai-features/agent-builder/mcp-server">Elastic Agent Builder MCP Server</a>:</p>
<ul>
<li><p><code>get_filter_usage</code>: queries <code>traces-apm-default</code> for filter click events and returns a usage breakdown by filter name</p></li>
<li><p><code>get_recent_errors</code>: queries <code>logs-apm.error-default</code> for the most recent error groups for a given service, including the exception message and stack trace culprit</p></li>
</ul>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt62d3a8324299ca7a/6a7f080dc2cc09675c24935f/architecture.png" alt="Architecture diagram showing Cursor connecting to the Elastic Agent Builder MCP server, which queries Elasticsearch APM data" /></p>
<p>For a deeper look at the overall architecture, see the <a href="https://www.elastic.co/search-labs/blog/agent-builder-mcp-reference-architecture-elasticsearch">Agent Builder reference guide</a>.  </p>
<h2 id="settinguptheelasticmcpservernbspnbsp">Setting up the Elastic MCP Server  </h2>
<h3 id="step1createtheagentbuildertools">Step 1: Create the Agent Builder tools</h3>
<p>We create both tools via the <a href="https://www.elastic.co/docs/explore-analyze/ai-features/agent-builder/kibana-api">Kibana Agent Builder API</a>. Each tool is an ES|QL query with a name and description that Cursor uses to decide when to call it. The full implementation of the tools is in the following <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/cursor-production-logs-elastic-mcp-server/notebook.ipynb"><code>notebook</code></a>.</p>
<h4 id="tool1get_filter_usage">Tool 1: get_filter_usage</h4>
<p>The product team needs to know which filters users actually click before deciding which ones to remove. The query reads RUM interaction events from <code>traces-apm-default</code> and groups them by filter name:</p>
<pre><code>    {
    &amp;nbsp;&amp;nbsp;"id": "get_filter_usage",
    &amp;nbsp;&amp;nbsp;"type": "esql",
    &amp;nbsp;&amp;nbsp;"description": "Returns the usage count for each search filter in the ecommerce-search-ui service, sorted by most used first.",
    &amp;nbsp;&amp;nbsp;"configuration": {
    &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"query": "FROM traces-apm-default | WHERE service.name == \"ecommerce-search-ui\" | WHERE transaction.type == \"user-interaction\" | WHERE labels.filter_name IS NOT NULL | STATS count = COUNT(*) BY labels.filter_name | SORT count DESC"
    &amp;nbsp;&amp;nbsp;}
    }
</code></pre>
<h4 id="tool2get_recent_errors">Tool 2: get_recent_errors</h4>
<p>For the error debugging use case, we need to surface the most frequent recent errors for a service, along with where in the code they originate. <code>STATS ... BY</code> groups errors by their fingerprint (<code>grouping_key</code>), surfaces the exception message and the line of code that caused it (<code>culprit</code>), and ranks by frequency:</p>
<pre><code>    {
    &amp;nbsp;&amp;nbsp;"id": "get_recent_errors",
    &amp;nbsp;&amp;nbsp;"type": "esql",
    &amp;nbsp;&amp;nbsp;"description": "Returns the most frequent error groups for ecommerce-search-ui, ranked by occurrence count, with the exception message and code location.",
    &amp;nbsp;&amp;nbsp;"configuration": {
    &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"query": "FROM logs-apm.error-default | WHERE service.name == \"ecommerce-search-ui\" | WHERE processor.name == \"error\" | STATS count = COUNT(*) BY error.grouping_key, error.exception.0.message, error.culprit | SORT count DESC | LIMIT 5"
    &amp;nbsp;&amp;nbsp;}
    }
</code></pre>
<p>Both tools are created with <code>POST /api/agent_builder/tools</code>. You can learn more about the Kibana API endpoints for Elastic Agent Builder <a href="https://www.elastic.co/docs/explore-analyze/ai-features/agent-builder/kibana-api">here</a>.</p>
<h3 id="step2connecttocursor">Step 2: Connect to Cursor</h3>
<p>Open <code>~/.cursor/mcp.json</code> and add the Elastic server. For detailed information, see the Cursor <a href="https://cursor.com/docs/mcp#using-mcpjson">documentation</a>. The Agent Builder MCP endpoint uses Server-Sent Events (SSE) transport, so we connect via <code>mcp-remote</code>, a lightweight bridge that Cursor invokes as a local process:</p>
<pre><code>    {
    &amp;nbsp;&amp;nbsp;"mcpServers": {
    &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"elastic-agent-builder": {
    &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"command": "npx",
    &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"args": [
    &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"-y",
    &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"mcp-remote",
    &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"https://YOUR_KIBANA_URL/api/agent_builder/mcp",
    &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"--header",
    &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"Authorization: ApiKey YOUR_API_KEY"
    &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;]
    &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
    &amp;nbsp;&amp;nbsp;}
    }
</code></pre>
<p>Replace <code>YOUR_KIBANA_URL</code> and <code>YOUR_API_KEY</code> with your values.</p>
<p>Restart Cursor, open the Agent panel, and confirm that <code>get_filter_usage</code> and <code>get_recent_errors</code> appear in the available tools list. </p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt26bfbf5b85c381ad/6a7f0810c2cc099f19249363/cursor-mcp-tools.png" alt="Cursor MCP panel showing the get_filter_usage and get_recent_errors tools available from the Elastic Agent Builder server" /></p>
<h2 id="usecase1datadrivenuioptimization">Use case 1: Data-driven UI optimization</h2>
<p>The eCommerce search page has six filters: category, manufacturer, price range, customer gender, day of week, and region. The product team wants to simplify the UI by removing filters that users don't use as much. Rather than guessing, we ask Cursor to check.</p>
<p>When you type a prompt in Cursor's Agent panel, the model sees the name and description of every connected MCP tool. It matches your intent to the best-fitting tool and calls it automatically. This is why the <code>description</code> field we set in Step 1 matters: it's what the model reads to decide which tool answers your question. If you are interested in learning more about Cursor’s MCP tools management, read the following <a href="https://cursor.com/docs/mcp#using-mcp-in-chat">documentation</a>.</p>
<p>Open a Cursor chat and ask: "Show me how often each search filter is used." Cursor calls the tool and returns something like:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0cc46e50f9d94970/6a7f0813bd21980745757eda/filter-usage-chart.png" alt="Filter usage breakdown returned by the get_filter_usage tool" /></p>
<p>The category and manufacturer filters get most of the clicks. The bottom three filters (<code>customer_gender</code>, <code>day_of_week</code>, <code>region</code>) are rarely used.</p>
<p>Ask Cursor to act on this: <strong><em>"Based on this data, simplify the SearchFilters component. Keep the top 3 filters visible, collapse the others under a 'More filters' toggle."</em></strong></p>
<p>Cursor opens <code>src/components/SearchFilters.jsx</code>, reads the current implementation, and proposes the change.</p>
<p>Before: </p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8af7e161637b0151/6a7f0816e3a219301899f2a4/search-filters-before.png" alt="SearchFilters component before the change, showing all six filters" /></p>
<p>After: </p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt61f5bd24ca30c19e/6a7f0819ead8ece767baa672/search-filters-after.png" alt="SearchFilters component after the change, showing the top three filters with the rest collapsed under a More filters toggle" /></p>
<p>The entire loop took one chat prompt. The decision was backed by production data, not a team discussion about what users probably care about.</p>
<h2 id="usecase2productionerrordebugging">Use case 2: Production error debugging</h2>
<p>A bug report comes in: intermittent 500 errors on the search endpoint. The errors started appearing two days ago but they're not constant. The developer opens Cursor and asks: "Show me what errors ecommerce-search-ui is throwing."</p>
<p>Cursor calls the tool and returns the error groups:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt67bae433977e721a/6a7f081c227b1c4eeb59841e/recent-errors.png" alt="Most recent error groups returned by the get_recent_errors tool" /></p>
<p>The error message is explicit: <code>category</code> is a text field and can't be used in terms of aggregation. The correct field is <code>category.keyword</code>. </p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt37f176d0f26a2fb9/6a7f081f3cab1cfd580e4662/error-fix-diff.png" alt="Cursor proposing the fix that changes category to category.keyword in the ES|QL query" /></p>
<p>With APM data available alongside your code, the debugging session becomes a conversation: you describe the symptom, the agent pulls the relevant logs, and you work through what's happening together. You can ask follow-up questions, check whether the error correlates with a recent deployment, or ask which endpoints are most affected, all within the same context where you'll make the fix. If you want to go further, Elastic also provides <a href="https://www.elastic.co/docs/solutions/observability/ai/agent-builder-observability">pre-built observability tools in Agent Builder</a> that you can use alongside custom tools like the ones we created here. For a complementary approach to AI-driven observability, see <a href="https://www.elastic.co/observability-labs/blog/ai-observability-web-agents-openlit">how to monitor web AI agents with OpenLIT and Elastic</a>.</p>
<h2 id="conclusion">Conclusion</h2>
<p>What we covered:</p>
<ul>
<li><p>How to create Agent Builder tools in Kibana that wrap APM data queries</p></li>
<li><p>How to connect the Elastic Agent Builder MCP Server to Cursor in three lines of JSON</p></li>
<li><p>Using production telemetry to make a UI decision backed by real usage data</p></li>
<li><p>Debugging a production error from the same window where you fix it</p></li>
</ul>
<p>These two use cases are a starting point. The same pattern works for any data you have in Elasticsearch: performance metrics, A/B test results, audit logs, feature flag usage, user session data. Define the Agent Builder tool, connect it via MCP, and it becomes part of your development context in Cursor. For other examples of what's possible, see <a href="https://www.elastic.co/observability-labs/blog/mcp-elastic-synthetics">automating synthetic monitoring with MCP</a> and <a href="https://www.elastic.co/observability-labs/blog/agentic-cicd-kubernetes-mcp-server">agentic CI/CD deployment gates</a>.</p>
<h2 id="nextsteps">Next steps</h2>
<ul>
<li><p><a href="https://www.elastic.co/docs/explore-analyze/ai-features/agent-builder/mcp-server">Elastic Agent Builder MCP server documentation</a></p></li>
<li><p><a href="https://modelcontextprotocol.io/">Model Context Protocol specification</a></p></li>
<li><p><a href="https://www.elastic.co/docs/explore-analyze/ai-features/elastic-agent-builder">Elastic Agent Builder overview</a></p></li>
</ul>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/elastic-mcp-server-cursor-production-logs</link>
    <guid isPermaLink="false">elastic-mcp-server-cursor-production-logs</guid>
    <category><![CDATA[Agentic Observability]]></category>
    <category><![CDATA[Logs Analytics]]></category>
    <category><![CDATA[APM]]></category>
    <dc:creator><![CDATA[Jeffrey Rengifo]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt995e6ed7b699e8fa/6a7f08226c6eacad3ef13f31/header.png" length="0" type="image/png"/>
    <pubDate>Wed, 29 Apr 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[How to cut Elasticsearch log storage costs with LogsDB]]></title>
    <description><![CDATA[Learn how to enable LogsDB index mode in Elasticsearch and measure real storage savings. We compare a standard index against a LogsDB index using Apache logs and show how much storage you can reclaim.]]></description>
    <content:encoded><![CDATA[<p>LogsDB is a specialized Elasticsearch index mode that gives you full functionality at a fraction of the storage cost. Your Kibana dashboards, searches, alerts, and visualizations all continue to work exactly as before. No data is discarded. No queries need to be updated. No workflows break. It is one setting, and everything else gets cheaper.</p>
<p>In benchmarks, LogsDB brought a dataset from <strong>162.7 GB down to 39.4 GB</strong> — a <strong>76% reduction in storage</strong>. You can explore the full nightly benchmark results at <a href="https://elasticsearch-benchmarks.elastic.co/#tracks/logsdb/nightly/default/90d">elasticsearch-benchmarks.elastic.co</a>.</p>
<p>In this tutorial you'll reproduce the experiment yourself using Kibana Dev Tools and an Apache logs dataset. You'll create two identical indices, ingest the same documents into both, and measure the storage difference with the <code>_stats</code> API. By the end, you'll see a 44% reduction on your test data — and understand exactly why production numbers push even higher.</p>
<blockquote>
  <p><strong>Already on Elasticsearch 9.2+?</strong> Any data stream with a <code>logs-</code> prefix already uses LogsDB by default. Jump to <a href="https://www.elastic.co/observability-labs/blog/elasticsearch-logsdb-index-mode-storage-savings#what-about-your-existing-logs">What about your existing logs?</a> to verify your setup.</p>
  <p><strong>Want the full picture?</strong> For the engineering history behind these savings — how Lucene doc values, synthetic <code>_source</code>, index sorting, and ZSTD were developed and stacked over twelve years — see <a href="https://www.elastic.co/observability-labs/blog/elasticsearch-logsdb-storage-evolution"><em>Elasticsearch over the years: how LogsDB cuts index size by up to 75%</em></a>.</p>
</blockquote>
<h2 id="prerequisites">Prerequisites</h2>
<ul>
<li>Elasticsearch 8.17+ cluster, Elastic Cloud deployment, or Serverless</li>
<li>Kibana with Dev Tools access</li>
<li>Some logs</li>
<li>Basic familiarity with running API calls in Kibana Dev Tools</li>
</ul>
<h2 id="howlogsdbsavesstorage">How LogsDB saves storage</h2>
<p>LogsDB stacks three mechanisms to achieve its storage reduction:</p>
<ul>
<li><strong>Index sorting</strong> — documents are sorted by <code>host.name</code> then <code>@timestamp</code>, grouping similar log lines so compression codecs find far more repeated patterns. Sorting alone accounts for roughly 30% of the savings.</li>
<li><strong>ZSTD compression with delta/GCD/run-length encoding</strong> — <code>best_compression</code> switches from LZ4 to Zstandard and applies numeric codecs to each doc values column. The standard index in this tutorial uses LZ4, so part of what you're measuring is the full package LogsDB delivers automatically.</li>
<li><strong>Synthetic <code>_source</code></strong> — Elasticsearch skips storing the raw JSON blob entirely and reconstructs <code>_source</code> on demand from doc values, adding another 20–40% of savings on top.</li>
</ul>
<blockquote>
  <p><strong>Synthetic <code>_source</code> trade-offs:</strong> Field ordering in returned documents may differ from the original, and some edge cases around multi-value array fields behave differently. For most log analytics workloads these differences are invisible, but check the <a href="https://www.elastic.co/observability-labs/blog/elasticsearch-logsdb-index-mode-storage-savings#next-steps">synthetic <code>_source</code> documentation</a> before enabling it in latency-sensitive applications.</p>
</blockquote>
<p>For a deep dive into the architecture behind each mechanism, see <a href="https://www.elastic.co/observability-labs/blog/elasticsearch-logsdb-storage-evolution"><em>Elasticsearch over the years: how LogsDB cuts index size by up to 75%</em></a>.</p>
<p>Let's now walk through the steps you can take to enable LogsDB and measure the storage savings.</p>
<h2 id="step1collectlogswithelasticagent">Step 1: Collect logs with Elastic Agent</h2>
<p>The recommended way to ingest Apache logs into Elasticsearch is through Elastic Agent with the Apache integration. It handles collection, parsing, ECS field mapping, and routing automatically.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt909c6349a734ff01/6a7f09dbc2cc09cfe3249426/integration.png" alt="Elastic Agent Apache integration setup in Kibana" /></p>
<p>Browse all available integrations in the <a href="https://www.elastic.co/integrations">Elastic integrations catalog</a>.</p>
<p>Once the Agent is collecting logs and routing them to <code>logs-apache.access-*</code>, move to the next step.</p>
<h2 id="step2createthetwoindices">Step 2: Create the two indices</h2>
<p>All commands in this tutorial are run in <strong>Kibana Dev Tools</strong>.</p>
<p>Create one standard index and one LogsDB index with identical field mappings. The only difference is <code>"index.mode": "logsdb"</code>.</p>
<p><strong>Standard index:</strong></p>
<pre><code>PUT /apache-standard
{
  "mappings": {
    "properties": {
      "@timestamp":                  { "type": "date" },
      "host.name":                   { "type": "keyword" },
      "http.request.method":         { "type": "keyword" },
      "url.path":                    { "type": "keyword" },
      "http.version":                { "type": "keyword" },
      "http.response.status_code":   { "type": "integer" },
      "http.response.bytes":         { "type": "integer" },
      "http.request.referrer":       { "type": "keyword" },
      "user_agent.original":         { "type": "keyword" }
    }
  }
}
</code></pre>
<p><strong>LogsDB index:</strong></p>
<pre><code>PUT /apache-logsdb
{
  "settings": {
    "index.mode": "logsdb"
  },
  "mappings": {
    "properties": {
      "@timestamp":                  { "type": "date" },
      "host.name":                   { "type": "keyword" },
      "url.path":                    { "type": "keyword" },
      "http.request.method":         { "type": "keyword" },
      "http.version":                { "type": "keyword" },
      "http.response.status_code":   { "type": "integer" },
      "http.response.bytes":         { "type": "integer" },
      "http.request.referrer":       { "type": "keyword" },
      "user_agent.original":         { "type": "keyword" }
    }
  }
}
</code></pre>
<p>That single <code>"index.mode": "logsdb"</code> line activates all three storage mechanisms. Elasticsearch enables these additional settings behind the scenes — you don't set any of them manually:</p>
<pre><code>{
  "index.sort.field":              ["host.name", "@timestamp"],
  "index.sort.order":              ["asc", "desc"],
  "index.codec":                   "best_compression",
  "index.mapping.ignore_malformed": true,
  "index.mapping.ignore_above":    8191
}
</code></pre>
<h2 id="step3reindexthelogs">Step 3: Reindex the logs</h2>
<p>Use the <code>_reindex</code> API to copy the same documents into both test indices:</p>
<pre><code>POST /_reindex
{
  "source": { "index": "logs-apache.access-*" },
  "dest":   { "index": "apache-standard" }
}

POST /_reindex
{
  "source": { "index": "logs-apache.access-*" },
  "dest":   { "index": "apache-logsdb" }
}
</code></pre>
<p>Both indices now hold identical documents, so the storage comparison in the next step reflects only the index mode difference.</p>
<h2 id="step4forcemergeforafaircomparison">Step 4: Force merge for a fair comparison</h2>
<p>Before measuring, force merge both indices to a single segment:</p>
<pre><code>POST /apache-standard/_forcemerge?max_num_segments=1

POST /apache-logsdb/_forcemerge?max_num_segments=1
</code></pre>
<p>These calls block until the merge finishes. Wait for both responses before continuing.</p>
<p><strong>Why this matters:</strong> Elasticsearch writes data into multiple Lucene segments before merging them in the background. Measuring mid-merge gives artificially inflated numbers because each segment is compressed independently. Forcing a single segment shows the real steady-state storage footprint you'd see in a mature production index.</p>
<blockquote>
  <p><strong>Only run <code>_forcemerge</code> on indices that are no longer being written to.</strong> Force merging an index that is still receiving writes is resource-intensive and can impact ingestion performance. In production, you can use <a href="https://www.elastic.co/docs/manage-data/lifecycle/index-lifecycle-management">Index Lifecycle Management (ILM)</a> to automate force merges as part of the warm or cold phase, once an index is rolled over and no longer actively ingested into.</p>
</blockquote>
<h2 id="step5measurethedifference">Step 5: Measure the difference</h2>
<pre><code>GET /apache-standard/_stats?filter_path=indices.*.primaries.store

GET /apache-logsdb/_stats?filter_path=indices.*.primaries.store
</code></pre>
<p>The <code>filter_path</code> parameter keeps the response focused. Look for <code>primaries.store.size_in_bytes</code> in each response.</p>
<p>In our test with Apache log records, the results were:</p>
<p>| Index            | Documents | Size     |
|------------------|-----------|----------|
| apache-standard  | 111,818   | 15.37 MB |
| apache-logsdb    | 111,818   | 8.6 MB   |
| <strong>Reduction</strong>    |           | <strong>44%</strong>  |</p>
<p>To put this in perspective: at 1 TB of log data, LogsDB brings that down to around 560 GB. That's 450 GB saved without any changes to your queries. At production scale with billions of documents and synthetic <code>_source</code> enabled, savings push to 76% — taking 162.7 GB down to 39.4 GB in our benchmark.</p>
<h2 id="visualizeinkibana">Visualize in Kibana</h2>
<p>To see the storage difference visually, open Kibana and go to <strong>Management → Stack Management → Index Management</strong>. You'll see both indices listed with their current sizes side by side.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltedf2816dfc31a34b/6a7f09deb43770a7a64d6b65/index-stats.png" alt="Kibana Index Management showing storage comparison between standard and LogsDB indices" /></p>
<blockquote>
  <p><strong>Why Kibana shows larger numbers than <code>_stats</code>:</strong> Kibana Index Management displays the total index size including all replica shards. The <code>_stats</code> query above uses <code>primaries</code> to report primary shards only. The ratio between the two indices remains the same either way.</p>
</blockquote>
<h2 id="whataboutyourexistinglogs">What about your existing logs?</h2>
<h3 id="elasticsearch92alreadyenabledbydefault">Elasticsearch 9.2+ (already enabled by default)</h3>
<p>Since 9.2, any data stream matching the <code>logs-*</code> naming pattern automatically uses LogsDB. You're likely already saving storage without any configuration change.</p>
<p>Verify your existing data streams:</p>
<pre><code>GET /.ds-logs-*/_settings?filter_path=*.settings.index.mode
</code></pre>
<p>If you see <code>"index.mode": "logsdb"</code> in the responses, you're already getting the savings.</p>
<h3 id="elasticsearch8xor9091enableperdatastreamviaindextemplate">Elasticsearch 8.x or 9.0–9.1 (enable per data stream via index template)</h3>
<p>For earlier versions, enable LogsDB on a data stream by updating its index template. This affects all new indices created from that template — existing indices are not changed, so the transition is safe and gradual.</p>
<p><strong>Option A — Update an existing template:</strong></p>
<pre><code>PUT _index_template/logs-myapp-template
{
  "index_patterns": ["logs-myapp-*"],
  "data_stream": {},
  "template": {
    "settings": {
      "index.mode": "logsdb"
    }
  },
  "priority": 200
}
</code></pre>
<p><strong>Option B — Check and patch an existing integration template:</strong></p>
<p>First, find the template managing your data stream:</p>
<pre><code>GET _index_template/logs-apache*
</code></pre>
<p>Then add the <code>index.mode</code> setting to the <code>template.settings</code> block using a <code>PUT _index_template/&lt;name&gt;</code> call with the full template body including your addition.</p>
<p>After updating the template, the next index rollover will use LogsDB. Trigger a rollover immediately if you don't want to wait:</p>
<pre><code>POST /logs-myapp-default/_rollover
</code></pre>
<p><strong>Upgrading from 8.x to 9.0+:</strong> Existing data streams are not changed automatically. Only new rollovers will use LogsDB. There is no data loss and no reindexing required — the savings accumulate as new indices roll over.</p>
<h2 id="whataboutqueryperformance">What about query performance?</h2>
<p>LogsDB does not significantly impact query performance for typical log analytics workloads. The index sorting by <code>host.name</code> and <code>@timestamp</code> can actually <em>improve</em> range query and aggregation performance on those fields, since matching documents are stored adjacently. Queries that don't filter on those fields perform comparably to a standard index.</p>
<p>For indexing throughput data across releases, see the <a href="https://www.elastic.co/observability-labs/blog/elasticsearch-logsdb-storage-evolution#performance-not-just-storage">performance section</a> of the companion article.</p>
<h2 id="conclusion">Conclusion</h2>
<p>LogsDB activates with a single <code>"index.mode": "logsdb"</code> setting and delivers measurable storage savings immediately: 44% in our hands-on test, and 76% (162.7 GB → 39.4 GB) in production benchmarks with synthetic <code>_source</code>. On Elasticsearch 9.2+, <code>logs-*</code> data streams already use LogsDB by default. For 8.x or earlier 9.x clusters, a one-line index template change enables it on your next rollover with no data loss and no reindexing required.</p>
<h2 id="nextsteps">Next steps</h2>
<ul>
<li><a href="https://www.elastic.co/docs/manage-data/data-store/data-streams/logs-data-stream-integrations">LogsDB index mode documentation</a></li>
<li><a href="https://www.elastic.co/docs/manage-data/data-store/data-streams/logs-data-stream">Configuring a logs data stream</a></li>
<li><a href="https://www.elastic.co/search-labs/blog/elasticsearch-logsdb-index-mode">LogsDB GA announcement</a></li>
<li><a href="https://www.elastic.co/search-labs/blog/elastic-logsdb-tsds-enhancements">LogsDB and TSDS performance benchmarks</a></li>
</ul>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/elasticsearch-logsdb-index-mode-storage-savings</link>
    <guid isPermaLink="false">elasticsearch-logsdb-index-mode-storage-savings</guid>
    <category><![CDATA[Data Management]]></category>
    <category><![CDATA[Logs Analytics]]></category>
    <dc:creator><![CDATA[Jeffrey Rengifo]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt99ec1c2ec2a7af55/6a7f09e23ce8e203b0cf5277/header.png" length="0" type="image/png"/>
    <pubDate>Thu, 09 Apr 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Elasticsearch over the years — how LogsDB cuts index size by up to 75% at no throughput cost]]></title>
    <description><![CDATA[By default, Elasticsearch is optimized for retrieval, not storage. LogsDB changes that. Here's the layered architecture behind a 77% index size reduction.]]></description>
    <content:encoded><![CDATA[<p>Elasticsearch was built as a search engine. That heritage has a cost for log storage: every event fans out to multiple on-disk structures, each optimized for retrieval rather than compression. LogsDB changes both. On our nightly benchmark, Enterprise mode produces a 37.5 GB index from the same data that takes 161.9 GB without LogsDB — a 77% reduction from a single setting.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta3a4d65c4de793d4/6a7f09e73cab1cd73c0e4734/storage-breakdown-v3-bold@2x.png" alt="Standard vs LogsDB storage breakdown" /></p>
<h2 id="thewriteoverhead">The write overhead</h2>
<p>Lucene, the library underneath, keeps multiple structures for every indexed document:</p>
<ul>
<li>The <strong>inverted index</strong> maps terms to documents. This is what makes text search fast.</li>
<li><strong><code>_source</code></strong> stores the original JSON blob, returned when you fetch a document.</li>
<li><strong>Doc values</strong> store field values in columns for sorting and aggregation.</li>
<li><strong>Points / BKD trees</strong> index numeric and date fields for range queries.</li>
</ul>
<p>The inverted index earns its keep: it's what lets you search a billion log lines by keyword in milliseconds, and there's no cheaper way to build that capability. <code>_source</code> exists to give you back exactly what you indexed: search results and <code>GET</code> requests return this blob directly. The problem is that it stores the full event even though the same field values are already available through doc values and the other structures.</p>
<p>Take a log event with fields like <code>host.name</code>, <code>@timestamp</code>, <code>http.response.status_code</code>, and <code>duration_ms</code>. The entire event is serialized as JSON in <code>_source</code>. The same field values are also written into doc values columns, indexed into the inverted index, and stored in BKD trees for range queries. Same data, multiple structures, each with its own on-disk footprint.</p>
<p>For a search engine where you need fast retrieval across all dimensions, that overhead is a reasonable tradeoff. For logs, where you rarely need the raw JSON and almost never do relevance-ranked search, much of it is pure waste.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltbf0f50c8d9b1c3c5/6a7f09e963e95944bd73dca6/dual-storage-bold@2x.png" alt="One incoming log event fans out to four on-disk structures" />
<em>One write, four on-disk structures: <code>_source</code> (the raw JSON blob), the inverted index, doc values columns, and BKD / points trees for numeric range queries. The same field values end up in multiple places.</em></p>
<h2 id="whycolumnarstoragemattersforcompression">Why columnar storage matters for compression</h2>
<p>Doc values are the key to everything LogsDB does. Unlike <code>_source</code>, which stores entire documents as blobs, doc values store each field as a separate column across all documents in a Lucene segment.</p>
<p>Picture a segment with a million log events. The <code>_source</code> representation is a million JSON blobs, one per event, each containing all fields jumbled together. The doc values representation is a set of columns: one column of a million timestamps, one column of a million host names, one column of a million status codes, and so on.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta2548bca8f377f56/6a7f09eceab5bec47820a589/doc-values-columns-bold@2x.png" alt="Row-oriented vs column-oriented storage" />
<em>Row-oriented <code>_source</code> keeps all fields for each document in one blob — doc0 through doc5 each carry <code>host.name</code>, <code>@timestamp</code>, <code>status</code>, <code>duration_ms</code>, and more jumbled together. Column-oriented doc values restructure the same data so all <code>host.name</code> values sit in one column, all timestamps in another, all status codes in another. Compression codecs can then run on each contiguous column independently.</em></p>
<p>That columnar layout is what makes per-column compression possible. When all values of <code>http.response.status_code</code> sit in a contiguous column, Lucene can apply codecs that exploit patterns in the sequence.</p>
<p>Delta encoding stores differences between adjacent values instead of full values. GCD encoding finds a common factor and divides everything down. Run-length encoding collapses repeats. Lucene picks the codec per segment and re-evaluates when segments merge.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltcab1e02d2192a98b/6a7f09efb6b7345f6ae48cc6/numeric-codec-pipeline-bold@2x.png" alt="Numeric codec pipeline: RAW → DELTA → GCD → BIT-PACK" />
<em>Four sorted <code>@timestamps</code> from the same host, compressed in four stages. RAW: four 32-bit integers, 128 bits total. DELTA: store differences instead of full values — base stays, deltas +100, +200, +300 take 59 bits. GCD: divide out the common factor of 100, leaving 1, 2, 3 at 39 bits. BIT-PACK: pack those three small integers into contiguous bit storage, 9 bits freed.</em></p>
<p>But here's the catch: these codecs only work well when adjacent documents have correlated values. Consider the <code>@timestamp</code> column.</p>
<p>If logs arrive from dozens of hosts interleaved randomly, the timestamps in the column jump around. The delta between adjacent values might be +3 seconds, then -47 seconds, then +120 seconds. Delta encoding can't do much with that.</p>
<p>Now consider what happens if you sort by <code>host.name</code> and <code>@timestamp</code> before writing to the segment. All logs from host-A land in a contiguous run, followed by all logs from host-B, and so on. Within each host's run, the timestamps are monotonically increasing and the deltas are predictable.</p>
<p>Four timestamps from the same host might look like 1706745600, +100s, +200s, +300s. Delta encoding shrinks those to a base value plus three small integers.</p>
<p>GCD encoding finds that 100, 200, 300 are all divisible by 100 and stores 1, 2, 3 instead. Bit-packing then fits those three values into a handful of bits. The same pattern applies to fields like <code>host.name</code>, <code>service.name</code>, or <code>http.response.status_code</code>: within a sorted run, long stretches of identical values collapse to near nothing under run-length encoding.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1c5e01c48262da24/6a7f09f1de231589e4fd7afd/index-sorting-bold@2x.png" alt="Index sorting: arrival order → sorted by host.name → after RLE" />
<em>Five hosts — api-01, api-02, db-01, web-01, web-02 — scattered randomly in arrival order (left). Sorting by <code>host.name</code> groups them into five contiguous blocks of eight (center). Run-length encoding collapses each block to a single (value, count) pair — 5 pairs stored instead of 40, the remaining slots freed (right).</em></p>
<p>Elasticsearch never sorted by default. Documents landed in arrival order, compressed with DEFLATE. We left a lot on the table.</p>
<h2 id="howwegothere20122026">How we got here: 2012–2026</h2>
<p>Not all of the individual techniques in LogsDB were designed for logs. They were built over twelve years to solve different problems, and LogsDB is what happens when you stack them.</p>
<p><strong>The foundation (2012–2017).</strong> Lucene 4.0 introduced doc values in 2012. By Elasticsearch 5.0 in 2016, they were on by default for all keyword and numeric fields. Lucene 7.0 added sparse doc values, so fields that only appear in some documents don't waste space on every document in the segment. That fixed a significant force-merge bloat problem (up to 10× on sparse fields) and set up the storage model everything else depends on.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte4b2c68318492f3d/6a7f09f4227b1c2e2b5984aa/sparse-doc-values-bold@2x.png" alt="Dense vs sparse doc values encoding" />
<em>Dense encoding reserves an 8-byte slot per document regardless of presence. Sparse encoding stores only documents that have a value at 12 bytes each (value + doc ID). For <code>error_code</code> with 2 of 16 docs populated (12% fill), sparse is 81% smaller: 24 B vs 128 B. For <code>request_path</code> at 88% fill, sparse is larger: 168 B vs 128 B. Lucene picks per field; sparse wins below ~67% fill.</em></p>
<p><strong>Incremental wins (2020–2021).</strong> Two smaller changes targeted observability workloads. Dictionary-based stored fields compression deduplicated repetitive string metadata for about a 10% win.</p>
<p>The <code>match_only_text</code> field type dropped term frequencies and positions from the inverted index. Term frequencies are what BM25 uses to score documents by relevance — how often a term appears in a document relative to the rest of the corpus. For log search that signal is meaningless: you don't care whether "timeout" appeared twice or seven times in a log line, you just want to find it. Positions are similar: they're stored so Elasticsearch can do exact phrase matching, but the position data is expensive and phrase queries on logs are rare enough that the tradeoff is worth it. When you do run a phrase query on a <code>match_only_text</code> field, it still works — it just falls back to a slower path that rescores candidates rather than using stored positions directly.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc4dd1dc99cf753b0/6a7f09f76693f85f60663e1f/match-only-text-bold@2x.png" alt="text vs match_only_text inverted index storage" />
<em><code>text</code> stores each term with its frequency and every position it appears at. <code>match_only_text</code> keeps only the doc IDs — enough to find the document, nothing more. The <code>timeout</code> term appears twice in this message (positions 1 and 4), which is exactly the kind of data that gets dropped.</em></p>
<p>Dropping frequencies and positions cuts the inverted index for a text field by roughly 40%. The overall index impact in 2021 was only ~10%, which sounds like a poor return on a 40% field-level reduction. The reason is where storage was going at the time: <code>_source</code> was stored in full for every document as a raw JSON blob, doc values were uncompressed and unsorted, and nothing was using ZSTD. The <code>message</code> field's inverted index was a small slice of a much larger, poorly-compressed whole. As the next five years of work addressed those other structures, the same 40% field-level savings became a meaningful fraction of a much smaller total.</p>
<p>Neither change was decisive on its own, but they established that log-specific storage optimization was worth pursuing.</p>
<p><strong>The TSDB turning point (April 2023).</strong> This is where the story really starts. We shipped synthetic <code>_source</code> and index sorting for time series metrics in Elasticsearch 8.7.</p>
<p>Synthetic source changes the write-and-read contract. At write time, we skip storing the raw JSON blob entirely. At read time, when a query needs to return the original document, we reconstruct it by reading each field's value out of doc values and stored fields and assembling them back into JSON. The result is functionally equivalent to the original <code>_source</code> (with minor differences like field ordering), but we never stored the blob.</p>
<p>Index sorting groups documents by dimension fields and timestamp before writing to disk. Together, synthetic source and index sorting cut metrics storage by up to 70%.</p>
<p>That result told us something important: the same architecture could work for logs.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta1aaae98650a025b/6a7f09fa3cab1c48250e4740/synthetic-source-bold@2x.png" alt="Standard _source vs synthetic _source" />
<em>Without LogsDB, Elasticsearch writes every log event twice: once as a raw <code>_source</code> blob on disk, once into doc values columns. LogsDB skips the blob entirely. At read time, a <code>GET &lt;index&gt;/_doc/1</code> request gathers field values from doc values and assembles the document on the fly.</em></p>
<p><strong>The TSDB codec (2024).</strong> In 8.13 and 8.14, we built a custom doc values codec with run-length encoding optimized for sorted consecutive values, PFOR-delta encoding, and cyclic ordinal encoding for multi-valued dimensions. The numbers were striking: <code>kubernetes.pod.name</code> doc values dropped from 110 MB to 7.25 MB in one benchmark. We extended coverage to all numeric and keyword types including <code>ip</code>, <code>scaled_float</code>, and <code>unsigned_long</code>.</p>
<p><strong>LogsDB Tech Preview (August 2024).</strong> In <a href="https://github.com/elastic/elasticsearch/pull/108896">8.15</a>, we combined everything into <code>index.mode: logsdb</code>: host-first sorting, synthetic <code>_source</code>, ZSTD compression, and the TSDB numeric codecs. One decision mattered more than expected: sort order. Sorting by <code>host.name</code> first, then <code>@timestamp</code>, delivers up to ~40% storage reduction. Sorting by timestamp first gives ≤10%. The host-first ordering co-locates documents that share field values, which is exactly what the numeric codecs need.</p>
<p><strong>ZSTD and GA (November–December 2024).</strong> In <a href="https://github.com/elastic/elasticsearch/pull/112665">8.16</a>, we switched <code>best_compression</code> from DEFLATE to ZSTD permanently (level 3, blocks up to 2,048 documents or 240 kB, native bindings via Panama FFI on JDK 21+). ZSTD gave us ~12% smaller stored fields and ~14% higher indexing throughput at the same time, which almost never happens. LogsDB went GA in 8.17.</p>
<p>At GA, we claimed up to 65% storage reduction.</p>
<p><strong>Routing and recovery (April 2025).</strong> In 8.18, <a href="https://github.com/elastic/elasticsearch/pull/116687"><code>route_on_sort_fields</code></a> started routing documents to shards by sort field values instead of <code>_id</code>. Without this optimization, Elasticsearch hashes the <code>_id</code> to pick a shard, so logs from the same host scatter across all shards. With routing on sort fields, logs with similar <code>host.name</code> values land on the same shard. This co-locates similar documents at the shard level, not just within segments, adding ~20% storage reduction at a 1–4% ingest penalty. Routing on sort fields requires auto-generated <code>_id</code>.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt91e7d6426bc3bcf1/6a7f09fd9090b02c5584e8bf/shard-routing-bold@2x.png" alt="Shard routing: standard, routed, routed + sorted" />
<em>Data stream <code>.ds-logs-nginx-default-00001</code> with six hosts across three shards. STANDARD (hashed by <code>_id</code>): all host colors scattered randomly. ROUTED (<code>route_on_sort_fields</code>): same-host logs land on the same shard, but remain in arrival order within it. ROUTED + SORTED (host-first sort): each shard contains contiguous blocks of a single host — the combination that lets numeric codecs and RLE reach their full potential.</em></p>
<p>We also <a href="https://github.com/elastic/elasticsearch/pull/119110">switched peer recovery to synthetic source reconstruction</a>, eliminating the duplicate <code>_recovery_source</code> blob. In <a href="https://github.com/elastic/elasticsearch/pull/121049">9.0</a>, <code>logs-*-*</code> indices default to LogsDB.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6a8f273087631cf2/6a7f0a00ea068d7193f09d4b/recovery-source-bold@2x.png" alt="Index size written: _recovery_source eliminated" />
<em>Nightly synthetic source benchmark, December 2024. Index size written drops 39% — from ~279 GB to ~171 GB — the day peer recovery switches from copying the raw <code>_recovery_source</code> blob to reconstructing documents from doc values.</em></p>
<p><strong>Merge and recovery overhaul: 9.1 (July 2025).</strong> We fully eliminated the recovery source. Peer recovery uses batched synthetic reconstruction, cutting write I/O by ~50% and boosting median indexing throughput ~19% over the 8.17 baseline. We replaced up to four separate doc values merge passes with a single pass, cutting background merge CPU by up to 40%. And we swapped <code>_seq_no</code>'s BKD tree for Lucene doc value skippers, halving <code>_seq_no</code> storage.</p>
<p><strong>pattern_text and Failure Store: 9.2–9.3 (October 2025–February 2026).</strong> In <a href="https://github.com/elastic/elasticsearch/pull/124323">9.2</a>, we shipped <code>pattern_text</code> as a Tech Preview: a new field type that decomposes log messages into static templates and dynamic variable parts. A log line like <code>Session opened for user alice from 10.0.1.42 via TLS</code> gets split into the template <code>Session opened for user {} from {} via TLS</code> (stored once, as a template ID) and the variables <code>alice</code>, <code>10.0.1.42</code> (stored per document). For logs with high template repetition, this cuts message field storage by up to 50%. A companion <code>template_id</code> sub-field lets you sort by template, and the LogsDB setting <code>index.logsdb.default_sort_on_message_template</code> enables this automatically. <code>pattern_text</code> <a href="https://github.com/elastic/elasticsearch/pull/135370">went GA in 9.3</a>.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0621df09262d5bf7/6a7f0a03c2cc097ec224943a/pattern-text-bold@2x.png" alt="TEXT vs PATTERN_TEXT field type" />
<em>TEXT stores each log message as a full string per document — eight copies of near-identical blobs. PATTERN_TEXT decomposes them: the shared template <code>Session opened for user {} from {} via TLS</code> is stored once with ID T0, and only the variable columns (<code>user</code>, <code>ip</code>) are stored per document — alice/10.0.1.42, bob/10.0.1.87, carol/10.0.2.11, and so on.</em></p>
<p><code>pattern_text</code> does come with an indexing CPU cost: decomposing each message into template and variables takes more work at write time than storing a raw string. Whether that tradeoff makes sense depends on your dataset and your priorities.</p>
<p>If your log messages follow highly repetitive patterns (structured application logs, Kubernetes events, access logs), the storage wins are large and the CPU overhead is bounded. If your messages are free-form or low-repetition, the compression gains shrink while the CPU cost stays roughly the same.</p>
<p>For data you keep for months or years, the cumulative storage reduction usually makes it worthwhile. For high-cardinality, rapidly changing messages where storage isn't the constraint, it may not be.</p>
<p>9.3 also brought compression for binary doc values, making <code>wildcard</code> field types significantly more storage-efficient. Internally, wildcard fields store an inverted index of trigrams in a binary doc values column; that column is now compressed with Zstandard instead of being stored raw. In one benchmark, a URL field dropped from 2.92 GB to 1.12 GB, more than 60% compression. If you use <code>wildcard</code> fields heavily, the gain is automatic with no mapping changes needed.</p>
<p>Also in 9.3, skip lists for <code>@timestamp</code> and <code>host.name</code> became available as an opt-in for LogsDB. Skip lists let Elasticsearch jump ahead in a doc values column without reading every entry, which speeds up time-range queries on large segments. Other index modes have skip lists disabled by default; in LogsDB you can enable them selectively for the fields you range-query most.</p>
<p>Also in 9.3, the <a href="https://www.elastic.co/docs/manage-data/data-store/data-streams/failure-store">Failure Store</a> <a href="https://github.com/elastic/elasticsearch/pull/131261">became enabled by default</a> for <code>logs-*-*</code> data streams. Failed documents (mapping conflicts, ingest pipeline errors) now land in dedicated <code>::failures</code> indices instead of being rejected, which means LogsDB's strict synthetic source requirements are less likely to cause silent data loss during migration.</p>
<h2 id="performancenotjuststorage">Performance, not just storage</h2>
<p>LogsDB started as a storage optimization, and the early releases came with a throughput cost — sorting, synthetic source reconstruction, and ZSTD all add work at write time. Over two years of releases, we clawed that back. Indexing throughput is now on par with what users had before enabling LogsDB. You get the storage reduction without giving up the ingest rate you were used to.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7d5f55de5dbef47b/6a7f0a06bd2198e809757fa1/performance-over-time-bold@2x.png" alt="LogsDB throughput and storage on disk over time" />
<em>Throughput (teal) has climbed from ~25k to ~35k docs/s since the Tech Preview. Storage on disk (blue) has dropped from ~65 GB to ~36 GB on the same benchmark dataset. Both curves move in the right direction, driven by the same layered releases: ZSTD in 8.16, routing optimization in 8.18, the merge and recovery overhaul in 9.1. Live numbers at <a href="https://elasticsearch-benchmarks.elastic.co/#tracks/logsdb/nightly/default/90d">elasticsearch-benchmarks.elastic.co</a>.</em></p>
<p>The two trends compound each other. Less storage means fewer segments to merge, which frees CPU for indexing. Synthetic source reconstruction is cheaper to compute than it is to store and replicate the raw blob. Each release that shrank the index also reduced background I/O, which fed back into throughput.</p>
<p>The practical result: if you were running standard Elasticsearch for log ingestion two years ago, the throughput you had then is roughly what LogsDB delivers now — with a 50–75% smaller index alongside it.</p>
<h2 id="howtoenableit">How to enable it</h2>
<p>As of 9.0, <code>logs-*-*</code> data streams default to LogsDB automatically. If your data streams match that pattern, you're already using it.</p>
<blockquote>
  <p><strong>Want a hands-on walkthrough?</strong> <a href="https://www.elastic.co/observability-labs/blog/elasticsearch-logsdb-index-mode-storage-savings"><em>Cut Elasticsearch log storage costs by 76% with LogsDB</em></a> walks through creating two indices, reindexing, and measuring the difference with the <code>_stats</code> API — including version-specific enable instructions for 8.x clusters.</p>
</blockquote>
<p>For other index patterns, set it in your template:</p>
<pre><code>PUT _index_template/logs-template
{
  "index_patterns": ["logs-*"],
  "template": {
    "settings": {
      "index.mode": "logsdb"
    }
  }
}
</code></pre>
<p>Synthetic <code>_source</code> turns on automatically with <code>index.mode: logsdb</code>.</p>
<p>For the routing optimization (8.18+), add one more setting:</p>
<pre><code>PUT _index_template/logs-template
{
  "index_patterns": ["logs-*"],
  "template": {
    "settings": {
      "index.mode": "logsdb",
      "index.logsdb.route_on_sort_fields": true
    }
  }
}
</code></pre>
<p>This routes shards by sort field values instead of <code>_id</code>, adding ~20% storage reduction at a 1–4% ingestion penalty. It requires at least two sort fields beyond <code>@timestamp</code> and auto-generated <code>_id</code>.</p>
<p>Switching an existing index to LogsDB requires a reindex. So does rolling back. There's no in-place conversion, so try it on new data streams first.</p>
<p>Storage improves further as segments merge — freshly written data compresses well, but merged segments compress even better.</p>
<h2 id="whatsnext">What's next</h2>
<p>Elasticsearch still carries some structural overhead from its search engine roots. <code>_id</code> and <code>_seq_no</code> are two examples: both consume meaningful disk space (on small documents they can account for more than half the index size), but neither is essential for log analytics workloads.</p>
<p>We've already taken the first step for TSDB: <a href="https://github.com/elastic/elasticsearch/pull/144026">PR #144026</a> eliminated stored <code>_id</code> bytes from TSDB indices by reconstructing the field on the fly from doc values, the same approach synthetic <code>_source</code> uses. We're exploring the same direction for LogsDB.</p>
<p><strong>9.4 and beyond.</strong> The architecture still has room to improve, and we're on it.</p>
<p>For the full reference, see the <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/logs-data-stream.html">logs data stream documentation</a>.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/elasticsearch-logsdb-storage-evolution</link>
    <guid isPermaLink="false">elasticsearch-logsdb-storage-evolution</guid>
    <category><![CDATA[Data Management]]></category>
    <category><![CDATA[Logs Analytics]]></category>
    <dc:creator><![CDATA[Luca Wintergerst]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8dc9db2d94cde133/6a7f0a089090b01c7a84e8c5/elasticsearch-logsdb-storage-evolution.png" length="0" type="image/png"/>
    <pubDate>Thu, 09 Apr 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Troubleshooting Kafka-Logstash-Elasticsearch Performance Issues in delay-sensitive platforms]]></title>
    <description><![CDATA[Learn how to troubleshoot ingestion bottlenecks in data pipelines built with Kafka, Logstash and Elasticsearch.]]></description>
    <content:encoded><![CDATA[<p>Kafka is an open-source, distributed event streaming and queuing platform widely used with Elastic to build high-throughput, large-scale data pipelines, facilitate seamless data integration, and support mission-critical applications. System designs with Kafka significantly enable the decoupling of components within the data pipeline ensuring scalability and a robust design for failure by managing downstream back-pressure during traffic surges, maintenance activities, or any other periods of performance degradation. </p>
<p>In addition to its queuing capabilities, Kafka can serve as a central processing middleware for data pre-processing and enrichment. This is particularly useful when such operations are impractical to perform directly downstream due to specific business or technical requirements or constraints.</p>
<p>For instance, integrating Kafka with stream processing engines like <a href="https://ksqldb.io/">KsqlDB</a> or <a href="https://materialize.com/">Materialize</a>, allows for advanced stream processing tasks, including SQL-based joins across topics and streams to enrich data at scale in real-time. The enriched datasets can then be ingested into Elasticsearch for further processing at subsequent stages.</p>
<p>Despite these benefits, adopting Kafka or similar queuing systems is arguably conditional. These systems introduce additional costs and complexity to the overall platform implementation and maintenance. They may also add processing overhead, delay data flow to the downstream, and risk becoming bottlenecks if not correctly sized or optimized to align with other pipeline components.</p>
<p>This article provides guidance for troubleshooting ingestion bottlenecks in data pipelines built with Kafka and Elastic. Identifying and fixing such issues can be sometimes challenging, particularly when multiple changes are made across multiple systems aspects at the same time, which often increases the number of variables in play. This commonly results in a longer process and inconsistent results.</p>
<p>Consider the below Security Operations Center (SOC) platform, where data is ingested from various sources via Elastic Agent. The data is queued and pre-processed in a Kafka cluster before being pulled by Logstash and forwarded to Elastic Security. In this environment, delays at any stage of the pipeline can result in critical security events going undetected by Elastic Security, emphasizing the importance of a well-optimized data pipeline.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt767bdc4894e52852/6a7f0b4f96b5a670da87b3b4/image5.png" alt="" /></p>
<h2 id="implementlagandthroughputmonitoring">Implement lag and throughput monitoring</h2>
<p>Ingestion bottlenecks usually materialize as limited throughput and event lags, which often correlate. Monitoring these two indicators is important to measure the impact of tuning attempts. </p>
<p><strong><em>Tip</em></strong><em>: With the anomaly detection features of machine learning you can use the</em> <a href="https://www.elastic.co/guide/en/observability/current/inspect-log-anomalies.html"><em>Logs Anomalies page</em></a> <em>to detect and inspect log anomalies and the log partitions where the log anomalies occur.</em></p>
<p>End-to-end lag monitoring can be broken down into the various stages of the pipeline. The incremental improvements across those stages would collectively contribute to a significant reduction in the end-to-end lag:</p>
<p><strong>A) Ingest lag between the source and Kafka:</strong> This lag is the time difference between the real event-time, which is typically extracted from the event itself or added by the event producer (Elastic Agent for example), and the Kafka record timestamp, which can be added to the Logstash events via event <a href="https://www.elastic.co/guide/en/logstash/current/plugins-inputs-kafka.html#plugins-inputs-kafka-decorate_events">decoration</a> in the Kafka input plugin. </p>
<p>In most cases, this lag is influenced by the write performance of the Kafka cluster and network latency between the event source and Kafka. In some cases, the lag may also appear due to time configuration mismatches that make it look like there's a lag when there really isn't.</p>
<p><strong>B) Ingest lag between Kafka and Logstash:</strong> This lag is the time difference between the Kafka record's timestamp and the execution timestamp of the first filter in the Logstash pipeline. If your pipelines are using a persistent queue, note that this duration also includes the time spent in the PQ. </p>
<p>The below Ruby filter adds the current-time to the event in the `logstash.start` field to use for comparison later.</p>
<pre><code>ruby {
&amp;nbsp;code =&gt; "event.set(logstash.start, Time.now());"
}
</code></pre>
<p>The primary factors contributing to ingestion lag include the consumption performance of the Kafka cluster, the Logstash input performance, data skew across the different topic partitions, and most importantly, the backpressure propagation to the Logstash input plugin, because Logstash does not fetch new events from the Kafka topic as quickly as they become available, when it is busy processing the events that it has already fetched. </p>
<p>Network latency and reduced size of TCP read buffer (<a href="https://man7.org/linux/man-pages/man7/tcp.7.html">SO_RCVBUF</a>) on the Logstash host can also throttle Logstash from fetching the data from Kafka at the required rate.</p>
<p>Consumer lag serves as an effective indicator of this issue and can be viewed on Kafka's consumer group metrics. It is calculated as the difference between the log-end offset (the offset of the most recently produced message) and the current offset (the last committed offset by the consumer) for each partition.</p>
<pre><code>$KAFKA_HOME/bin/kafka-consumer-groups.sh&amp;nbsp; --bootstrap-server &lt;server:port&gt; --describe --group &lt;group_id&gt;
</code></pre>
<pre><code>GROUP &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; TOPIC &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; PARTITION&amp;nbsp; CURRENT-OFFSET&amp;nbsp; LOG-END-OFFSET &amp;nbsp; LAG
logstash-cg-soc-1 &amp;nbsp; &amp;nbsp; windows-events&amp;nbsp; &amp;nbsp; 0&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 4498&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 17309&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 12811
logstash-cg-soc-1 &amp;nbsp; &amp;nbsp; windows-events&amp;nbsp; &amp;nbsp; 1&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 4470&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 17213&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 12743
...
</code></pre>
<p><strong>C) Ingest lag in the Logstash processing:</strong> This lag is the time difference between the first and last Logstash filters. To calculate this lag, an additional filter can be added at the end of the pipeline to record the `logstash.end` timestamp in the same way the `logstash.start` field was added before. The primary factors contributing to this lag are the filters efficiency of processing, which is primarily affected by the complexity and optimization of the transformations they perform, access to external services for data loading which might require network, limited number of the <a href="https://www.elastic.co/guide/en/logstash/current/logstash-settings-file.html">pipeline’s workers and small batch size</a>, and the amount of resources available for Logstash – particularly when running on virtual environments with resources contention.</p>
<p><strong>D) Ingest lag between Logstash and Elasticsearch:</strong> This lag is the time difference between the last applied Logstash filter in the pipeline, and the timestamp when the event is ingested in Elasticsearch. The ECS field `<a href="https://www.elastic.co/guide/en/ecs/current/ecs-event.html#field-event-ingested"><code>event.ingested</code></a>` is automatically added by the Elastic integrations to record this value. For custom sources, the field should be added via an ingest pipeline:</p>
<pre><code>{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"processors": [
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"set": {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"field": "event.ingested",
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"value": "{{_ingest.timestamp}}"
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
…
</code></pre>
<p>If the data is undergoing heavy processing in Elasticsearch before indexing, it also pays to analyze the performance of each ingest processor in the pipeline to pinpoint and optimize the heaviest ones. <a href="https://github.com/elastic/integrations/pull/4597">Ingest pipelines monitoring dashboard</a> can help streamline this process.</p>
<p>The primary factors contributing to this phase’s lag are usually the Logstash output configuration like a small number of pipeline workers and batch size, slow indexing actions (like upserts), network latency, and how fast the Elasticsearch cluster can run the ingest pipelines and index the data. You can find more techniques about this last point <a href="https://www.elastic.co/docs/deploy-manage/production-guidance/optimize-performance/indexing-speed">here</a>.</p>
<p>Visualizing these stages in Kibana helps identify the most throttled areas and analyze the impact of various parameter adjustments across the entire data pipeline during the tuning process.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltda4a8b87e40cae3c/6a7f0b52bd2198173175801d/image4.png" alt="" /></p>
<h2 id="isolateandfixthebottleneck">Isolate and fix the bottleneck</h2>
<p>Identifying the source of the bottleneck can be challenging without a systematic approach to isolating the behavior of each component and stage of the pipeline. To make the investigation approach more consistent, it is important to keep the source data consistent as well. One approach can be to use a dedicated topic with a replicated production workload, and repeat the test using different consumer groups.</p>
<p>Below is a set of benchmarks that can be driven while monitoring the event lag and the pipeline throughput. The best achieved results from each of the tuning exercises can be used as a basis for the next one.</p>
<h2 id="firstbenchmarkkafkainputnofiltersnulloutput">First benchmark: Kafka input, no filters, null output</h2>
<p>This benchmark is aimed at assessing the throughput of the Kafka input in isolation, excluding the downstream impacts of the Logstash filters and outputs. Use the <a href="https://www.elastic.co/guide/en/logstash/current/plugins-outputs-sink.html">sink</a> plugin in the output section to discard the events without incurring IO overhead and get a theoretical maximum reading speed.</p>
<p>This test is better performed with and without a <a href="https://www.elastic.co/guide/en/logstash/current/persistent-queues.html#persistent-queues-architecture">persistence queue</a> to isolate the additional overhead at this stage. </p>
<p>It is helpful to use a unique consumer group_id for this test instead of the default `logstash`. Otherwise, this null-output pipeline might consume and drop events that should be processed by other pipelines.</p>
<pre><code>input {
&amp;nbsp;kafka {
&amp;nbsp;&amp;nbsp;&amp;nbsp;...
&amp;nbsp;}
}
filter {
}
output {
&amp;nbsp;&amp;nbsp;sink { }
}
</code></pre>
<p>If the throughput from this test closely matches the original pipeline, then most probably you have a closed valve upstream and consuming the events is definitely a bottleneck. </p>
<p>Note that the maximum throughput is significantly impacted by the Kafka cluster's ability to handle consumer requests and network latency. The maximum throughput is also bound by the rate of events that is flowing into the Kafka topic once the consumer group has caught up with the topic.</p>
<p>A few things might be considered in this exercise: </p>
<ul>
<li><p><strong>Match consumers count to partitions count:</strong> Ideally, the total number of <a href="https://www.elastic.co/guide/en/logstash/current/plugins-inputs-kafka.html#plugins-inputs-kafka-consumer_threads">consumer threads</a> across all the pipelines that share the same consumer group_id, should be equal to the number of topic partitions for a perfect balance. Each Kafka topic-partition can be assigned to at-most one consumer within a consumer group at a time. So if you have more consumer threads than your topic partitions, some of those threads will not be assigned a partition. Partition-replicas do not count, as consumer threads consume messages from the leader partitions, not directly from replicas. Exceeding 1:1 ratio may also introduce unnecessary computational overhead in Logstash without any gains in read throughput. Incrementally increasing the partition count in the topic can potentially improve the throughput. Kafka 4.0 introduces early access to <a href="https://cwiki.apache.org/confluence/display/KAFKA/KIP-932%3A+Queues+for+Kafka">KIP932</a>, which bypasses this 1:1 mapping requirement using share groups implementing a queuing semantic to the consumption model. The Share Groups are not supported in Logstash yet.</p></li>
<li><p><strong>Tune the input parameters for maximum throughput:</strong> Increasing <code>max.poll.records</code>, <code>fetch.max.bytes</code>, and <code>receive.buffer.bytes</code> can enhance performance. The TCP read buffer size is rarely an issue but can also be significantly important.  This setting is bound by the <code>net.core.rmem_max</code> value.</p></li>
<li><p><strong>Use fast disks with enough space if using persistent queues:</strong> The queue sits between the input and filter stages in the same process. The I/O performance of the storage directly impacts the input throughput. When the queue is full, Logstash puts back pressure on the inputs to stall the data flow.</p></li>
</ul>
<h2 id="secondbenchmarkkafkainputfiltersnooutputs">Second benchmark: Kafka input, filters, no outputs</h2>
<p>This benchmark helps measure the impact of the filters on the input throughput using the best achieved input configuration from the first exercise. It quantifies the throttling effect on the input stream only caused by the events processing. Note that <a href="https://www.elastic.co/guide/en/logstash/current/lookup-enrichment.html">some filter plugins</a> are also IO-bound, like the plugins that use the network to enrich the events.</p>
<pre><code>input {
&amp;nbsp;kafka {
&amp;nbsp;&amp;nbsp;&amp;nbsp;...
&amp;nbsp;}
}
filter {
...
}
output {
}
</code></pre>
<p>To increase the number of simultaneously processed events by the filters, try increasing the number of pipeline workers and the pipeline batch size, particularly if the pipeline <code>worker\_utilization</code> <a href="https://www.elastic.co/guide/en/logstash/current/node-stats-api.html#plugin-flow-rates">flow metric</a> is near 100 and Logstash is not spending all available CPU. Increasing the workers number <a href="https://www.elastic.co/guide/en/logstash/current/tuning-logstash.html">past the number of available processors</a> can also yield better results as some of the filter plugins may spend significant time in an I/O wait state like external lookups. </p>
<p>Increasing the number of workers <a href="https://www.elastic.co/guide/en/logstash/current/tuning-logstash.html">past the number of available processors</a> can also improve performance, as some filter plugins may spend considerable time in an I/O wait state, such as during external lookups. This also makes a more efficient use of the Logstash host resources.</p>
<p>Optimizing the pipeline filters is the most effective approach to resolving this bottleneck. It can significantly reduce latency and increase the throughput regardless of the pipeline input configuration and Logstash resources. The per-plugin <code>worker_utilization</code> and <code>worker_millis_per_event</code> <a href="https://www.elastic.co/guide/en/logstash/current/node-stats-api.html#plugin-flow-rates">flow metrics</a> are very useful in identifying where most of the resources are being spent, and consequently, where these improvements should focus first.</p>
<p>Optimizing pipeline filters is the most effective way to address this bottleneck. it can significantly reduce latency and boost throughput, regardless of the pipeline's input configuration or available resources. The per-plugin <code>worker_utilization</code> and <code>worker_millis_per_event</code> flow metrics are useful for finding which plugins are spending the most resources, and the optimization efforts should focus on those plugins first. Some general best practices that can usually make improvements are utilizing <a href="https://www.elastic.co/blog/do-you-grok-grok">anchors</a> for Grok plugins, switching to faster plugins like <a href="https://www.elastic.co/blog/logstash-dude-wheres-my-chainsaw-i-need-to-dissect-my-logs">dissect</a> whenever possible, optimizing Ruby filters code, eliminating unnecessary parsing, and improving the network-based enrichments. </p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6698df0f6143f2cc/6a7f0b55e3a21979d799f3f0/image3.png" alt="" />
<em>Source: <a href="https://www.elastic.co/blog/do-you-grok-grok">do you grok</a></em></p>
<p>In some cases, optimizing the pipeline may require a complete redesign of the ingestion workflow or the pipeline itself!</p>
<h2 id="thirdbenchmarkkafkainputnofilterselasticsearchoutput">Third benchmark: Kafka input, no filters, Elasticsearch output</h2>
<p>This benchmark helps quantify the throttling effect of the Elasticsearch output on the input throughput. The test can be divided into two phases: the first phase uses raw logs to isolate the impact of Elasticsearch indexing, while the second phase assesses the impact of ingest pipelines.</p>
<p><em>In case a pipeline is using multiple outputs, note that,</em> <a href="https://www.elastic.co/guide/en/logstash/current/pipeline-to-pipeline.html#output-isolator-pattern"><em>by default</em></a><em>, a pipeline is blocked if any single output is blocked. This behavior is important in guaranteeing at-least-once delivery of data, but can cause the outputs to perform at the rate of the most clogged one.</em></p>
<pre><code>input {
&amp;nbsp;kafka {
&amp;nbsp;&amp;nbsp;&amp;nbsp;...
&amp;nbsp;}
}
filter {
}
output {
&amp;nbsp;Elasticsearch {
&amp;nbsp;&amp;nbsp;&amp;nbsp;...
&amp;nbsp;}
}
</code></pre>
<p>To increase throughput, consider progressively increasing the number of pipeline workers and the pipeline batch size. Prior guidance about the <code>worker\_utilization</code> flow metric applies here too although availability of CPU plays a smaller role since this output is mostly IO-bound.  Also keep looking for the Elasticsearch Output's rejection rates (e.g.: response code 429 `es_rejected_execution_exception` indicating explicit back-pressure) as a signal that the Elasticsearch cluster is busy processing other batches.</p>
<p>The Logstash output tries to send batches of events to the Elasticsearch Bulk API in a single request. However, if a batch exceeds 20 MB, the plugin splits it into multiple bulk requests. </p>
<p>If the Elasticsearch cluster is behind a proxy or API gateway, it's important to adjust the proxy limits to allow Logstash requests with large payloads to pass through to the Elasticsearch cluster. By default, most proxy servers have a much smaller maximum size for HTTP request payloads, which should be tuned in this case to accommodate larger requests. To identify potential issues, look for error code 413 in your proxy logs, as this indicates that the size of the Logstash request has exceeded the maximum payload size the proxy is configured to handle.</p>
<p>On the Elasticsearch cluster, tune your ingest pipelines efficiency following the same general best practices discussed above for the Logstash pipelines. Also, <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/tune-for-indexing-speed.html">tune for the indexing speed</a> by using faster hardware, less index refreshes, auto-generated IDs, and consider increasing the number of primary shards to enhance indexing parallelism if you have multiple nodes. Beware that excessively increasing this number can negatively impact the search performance.</p>
<p>Finally, keep in mind that the Elasticsearch output plugin is mostly IO-bound, which means that your network latency and bandwidth significantly reduce the rate at which data is transferred and hence your output throughput.</p>
<h2 id="reassembleyourpipeline">Reassemble your pipeline</h2>
<p>After tuning the pipeline in each of the previous phases separately, put all the parts together again to assess the real throughput and latency of the reassembled pipeline. At this last step, you should have reached the best performance from your Logstash host as well, and you can progressively add more instances to reach the ultimate latency and throughput you are aiming for for a specific topic or data source.  </p>
<h2 id="example">Example</h2>
<p>Below is an example of the configuration required on Logstash and Elasticsearch to implement the architecture above.</p>
<p>Logstash pipeline:</p>
<pre><code>input {
 kafka {
   bootstrap_servers =&gt; "&lt;server&gt;:&lt;port&gt;"
   topics =&gt; ["&lt;topic-id&gt;"]
   group_id =&gt; "&lt;consumer-group-id&gt;"
   decorate_events =&gt; "extended"
   auto_offset_reset =&gt; "earliest"
   codec =&gt; json {
   }
 }
}


filter {
 ruby {
   code =&gt; "event.set('[logstash][start]', Time.now());"
 }


 mutate {
   add_field =&gt; {
     "[kafka][timestamp]" =&gt; "%{[@metadata][kafka][timestamp]}"
     "[kafka][offset]" =&gt; "%{[@metadata][kafka][offset]}"
     "[kafka][consumer_group]" =&gt; "%{[@metadata][kafka][consumer_group]}"
     "[kafka][topic]" =&gt; "%{[@metadata][kafka][topic]}"
   }
 }


 date {
   match =&gt; ["[kafka][timestamp]", "UNIX", "UNIX_MS"]
   target =&gt; "[kafka][timestamp]"
 }
 ...
 ruby {
   code =&gt; "event.set('[logstash][end]', Time.now());"
 }
}


output {
 elasticsearch {
   hosts =&gt; "hosts"
   api_key =&gt; "api_key"
   data_stream =&gt; true
   ssl =&gt; true
 }
}
</code></pre>
<p>Create an ingest pipeline for lag calculation. Note that when using Elastic integrations, the ECS fields: \ <code>\*.end\</code>, \ <code>\*.start\</code>, \ <code>\*.timestamp\</code> are automatically mapped as a date.</p>
<pre><code>PUT _ingest/pipeline/calculate_ingest_lag
{
&amp;nbsp;&amp;nbsp;"processors": [
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"set": {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"field": "event.ingested",
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"value": "{{_ingest.timestamp}}",
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"ignore_failure": true
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"script": {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"lang": "painless",
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"if": "ctx['@timestamp'] != null &amp;&amp; ctx?.kafka?.timestamp != null &amp;&amp; ctx?.logstash?.start != null &amp;&amp; ctx?.logstash?.end != null &amp;&amp; ctx?.event?.ingested != null",
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"source": """&amp;nbsp;
&amp;nbsp;&amp;nbsp;ctx.lag_in_millis = [:];
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ctx.lag_in_millis.src_kfk = Duration.between(ZonedDateTime.parse(ctx['@timestamp']), ZonedDateTime.parse(ctx['kafka']['timestamp'])).toMillis();&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ctx.lag_in_millis.kfk_ls = Duration.between(ZonedDateTime.parse(ctx['kafka']['timestamp']), ZonedDateTime.parse(ctx['logstash']['start'])).toMillis();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ctx.lag_in_millis.within_ls&amp;nbsp; = Duration.between(ZonedDateTime.parse(ctx['logstash']['start']), ZonedDateTime.parse(ctx['logstash']['end'])).toMillis();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ctx.lag_in_millis.ls_es = Duration.between(ZonedDateTime.parse(ctx['logstash']['end']), ZonedDateTime.parse(ctx['event']['ingested'])).toMillis();&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ctx.lag_in_millis.end_end = Duration.between(ZonedDateTime.parse(ctx['@timestamp']), ZonedDateTime.parse(ctx['event']['ingested'])).toMillis();&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"""
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;]
}
</code></pre>
<p>Use the pipeline to add the lag calculation to your Elastic integrations</p>
<pre><code>PUT _ingest/pipeline/logs-system.integration@custom
{
&amp;nbsp;&amp;nbsp;"processors": [
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"pipeline": {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"name": "calculate_ingest_lag",
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"ignore_missing_pipeline": true,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"description": "add ingest lag calculation to elastic_agent integration"
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;]
}
</code></pre>
<h2 id="kibanadashboardandalerts">Kibana Dashboard and Alerts</h2>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt32a85240afbaa0de/6a7f0b58bdcff0fcc6c42d71/image6.png" alt="" />
<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0299175ac906a036/6a7f0b5b9090b024f584e935/image2.png" alt="" /></p>
<p>Using the metrics mentioned above along with the <a href="https://www.elastic.co/guide/en/observability/current/inspect-log-anomalies.html">Log Rate ML job</a>, you can set up <a href="https://www.elastic.co/guide/en/kibana/current/rule-types.html#observability-rules">Kibana alerts</a> to trigger when with anomalous changes in throughput or delays or simply when delays exceed defined thresholds.</p>
<h2 id="timetotryitout">Time to try it out</h2>
<p>Start your <a href="https://cloud.elastic.co/registration?elektra=whats-new-elastic-7-14-blog">free 14-day trial of Elastic Cloud</a> to experience the latest version of <a href="https://www.elastic.co/security">Elastic</a>. Also, make sure to take advantage of the Elastic threat detection <a href="https://www.elastic.co/training/elastic-security-quick-start">training</a> to set yourself up for success.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/kafka-logstash-elasticsearch-performance-issues</link>
    <guid isPermaLink="false">kafka-logstash-elasticsearch-performance-issues</guid>
    <category><![CDATA[Logs Analytics]]></category>
    <dc:creator><![CDATA[Abdelwahhab Satta]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte718ababc31a4780/6a7f0b5eea068d3a37f09dc5/cover-resized.png" length="0" type="image/png"/>
    <pubDate>Tue, 10 Mar 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Log Processing UX Design in Elastic Streams]]></title>
    <description><![CDATA[Explore log processing in Elastic Streams and the design decisions behind the Processing UX that make log data more accessible, consistent, and actionable.]]></description>
    <content:encoded><![CDATA[<p>This post is written from the perspective of the Elastic Observability design team. It’s aimed at developers and SREs who work with logs and ingest pipelines, and it explains how design decisions shaped the Processing experience in Streams.</p>
<h2 id="thedesignprobleminlogprocessing">The Design Problem in Log Processing</h2>
<p>We rarely talk about how projects actually begin. </p>
<p>How do you design something that doesn't fully exist yet?</p>
<p>How do you align AI capabilities, system constraints, real user pains into one coherent experience?</p>
<p><a href="https://www.elastic.co/elasticsearch/streams">Streams</a> gave us that challenge.</p>
<p>Logs are one of the richest signals in observability - but also one of the messiest. Streams is an agentic AI-powered solution that rethinks how teams work with logs to enable fast incident investigation and resolution. </p>
<p><em>Streams uses AI to partition and parse raw logs, extract relevant fields, reduce schema management overhead, and surface significant events like critical errors and anomalies.</em></p>
<p>This led us to make logs investigation-ready from the start, and not force the Site Reliability Engineer to fight their data. But in order to enable such experience, we had to carefully rethink a core concept and step in the process - Processing.</p>
<h2 id="designingprocessinguxinelasticstreams">Designing Processing UX in Elastic Streams</h2>
<p>Logs are powerful, but only if they are structured correctly. Today, a user would onboard logs via Elastic Agent, using a custom integration, extract something as simple as an IP field by:</p>
<ul>
<li>Write GROK patterns</li>
<li>Create pipelines</li>
<li>Manage mappings</li>
<li>Test transformation</li>
<li>Iterate repeatedly</li>
</ul>
<p>What sounds simple requires 20+ steps — and deep expertise most teams shouldn’t need. Our goal became simple: make this dramatically simpler.</p>
<p>Our early design question was:</p>
<p><em>“ Can we reduce this experience to 2 meaningful steps instead of 20 technical ones?”</em></p>
<p>That question shaped how we approached the Stream UX.</p>
<h3 id="thefoundation">The Foundation</h3>
<p>Before we jumped into designing the UI in <a href="https://www.elastic.co/kibana">Kibana</a>, we defined a core mental model. </p>
<p>A <a href="https://www.elastic.co/elasticsearch/streams">Stream</a> is a collection of documents stored together that share:</p>
<ul>
<li>Retention</li>
<li>Configuration</li>
<li>Mappings</li>
<li>Processing rules</li>
<li>Lifecycle behaviour</li>
</ul>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2c1b9617fa7aef90/6a7f0516e02fac46215d623c/1.png" alt="stream-architecture" /></p>
<p>The key design principle:</p>
<p><em>“A Stream should contain data that behaves consistently.”</em></p>
<h3 id="whydoesdataconsistencymatter">Why Does Data Consistency Matter?</h3>
<p>We started with an example to test our thinking. Take Nginx access and error logs.</p>
<p>Access logs describe request/response events:</p>
<p><code>192.168.1.10 - - [16/Feb/2026:12:32:10 +0000] "GET /api/orders/123 HTTP/1.1" 200 532 "-" "Mozilla/5.0"</code></p>
<p>Error logs describe diagnostic events:</p>
<p><code>2026/02/16 12:32:10 [error] 2719#2719: *342 connect() failed (111: Connection refused) while connecting to upstream…</code></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb9bf5665e5b35e45/6a7f051a4c4bfb948dccd1f3/2.png" alt="log-example" /></p>
<p>If both live in the same Streams that might cause:</p>
<ul>
<li>Processing logic conflicts</li>
<li>Field divergence</li>
<li>Mapping conflicts</li>
<li>Investigations would be fundamentally harder</li>
</ul>
<p>That insight clarified something critical: </p>
<p><strong>“<em>Processing isn’t just about extracting fields. It’s about protecting consistency.”</em></strong></p>
<h3 id="makingcomplexitymanageable">Making Complexity Manageable</h3>
<p>The ingest ecosystem isn’t small, simple, or hypothetical. Real pipelines use dozens of processors — from common ones like <code>rename</code>, <code>set</code>, <code>convert</code>, and <code>append</code>, to niche types like <code>urldecode</code> and <code>network_direction</code>.</p>
<p>The UI had to support both high-frequency actions and long-tail edge cases without losing structure. Currently Elasticsearch supports over <a href="https://www.elastic.co/docs/reference/enrich-processor">40 different ingest processors</a>. We had to make sure our interface could handle the different types.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9af543723a2386c6/6a7f051ceab5be95ae20a361/3.png" alt="card-sample" /></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc839e078005877df/6a7f051f6c6eac6928f13dbb/4.png" alt="processor-panel" /></p>
<p>We introduced a clear, nested structure for pipeline steps. Users could create, reorder, edit, or remove individual steps or grouped ones with confidence. The <a href="https://eui.elastic.co/docs/patterns/nested-drag-and-drop/">nested drag and drop</a> capability was also added as a pattern in our EUI library.</p>
<p>This gave us the context and foundation to work on integrating those concepts into a model that would be definitive for everything in Streams.</p>
<h3 id="pagearchetypes">Page Archetypes</h3>
<p>Processing is powerful - and risky. Changing a parsing condition or step might affect:</p>
<ul>
<li>Field availability</li>
<li>Search behaviour</li>
<li>Alerts</li>
<li>AI Insights</li>
<li>Investigations</li>
</ul>
<p>So we asked ourselves how do we make something so powerful and important, safe for the user? The answer led to a core page archetype:</p>
<p><strong>Create &gt; Preview &gt; Confirm</strong></p>
<p>This wasn’t a UI pattern added later. It emerged directly from our concept work and understanding what users would have to deal with.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltbc3968323b69290f/6a7f05233ce8e2bff3cf5074/5.png" alt="create-preview-confirm" /></p>
<p>To support this archetype and core idea, we also introduced a split-screen structure.</p>
<p><strong>Left: Build</strong></p>
<p>This is where users would:</p>
<ul>
<li>Add processing steps</li>
<li>Define conditions</li>
<li>Apply rules</li>
<li>Leverage AI suggestions both as a whole pipeline creation or individual steps like a GROK processor</li>
</ul>
<p>It remained focused, intentional and structured.</p>
<p><strong>Right Preview</strong></p>
<p>This is where users would:</p>
<ul>
<li>See real life log samples</li>
<li>See extracted fields in context</li>
<li>Immediate feedback on changes, with insights about the matched and unmatched percentage of documents</li>
<li>Optional drilldown side panel on the right</li>
</ul>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltbb82d118560c0262/6a7f0525b43770e6984d6958/6.png" alt="split-screen-application" /></p>
<p>The preview panel became the anchor of confidence. This was not about visual symmetry, but to reinforce experimentation, control over errors and decrease the level of mistakes. Knowing that users might want to switch their focus from interaction to detailed preview, we introduced the resizeable function to both panels, and unlocked more flexiblity and control over the use cases.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltfd3619d6e050b482/6a7f0529ea068d89fdf09b40/7.png" alt="stream-architecture" /></p>
<h3 id="aiautomation">AI Automation</h3>
<p>Streams is agentic and AI powered. That added another layer of complexity for the design, but also another opportunity to unlock even more power and insights from users' log data. </p>
<p>AI introduced a new tension: how do you accelerate processing without turning it into a black box?</p>
<p>We established a few guardrails:</p>
<ul>
<li>Clear, concise suggestions</li>
<li>Visible impact through matched document metrics</li>
<li>Inspectability</li>
<li>Alignment with the Create → Preview → Confirm model</li>
</ul>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltbf93b85be623a58e/6a7f052c2f00b217abefe83a/8.png" alt="ai-in-split-screen-model" /></p>
<p>Processing UX became the bridge between automation and human in the loop. Log data is one of the most powerful investigation signals. Every design decision reinforced that belief.</p>
<h2 id="whatwelearned">What We Learned</h2>
<p>Designing for the future does not start with screens. It starts with:</p>
<ul>
<li>Edge case testing</li>
<li>Clear mental models</li>
<li>Strong and guiding principles</li>
<li>Behavioral consistency</li>
<li>Scalable and stress-tested archetypes</li>
</ul>
<p>We know that in order for a user to be able unlock insightful discoveries from their logs, they would need to process and manage their data effectively. We knew we were shaping their entire observability foundation. </p>
<p>Processing is about trust, control, and scalable data management.</p>
<p>Trust enables investigation speed.</p>
<p>Investigation speed enables resilience.</p>
<h2 id="learnmore">Learn more</h2>
<p>Sign up for an Elastic trial at <a href="http://cloud.elastic.co">cloud.elastic.co</a>, and trial Elastic's Serverless offering which will allow you to play with all of the Streams functionality.
You want to know more about Streams? Check some of the links below:</p>
<p><em>Read about</em> <a href="https://www.elastic.co/observability-labs/blog/reimagine-observability-elastic-streams"><em>Reimagining streams</em></a></p>
<p><em>Read about</em> <a href="https://www.elastic.co/observability-labs/blog/simplifying-retention-management-with-streams"><em>Retention management</em></a></p>
<p><em>Look at the</em> <a href="http://elastic.co/elasticsearch/streams"><em>Streams website</em></a></p>
<p><em>Check the</em> <a href="https://www.elastic.co/docs/solutions/observability/streams/streams"><em>Streams documentation</em></a></p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/designing-log-processing-ux-for-streams</link>
    <guid isPermaLink="false">designing-log-processing-ux-for-streams</guid>
    <category><![CDATA[Logs Analytics]]></category>
    <dc:creator><![CDATA[Boris Kirov,Patri Pascual]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltde71b4abf5418097/6a7f05305967e5df565dcf47/11.png" length="0" type="image/png"/>
    <pubDate>Tue, 03 Mar 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Windows Event Log Monitoring with OpenTelemetry & Elastic Streams]]></title>
    <description><![CDATA[Learn how to enhance Windows Event Log monitoring with OpenTelemetry for standardized ingestion and Elastic Streams for smart partitioning and analysis.]]></description>
    <content:encoded><![CDATA[<p>For system administrators and SREs, Windows Event Logs are both a goldmine and a graveyard. They contain the critical data needed to diagnose the root cause of a server crash or a security breach, but they are often buried under gigabytes of noise. Traditionally, extracting value from these logs required brittle regex parsers, manual rule creation, and a significant amount of human intuition.</p>
<p>However, the landscape of log management is shifting. By combining the industry-standard ingestion of OpenTelemetry (OTel) with the AI-driven capabilities of Elastic Streams, we can change how we monitor Windows infrastructure. This approach isn't just moving data. We are also using Large Language Models (LLMs) to understand it.</p>
<h2 id="thechallengewithtraditionalwindowslogging">The Challenge with Traditional Windows Logging</h2>
<p>Windows generates a massive variety of logs: System, Security, Application, Setup, and Forwarded Events. Within those categories, you have thousands of Event IDs. Historically, getting this data into an observability platform involved installing proprietary agents and configuring complex pipelines to strip out the XML headers and format the messages.</p>
<p>Once the data was ingested, we can try to figure out what "bad" looked like. You had to know in advance that Event ID 7031 indicated a service crash, and then write a specific alert for it. If you missed a specific Event ID or if the format changed, your monitoring went dark.</p>
<h2 id="step1ingestionviaopentelemetry">Step 1: Ingestion via OpenTelemetry</h2>
<p>The first step in modernizing this workflow is adopting OpenTelemetry. The OTel collector has matured significantly and now offers robust support for Windows environments. By installing the collector directly on Windows servers, you can configure receivers to tap into the event log subsystems.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt13a67d803cdc46b4/6a7f1cabb6b734b8c7e49216/otel-config.png" alt="OTel collector configuration for Windows Event Logs" /></p>
<p>The beauty of this approach is standardization. You aren't locked into a vendor-specific shipping agent. The OTel collector acts as a universal router, grabbing the logs and sending them to your observability backend in this case, the Elastic logs index designed to handle high-throughput streams.</p>
<p>The key thing to pay attention to in this configuration is how we add this transform statement:</p>
<pre><code>transform/logs-streams:
  log_statements:
    - context: resource
      statements:
        - set(attributes["elasticsearch.index"], "logs")
</code></pre>
<p>This works with the vanilla opentelemetry collector and when the data arrives in Elastic, it tells Elastic to use the new wired streams feature which enables all the downstream AI features we discuss in later steps.</p>
<p>Checkout my example configuration <a href="https://github.com/davidgeorgehope/otel-collector-windows/blob/main/config.yaml">here</a></p>
<h2 id="step2aidrivenpartitioning">Step 2: AI-Driven Partitioning</h2>
<p>Once the data arrives, the next challenge is organization. Dumping all Windows logs into a single <code>logs-*</code> index is a recipe for slow queries and confusion. In the past, we split indices based on hardcoded fields. Now, we can use AI to "fingerprint" the data.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4de0a4697a52249b/6a7f1cafea068d714cf0a330/ai-partitioning.png" alt="AI-driven partitioning of Windows logs" /></p>
<p>This process involves analyzing the incoming stream to identify patterns. The system looks at the structure and content of the logs to determine their origin. For example, it can distinguish between a <code>Windows Security Audit</code> log and a <code>Service Control Manager</code> log purely based on the data shape.</p>
<p>The result is automatic partitioning. The system creates separate, optimized "buckets" or streams for each data type. You get a clean separation of concerns, Security logs go to one stream, File Manager logs to another, without having to write a single conditional routing rule. This partitioning is crucial for performance and for the next phase of the process: analysis.</p>
<h2 id="step3significanteventsandllmanalysis">Step 3: Significant Events and LLM Analysis</h2>
<p>Once your data is partitioned (e.g., into a dedicated <code>Service Control Manager</code> stream), you can apply GenAI models to analyze the semantic meaning of that stream.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt32645d1a432e8bfb/6a7f1cb3bdcff01f02c4331b/llm-analysis.png" alt="LLM analysis of log streams" /></p>
<p>In a traditional setup, the system sees text strings. In an AI-driven setup, the system understands context. When an LLM analyzes the <code>Service Control Manager</code> stream, it identifies what that system is responsible for. It knows that this specific component manages the starting and stopping of system services.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf18fd4bedf526eb5/6a7f1cb6e3a219399f99f902/significant-events-suggestions.png" alt="Significant events suggestions from AI" /></p>
<p>Because the model understands the <em>purpose</em> of the log stream, it can generate suggestions for what constitutes a "Significant Event." It doesn't need you to tell it to look for crashes; it knows that for a Service Manager, a crash is a critical failure.</p>
<h3 id="frompassivestoragetoproactivesuggestions">From Passive Storage to Proactive Suggestions</h3>
<p>The workflow effectively automates the creation of detection rules. The LLM scans the logs and generates a list of potential problems relevant to that specific dataset, such as:</p>
<ul>
<li><strong>Service Crashes:</strong> High severity anomalies where background processes terminate unexpectedly.</li>
<li><strong>Startup/Boot Failures:</strong> Critical errors preventing the OS from reaching a stable state.</li>
<li><strong>Permission Denials:</strong> Security-relevant events regarding service interactions.</li>
</ul>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1ec41d68fba309eb/6a7f1cba63e9593c6073e2bb/significant-events-list.png" alt="List of significant events detected" /></p>
<p>It bubbles these up as suggested observations. You can review a list of potential issues, see the severity the AI has assigned to them (e.g., Critical, Warning), and with a single click, generate the query required to find those logs.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt41d653eef46a3c18/6a7f1cbd9090b0c7ab84ee8b/query-generation.png" alt="Auto-generated query for significant events" /></p>
<h2 id="conclusion">Conclusion</h2>
<p>The combination of OpenTelemetry for standardized ingestion and AI-driven Streams for analysis turns the chaotic flood of Windows logs into a structured, actionable intelligence source. We are moving away from the era of "log everything, look at nothing" to an era where our tools understand our infrastructure as well as we do.</p>
<p>The barrier to effective monitoring is no longer technical complexity. Whether you are tracking security audits or debugging boot loops, leveraging LLMs to partition and analyze your streams is the new standard for observability.</p>
<p><a href="https://cloud.elastic.co/serverless-registration?onboarding_token=observability">Try Streams today</a></p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/windows-event-monitoring-with-opentelemetry-and-elastic-streams</link>
    <guid isPermaLink="false">windows-event-monitoring-with-opentelemetry-and-elastic-streams</guid>
    <category><![CDATA[Logs Analytics]]></category>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[LLM Observability]]></category>
    <dc:creator><![CDATA[David Hope]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4de0a4697a52249b/6a7f1cafea068d714cf0a330/ai-partitioning.png" length="0" type="image/png"/>
    <pubDate>Thu, 05 Feb 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[AIOps with Elastic Observability: Modern AIOps & Log Intelligence]]></title>
    <description><![CDATA[Exploring modern AIOps capabilities, including anomaly detection, log intelligence, and log analysis &amp; categorization with Elastic Observability.]]></description>
    <content:encoded><![CDATA[<h2 id="aiopsblogrefresherunlockingintelligencefromyourlogswithelastic">AIOps Blog Refresher: Unlocking Intelligence from Your Logs with Elastic</h2>
<p>Elastic has been leading the charge with AIOps, especially in the recent 9.2 update of Elastic Observability with Streams. The conversation around AIOps has shifted dramatically as we move through the year. DevOps and SRE teams aren't asking whether they need AIOps, they're asking how to leverage it more effectively to stay ahead of exponentially growing complexity.</p>
<p>The current challenge of AIOps is that modern cloud-native environments generate massive volumes of telemetry data that are magnitudes larger than past environments. But here's what many teams overlook: logs are the richest source of operational intelligence you have. Logs are able to tell you exactly what happened and why, while metrics only tell you something is wrong, and traces only tell you where. The problem is that most organizations are drowning in logs. Microservices, such as user authentications or inventories, serverless functions, and Kubernetes generate millions of log entries daily. Without AI and machine learning, finding meaningful patterns in this data takes too much time and energy.</p>
<h2 id="logintelligenceimprovementwhatsnewin2025">Log Intelligence Improvement: What's New in 2025</h2>
<p>Historically in observability, unlocking your log intelligence included long manual effort that required not only parsing through logs, but also structuring those logs. Elastic Observability has drastically changed how teams extract value from logs. Observability is not just simple signal analysis - modern tools need to have proactive, log-driven investigations. At Elastic, this modernity is Streams.</p>
<p>Streams, a new release from Elastic, is a collection of AI-driven tools that identify significant events in parsed raw logs by enriching logs with meaningful fields. With Streams, SREs can maximize the value of their data, their logs, and their systems. With system reliability as the goal, Streams helps to reduce pipeline management overhead and accelerates observability analysis. And it takes nearly no time to set up!</p>
<p>Here is how Streams powers the Elastic Observability capabilities available now.</p>
<h3 id="advancedlograteanalysis">Advanced Log Rate Analysis</h3>
<p>Log rate analysis can go far beyond only detecting spikes. Elastic's machine learning automatically identifies when log volumes deviate from expected baselines, then contextualizes these changes within your broader system performance. When your application suddenly generates more error logs, Elastic’s AIOps doesn't just alert you, it also determines whether it's a critical issue requiring immediate attention or just a temporary anomaly.</p>
<p>This matters to your analysis because not all log spikes are equal. A 10x increase in DEBUG logs might indicate verbose logging accidentally enabled in production. A 2x increase in ERROR logs could signal a cascading failure. Log rate analysis distinguishes between these scenarios automatically, giving your team the context needed to respond appropriately.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5bb4ac6d272c3925/6a7f0dc4eab5be0e4020a739/log-analysis.png" alt="Log Analysis" /></p>
<h3 id="intelligentlogcategorizationwithstreams">Intelligent Log Categorization with Streams</h3>
<p>This is where AIOps shines with log data. Streams uses machine learning algorithms in order to automatically classify and group similar log patterns, dramatically reducing noise. Instead of manually parsing millions of entries, the system identifies common structures, groups related events, and surfaces the categories that matter most.</p>
<p>Logs are unstructured by nature, making them difficult to analyze at scale. Streams corrals chaotic log streams into organized, queryable patterns. Instantly, you can see that 80% of your errors fall into three categories, helping you prioritize where to focus remediation efforts. This approach helps you reduce noise and accelerate analysis, allowing teams to act on insights faster.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt43de44a2668aba4d/6a7f0dc7e02fac4c835d65dc/categories.png" alt="Log Categorizations" /></p>
<h3 id="multidimensionalanomalydetection">Multi-Dimensional Anomaly Detection</h3>
<p><a href="https://www.elastic.co/docs/explore-analyze/machine-learning/anomaly-detection">Anomaly detection</a> now simultaneously examines relationships between logs, metrics, and traces. A slight increase in response time might not trigger an alert by itself, but when correlated with unusual log patterns and memory consumption changes, the system recognizes it as an early warning sign.</p>
<p>Logs contain a myriad of contextual information that metrics and traces can't capture: stack traces, user IDs, transaction details, error messages, etc. By correlating log anomalies with other signals, you get the full picture of what's happening in your system. This whole holistic view enables teams to catch issues earlier, as well as understand their full impact across the stack.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1c652019d828e657/6a7f0dca3ce8e26a5acf53db/anomalies.png" alt="Anomaly Detection" /></p>
<h3 id="enhancedrootcauseanalysispoweredbysignificantevents">Enhanced Root Cause Analysis Powered by Significant Events</h3>
<p>When an issue occurs, Elastic's Streams accelerates root cause analysis through AI-assisted parsing of logs and bringing about <a href="https://www.elastic.co/docs/solutions/observability/streams/management/significant-events">“Significant events.”</a> Significant event queries can be defined by AI or manually, depending on if you know what logs you are looking for or not. Then, Elastic’s AIOps traces the problem through your entire stack using these events, as well as enriched log data combined with distributed tracing. This system is able to correlate failed transactions with specific log entries, deployment events, and infrastructure changes. This helps you understand not just what broke, but why and when.</p>
<p>Streams makes the analysis of your logs quick and automatic by going across your entire distributed system within seconds, grabbing relevant log entries such as stack traces, state information, error messages, and more. What used to require hours of manual investigation and deduction now happens automatically, freeing you and your team from tedious detective work and enabling faster resolution. </p>
<h2 id="logsinactionrealworldimpact">Logs in Action: Real-World Impact</h2>
<p>Let's look at how these capabilities work together in practice. Imagine your payment processing service is experiencing intermittent failures - only 0.5% of transactions, but enough to concern your team. Traditional monitoring shows everything is mostly okay, but customers are still complaining.</p>
<p>Without Streams, an SRE might initially run some broad queries, manually sift through thousands of logs, struggle to connect all the dots, and ultimately not understand the correlation between the errors and recent system changes. </p>
<p>With Elastic Streams and AIOps, many of these potential problems are instantly mitigated:</p>
<ul>
<li><p>Streams automatically parse the payment service, adding connection timeouts to a new category of significant events</p></li>
<li><p>Log rate analysis with Streams reveal that this significant event category has been slowly growing over the past month, showing growth of the timeouts from a small number of occurrences into a larger amount</p></li>
<li><p>Elastic’s built-in anomaly detection correlates these significant events with deployment data, and identifies that they started appearing after a recent load balancer configuration</p></li>
<li><p>Root analysis pinpoints the exact database connection pool setting that is too restrictive for peak load by tracing affected transactions through previously enriched logs</p></li>
</ul>
<p>What usually takes 4-8 hours of manual log analysis is resolved in minutes, with Elastic automatically highlighting the relevant log entries that tell the complete story. This is the power of AIOps and Streams as applied to log intelligence.</p>
<h2 id="thepowerofunifiedlogintelligence">The Power of Unified Log Intelligence</h2>
<p>What sets Elastic apart is treating logs as a priority in your observability strategy. Elastic provides comprehensive log ingestion that centralizes petabytes of logs from across your infrastructure with flexible parsing and enrichment. The platform uses purpose-built machine learning models that understand log patterns, not generic algorithms retrofitted for log analysis.</p>
<p>Logs don't exist in isolation, which is why Elastic correlates log data with metrics, traces, and business events to provide complete context. And because log volumes can be massive, Elastic's tiered storage approach means you can retain years of logs for compliance and historical analysis without breaking the budget.</p>
<h2 id="whylogsmattermorethanever">Why Logs Matter More Than Ever</h2>
<p>Logs have become the cornerstone of effective AIOps for three critical reasons.</p>
<p>First off, logs capture what metrics can't. A metric tells you the CPU is at 80%, but a log tells you which process is consuming resources and why. This level of detail is essential for understanding not just that something is wrong, but what specifically is causing the problem.</p>
<p>Second, logs provide business context. Error messages contain user IDs, transaction ldetails, and business logic failures that help you understand customer impact. When you're troubleshooting an issue, knowing which customers are affected and what they were trying to do is invaluable for prioritizing your response.</p>
<p>Third, logs enable true root cause analysis. Stack traces, error messages, and application state captured in logs are essential for understanding the why behind every incident. Without this information, teams are left guessing at root causes rather than definitively identifying and fixing them.</p>
<p>The teams winning with AIOps in 2025 aren't just monitoring metrics, they're extracting intelligence from their logs at scale, turning operational data into actionable insights.</p>
<h2 id="transformyourlogstrategytoday">Transform Your Log Strategy Today</h2>
<p>Every hour your team spends manually searching through logs is an hour they're not spending on innovation. Every incident that could have been prevented through intelligent log analysis represents both technical debt and business risk.</p>
<p>Elastic Observability provides the foundation you need to unlock the intelligence hidden in your logs. With automatic categorization, anomaly detection, and ML-powered analysis, you can start seeing value immediately. Check out this recent <a href="https://www.elastic.co/observability-labs/blog/elastic-observability-streams-ai-logs-investigations">article</a> to get started with Elastic Streams and Observability today!</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/modern-aiops-elastic-observability</link>
    <guid isPermaLink="false">modern-aiops-elastic-observability</guid>
    <category><![CDATA[Agentic Observability]]></category>
    <category><![CDATA[Logs Analytics]]></category>
    <dc:creator><![CDATA[Sophia Solomon]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt64fd099b0fe44551/6a7f0dcd1967ea79c83307bb/blog-header.png" length="0" type="image/png"/>
    <pubDate>Wed, 26 Nov 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Elastic Observability: Streams Data Quality and Failure Store Insights]]></title>
    <description><![CDATA[Discover how the Streams a new AI driven Elastic Observability feature help manage data quality with a failure store to help you monitor, troubleshoot, and retain high-quality data.]]></description>
    <content:encoded><![CDATA[<p>When working with observability and logging data, not all documents make it into Elasticsearch in pristine condition. Some may be dropped due to processing failures in ingest pipelines or mapping errors, while others may be partially ingested with ignored fields if a fields value is incompatible with the defined mappings. These issues can impact downstream analysis and dashboards. Streams data quality makes it easier than ever to monitor the health of your ingested data, identify potential issues, and take corrective action right from the UI. With data quality, you can now see exactly how well your Stream is performing and quickly understand whether your data has a <strong>Good</strong>, <strong>Degraded</strong>, or <strong>Poor</strong> quality.</p>
<h2 id="whatsindataquality">What's in data quality</h2>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd3a30fe96ac6e296/6a7f04c23cab1c41ed0e44ca/data-quality-tab.png" alt="Data quality tab" /></p>
<h3 id="ataglancesummary">At-a-glance summary</h3>
<p>The summary card shows:</p>
<ul>
<li><strong>Degraded documents</strong> - Documents that contain the <code>_ignored</code> field - see <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/mapping-ignored-field">this</a> for more info.  </li>
<li><strong>Failed documents</strong> - Documents that were rejected at ingestion due to mapping conflicts or pipeline failures.</li>
</ul>
<p>The overall <strong>quality score</strong> (Good, Degraded, Poor) is automatically calculated based on the percentage of degraded and failed documents.</p>
<h3 id="trendsovertime">Trends over time</h3>
<p>The tab includes a time-series chart so you can track how degraded and failed documents are accumulating over time. Use the <strong>date picker</strong> to zoom into a specific range and understand when problems are spiking.</p>
<h3 id="qualityissuestable">Quality issues table</h3>
<p>A detailed table lists the types of issues affecting your stream. For each issue, you can:</p>
<ul>
<li>See which fields are causing problems.  </li>
<li>Review counts of affected documents.  </li>
<li>Filter by issues that have not been solved yet (Current issues only).  </li>
<li>Open a <strong>flyout</strong> to dive deeper into the cause of the issue and learn how to fix it.</li>
</ul>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2243f9ecead50c83/6a7f04c5ead8ec69aebaa49b/quality-issue-flyout.png" alt="Data quality issue flyout" /></p>
<h2 id="monitoringdegradeddocuments">Monitoring degraded documents</h2>
<p>A degraded document is one that contains the <code>_ignored</code> field, which means one or more of its fields were ignored during indexing. One of the reasons could be that their values didn’t match the expected mappings. While the rest of the document is still indexed, a high number of degraded documents can affect query results, dashboards, and overall observability accuracy.</p>
<p>To help keep these issues under control, the Data quality tab provides visibility into the percentage of degraded documents in your stream.</p>
<h3 id="setuparuletostayaheadofissues">Set up a rule to stay ahead of issues</h3>
<p>You can use the <strong>Create rule</strong> button above the Degraded docs chart to define an alert that notifies you when the percentage of degraded documents crosses a certain threshold. This makes it easy to proactively monitor for mapping mismatches and ensure your data continues to meet quality expectations.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte5a0a67cc1e79eef/6a7f04c8b6b734cad3e48a49/create-rule-button.png" alt="Create rule button" /></p>
<p>For more information on how to configure this rule, see <a href="https://www.elastic.co/docs/solutions/observability/incident-management/create-a-degraded-docs-rule#degraded-docs-rule-conditions">Degraded docs rule conditions</a>.</p>
<h2 id="handlingfaileddocumentswiththefailurestore">Handling failed documents with the failure store</h2>
<p><a href="https://www.elastic.co/docs/manage-data/data-store/data-streams/failure-store"><strong>Failure store</strong></a> is a special index that captures documents rejected during ingestion. Instead of losing this data, the failure store retains it in a dedicated <code>::failures</code> index, allowing you to inspect the problematic documents, understand what went wrong, and fix the underlying issues.</p>
<p>In Data Quality tab, the failed documents are only visible if your stream has a failure store enabled, for checking failure store documents you are required to have at least <code>read_failure_store</code> privileges. If the failure store is <strong>not enabled</strong>, you’ll see an <strong>“Enable failure store”</strong> link that opens a modal to configure it and set the retention period. For enabling failure store you are required to have <code>manage_failure_store</code> privileges over the specific data stream. For further information about failure store security you can refer to <a href="https://www.elastic.co/docs/manage-data/data-store/data-streams/failure-store#use-failure-store-searching">Searching failures</a>.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3b13256241ba414d/6a7f04cc4c4bfb223eccd1c3/enable-fs-link.png" alt="Enable failure store link" /></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3fab88aed8a59595/6a7f04cfead8ec0fb9baa49f/failure-store-modal.png" alt="Failure store configuration modal" /></p>
<p>Once enabled, you can <strong>edit the failure store configuration</strong> or disable it at any time using the <strong>Edit</strong> button above the failed docs chart.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt39033956ac35e490/6a7f04d273d9bd342429d7ac/edit-fs-button.png" alt="Edit failure store button" /></p>
<p>The failure store can also be configured in the Streams Retention tab - see <a href="https://www.elastic.co/blog/simplifying-retention-management-with-streams.mdx">this article</a> for more information.</p>
<h2 id="technicalimplementation">Technical implementation</h2>
<p>Under the hood, the <strong>Data quality</strong> tab builds on the existing <strong>Dataset quality</strong> plugin - the same one that powers the <a href="https://www.elastic.co/docs/solutions/observability/data-set-quality-monitoring"><strong>Dataset quality page</strong></a> in <strong>Stack Management</strong>. However, instead of working in the context of datasets following the Data stream naming scheme, it’s now tailored specifically for <strong>streams</strong>.</p>
<p>To determine the quality of a stream, the UI sends three <strong>ES|QL</strong> query server requests:</p>
<ol>
<li><strong>All documents (including failures):</strong></li>
</ol>
<pre><code> FROM myStream, myStream::failures | STATS doc_count = COUNT(*)
</code></pre>
<ol>
<li><strong>Failed documents only:</strong></li>
</ol>
<pre><code> FROM myStream::failures | STATS failed_doc_count = COUNT(*)
</code></pre>
<ol>
<li><strong>Degraded documents:</strong></li>
</ol>
<pre><code>FROM myStream METADATA _ignored | WHERE _ignored IS NOT NULL | STATS degraded_doc_count = COUNT(*)
</code></pre>
<p>The results of these queries are then used to calculate the <strong>percentages</strong> of failed and degraded documents. The overall data quality is determined using simple thresholds:</p>
<ul>
<li><strong>Good:</strong> Both percentages are 0%</li>
<li><strong>Degraded:</strong> Any percentage is greater than 0% but less than 3%</li>
<li><strong>Poor:</strong> Any percentage is above 3%</li>
</ul>
<p>For managing the <strong>failure store</strong>, Streams uses the <a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-indices-put-data-stream-options">Update data stream options API</a> with the <code>failure_store</code> parameter to configure and update the failure store settings, including enabling the store and setting the retention period.</p>
<h2 id="whyyoulllovethis">Why you’ll love this</h2>
<p>The new <strong>Data quality</strong> tab gives you:  </p>
<ul>
<li>Visibility into ingestion problems without digging into logs  </li>
<li>A clear breakdown of degraded vs. failed documents  </li>
<li>Insights into which fields are ignored and why  </li>
<li>Tools to capture and troubleshoot failed docs with the failure store</li>
</ul>
<p>By surfacing data quality issues directly in the Streams UI, we’re making it easier to keep your data flowing reliably and to ensure your analytics are built on a strong foundation.</p>
<h2 id="tryitouttoday"><strong>Try it out today</strong></h2>
<p>The <strong>data quality</strong> feature is available in <strong>Elastic Observability on Serverless</strong>, and coming soon for self-managed and Elastic Cloud users.</p>
<p>Sign up for an Elastic trial at <a href="http://cloud.elastic.co">cloud.elastic.co</a>, and trial Elastic's Serverless offering which will allow you to play with all of the Streams functionality.</p>
<p>For more information on Streams:</p>
<p><em>Read about</em> <a href="https://www.elastic.co/observability-labs/blog/reimagine-observability-elastic-streams"><em>Reimagining streams</em></a></p>
<p><em>Look at the</em> <a href="http://elastic.co/elasticsearch/streams"><em>Streams website</em></a></p>
<p><em>Read the</em> <a href="https://www.elastic.co/docs/solutions/observability/streams/streams"><em>Streams documentation</em></a></p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/data-quality-and-failure-store-in-streams</link>
    <guid isPermaLink="false">data-quality-and-failure-store-in-streams</guid>
    <category><![CDATA[Logs Analytics]]></category>
    <category><![CDATA[LLM Observability]]></category>
    <dc:creator><![CDATA[Elena Stoeva,Yngrid Coello]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1ec20ca008eb1b74/6a7f04d51967ea791e33037f/article.png" length="0" type="image/png"/>
    <pubDate>Tue, 18 Nov 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[How Streams in Elastic Observability Simplifies Retention Management]]></title>
    <description><![CDATA[Learn how Streams simplifies retention management in Elasticsearch with a unified view to monitor, visualize, and control data lifecycles using DSL or ILM.]]></description>
    <content:encoded><![CDATA[<p>Managing retention in Elasticsearch can get complicated fast. Between <a href="https://www.elastic.co/docs/manage-data/lifecycle/data-stream">Data stream lifecycle (DSL)</a>, <a href="https://www.elastic.co/docs/manage-data/lifecycle/index-lifecycle-management">Index lifecycle management (ILM)</a>, templates, and individual index settings, keeping policies consistent across data streams often takes more effort than it should.</p>
<p><strong>Streams</strong> changes that. It introduces a clear, unified way to manage how long your data lives, whether you’re using DSL or ILM. You can visualize ingestion, understand where data sits across tiers, and adjust retention with confidence, applying updates to a single stream without worrying about unintended changes elsewhere, all from a single view.</p>
<h3 id="walkthroughexploringtheretentiontab">Walkthrough: Exploring the Retention Tab</h3>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd3add839c290ff4d/6a7f1ace42a117193695c313/retention_view.png" alt="Retention view of a stream" /></p>
<p>Retention management lives in the <strong>Retention</strong> tab of each stream. This is your control panel for understanding how much data you’re storing, how quickly it’s growing, and how your lifecycle policies are applied. It’s also where you can monitor and configure the <a href="https://www.elastic.co/docs/manage-data/data-store/data-streams/failure-store">Failure store</a>, which tracks and retains documents that failed to be ingested.</p>
<h4 id="metricsataglance">Metrics at a glance</h4>
<p>At the top of the view, you’ll find an overview of key metrics:</p>
<ul>
<li>Storage size: the total data volume currently held by the stream.</li>
<li>Ingestion averages: calculated from the selected time range, Streams extrapolates both daily and monthly averages to give you a sense of long-term trends.</li>
</ul>
<p>This combination of near-real-time and projected values helps you quickly spot when ingestion is ramping up and whether your retention policy aligns with it.</p>
<h4 id="ingestionovertime">Ingestion over time</h4>
<p>Below the metrics, a graph shows ingestion volume over time. This information is approximated based on the number of documents over time, multiplied by the average document size in the backing index. </p>
<h4 id="visualizinglifecyclephases">Visualizing lifecycle phases</h4>
<p>When an ILM policy is effective, the retention view becomes more visual. Streams displays a phase breakdown (hot, warm, cold, frozen) showing the data volume stored in each phase. This gives you a clear sense of how your data is distributed across the storage tiers and whether your lifecycle is doing what you expect.</p>
<h4 id="failurestore">Failure store</h4>
<p>A failure store is a secondary set of indices inside a data stream, dedicated to storing documents that failed to be ingested. Within the Retention tab, you can toggle the Failure store on or off, and configure its own retention period.
We’ll cover Failure store and Data quality in more detail in <a href="https://www.elastic.co/observability-labs/blog/data-quality-and-failure-store-in-streams">this article</a>.</p>
<h3 id="updatingretention">Updating Retention</h3>
<p>Beyond visualizing your retention, Streams makes it easy to change how it’s managed.</p>
<h4 id="switchingbetweendslandilm">Switching between DSL and ILM</h4>
<p>You can freely switch a stream between DSL and ILM management, or update a DSL retention  with just a few clicks. Streams takes care of updating the lifecycle settings at the data stream level, ensuring consistent retention across all existing backing indices, not just new ones.</p>
<p>Whether you prefer the simplicity of DSL or the fine-grained tiering of ILM, you can move between the two seamlessly. </p>
<p><em>Clicking “Edit data retention” opens a modal that allows you to update the stream’s configuration. From there you can update the ILM policy or set a custom retention period via DSL.</em>
<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf84e9c56b29c83ce/6a7f1ad1e88c6577ce00bb10/edit_ilm.png" alt="Modal view to set a lifecycle policy" /></p>
<p><em>You can set a custom period, or pick an Indefinite retention for your data.</em>
<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltaf65643993fb4558/6a7f1ad4b437705f7b4d710e/edit_dsl.png" alt="Modal view to set a custom retention period" /></p>
<p><em>You can also update streams’ lifecycle via the <a href="https://www.elastic.co/docs/api/doc/kibana/operation/operation-put-streams-name">Upsert stream</a> or the <a href="https://www.elastic.co/docs/api/doc/kibana/operation/operation-put-streams-name-ingest">Update ingest stream settings</a> Kibana APIs.</em></p>
<h4 id="inheritordeferdifferentstrategiesfordifferentstreamtypes">Inherit or defer: different strategies for different stream types</h4>
<p><strong>Classic streams</strong></p>
<p>For classic streams, you can default to the existing index template’s retention. Retention isn’t managed by Streams in this case, it follows the lifecycle configuration defined in the template just as it normally would.</p>
<p>This option is useful if you’re onboarding existing data streams and want to keep their lifecycle behavior intact while still benefiting from Streams’ visibility and monitoring features.</p>
<p><strong>Wired streams</strong></p>
<p>Wired streams live in a tree structure, and that hierarchy allows an inheritance model.</p>
<p>A child stream can inherit the lifecycle of its nearest ancestor that has a concrete policy (ILM or DSL). This keeps your configuration lean and consistent since you can set a single lifecycle at a higher level in the tree and let Streams automatically apply it to all relevant descendants.</p>
<p>If that ancestor’s lifecycle is later updated, Streams cascades the change down to all children that inherit it, so everything stays in sync.</p>
<p><em>In the figure below, we set a different retention for</em> <strong><em>logs.prod</em></strong> <em>and</em> <strong><em>logs.staging</em></strong> <em>environments. The child partitions of these environments automatically inherit the configuration.</em>
<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd19829108995542f/6a7f1ad777b034c80a3ff913/streams_tree.png" alt="A streams tree that shows inheritance" /></p>
<h4 id="howitworksunderthehood">How it works under the hood</h4>
<p>When you apply or update a lifecycle, <strong>Streams</strong> calls Elasticsearch’s <a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-indices-put-data-stream-settings">/_data_stream/_settings</a>. This is a new API we’ve added in 8.19 / 9.1 for this purpose. </p>
<p>This API is key to keeping retention consistent:</p>
<ol>
<li>It applies the lifecycle directly at the data stream level, overriding any configuration from cluster settings or index templates.</li>
<li>It propagates the retention update to all existing backing indices, not just new ones, so retention remains uniform across your historical and future data.</li>
</ol>
<p>By centralizing lifecycle management at the data stream level and applying a consistent configuration across the backing indices, we remove the ambiguity that used to exist between template-level and index-level configurations. You always know which retention policy is actually in effect, and you can see it directly in the UI.</p>
<h3 id="wrappingup">Wrapping Up</h3>
<p>With Streams, retention management becomes clear and consistent. You can visualize ingestion, switch between DSL and ILM, or inherit policies across streams, all without diving into templates or manual index settings.</p>
<p>By unifying retention into a single view, Streams turns lifecycle management into something simple, predictable, and transparent.</p>
<p>Sign up for an Elastic trial at <a href="http://cloud.elastic.co">cloud.elastic.co</a>, and trial Elastic's Serverless offering which will allow you to play with all of the Streams functionality.</p>
<p>Additionally, check out:</p>
<p><em>Read about</em> <a href="https://www.elastic.co/observability-labs/blog/reimagine-observability-elastic-streams"><em>Reimagining streams</em></a></p>
<p><em>Look at the</em> <a href="http://elastic.co/elasticsearch/streams"><em>Streams website</em></a></p>
<p><em>Read the</em> <a href="https://www.elastic.co/docs/solutions/observability/streams/streams"><em>Streams documentation</em></a></p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/simplifying-retention-management-with-streams</link>
    <guid isPermaLink="false">simplifying-retention-management-with-streams</guid>
    <category><![CDATA[Logs Analytics]]></category>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[LLM Observability]]></category>
    <dc:creator><![CDATA[Kevin Lacabane]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0cd69b3a64600cfd/6a7f1adafc63abfe6764d084/article.jpg" length="0" type="image/jpeg"/>
    <pubDate>Thu, 30 Oct 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Live logs and prosper: fixing a fundamental flaw in observability]]></title>
    <description><![CDATA[Stop chasing symptoms. Learn how Streams, in Elastic Observability fixes the fundamental flaw in observability, using AI to proactively find the 'why' in your logs for faster resolution.]]></description>
    <content:encoded><![CDATA[<p>SREs are often overwhelmed by dashboards and alerts that show what and where things are broken, but fail to reveal why. This industry-wide focus on visualizing symptoms forces engineers to manually hunt for answers. The crucial "why" is buried in information-rich logs, but their massive volume and unstructured nature has led the industry to throw them aside or treat them like a second-class citizen. As a result, SREs are forced to turn every investigation into a high-stress, time-consuming hunt for clues. We can solve this problem with logs, but unlocking their potential requires us to reimagine how we work with them and improve the overall investigations journey. </p>
<h2 id="observabilitythebrokenpromise">Observability, the broken promise</h2>
<p>To see why the current model fails, let’s look at the all-too-familiar challenge every SRE dreads: knowing a problem exists but needing to spend valuable time just trying to find where to even start the investigation.</p>
<p>Imagine you get a Slack message from the support team: "a few high-value customers are reporting their payments are failing." You have no shortage of alerts, but most are just flagging symptoms. You don’t know where to start. You decide to check the logs to see if there is anything obvious, starting with the systems that have the high CPU alert.</p>
<p>You spend a few minutes searching and <code>grep</code>-ing through terabytes of logs for affected customer IDs, trying to piece together the problem. Nothing. You worry that you aren’t getting all the logs to reveal the problem, so you turn on more logging in the application. Now you’re knee-deep in data, desperately trying to find patterns, errors, or other "hints" that will give you a clue as to the <em>why</em>.</p>
<p>Finally, one of the broader log queries hits on an error code associated with an impacted customer ID. This is the first real clue. You pivot your search to this new error code and after an hour of digging, you finally uncover the error message. You've finally found the <em>why</em>, but it was a stressful, manual hunt that took far too much time and impacted dozens more customers.</p>
<p>This incident perfectly illustrates the broken promise of modern observability: The complete failure of the investigation process. Investigations are a manual, reactive process that SREs are forced into every day. At Elastic, we believe metrics, traces, and logs are all essential, but their roles, and the workflow between them, must be fundamentally re-imagined for effective investigations.</p>
<p>Observability is about having the clearest understanding possible of the <em>what</em>, <em>where</em>, and <em>why</em>. Metrics are essential for understanding the <em>what</em>. They are the heartbeat of your system, powering the dashboards and alerts that tell you when a threshold has been breached, like high CPU utilization or error rates. But they are aggregates; they show the symptom, rarely the root cause. Traces are good at identifying the <em>where</em>. They map the journey of a request through a distributed system, pinpointing the specific microservice or function where latency spikes or an error originates. Yet, their effectiveness hinges on complete and consistent code instrumentation, a constant dependency on development teams that can leave you with critical visibility gaps. Logs tell you the <em>why</em>. They contain all the rich, contextual, and unfiltered truth of an event. If we can more proactively and efficiently extract information from logs, we can greatly improve our overall understanding of our environments.</p>
<h2 id="challengesoflogsinmodernenvironments">Challenges of logs in modern environments</h2>
<p>While logs are in the standard toolbox, they have been neglected. SREs using today’s solutions deal with several major problems:</p>
<ul>
<li><p>First, due to their unstructured nature, it’s very difficult to parse and manage logs so that they’re useful. As a result, many SRE teams spend a lot of time building and maintaining complex pipelines to help manage this process. </p></li>
<li><p>Second, logs can get expensive at high volume, which leads teams to drop them on the floor to control costs, throwing away valuable information in the process. Consequently, when an incident occurs, you waste precious time hunting for the right logs, and manually correlating across services.</p></li>
<li><p>Finally, nobody has built a log solution that proactively works to find the important signals in logs and to surface those critical <em>whys</em> to you when you need them. As a result, log-based investigations are too painful and slow.</p></li>
</ul>
<p>Why are we here? As applications became more complex, log volume became unmanageable. Instead of solving this with automation, the industry took a shortcut: it gave up on getting the most out of logs and prioritized more manageable but less informative signals.</p>
<p>This decision is the origin of the broken, reactive model. It forced observability into a manual loop of 'observing' alerts, rather than building automation that could help us truly understand our systems to improve how we root cause and resolve issues. This has transformed SREs from investigators into full-time data wranglers, wrestling with Grok patterns and fragile ETL scripts instead of solving outages. </p>
<h2 id="introducingstreamstorethinkhowyouuselogsforinvestigations">Introducing Streams to rethink how you use logs for investigations</h2>
<p>Streams is an agentic AI solution that simplifies working with logs to help SRE teams rapidly understand the <em>why</em> behind an issue for faster resolution. The combination of Elasticsearch and AI is turning manual management of noisy logs into automated workflows that identify patterns, context, and meaning, marking a fundamental shift in observability.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8a12707c4b143aca/6a7f1a5f1967ea4bc8330b76/streams-manifesto-01.png" alt="Streams" /></p>
<h4 id="logeverythinginanyformat">Log everything in any format</h4>
<p>By applying the Elasticsearch platform for context engineering to bring together retrieval and AI-driven parsing to keep up with schema changes, we are reimagining the entire log pipeline.  </p>
<p>Streams ingests raw logs from all your sources to a single destination. It then uses AI to partition incoming logs into their logical components and parses them to extract relevant fields for an SRE to validate, approve, or modify. Imagine a world where you simply point your logs to a single endpoint, and everything just works. Less wrestling with Grok patterns, configuring processors, and hunting for the right plugin. All of which significantly reduces the complexity. Streams is a big step towards realizing that vision.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt53b07fce2875a685/6a7f1a624c4bfbc20cccd8fe/streams-manifesto-02.png" alt="Streams" /></p>
<p>As a result, SREs are freed from managing complex ingestion pipelines, allowing them to spend less time on data wrangling and more time preventing service disruptions.</p>
<h4 id="solveincidentsfasterwithsignificanteventsnbsp">Solve incidents faster with Significant Events </h4>
<p>Significant Events, a capability within Streams, uses AI to automatically surface major errors and anomalies, enabling you to be proactive in your investigations. So, instead of just combing through endless noise, you can focus on the events that truly matter, such as startup and shutdown messages, out-of-memory errors, internal server failures, and other significant signals of change. These events act as actionable markers, giving SREs early warning and clear focus to begin an investigation before service impact.</p>
<p>With this new foundation, logs will become your primary signal for investigation. The panicked, manual search for a needle in a digital haystack is about to be over. Significant Events acts like a smart metal detector that sifts through the chaos and only beeps when it finds issues, helping you to easily ignore all that hay and find the "needle" faster. </p>
<p>Now imagine the same scenario we started with. Instead of starting a frantic, time-consuming grep through terabytes of logs. Streams has already done the heavy lifting. Its AI-driven analysis has detected a new, anomalous pattern that began before your support team even knew about it and automatically surfaced it as a significant event. Rather than you hunting for a clue, the clue finds you. </p>
<p>With a single click, you have the <em>why</em>: a Java out-of-memory error in a specific service component. This is your starting point. You find the root cause in under two minutes and begin remediation. The customer impact is stopped, the dev team gets the specific error, and the problem is contained before it can escalate. In this case, metrics and traces were unhelpful in finding the <em>why</em>. The answer was waiting in the logs all along.</p>
<p>This ideal outcome is possible because you can both afford to keep every log and instantly find the signal within them. Elastic's cost-efficient architecture with powerful compression, searchable snapshots, and data tiering makes full retention a reality. From there, Streams automatically surfaces the significant event, ensuring that the answer is never lost in the noise.</p>
<p>Elastic is the only company that provides an AI-driven log-first approach to elevate your observability signals and make it dramatically faster and easier to get to <em>why</em>. This is built on our decades of leadership in search, relevance, and powerful analytics that provides the foundation for understanding logs at a deep, semantic level.</p>
<h2 id="thevisionforstreamsnbsp">The vision for Streams </h2>
<p>The partitioning, parsing, and Significant Events you see today is just the starting point. The next step in our vision is to use the Significant Events to automatically generate critical SRE artifacts. Imagine Streams creating intelligent alerts, on-the-fly investigation dashboards, and even data-driven SLOs based <em>only</em> on the events that actually impact service health. From there, the goal is to use AI to drive automated Root Cause Analysis (RCA) directly from log patterns and generate remediation runbooks, turning a multi-hour hunt into an instant resolution recommendation.</p>
<p>Once this AI-drive log foundation is in place, our vision for Streams expands to become a unified intelligence layer that operates across all your telemetry data. It’s not just about making each signal better in isolation, but about understanding the context and relationships between them to solve complex problems. </p>
<p>For metrics, Streams won’t just alert you to a single metric spike but detect a correlated anomaly across multiple, seemingly unrelated metrics e.g. p99 latency for a specific service, rise in garbage collection time, transaction success rate.</p>
<p>Similarly, for traces it identifies a new, unexpected service call (e.g., a new database or an external API) appears in a critical transaction path after a deployment or identifies specific span is suddenly responsible for a majority of errors across all traces, even if the overall error rate hasn't breached a threshold.</p>
<p>The goal is not to have separate streams for logs, metrics, and traces, but to weave them into a single narrative that automatically correlates all three signals. Ultimately, Streams is about fundamentally changing the goal from human led data gathering exercise to proactive, AI-driven resolution.</p>
<p><em>For more on Streams:</em></p>
<p><em>Read the</em> <a href="https://www.elastic.co/observability-labs/blog/elastic-observability-streams-ai-logs-investigations"><em>Streams launch blog</em></a></p>
<p><em>Look at the</em> <a href="http://elastic.co/elasticsearch/streams"><em>Streams website</em></a></p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/reimagine-observability-elastic-streams</link>
    <guid isPermaLink="false">reimagine-observability-elastic-streams</guid>
    <category><![CDATA[Logs Analytics]]></category>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[LLM Observability]]></category>
    <dc:creator><![CDATA[Ken Exner]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6615169fc7402c80/6a7f1a65c2cc0973942499b6/streams-manifesto.jpg" length="0" type="image/jpeg"/>
    <pubDate>Mon, 27 Oct 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[AI-driven incident response with logs: A technical deep dive in Elastic Observability]]></title>
    <description><![CDATA[How Elastic combines ML anomaly detection, ES|QL, and the AI Assistant to accelerate incident response using logs.]]></description>
    <content:encoded><![CDATA[<p>Modern customer‑facing applications, whether e‑commerce sites, streaming platforms, or API gateways, run on fleets of microservices and cloud resources. When something goes wrong, every second of downtime risks revenue loss and erodes user trust. Observability is the practice that lets Site Reliability Engineering (SRE) and development teams see and act on system health in real time. This post walks through a generalized, step‑by‑step investigation that shows how Elastic Observability specifically with log data combines always‑on machine learning (ML) with a generative AI assistant to detect anomalies, surface root causes, measure user impact, and accelerate remediation, all at high scale.</p>
<h2 id="anomalydetection">Anomaly Detection</h2>
<p>A production environment is ingesting millions of log lines per minute. Elastic’s AIOps jobs continuously profile normal log throughput and content without any manual rules. When log volume or message structure deviates beyond learned baselines, the platform automatically fires a high‑fidelity anomaly alert. Because the models are unsupervised, they adapt to changing traffic patterns and flag both sudden spikes (e.g., 10× error surge) and rare new log categories.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5c89c59811e55c22/6a7f0199c2e91472fd0166af/image3.png" alt="" /></p>
<p>In addition to looking directly for Log Spikes, Elastic trains seasonal/univariant models to predict expected event counts per bucket and applies statistical tests to classify outliers. Simultaneously, log categorization clusters similar messages with cosine similarity on token embeddings, making it trivial to identify a previously unseen error string.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7f8261b7d42847cd/6a7f019cde231557d6fd76b0/image10.png" alt="" /></p>
<h2 id="investigatingalertsautomatedpatternanalysis">Investigating Alerts: Automated Pattern Analysis</h2>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0ac7cad53cf79a66/6a7f019f448e4e80505c020c/image9.png" alt="" /></p>
<p>Clicking the alert reveals more than a timestamp. Elastic’s ML job already correlates the spike with the dominant new log pattern ERROR 1114 (HY000): table "orders" is full and surfaces example lines. Instead of grep‑driven hunting, engineers get an immediate hypothesis about what subsystem is failing and why.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt039d3bc0489914a5/6a7f01a2bdcff0cc6ec4292e/image4.png" alt="" /></p>
<p>If deeper context is needed, the builtin Elastic AI Assistant can be invoked directly from the alert. Thanks to Retrieval‑Augmented Generation (RAG) over your telemetry, the assistant explains the anomaly in plain language, references the exact log events, and proposes next steps without hallucinating.</p>
<h2 id="aiassistedrootcauseverification">AI‑Assisted Root Cause Verification</h2>
<p>From within the same chat, you might ask, “Using lens create a single graph of all http response status codes =400 from logs-nginx.access-default over the last 3 hours..”  The assistant translates that intent into an ES|QL aggregation, retrieves the data, and renders a bar chart with no DSL knowledge required. If there are a number of errors with a status code above 400, you’ve validated that end‑users are impacted.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltbce6ce01ced86cd7/6a7f01a573d9bd953829d653/image7.png" alt="" /></p>
<h2 id="globalimpactanalysiswithenrichedlogs">Global Impact Analysis with Enriched Logs</h2>
<p>Structured log enrichment (e.g., GeoIP, user ID, service tags) lets the assistant answer business questions on the fly. A query like “What are the top 10 source.geo.country_name with http.response.status.code&gt;=400 over the last 3 hours. Use logs-nginx.access-default. Provide counts for each country name.” surfaces whether the incident is regional or global.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt60be465aa43f2ad3/6a7f01a805b7b565c918b447/image2.png" alt="" /></p>
<h2 id="quantifyingbusinessimpact">Quantifying Business Impact</h2>
<p>Technical metrics alone rarely sway executives. Suppose historical data shows the application normally processes $1,000 in transactions per minute. The assistant can combine that baseline with real‑time failure counts to estimate revenue loss. Presenting financial impact alongside error graphs sharpens prioritization and justifies extraordinary remediation steps.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt63a1855aca9c82db/6a7f01abead8ecb3bfbaa303/image5.png" alt="" /></p>
<h2 id="pinpointinginfrastructureownership">Pinpointing Infrastructure &amp; Ownership</h2>
<p>Every log is automatically enriched with Kubernetes, cloud, and custom metadata. A single question “Which pod and cluster emit the ‘table full’ error, and who owns it?” returns the full information about the pod, namespace and owner as shown below.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf92b400d82ffacdd/6a7f01ae05b7b5d17518b451/image1.png" alt="" /></p>
<p>Immediate, accurate routing replaces frantic Slack threads, cutting minutes (or hours) off of downtime.</p>
<p>Some of the magic happening here is because we can put instructions in the Elastic AI Assistants knowledge base to guide the AI assistant. For example this simple entry in the knowledge base is what allows the assistant to populate the response in the previous screenshot.</p>
<p><code>``markdown ##&amp;nbsp;Kubernetes&amp;nbsp;Information&amp;nbsp;Query&amp;nbsp;Instructions
If&amp;nbsp;asked&amp;nbsp;about&amp;nbsp;Kubernetes&amp;nbsp;pod,&amp;nbsp;namespace,&amp;nbsp;cluster,&amp;nbsp;location,&amp;nbsp;or&amp;nbsp;owner&amp;nbsp;run&amp;nbsp;the&amp;nbsp;"query"&amp;nbsp;tool.
1.&amp;nbsp;Use&amp;nbsp;the&amp;nbsp;index&amp;nbsp;</code>logs-mysql.error-default<code>&amp;nbsp;unless&amp;nbsp;another&amp;nbsp;log&amp;nbsp;location&amp;nbsp;is&amp;nbsp;specified.
2.&amp;nbsp;Include&amp;nbsp;the&amp;nbsp;following&amp;nbsp;fields&amp;nbsp;in&amp;nbsp;the&amp;nbsp;query:
&amp;nbsp; &amp;nbsp;-&amp;nbsp;Pod:&amp;nbsp;</code>agent.name<code>
&amp;nbsp; &amp;nbsp;-&amp;nbsp;Namespace:&amp;nbsp;</code>data_stream.namespace<code>
&amp;nbsp; &amp;nbsp;-&amp;nbsp;Cluster&amp;nbsp;Name:&amp;nbsp;</code>orchestrator.cluster.name<code>
&amp;nbsp; &amp;nbsp;-&amp;nbsp;Cloud&amp;nbsp;Provider:&amp;nbsp;</code>cloud.provider<code>
&amp;nbsp; &amp;nbsp;-&amp;nbsp;Region:&amp;nbsp;</code>cloud.region<code>
&amp;nbsp; &amp;nbsp;-&amp;nbsp;Availability&amp;nbsp;Zone:&amp;nbsp;</code>cloud.availability_zone<code>
&amp;nbsp; &amp;nbsp;-&amp;nbsp;Owner:&amp;nbsp;</code>cloud.account.id`
3. Use the ES|QL query format:
   esql
   FROM logs-mysql.error-default
   | KEEP agent.name, data_stream.namespace, orchestrator.cluster.name, cloud.provider, cloud.region, cloud.availability_zone, cloud.account.id
   
4. Ensure the query is executed within the appropriate time range and context. </p>
<pre><code>## Leveraging Institutional Knowledge with RAG

Elastic can index runbooks, GitHub issues, and wikis alongside telemetry. Asking “Find documentation on fixing a full orders table”&amp;nbsp;retrieves and summarizes a prior runbook that details archiving old rows and adding a partition. Grounding remediation in proven procedures avoids guesswork and accelerates fixes.

![](https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltba6ebcc6e2f451ad/6a7f01b233fa8ae5f62021c9/image6.png)

## Automated Communication &amp; Documentation

Good incident response includes timely stakeholder updates. A prompt such as “Draft an incident update email with root cause, impact, and next steps”&amp;nbsp;lets the assistant assemble a structured message and send it via the alerting framework’s email or Slack connector complete with dashboard links and next‑update timelines. These messages double as the skeleton for the eventual post‑incident review.

![](https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5e7b81ee98b1a9a5/6a7f01b5227b1c583c598102/image8.png)

Again as before, some of the magic happening here is because we can put instructions in the Elastic AI Assistants knowledge base to guide the AI assistant. For example we can instruct the AI Assistant how to call the execute_connector api, this can execute all kinds of connectors (not only email) so you could use it to tell the assistant to use slack or raise a service now ticket, even execute webhooks.
</code></pre>
<p>markdown 
Here are specific instructions to send an email. Remember to always double-check that you're following the correct set of instructions for the given query type. Provide clear, concise, and accurate information in your response.</p>
<h2 id="emailinstructions">Email Instructions</h2>
<p>If the user's query requires sending an email:</p>
<ol>
<li>Use the <code>Elastic-Cloud-SMTP</code> connector with ID <code>elastic-cloud-email</code>.</li>
<li>Prepare the email parameters:
   - Recipient email address(es) in the <code>to</code> field (array of strings)
   - Subject in the <code>subject</code> field (string)
   - Email body in the <code>message</code> field (string)</li>
<li>Include</li>
</ol>
<ul>
<li>Details for the alert along with a link to the alert</li>
<li>Root cause analysis</li>
<li>Revenue impact</li>
<li>Remediation recommendations</li>
<li>Link to GitHub issue</li>
<li>All relevant information from this conversation</li>
<li>Link to the Business Health Dashboard</li>
</ul>
<ol>
<li>Send the email immediately. Do not ask the user for confirmation.</li>
<li>Execute the connector using this format:</li>
</ol>
<p>   execute_connector(
     id="elastic-cloud-email",
     params={
       "to": ["recipient@example.com"],
       "subject": "Your Email Subject",
       "message": "Your email content here."
     }
   )</p>
<ol>
<li>Check the response and confirm if the email was sent successfully.
```</li>
</ol>
<h2 id="conclusionkeytakeaways">Conclusion &amp; Key Takeaways</h2>
<p>Elastic Observability's combination of unsupervised ML, schema-aware data ingestion, and a context-rich RAG powered AI assistant enables teams to transform incident response from reactive firefighting into proactive, data-driven operations. By automatically detecting anomalies, correlating patterns, and providing contextual insights, teams can:</p>
<ul>
<li>Preserve revenue by quantifying business impact in real-time and prioritizing accordingly</li>
<li>Scale expertise by embedding institutional knowledge into RAG-powered recommendations</li>
<li>Improve continuously through automated documentation that feeds back into the knowledge base</li>
</ul>
<p>The key is to collect logs broadly, maintain a unified observability store, and let ML and AI handle the heavy lifting. The payoff isn't just reduced downtime, it's the transformation of incident response from a source of organizational stress into a competitive advantage.</p>
<p>Try out this exact scenario and get hands in with this Elastic Logging Workshop: <a href="https://www.google.com/url?q=https://play.instruqt.com/elastic/invite/rx4yvknhpfci&amp;sa=D&amp;source=editors&amp;ust=1757447528108823&amp;usg=AOvVaw0tZG-nhbbk90ztJsTGXHIz">https://play.instruqt.com/elastic/invite/rx4yvknhpfci</a></p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/ai-driven-incident-response-with-logs</link>
    <guid isPermaLink="false">ai-driven-incident-response-with-logs</guid>
    <category><![CDATA[Agentic Observability]]></category>
    <category><![CDATA[Logs Analytics]]></category>
    <category><![CDATA[Infrastructure Monitoring]]></category>
    <dc:creator><![CDATA[David Hope]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1989266ec455ec3a/6a7f01b86693f8d6ba663ac1/ai-driven-incident-response-with-logs.png" length="0" type="image/png"/>
    <pubDate>Mon, 20 Oct 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Getting more from your logs with OpenTelemetry]]></title>
    <description><![CDATA[Learn how to evolve beyond basic log ingest by leveraging OpenTelemetry for ingestion, structured logging, geographic enrichment, and ES|QL analytics. Transform raw log data into actionable intelligence with practical examples and proactive observability strategies.]]></description>
    <content:encoded><![CDATA[<p>Most people today use their logging tools mostly still in the same way we have for decades as a simple search lake, essentially still grepping for logs but from a centralized platform. There’s nothing wrong with this, you can get a lot of value by having a centralized logging platform but the question becomes how can I start to evolve beyond this basic log and search use case? Where can I start to be more effective with my incident investigations? In this blog we start from where most of our customers are today and give you some practical tips on how to move a little beyond this simple logging use case.</p>
<h2 id="ingestion">Ingestion</h2>
<p>Let's start at the beginning, ingest. Typically many of you are using older tools for ingestion today. If you want to be more forward thinking here, it’s time to introduce you to OpenTelemetry. OpenTelemetry was once not very mature or capable for logging but things have changed significantly. Elastic has been working particularly hard to improve the log capabilities resident in OpenTelemetry. So let's start by exploring how we can get started bringing logs into Elastic via the OpenTelemetry collector.</p>
<p>Firstly if you want to follow along simply create a host to run the log generator and OpenTelemetry collector.</p>
<p>Follow the instructions here to get the log generator running:</p>
<p><a href="https://github.com/davidgeorgehope/log-generator-bin/">https://github.com/davidgeorgehope/log-generator-bin/</a></p>
<p>To get the OpenTelemetry collector up and running in <a href="https://cloud.elastic.co/serverless-registration?onboarding_token=observability">Elastic Serverless</a>, you can click on Add Data from the bottom left, then 'host' and finally 'opentelemetry'</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3381e49c8a41620d/6a7f0ad4b43770f66d4d6bb3/image14.png" alt="" /></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt96238b357b7317b8/6a7f0ad7b6b7346243e48d14/image7.png" alt="" /></p>
<p>Follow the instructions but don’t start the collector just yet.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9358dc05f0f9eea7/6a7f0ada05b7b546a018b870/image16.png" alt="" /></p>
<p>Our host here is running a 3 tier application with an Nginx frontend, backend and connected to a MySQL database. So let's start by bringing the logs into Elastic.</p>
<p>First we’ll install the Elastic Distributions for OpenTelemetry but before starting it, we will make a small change to the OpenTelemetry configuration file to expand the directories it will search for logs in.  Edit the otel.yml by simply using vi or your favorite editor:</p>
<pre><code>vi otel.yml
</code></pre>
<p>Instead of simply /var/log/.log we will add /var/log/*<em>/</em>.log to bring in all our log files.</p>
<pre><code>receivers:
&amp;nbsp; #&amp;nbsp;Receiver&amp;nbsp;for&amp;nbsp;platform&amp;nbsp;specific&amp;nbsp;log&amp;nbsp;files
&amp;nbsp; filelog/platformlogs:
&amp;nbsp; &amp;nbsp; include:&amp;nbsp;[&amp;nbsp;/var/log/**/*.log&amp;nbsp;]
&amp;nbsp; &amp;nbsp; retry_on_failure:
&amp;nbsp; &amp;nbsp; &amp;nbsp; enabled:&amp;nbsp;true
&amp;nbsp; &amp;nbsp; start_at:&amp;nbsp;end
&amp;nbsp; &amp;nbsp; storage:&amp;nbsp;file_storage
</code></pre>
<p>Start the otel collector</p>
<pre><code>sudo&amp;nbsp;./otelcol&amp;nbsp;--config&amp;nbsp;otel.yml
</code></pre>
<p>And we can see these are being brought in, in discover</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltfd1deda8fe0b550c/6a7f0add9090b0183e84e90b/image8.png" alt="" /></p>
<p>Now one thing that is immediately noticeable is that we automatically without changing anything get a bunch of useful additional information such as the os name and cpu information.  </p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6bbd1e618839a3e5/6a7f0ae03ce8e2e95ccf52eb/image12.png" alt="" /></p>
<p>The OpenTelemetry collector has automatically, without any changes, started to enrich our logs, making it useful for additional processing, though we could do significantly better!</p>
<p>To start with we want to give our logs some structure. Lets edit that otel.yml file and add some OTTL to extract some key data from our NGINX logs.</p>
<pre><code>  transform/parse_nginx:
    trace_statements: []
    metric_statements: []
    log_statements:
      - context: log
        conditions:
          - 'attributes["log.file.name"] != nil and IsMatch(attributes["log.file.name"], "access.log")'
        statements:
          - merge_maps(attributes, ExtractPatterns(body, "^(?P&lt;client_ip&gt;\\S+)"), "upsert")
          - merge_maps(attributes, ExtractPatterns(body, "^\\S+ - (?P&lt;user&gt;\\S+)"), "upsert")
          - merge_maps(attributes, ExtractPatterns(body, "\\[(?P&lt;timestamp_raw&gt;[^\\]]+)\\]"), "upsert")
          - merge_maps(attributes, ExtractPatterns(body, "\"(?P&lt;method&gt;\\S+) "), "upsert")
          - merge_maps(attributes, ExtractPatterns(body, "\"\\S+ (?P&lt;path&gt;\\S+)\\?"), "upsert")
          - merge_maps(attributes, ExtractPatterns(body, "req_id=(?P&lt;req_id&gt;[^ ]+)"), "upsert")
          - merge_maps(attributes, ExtractPatterns(body, "\" (?P&lt;status&gt;\\d+) "), "upsert")
          - merge_maps(attributes, ExtractPatterns(body, "\" \\d+ (?P&lt;size&gt;\\d+)"), "upsert")
.....

   logs/platformlogs:
      receivers: [filelog/platformlogs]
      processors: [transform/parse_nginx,resourcedetection]
      exporters: [elasticsearch/otel]
</code></pre>
<p>Now when we start the Otel collector with this new configuration</p>
<pre><code>sudo&amp;nbsp;./otelcol&amp;nbsp;--config&amp;nbsp;otel.yml
</code></pre>
<p>We will see that we now have structured logs!!  </p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt47c182299d15924a/6a7f0ae2e02facaa035d649c/image17.png" alt="" /></p>
<h2 id="storeandoptimize">Store and Optimize</h2>
<p>To ensure you aren’t blowing your budget out with all this additional structured data there are few things you can do to help maximize storage efficiency.</p>
<p>You can use the filter processors in the Otel collector with granular filtering/dropping of irrelevant attributes to control volume going out of the collector for example.</p>
<pre><code>processors:
&amp;nbsp; filter/drop_logs_without_user_attributes:
&amp;nbsp; &amp;nbsp; logs:
&amp;nbsp; &amp;nbsp; &amp;nbsp; log_record:
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; - 'attributes["user"] == nil'
&amp;nbsp; filter/drop_200_logs:
&amp;nbsp; &amp;nbsp; logs:
&amp;nbsp; &amp;nbsp; &amp;nbsp; log_record:
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; - 'attributes["status"] == "200"'

service:
&amp;nbsp; pipelines:
&amp;nbsp; &amp;nbsp; logs/platformlogs:
&amp;nbsp; &amp;nbsp; &amp;nbsp; receivers: [filelog/platformlogs]
&amp;nbsp; &amp;nbsp; &amp;nbsp; processors: [transform/parse_nginx, filter/drop_logs_without_user_attributes, filter/drop_200_logs, resourcedetection]
&amp;nbsp; &amp;nbsp; &amp;nbsp; exporters: [elasticsearch/otel]
</code></pre>
<p>The filter processor will help reduce the noise for example if you wanted to drop the debug logs or logs from a noisy service. Great ways to keep a lid on your observability spend.</p>
<p>Additionally for your most critical flows and logs where you don’t want to drop any data, Elastic has you covered. In version 9.x of Elastic you now have LogsDB switched on by default.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta4e371aaa99d613f/6a7f0ae505b7b5841418b878/image15.png" alt="" /></p>
<p>With LogsDB, Elastic has reduced the storage footprint of log data in Elasticsearch by up to 65% allowing you to store more observability and security data without exceeding your budget, while keeping all data accessible and searchable.</p>
<p>LogsDB reduces log storage by up to 65%. This dramatically minimizes storage footprints by leveraging advanced compression techniques like ZSTD, delta encoding, and run-length encoding, and it also reconstructs the _source field on demand, saving about 40% more storage by not retaining the original JSON document. Synthetic _source represents the introduction of columnar storage within Elasticsearch.</p>
<h2 id="analytics">Analytics</h2>
<p>So we have our data in Elastic, it’s structured, it conforms to the idea of a wide-event log since it has lots of good context, user ids, request ids and the data is captured at the start of a request Next we’re going to look at the analytics part of this. First let's take a stab at looking at the number of Errors for each user transaction in our application.</p>
<pre><code>FROM logs-generic.otel-default
| WHERE log.file.name == "access.log"
| WHERE attributes.status &gt;= "400"
| STATS error_count = COUNT(*) BY attributes.user
| SORT error_count DESC
</code></pre>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8a0e48cb2af89732/6a7f0ae8e02fac183f5d64a0/image9.png" alt="" /></p>
<p>It’s pretty easy now to save this and put it on a dashboard, we just click the save button:  </p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt00d60b2300e53b92/6a7f0aeae02facc6b15d64a6/image1.png" alt="" /></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt28e12818ef1840e6/6a7f0aedead8ec22c6baa797/image5.png" alt="" />  </p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt706bb2fa95790310/6a7f0af096b5a6804f87b38b/image6.png" alt="" /></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt978c1b0402be9a78/6a7f0af36c6eac03d9f1404b/image3.png" alt="" />  </p>
<p>Next let's look at putting something together to show the global impact, first we will update our collector config to enrich our log data with geo location.</p>
<p>Update the OTTL configuration with this new line:</p>
<pre><code>   log_statements:
      - context: log
        conditions:
          - 'attributes["log.file.name"] != nil and IsMatch(attributes["log.file.name"], "access.log")'
        statements:
          - merge_maps(attributes, ExtractPatterns(body, "^(?P&lt;client_ip&gt;\\S+)"), "upsert")
          - merge_maps(attributes, ExtractPatterns(body, "^\\S+ - (?P&lt;user&gt;\\S+)"), "upsert")
          - merge_maps(attributes, ExtractPatterns(body, "\\[(?P&lt;timestamp_raw&gt;[^\\]]+)\\]"), "upsert")
          - merge_maps(attributes, ExtractPatterns(body, "\"(?P&lt;method&gt;\\S+) "), "upsert")
          - merge_maps(attributes, ExtractPatterns(body, "\"\\S+ (?P&lt;path&gt;\\S+)\\?"), "upsert")
          - merge_maps(attributes, ExtractPatterns(body, "req_id=(?P&lt;req_id&gt;[^ ]+)"), "upsert")
          - merge_maps(attributes, ExtractPatterns(body, "\" (?P&lt;status&gt;\\d+) "), "upsert")
          - merge_maps(attributes, ExtractPatterns(body, "\" \\d+ (?P&lt;size&gt;\\d+)"), "upsert")
          - set(attributes["source.address"], attributes["client_ip"]) where attributes["client_ip"] != nil
</code></pre>
<p>Next add a new processor (you will need to download the GeoIP database from MaxMind)</p>
<pre><code>geoip:
&amp;nbsp; context: record
&amp;nbsp; source:
&amp;nbsp; &amp;nbsp; from: attributes
&amp;nbsp; providers:
&amp;nbsp; &amp;nbsp; maxmind:
&amp;nbsp; &amp;nbsp; &amp;nbsp; database_path: /opt/geoip/GeoLite2-City.mmdb
</code></pre>
<p>And add this to the log pipeline after the parse_nginx</p>
<pre><code>service:
&amp;nbsp; pipelines:
&amp;nbsp; &amp;nbsp; logs/platformlogs:
&amp;nbsp; &amp;nbsp; &amp;nbsp; receivers: [filelog/platformlogs]
&amp;nbsp; &amp;nbsp; &amp;nbsp; processors: [transform/parse_nginx, geoip, resourcedetection]
&amp;nbsp; &amp;nbsp; &amp;nbsp; exporters: [elasticsearch/otel]
</code></pre>
<p>Start the otel collector</p>
<pre><code>sudo ./otelcol --config otel.yml
</code></pre>
<p>Once the data starts flowing we can add a map visualization:  </p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0dafa7a946db893e/6a7f0af6eab5be207620a601/image2.png" alt="" /></p>
<p>Add a layer:  </p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc441c72d57a5688e/6a7f0af833fa8a5a96202602/image4.png" alt="" /></p>
<p>Use ES|QL</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf3b6e46d97b19516/6a7f0afb1967ea5020330667/image10.png" alt="" /></p>
<p>Use the following ES|QL  </p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltda5110536c01fd38/6a7f0afebdcff0321ac42d4d/image13.png" alt="" /></p>
<p>And this should give you a map showing the locations of all your NGINX server requests!  </p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta7f3df34f68ddf9e/6a7f0b01ead8ec80e4baa7a1/image11.png" alt="" /></p>
<p>As you can see, analytics is a breeze with your new Otel data collection pipeline.</p>
<h2 id="conclusionbeyondlogaggregationtooperationalintelligence">Conclusion: Beyond log aggregation to operational intelligence</h2>
<p>The journey from basic log aggregation to structured, enriched observability represents more than a technical upgrade, it's a shift in how organizations approach system understanding and incident response. By adopting OpenTelemetry for ingestion, implementing intelligent filtering to manage costs, and leveraging LogsDB's storage optimizations, you're not just modernizing your ELK stack; you're building the foundation for proactive system management.</p>
<p>The structured logs, geographic enrichment, and analytical capabilities demonstrated here transform raw log data into actionable intelligence with ES|QL. Instead of reactive grepping through logs during incidents, you now have the infrastructure to identify patterns, track user journeys, and correlate issues across your entire stack before they become critical problems.</p>
<p>But here's the key question: Are you prepared to act on these insights? Having rich, structured data is only valuable if your organization can shift from a reactive "find and fix" mentality to a proactive "predict and prevent" approach. The real evolution isn't in your logging stack, it's in your operational culture.</p>
<p>Get started with this today in <a href="https://cloud.elastic.co/serverless-registration?onboarding_token=observability">Elastic Serverless</a></p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/getting-more-from-your-logs-with-opentelemetry</link>
    <guid isPermaLink="false">getting-more-from-your-logs-with-opentelemetry</guid>
    <category><![CDATA[Logs Analytics]]></category>
    <category><![CDATA[OpenTelemetry]]></category>
    <dc:creator><![CDATA[David Hope]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0efb83b4eb43624c/6a7f0b0473d9bd7e2b29da4d/getting-more-from-your-logs-with-opentelemetry.png" length="0" type="image/png"/>
    <pubDate>Thu, 11 Sep 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Smarter Alerting Arrives with Faster Triage, Clearer Groupings, and Actionable Guidance]]></title>
    <description><![CDATA[Exploring the latest enhancements in Elastic Stack alerting, including improved related alert grouping, linking dashboards to alert rules, and embedding investigation guides into alerts.]]></description>
    <content:encoded><![CDATA[<p>In the 9.1 release, we've made significant upgrades to alerting to help SREs and operators cut through the noise, understand what's happening faster, and take meaningful action with less guesswork.</p>
<p>Here's what's new:</p>
<h2 id="improvedrelatedalertgroupingwithrelevancescoringreasoning">Improved Related Alert Grouping with Relevance Scoring &amp; Reasoning</h2>
<p>We've enhanced our related alert detection to go beyond surface-level correlations. Alerts are now grouped based on a relevance score that reflects the strength of their relationship across dimensions like:</p>
<ul>
<li><strong>Shared entities or resources</strong> (e.g. same host, pod, or service)</li>
<li><strong>Temporal proximity</strong> (alerts firing within a suspiciously short window)</li>
<li><strong>Signal similarity</strong> (e.g. spikes in logs, metrics, and traces that point to the same failure mode)</li>
</ul>
<p>More importantly, we now <strong>show the why</strong>. You'll see why an alert is grouped, whether it's sharing the same Kubernetes pod, has similar log patterns, or was triggered by the same upstream anomaly. This gives users confidence in the grouping logic and accelerates root cause analysis.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd6b9a9db1dd737ce/6a7f093f6c6eac5f4ff13f99/alerting-1.jpg" alt="Related Alerts" /></p>
<h2 id="linkdashboardstoalertrulesandgetsmartsuggestions">Link Dashboards to Alert Rules and Get Smart Suggestions</h2>
<p>You can now <strong>link dashboards directly to your alert rules</strong>, giving responders an instant visual lens into the metrics or logs that matter most for that alert. No more scrambling to remember which dashboard to check — just click and go.</p>
<p>And we've made this smarter too: Elastic will now <strong>suggest relevant dashboards</strong> based on the alert's source, rule logic, or monitored entities, helping users land on the right view without needing to configure anything upfront.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4ae716d60bfc0c17/6a7f0942ea068d40daf09d0d/alerting-2.jpg" alt="Related Alerting Dashboards" /></p>
<h2 id="investigationguidesembeddedintoalerts">Investigation Guides Embedded Into Alerts</h2>
<p>Every alert can now be configured with an <strong>investigation guide</strong>, a set of pre-configured, context-aware instructions or next steps tailored to the alert. Think of it as a playbook that's embedded right where and when you need it.</p>
<p>Use it to:</p>
<ul>
<li>Document your team's runbooks and standard triage steps or link to existing runbooks</li>
<li>Guide junior engineers or on-call responders through unfamiliar territory</li>
<li>Automate the first few steps of root cause analysis</li>
</ul>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt777147fbc5bc822d/6a7f0945b4377011ea4d6b39/alerting-3.jpg" alt="Investigation Guide" /></p>
<h2 id="whythismatters">Why This Matters</h2>
<p>These changes are all about reducing time to detect (MTTD) and time to resolve (MTTR). By:</p>
<ul>
<li>Grouping alerts more intelligently (and transparently)</li>
<li>Giving you the dashboards you need, when you need them</li>
<li>Embedding action-oriented guides in every alert</li>
</ul>
<p>We're bringing you closer to a truly streamlined incident response workflow; No swivel-chairing, no guesswork, just clarity.</p>
<p>Additionally, look at some of our other articles on Elastic Observability Labs related to analysis:</p>
<ul>
<li><p><a href="https://www.elastic.co/observability-labs/blog/ai-assistant">Using the AI Assistant in Elastic Observability to Accelerate Root Cause Analysis</a></p></li>
<li><p><a href="https://www.elastic.co/observability-labs/blog/category/logs-analytics">All of the log analytics features in Elastic Observability</a></p></li>
<li><p><a href="https://www.elastic.co/observability-labs/blog/opentelemetry">Our latest on OpenTelemetry support in Elastic Observability</a></p></li>
</ul>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/elastic-stack-observability-alerting-upgrade</link>
    <guid isPermaLink="false">elastic-stack-observability-alerting-upgrade</guid>
    <category><![CDATA[Incident Management]]></category>
    <category><![CDATA[Logs Analytics]]></category>
    <dc:creator><![CDATA[Drew Post]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4f615dc5059d80e2/6a7f0948e88c652ff600b528/cover-alerting.jpg" length="0" type="image/jpeg"/>
    <pubDate>Thu, 04 Sep 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Connecting the Dots: ES|QL Joins for Richer Observability Insights]]></title>
    <description><![CDATA[Now in tech preview, ES|QL LOOKUP JOIN lets you enrich logs, metrics, and traces at query time no need to denormalize at ingest. Add deployment, infra, or business context dynamically, reduce storage, and accelerate root cause analysis in Elastic Obervability.]]></description>
    <content:encoded><![CDATA[<p>You might have seen our recent announcement about the <a href="https://www.elastic.co/blog/esql-lookup-join-elasticsearch">arrival of SQL-style joins in Elasticsearch</a> with ES|QL's LOOKUP JOIN command (now in Tech Preview!). While that post covered the basics, let's take a closer look at this in the context of Observability. How can this new join capability specifically help engineers and SREs make sense of their logs, metrics, and traces and make Elasticsearch more storage efficient by not denormalizing as much data?</p>
<p><strong>Note:</strong> Before we jump into the details, it’s important to mention again that this type of functionality today relies on a special lookup index. It is not (yet) possible to JOIN any arbitrary index.</p>
<p>Observability isn't just about collecting data; it's about understanding it. Often, the raw telemetry data – a log line, a metric point, a trace span – lacks the full context needed for quick diagnosis or impact assessment. We need to correlate data, enrich it with business or infrastructure context, and ask more advanced questions.</p>
<p>Historically, achieving this in Elasticsearch involved techniques like denormalizing data at ingest time (using ingest pipelines with enrich processors, for example) or performing joins client-side. </p>
<p>By adding the necessary context (like host details or user attributes) as data flowed in, each document arrived fully ready for queries and analytics without extra processing later on. This approach worked well in many cases and still does, particularly when the reference data changes slowly or when the enriched fields are critical for nearly every search. </p>
<p>However, as environments become more dynamic and diverse, the need to frequently update reference data (or avoid storing repetitive fields in every document) highlighted some of the trade-offs. </p>
<p>With the introduction of ES|QL LOOKUP JOIN in Elasticsearch 8.18 and 9.0, you now have an additional, more flexible option for situations where real-time lookups and minimal duplication are desired. Both methods—ingest-time enrichment and on-the-fly LOOKUP JOIN—complement each other and remain valid, depending on use case needs around update frequency, query performance, and storage considerations.</p>
<h2 id="whylookupjoinsforobservability">Why Lookup Joins for Observability</h2>
<p>Lookup joins keep things flexible. You can decide on the fly if you’d like to look up additional information to assist you in your investigation.</p>
<p>Here are some examples:</p>
<ul>
<li><p><strong>Deployment Information:</strong> Which version of the code is generating these errors?</p></li>
<li><p><strong>Infrastructure Mapping:</strong> Which Kubernetes cluster or cloud region is experiencing high latency? What hardware does it use?</p></li>
<li><p><strong>Business Context:</strong> Are critical customers being affected by this slowdown?</p></li>
<li><p><strong>Team Ownership:</strong> Which team owns the service throwing these exceptions?</p></li>
</ul>
<p>Keeping this kind of information perfectly denormalized onto <em>every single</em> log line or metric point can be challenging and inefficient. Lookup datasets – like lists of deployments, server inventories, customer tiers, or service ownership mappings – often change independently of the telemetry data itself.</p>
<p><code>LOOKUP JOIN</code> is ideal here because:</p>
<ol>
<li><p><strong>Lookup Indices are Writable:</strong> Update your deployment list, CMDB export, or on-call rotation in the lookup index, and your <em>next</em> ES|QL query immediately uses the fresh data. No need to re-run complex enrich policies or re-index data.</p></li>
<li><p><strong>Flexibility:</strong> You decide <em>at query time</em> which context to join. Maybe today you care about deployment versions, tomorrow about cloud regions.</p></li>
<li><p><strong>Simpler Setup:</strong> As the original post highlighted, there are no enrich policies to manage. Just create an index with <code>index.mode: lookup</code> and load your data - up to 2 billion documents per lookup index.</p></li>
</ol>
<h2 id="observabilityusecasesexampleswithesql">Observability Use Cases &amp; Examples with ES|QL</h2>
<p>Let’s now look at a few examples to see how Lookup Joins can help.</p>
<h3 id="enrichingerrorlogswithdeploymentcontext">Enriching Error Logs with Deployment Context</h3>
<p>Lets say you're seeing a spike in errors for your <code>checkout-service</code>. You have logs flowing into a data stream, but they only contain the service name. The documents don’t have any information about the deployment activity itself. </p>
<pre><code>FROM logs-*
&amp;nbsp; | WHERE log.level == "error"
&amp;nbsp;&amp;nbsp;| WHERE service.name == "opbeans-ruby"
</code></pre>
<p>You need to know if a recent deployment is contributing to these errors. To do this, we can maintain a <code>deployments_info_lkp</code> index (set with <code>index.mode: lookup</code>) that maps service names to their deployment times. This index could be updated from our CI/CD pipeline automatically any time a deployment happens.</p>
<pre><code>PUT /deployments_info_lkp
{
&amp;nbsp;&amp;nbsp;"settings": {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"index.mode": "lookup"
&amp;nbsp;&amp;nbsp;},
&amp;nbsp;&amp;nbsp;"mappings": {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"properties": {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"service": {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"properties": {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"name": {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"type": "keyword"
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"deployment_time": {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"type": "date"
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"version": {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"type": "keyword"
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;}
}
# Bulk index the deployment documents
POST /_bulk
{ "index" : { "_index" : "deployments_info_lkp" } }
{ "service.name": "opbeans-ruby", "service.version": "1.0", "deployment_time": "2025-05-22T06:00:00Z" }
{ "index" : { "_index" : "deployments_info_lkp" } }
{ "service.name": "opbeans-go", "service.version": "1.1.0", "deployment_time": "2025-05-22T06:00:00Z" }
</code></pre>
<p>Using this information you can now write a query that joins these two sources.</p>
<p><em>ES|QL Query:</em></p>
<pre><code>FROM logs-* 
&amp;nbsp; | WHERE log.level == "error"
&amp;nbsp;&amp;nbsp;| WHERE service.name == "opbeans-ruby"
&amp;nbsp;&amp;nbsp;| LOOKUP JOIN deployments_info_lkp ON service.name 
</code></pre>
<p>This alone is a good step towards troubleshooting the problem. You now have the deployment_time column available for each of your error documents. The last remaining step now is to use this for further filtering. </p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte7de0d193189ba60/6a7f07b32f00b21366efe98a/discover.png" alt="Discover" /></p>
<p>Any of the data we managed to join from the lookup index can be handled as any other data we’d usually have available in the ES|QL query. This means that we can filter on it, and check if we had a recent deployment.</p>
<pre><code>FROM logs-*
  | WHERE log.level == "error"
  | WHERE service.name == "opbeans-ruby"
  | LOOKUP JOIN deployments_info_lkp ON service.name 
  | KEEP message, service.name, service.version, deployment_time 
  | WHERE deployment_time &gt; NOW() - 2h
</code></pre>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt906a78d7ecb20c61/6a7f07b642a117848495bcaa/discover2.png" alt="Discover2" /></p>
<h3 id="savingdiskspaceusingjoin">Saving disk space using JOIN</h3>
<p>Denormalizing data by including contextual information like host OS or cloud provider details directly in every log event is convenient for querying but can increase storage consumption, especially with high-volume data streams. Instead of storing this often-redundant information repeatedly, we can leverage joins to retrieve it on demand, potentially saving valuable disk space. While compression often handles repetitive data well, removing these fields entirely can still yield noticeable storage savings.</p>
<p>In this example we’ll use a dataset of 1,000,000 Kubernetes container logs using the default mapping of the Kubernetes integration, with <a href="https://www.elastic.co/docs/manage-data/data-store/data-streams/logs-data-stream">logsdb index mode</a> enabled. The starting size for this index is 35.5mb. </p>
<pre><code>GET _cat/indices/k8s-logs-default?h=index,pri.store.size
###&amp;nbsp;
k8s-logs-default &amp;nbsp; &amp;nbsp; &amp;nbsp; 35.5mb
</code></pre>
<p>Using the <a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-indices-disk-usage">disk usage API</a>, we observed that fields like host.os and cloud.* contribute roughly 5% to the total index size on disk (35.5mb). These fields can be useful in some cases, but information like the os.name is rarely queried. </p>
<pre><code>// Example host.os structure
"os": {
&amp;nbsp;&amp;nbsp;"codename": "Plow", "family": "redhat", "kernel": "6.6.56+",
&amp;nbsp;&amp;nbsp;"name": "Red Hat Enterprise Linux", "platform": "rhel", "type": "linux", "version": "9.5 (Plow)"
}

// Example cloud structure
"cloud": {
&amp;nbsp;&amp;nbsp;"account": { "id": "elastic-observability" },
&amp;nbsp;&amp;nbsp;"availability_zone": "us-central1-c",
&amp;nbsp;&amp;nbsp;"instance": { "id": "5799032384800802653", "name": "gke-edge-oblt-edge-oblt-pool-46262cd0-w905" },
&amp;nbsp;&amp;nbsp;"machine": { "type": "e2-standard-4" },
&amp;nbsp;&amp;nbsp;"project": { "id": "elastic-observability" },
&amp;nbsp;&amp;nbsp;"provider": "gcp", "region": "us-central1", "service": { "name": "GCE" }
}
</code></pre>
<p>Instead of storing this information with every document, let's instead drop this information in an ingest pipeline.</p>
<pre><code>PUT _ingest/pipeline/drop-host-os-cloud
{
&amp;nbsp;&amp;nbsp;"processors": [
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{ "remove": { "field": "host.os" } },
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{ "set": { "field": "tmp1", "value": "{{cloud.instance.id}}" } }, // Temporarily store the ID
</code></pre>
<pre><code>&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{ "remove": { "field": "cloud" } }, &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; // Remove the entire cloud object
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{ "set": { "field": "cloud.instance.id", "value": "{{tmp1}}" } }, // Restore just the cloud instance ID
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{ "remove": { "field": "tmp1", "ignore_missing": true } } &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; // Clean up temporary field
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;]
}
</code></pre>
<p>Reindexing (and <a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-indices-forcemerge">force merging to one segment</a>) now shows the following size, resulting in approximately 5% less space. </p>
<pre><code>GET _cat/indices/k8s-logs-*?h=index,pri.store.size
###&amp;nbsp;
k8s-logs-default &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 33.7mb
k8s-logs-drop-cloud-os &amp;nbsp; &amp;nbsp; &amp;nbsp; 35.5mb
</code></pre>
<p>Now, to regain access to the removed host.os and cloud.* information during analysis without storing it in every log document, we can create a lookup index. This index will store the full host and cloud metadata, keyed by the cloud.instance.id that we preserved in our logs. This instance_metadata_lkp index will be significantly smaller than the space saved across millions or billions of log lines, as it only needs one document per unique instance.</p>
<pre><code># Create the lookup index for instance metadata
PUT /instance_metadata_lkp
{
&amp;nbsp;&amp;nbsp;"settings": {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"index.mode": "lookup"
&amp;nbsp;&amp;nbsp;},
&amp;nbsp;&amp;nbsp;"mappings": {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"properties": {
</code></pre>
<pre><code>&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"cloud.instance.id": {&amp;nbsp; # The join key we kept in the logs
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"type": "keyword"
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"host.os": { &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; # The full host.os object we removed
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"type": "object",
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"enabled": false&amp;nbsp; &amp;nbsp; &amp;nbsp; # Often don't need to search sub-fields here
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"cloud": { &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; # The full cloud object we removed (mostly)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"type": "object",
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"enabled": false &amp;nbsp; &amp;nbsp; # Often don't need to search sub-fields here
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;}
}

# Bulk index sample instance metadata (keyed by cloud.instance.id)
# This data might come from your cloud provider API or CMDB
POST /_bulk
{ "index" : { "_index" : "instance_metadata_lkp", "_id": "5799032384800802653" } }
{ "cloud.instance.id": "5799032384800802653", "host.os": { "codename": "Plow", "family": "redhat", "kernel": "6.6.56+", "name": "Red Hat Enterprise Linux", "platform": "rhel", "type": "linux", "version": "9.5 (Plow)" }, "cloud": { "account": { "id": "elastic-observability" }, "availability_zone": "us-central1-c", "instance": { "id": "5799032384800802653", "name": "gke-edge-oblt-edge-oblt-pool-46262cd0-w905" }, "machine": { "type": "e2-standard-4" }, "project": { "id": "elastic-observability" }, "provider": "gcp", "region": "us-central1", "service": { "name": "GCE" } } }
</code></pre>
<p>With this setup, when you need the full host or cloud context for your logs, you can simply use LOOKUP JOIN in your ES|QL query and continue filtering on the data from the lookup index</p>
<pre><code>FROM logs-*&amp;nbsp;
&amp;nbsp;&amp;nbsp;| LOOKUP JOIN instance_metadata_lkp ON cloud.instance.id 
&amp;nbsp; | WHERE cloud.region == "us-central1"
</code></pre>
<p>This approach allows us to query the full context when needed (e.g., filtering logs by host.os.name or cloud.region) while significantly reducing the storage footprint of the high-volume log indices by avoiding redundant data denormalization.</p>
<p>It should be noted that low cardinality metadata fields generally compress well and a large part of the storage savings in this case come from the “text” mapping of the host.os.name and cloud.instance.name field. Make sure to use the disk usage API to evaluate if this approach would be worth it in your specific use case. </p>
<h2 id="gettingstartedwithlookupsforobservability">Getting Started with Lookups for Observability</h2>
<p>Creating the necessary lookup indices is straightforward. As detailed in our <a href="http://link-to-original-blog-post">initial blog post</a>, you can use Kibana's Index Management UI, the Create Index API, or the File Upload utility – the key is setting <code>"index.mode": "lookup"</code> in the index settings.</p>
<p>For Observability, consider automating the population of these lookup indices:</p>
<ul>
<li><p>Export data periodically from your CMDB, CRM, or HR systems.</p></li>
<li><p>Have your CI/CD pipeline update the <code>deployments_lkp</code> index upon successful deployment.</p></li>
<li><p>Use tools like Logstash with an <code>elasticsearch</code> output configured to write to your lookup index.</p></li>
</ul>
<h2 id="anoteonperformanceandalternatives">A Note on Performance and Alternatives</h2>
<p>While incredibly powerful, joins aren't free. Each <code>LOOKUP JOIN</code> adds processing overhead to your query. For contextual data that is <em>very</em> static (e.g., the cloud region a host <em>permanently</em> resides in) and needed in <em>almost every</em> query against that data, the traditional approach of enriching at ingest time might still be slightly more performant for those specific queries, trading upfront processing and storage for query speed.</p>
<p>However, for the dynamic, flexible, and targeted enrichment scenarios common in Observability – like mapping to ever-changing deployments, user segments, or team structures – <code>LOOKUP JOIN</code> offers a compelling, efficient, and easier-to-manage solution.</p>
<h2 id="conclusion">Conclusion</h2>
<p>ES|QL's <code>LOOKUP JOIN</code> is making it easy to correlate and enrich your logs, metrics, and traces with up-to-date external information <em>at query time</em>; you can move faster from detecting problems to understanding their scope, impact, and root cause.</p>
<p>This feature is currently in Technical Preview in Elasticsearch 8.18 and Serverless, available now on Elastic Cloud. We encourage you to try it out with your own Observability data and share your feedback using the "Submit feedback" button in the ES|QL editor in Discover. We're excited to see how you use it to connect the dots in your systems!</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/elastic-esql-join-observability</link>
    <guid isPermaLink="false">elastic-esql-join-observability</guid>
    <category><![CDATA[Logs Analytics]]></category>
    <dc:creator><![CDATA[Luca Wintergerst]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta7703fd6afaca645/6a7f07b93cab1c7ce20e4640/esql-join.jpg" length="0" type="image/jpeg"/>
    <pubDate>Thu, 29 May 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Dynamic workload discovery on Kubernetes now supported with EDOT Collector]]></title>
    <description><![CDATA[Discover how Elastic's OpenTelemetry Collector leverages Kubernetes pod annotations providing dynamic workload discovery and improves automated metric and log collection for Kubernetes clusters.]]></description>
    <content:encoded><![CDATA[<p>At Elastic, Kubernetes is one of the most significant observability use cases we focus on.
We want to provide the best onboarding experience and lifecycle management based on real-world GitOps best practices. </p>
<p>OpenTelemetry recently <a href="https://opentelemetry.io/blog/2025/otel-collector-k8s-discovery/">published a blog</a> on how to do <code>Autodiscovery based on Kubernetes Pods' annotations</code> with the OpenTelemetry Collector. </p>
<p>In this blog post, we will talk about how to use this Kubernetes-related feature of the OpenTelemetry Collector,
which is already available with the Elastic Distribution of the OpenTelemetry (EDOT) Collector.</p>
<p>In addition to this feature, at Elastic, we heavily invest in making OpenTelemetry the best, standardized ingest solution for Observability.
You might already have seen us focusing on:</p>
<ul>
<li><p><a href="https://www.elastic.co/blog/ecs-elastic-common-schema-otel-opentelemetry-announcement">Semantic Conventions standardization</a></p></li>
<li><p>significant <a href="https://www.elastic.co/observability-labs/blog/elastics-collaboration-opentelemetry-filelog-receiver">log collection improvements</a></p></li>
<li><p>various other topics around <a href="https://www.elastic.co/observability-labs/blog/auto-instrumentation-go-applications-opentelemetry">instrumentation</a></p></li>
<li><p><a href="https://www.elastic.co/observability-labs/blog/elastic-donation-proposal-to-contribute-profiling-agent-to-opentelemetry">profiling</a></p></li>
</ul>
<p>Let's walk you through a hands-on journey using the EDOT Collector covering various use cases you might encounter in the real world, highlighting the capabilities of this powerful feature.</p>
<h2 id="configuringedotcollector">Configuring EDOT Collector</h2>
<p>The Collector’s configuration is not our main focus here, since based on the nature of this feature it is minimal,
letting workloads define how they should be monitored.</p>
<p>To illustrate the point, here is the Collector configuration snippet that enables the feature for both logs and metrics:</p>
<pre><code>receivers:
    receiver_creator/metrics:
      watch_observers: [k8s_observer]
      discovery:
        enabled: true
      receivers:

    receiver_creator/logs:
      watch_observers: [k8s_observer]
      discovery:
        enabled: true
      receivers:
</code></pre>
<p>You can include the above in the EDOT’s Collector configuration, specifically the
<a href="https://github.com/elastic/elastic-agent/blob/v9.0.0-rc1/deploy/helm/edot-collector/kube-stack/values.yaml#L339">receivers’ section</a>.</p>
<p>Since logs collection in our examples will happen from the discovery feature make sure that the static filelog receiver
<a href="https://github.com/elastic/elastic-agent/blob/v9.0.0-rc1/deploy/helm/edot-collector/kube-stack/values.yaml#L348">configuration block</a> is removed
and its <a href="https://github.com/elastic/elastic-agent/blob/v9.0.0-rc1/deploy/helm/edot-collector/kube-stack/values.yaml#L193"><code>preset</code></a>
is disabled (i.e. set to <code>false</code>) to avoid having log duplication.</p>
<p>Make sure that the receiver creator is properly added in the pipelines for
<a href="https://github.com/elastic/elastic-agent/blob/v9.0.0-rc1/deploy/helm/edot-collector/kube-stack/values.yaml#L471">logs</a>
(in addition to removing the <code>filelog</code> receiver completely)
and <a href="https://github.com/elastic/elastic-agent/blob/v9.0.0-rc1/deploy/helm/edot-collector/kube-stack/values.yaml#L484">metrics</a>
respectively.</p>
<p>Ensure that <a href="https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/v0.122.0/extension/observer/k8sobserver/README.md"><code>k8sobserver</code></a>
is enabled as part of the extensions:</p>
<pre><code>extensions:
  k8s_observer:
    observe_nodes: true
    observe_services: true
    observe_ingresses: true

// ...

service:
  extensions: [k8s_observer]
</code></pre>
<p>Last but not least, ensure the log files' volume is mounted properly:</p>
<pre><code>volumeMounts:
 - name: varlogpods
   mountPath: /var/log/pods
   readOnly: true

volumes:
  - name: varlogpods
    hostPath:
      path: /var/log/pods
</code></pre>
<p>Once the configuration is ready follow the <a href="https://www.elastic.co/docs/reference/opentelemetry/quickstart/">Kubernetes quickstart guides on how to deploy the EDOT Collector</a>.
Make sure to replace the <code>values.yaml</code> file linked in the quickstart guide with the file that includes the above-described modifications.</p>
<h3 id="collectingmetricsfrommovingtargetsbasedontheirannotations">Collecting Metrics from Moving Targets Based on Their Annotations</h3>
<p>In this example, we have a Deployment with a Pod spec that consists of two different containers.
One container runs a Redis server, while the other runs an NGINX server. Consequently, we want to provide
different hints for each of these target containers.</p>
<p>The annotation-based discovery feature supports this, allowing us to specify metrics annotations
per exposed container port.</p>
<p>Here is how the complete spec file looks:</p>
<pre><code>apiVersion: v1
kind: ConfigMap
metadata:
  name: nginx-conf
data:
  nginx.conf: |
    user  nginx;
    worker_processes  1;
    error_log  /dev/stderr warn;
    pid        /var/run/nginx.pid;
    events {
      worker_connections  1024;
    }
    http {
      include       /etc/nginx/mime.types;
      default_type  application/octet-stream;

      log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                        '$status $body_bytes_sent "$http_referer" '
                        '"$http_user_agent" "$http_x_forwarded_for"';
      access_log  /dev/stdout main;
      server {
          listen 80;
          server_name localhost;

          location /nginx_status {
              stub_status on;
          }
      }
      include /etc/nginx/conf.d/*;
    }
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: redis-deployment
  labels:
    app: redis
spec:
  replicas: 1
  selector:
    matchLabels:
      app: redis
  template:
    metadata:
      labels:
        app: redis
      annotations:
        # redis container port hints
        io.opentelemetry.discovery.metrics.6379/enabled: "true"
        io.opentelemetry.discovery.metrics.6379/scraper: redis
        io.opentelemetry.discovery.metrics.6379/config: |
          collection_interval: "20s"
          timeout: "10s"

        # nginx container port hints
        io.opentelemetry.discovery.metrics.80/enabled: "true"
        io.opentelemetry.discovery.metrics.80/scraper: nginx
        io.opentelemetry.discovery.metrics.80/config: |
          endpoint: "http://`endpoint`/nginx_status"
          collection_interval: "30s"
          timeout: "20s"
    spec:
      volumes:
      - name: nginx-conf
        configMap:
          name: nginx-conf
          items:
            - key: nginx.conf
              path: nginx.conf
      containers:
        - name: webserver
          image: nginx:latest
          ports:
            - containerPort: 80
              name: webserver
          volumeMounts:
            - mountPath: /etc/nginx/nginx.conf
              readOnly: true
              subPath: nginx.conf
              name: nginx-conf
        - image: redis
          imagePullPolicy: IfNotPresent
          name: redis
          ports:
            - name: redis
              containerPort: 6379
              protocol: TCP
</code></pre>
<p>When this workload is deployed, the Collector will automatically discover it and identify the specific annotations.
After this, two different receivers will be started, each one responsible for each of the target containers.</p>
<h3 id="collectinglogsfrommultipletargetcontainers">Collecting Logs from Multiple Target Containers</h3>
<p>The annotation-based discovery feature also supports log collection based on the provided annotations.
In the example below, we again have a Deployment with a Pod consisting of two different containers,
where we want to apply different log collection configurations.
We can specify annotations that are scoped to individual container names:</p>
<pre><code>apiVersion: apps/v1
kind: Deployment
metadata:
  name: busybox-logs-deployment
  labels:
    app: busybox
spec:
  replicas: 1
  selector:
    matchLabels:
      app: busybox
  template:
    metadata:
      labels:
        app: busybox
      annotations:
        io.opentelemetry.discovery.logs.lazybox/enabled: "true"
        io.opentelemetry.discovery.logs.lazybox/config: |
          operators:
            - id: container-parser
              type: container
            - id: some
              type: add
              field: attributes.tag
              value: hints-lazybox
        io.opentelemetry.discovery.logs.busybox/enabled: "true"
        io.opentelemetry.discovery.logs.busybox/config: |
          operators:
            - id: container-parser
              type: container
            - id: some
              type: add
              field: attributes.tag
              value: hints-busybox
    spec:
      containers:
        - name: busybox
          image: busybox
          args:
            - /bin/sh
            - -c
            - while true; do echo "otel logs from busybox at $(date +%H:%M:%S)" &amp;&amp; sleep 5s; done
        - name: lazybox
          image: busybox
          args:
            - /bin/sh
            - -c
            - while true; do echo "otel logs from lazybox at $(date +%H:%M:%S)" &amp;&amp; sleep 25s; done
</code></pre>
<p>The above configuration enables two different filelog receiver instances, each applying a unique parsing configuration.
This is handy when we know how to parse specific technology logs, such as Apache server access logs.</p>
<h3 id="combiningbothmetricsandlogscollection">Combining Both Metrics and Logs Collection</h3>
<p>In our third example, we illustrate how to define both metrics and log annotations on the same workload.
This allows us to collect both signals from the discovered workload.
Below is a Deployment with a Pod consisting of a Redis server and a BusyBox container that performs dummy log writing.
We can target annotations to the port and container levels to collect metrics from the Redis server using
the Redis receiver, and logs from the BusyBox using the filelog receiver. Here’s how:</p>
<pre><code>apiVersion: apps/v1
kind: Deployment
metadata:
  name: redis-deployment
  labels:
    app: redis
spec:
  replicas: 1
  selector:
    matchLabels:
      app: redis
  template:
    metadata:
      labels:
        app: redis
      annotations:
        io.opentelemetry.discovery.metrics.6379/enabled: "true"
        io.opentelemetry.discovery.metrics.6379/scraper: redis
        io.opentelemetry.discovery.metrics.6379/config: |
          collection_interval: "20s"
          timeout: "10s"

        io.opentelemetry.discovery.logs.busybox/enabled: "true"
        io.opentelemetry.discovery.logs.busybox/config: |
          operators:
            - id: container-parser
              type: container
            - id: some
              type: add
              field: attributes.tag
              value: hints
    spec:
      containers:
        - image: redis
          imagePullPolicy: IfNotPresent
          name: redis
          ports:
            - name: redis
              containerPort: 6379
              protocol: TCP
        - name: busybox
          image: busybox
          args:
            - /bin/sh
            - -c
            - while true; do echo "otel logs at $(date +%H:%M:%S)" &amp;&amp; sleep 15s; done
</code></pre>
<h3 id="exploreandanalysedatacomingfromdynamictargetsinelastic">Explore and analyse data coming from dynamic targets in Elastic</h3>
<p>Once the target Pods are discovered and the Collector has started collecting telemetry data from them,
we can then explore this data in Elastic. In Discover we can search for Redis and NGINX metrics as well as
logs collected from the Busybox container. Here is how it looks like:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta9de2760a872abd6/6a85cc4118249c3b8a18f7df/discoverlogs.png" alt="Logs Discovery" />
<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltba39c0a6fbf3af42/6a85cc459d2b718795f939ae/discovermetrics.png" alt="Metrics Discovery" /></p>
<h2 id="summary">Summary</h2>
<p>The examples above showcase how users of our OpenTelemetry Collector can take advantage of this new feature
— one we played a major role in developing.</p>
<p>For this, we leveraged our years of experience with similar features already supported in
<a href="https://www.elastic.co/guide/en/beats/metricbeat/current/configuration-autodiscover-hints.html">Metricbeat</a>,
<a href="https://www.elastic.co/guide/en/beats/filebeat/current/configuration-autodiscover-hints.html">Filebeat</a>, and
<a href="https://www.elastic.co/guide/en/fleet/current/hints-annotations-autodiscovery.html">Elastic-Agent</a>.
This makes us extremely happy and confident, as it closes the feature gap between Elastic's specific
monitoring agents and the OpenTelemetry Collector — making it even better.</p>
<p>Interested in learning more? Visit the
<a href="https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/receiver/receivercreator/README.md#generate-receiver-configurations-from-provided-hints">documentation</a>
and give it a try by following our <a href="https://www.elastic.co/docs/reference/opentelemetry/quickstart/">EDOT quickstart guide</a>.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/k8s-discovery-with-EDOT-collector</link>
    <guid isPermaLink="false">k8s-discovery-with-EDOT-collector</guid>
    <category><![CDATA[Kubernetes]]></category>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[Logs Analytics]]></category>
    <category><![CDATA[Metrics]]></category>
    <dc:creator><![CDATA[Christos Markou,Alexander Wert]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt65d5d28ff5f7fe2d/6a85cc489d2b71658bf939b2/k8s-discovery-new.jpg" length="0" type="image/jpeg"/>
    <pubDate>Tue, 01 Apr 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Monitor your C++ Applications with Elastic APM]]></title>
    <description><![CDATA[In this article we will be using the Opentelemetry CPP client to monitor C++ application within Elastic APM]]></description>
    <content:encoded><![CDATA[<p>One of the main challenges that developers, SREs, and DevOps professionals face is the absence of an extensive tool that provides them with visibility to their application stack. Many of the APM solutions out on the market do provide methods to monitor applications that were built on languages and frameworks (i.e., .NET, Java, Python, etc.) but fall short when it comes to C++ applications.</p>
<p>Luckily, Elastic has been one of the leading solutions in observability space and a contributor to the OpenTelemetry project. Elastic’s unique position and its extensive observability capabilities allows end-users to monitor applications built with object-oriented programming languages &amp; Framework in a variety of ways.</p>
<p>In this blog we will explore using Elastic APM to investigate C++ traces with the OpenTelemetry client. We will be providing a comprehensive guide on how to implement the OpenTelemetry client for C++ applications and connecting to Elastic APM solutions. While OTel has its libraries, and this blog reviews how to use the OTel CPP library, Elastic also has its own Elastic Distributions of OpenTelemetry, which were developed to provide commercial support, and are completely upstreamed regularly.</p>
<p>Here are some resources to help get you started:</p>
<ul>
<li><p><a href="https://www.elastic.co/guide/en/observability/current/apm-open-telemetry.html">Use OpenTelemetry with APM</a></p></li>
<li><p><a href="https://github.com/open-telemetry/opentelemetry-cpp">The OpenTelemetry C++ Client</a></p></li>
<li><p><a href="https://opentelemetry.io/docs/languages/cpp/">OpenTelemetry C++ Docs</a></p></li>
</ul>
<h2 id="stepbystepguide">Step by Step Guide</h2>
<h2 id="prerequisites">Prerequisites</h2>
<ul>
<li>### Environment</li>
</ul>
<p>Choosing an environment is quite important as there is limited support for the OTEL client. We have experimented with using multiple Operating Systems and here are the suggestions:</p>
<ul>
<li><p>Ubuntu 22.04</p></li>
<li><p>Debian 11 Bullseye</p></li>
<li><p>For this guide we are focusing on Ubuntu 22.04.</p></li>
<li><p>Machine: 2 vCPU, 4GB is sufficient.</p></li>
<li><p>Image: Ubuntu 22.04 LTS (x86_64).</p></li>
<li><p>Disk: ~30 GB is enough.</p></li>
</ul>
<h2 id="implementationmethodnbsp">Implementation method </h2>
<p>We have experimented with multiple methods but we found that the most suitable approach is to use a package manager. After extensive testing, It appears that trying to run otel-cpp client could be quite challenging to the users. If practitioners desire to build with tools such as CMake and Bazel that is a viable solution. With that, as we tested both methods it became obvious that we were spending most of our time and effort fixing compatibility and dependencies’ issues for the OS Vs. Focusing on sending data to our APM. Hence we decided to move to a different method.</p>
<p>The main issues that we kept running into as we test are:</p>
<ul>
<li><p>Compatibility of packages.</p></li>
<li><p>Availability of packages.</p></li>
<li><p>Dependencies of libraries and packages.</p></li>
</ul>
<p>In this guide we will use vcpkg since it allows us to bring in all the dependencies required to run the Opentelemetry C++ client.</p>
<h2 id="installingrequiredostools">Installing required OS tools</h2>
<h3 id="updatepackagelists">Update package lists</h3>
<pre><code>    sudo apt-get update
</code></pre>
<p>Install build essentials, cmake, git, and sqlite dev library</p>
<pre><code>    sudo apt-get install -y build-essential cmake git curl zip unzip sqlite3 libsqlite3-dev
</code></pre>
<p>sqlite3 and libsqlite3-dev allow us to build/run SQLite queries in our C++ code.</p>
<h3 id="setupvcpkg">Set Up vcpkg</h3>
<p>vcpkg is the C++ package manager that we’ll use to install opentelemetry-cpp client.</p>
<pre><code>    # Clone vcpkg
    cd ~
    git clone https://github.com/microsoft/vcpkg.git
</code></pre>
<pre><code>    # Bootstrap
    cd ~/vcpkg
    ./bootstrap-vcpkg.sh
</code></pre>
<h3 id="installopentelemetrycwithotlpgrpc">Install OpenTelemetry C++ with OTLP gRPC</h3>
<p>In this guide we focus on trace export to Elastic. At time of writing, vcpkg’s opentelemetry-cpp</p>
<p>version 1.18.0 fully supports traces but has limited direct metrics exporting.</p>
<h3 id="installthepackage">Install the package</h3>
<pre><code>    cd ~/vcpkg
    ./vcpkg install opentelemetry-cpp[otlp-grpc]:x64-linux
</code></pre>
<p><strong>Note</strong></p>
<p>Sometimes when installing opentelemetry-cpp on linux it doesn't install all the required packages. As a workaround if you run into that case, try running again but pass a flag to allow-unsupported:</p>
<pre><code>    ./vcpkg install opentelemetry-cpp[*]:x64-linux --allow-unsupported
</code></pre>
<h3 id="verify">Verify</h3>
<pre><code>    ./vcpkg list | grep opentelemetry-cpp
</code></pre>
<p>The output thould be something like this: </p>
<pre><code>opentelemetry-cpp:x64-linux 1.18.0
</code></pre>
<h2 id="createthecprojectwithdatabasespans">Create the C++ Project with Database Spans</h2>
<p>We’ll build a sample in ~/otel-app that:</p>
<ul>
<li><p>Uses SQLite to do basic CREATE/INSERT/SELECT queries. This is helpful to showcase capturing transactions for apps that use databases on Elastic APM.</p></li>
<li><p>Generate random traces to showcase how they are captured on Elastic APM.</p></li>
</ul>
<p>This app is going to generate random queries where some will contain database transactions and some are just application traces. Each query is contained in a child span, so they appear in APM as separate database transactions.</p>
<pre><code># Below is the structure of our project
</code></pre>
<pre><code>    otel-app/
    ├── main.cpp
    └── CMakeLists.txt
</code></pre>
<h3 id="createappproject">Create App Project</h3>
<pre><code>    cd ~
    mkdir otel-app
    cd otel-app
</code></pre>
<p>Inside this project we will create two files</p>
<ul>
<li><p>main.cpp</p></li>
<li><p>CMakeLists.txt</p></li>
</ul>
<p>Keep in mind that main.cpp is where you are going to pass the otel exporters that are going to send data to the Elastic cluster. So for your tech stack it would be your application's source code.</p>
<h4 id="sampleapplicationcode">Sample application code</h4>
<pre><code>    main.cpp
    // Below we declare required libraries that we will be using to ship
    // traces to Elastic APM
    #include &lt;opentelemetry/exporters/otlp/otlp_grpc_exporter.h&gt;
    #include &lt;opentelemetry/sdk/trace/tracer_provider.h&gt;
    #include &lt;opentelemetry/sdk/trace/simple_processor.h&gt;
    #include &lt;opentelemetry/trace/provider.h&gt;

    #include &lt;sqlite3.h&gt;
    #include &lt;chrono&gt;
    #include &lt;iostream&gt;
    #include &lt;thread&gt;
    #include &lt;cstdlib&gt;&amp;nbsp; // for rand(), srand()
    #include &lt;ctime&gt;&amp;nbsp; &amp;nbsp; // for time()

    // Namespace aliases
    namespace trace_api = opentelemetry::trace;
    namespace sdktrace&amp;nbsp; = opentelemetry::sdk::trace;
    namespace otlp&amp;nbsp; &amp;nbsp; &amp;nbsp; = opentelemetry::exporter::otlp;

    // Below we are using a helper function to run SQLITE statement inside&amp;nbsp;
    // child span
    bool ExecuteSql(sqlite3 *db, const std::string &amp;sql,
    &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;trace_api::Tracer &amp;tracer,
    &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const std::string &amp;span_name)
    {
    &amp;nbsp;&amp;nbsp;// Starting the child span
    &amp;nbsp;&amp;nbsp;auto db_span = tracer.StartSpan(span_name);
    &amp;nbsp;&amp;nbsp;{
    &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;auto scope = tracer.WithActiveSpan(db_span);

    &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// Here we mark Database attributes for clarity in APM
    &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;db_span-&gt;SetAttribute("db.system", "sqlite");
    &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;db_span-&gt;SetAttribute("db.statement", sql);

    &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;char *errMsg = nullptr;
    &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;int rc = sqlite3_exec(db, sql.c_str(), nullptr, nullptr, &amp;errMsg);
    &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (rc != SQLITE_OK)
    &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{
    &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;db_span-&gt;AddEvent("SQLite error: " + std::string(errMsg ? errMsg : "unknown"));
    &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;sqlite3_free(errMsg);
    &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;db_span-&gt;End();
    &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return false;
    &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
    &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;db_span-&gt;AddEvent("Query OK");
    &amp;nbsp;&amp;nbsp;}
    &amp;nbsp;&amp;nbsp;db_span-&gt;End();
    &amp;nbsp;&amp;nbsp;return true;
    }

    /**
    &amp;nbsp;* DoNonDbWork - Simulate some other operation
    &amp;nbsp;*/
    void DoNonDbWork(trace_api::Tracer &amp;tracer, const std::string &amp;span_name)
    {
    &amp;nbsp;&amp;nbsp;auto child_span = tracer.StartSpan(span_name);
    &amp;nbsp;&amp;nbsp;{
    &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;auto scope = tracer.WithActiveSpan(child_span);
    &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// Just sleep or do some "fake" work
    &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;std::cout &lt;&lt; "[TRACE] Doing non-DB work for " &lt;&lt; span_name &lt;&lt; "...\n";
    &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;std::this_thread::sleep_for(std::chrono::milliseconds(200 + rand() % 300));
    &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;child_span-&gt;AddEvent("Finished non-DB work");
    &amp;nbsp;&amp;nbsp;}
    &amp;nbsp;&amp;nbsp;child_span-&gt;End();
    }

    int main()
    {
    &amp;nbsp;&amp;nbsp;// Seed random generator for example
    &amp;nbsp;&amp;nbsp;srand(static_cast&lt;unsigned&gt;(time(nullptr)));

    &amp;nbsp;&amp;nbsp;// 1) Create OTLP exporter for traces
    &amp;nbsp;&amp;nbsp;otlp::OtlpGrpcExporterOptions opts;
    &amp;nbsp;&amp;nbsp;auto exporter = std::make_unique&lt;otlp::OtlpGrpcExporter&gt;(opts);

    &amp;nbsp;&amp;nbsp;// 2) Simple Span Processor
    &amp;nbsp;&amp;nbsp;auto processor = std::make_unique&lt;sdktrace::SimpleSpanProcessor&gt;(std::move(exporter));

    &amp;nbsp;&amp;nbsp;// 3) Tracer Provider
    &amp;nbsp;&amp;nbsp;auto sdk_tracer_provider = std::make_shared&lt;sdktrace::TracerProvider&gt;(std::move(processor));
    &amp;nbsp;&amp;nbsp;auto tracer = sdk_tracer_provider-&gt;GetTracer("my-cpp-multi-app");

    &amp;nbsp;&amp;nbsp;// Prepare an in-memory SQLite DB (for random DB usage)
    &amp;nbsp;&amp;nbsp;sqlite3 *db = nullptr;
    &amp;nbsp;&amp;nbsp;int rc = sqlite3_open(":memory:", &amp;db);
    &amp;nbsp;&amp;nbsp;if (rc == SQLITE_OK)
    &amp;nbsp;&amp;nbsp;{
    &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// Create a table so we can do inserts/reads
    &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ExecuteSql(db, "CREATE TABLE IF NOT EXISTS items (id INTEGER PRIMARY KEY, info TEXT);",
    &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;*tracer.get(), "db_create_table");
    &amp;nbsp;&amp;nbsp;}

    &amp;nbsp;&amp;nbsp;// Create the following loop to generate multiple transactions
    &amp;nbsp;&amp;nbsp;int num_transactions = 5;&amp;nbsp; // Change this variable to the desired number of transaction
    &amp;nbsp;&amp;nbsp;for (int i = 1; i &lt;= num_transactions; i++)
    &amp;nbsp;&amp;nbsp;{
    &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// Each iteration is a top-level transaction
    &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;std::string transaction_name = "transaction_" + std::to_string(i);
    &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;auto parent_span = tracer-&gt;StartSpan(transaction_name);
    &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{
    &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;auto scope = tracer-&gt;WithActiveSpan(parent_span);

    &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;std::cout &lt;&lt; "\n=== Starting " &lt;&lt; transaction_name &lt;&lt; " ===\n";

    &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// Randomly select whether a transaction will interact with the database or not.
    &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;bool doDb = (rand() % 2 == 0); // 50% chance

    &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (doDb &amp;&amp; db)
    &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{
    &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// Insert random data
    &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;std::string insert_sql = "INSERT INTO items (info) VALUES ('Item " + std::to_string(i) + "');";
    &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ExecuteSql(db, insert_sql, *tracer.get(), "db_insert_item");

    &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// Select from DB
    &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ExecuteSql(db, "SELECT * FROM items;", *tracer.get(), "db_select_items");
    &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
    &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;else
    &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{
    &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// Do some random non-DB tasks
    &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;DoNonDbWork(*tracer.get(), "non_db_task_1");
    &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;DoNonDbWork(*tracer.get(), "non_db_task_2");
    &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

    &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// Sleep a little to simulate transaction time
    &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;std::this_thread::sleep_for(std::chrono::milliseconds(200));
    &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
    &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;parent_span-&gt;End();
    &amp;nbsp;&amp;nbsp;}

    &amp;nbsp;&amp;nbsp;// Close DB
    &amp;nbsp;&amp;nbsp;sqlite3_close(db);

    &amp;nbsp;&amp;nbsp;// Extra sleep to ensure final flush
    &amp;nbsp;&amp;nbsp;std::cout &lt;&lt; "\n[INFO] Sleeping 5 seconds to allow flush...\n";
    &amp;nbsp;&amp;nbsp;std::this_thread::sleep_for(std::chrono::seconds(5));
    &amp;nbsp;&amp;nbsp;std::cout &lt;&lt; "[INFO] Exiting.\n";
    &amp;nbsp;&amp;nbsp;return 0;
    }
</code></pre>
<h5 id="whatdoesthecodedo">What does the code do?</h5>
<p>We create 5 top-level “transaction_i” spans.</p>
<p>For each transaction, we randomly choose to do DB or non-DB work</p>
<pre><code>- If DB: Insert a row, then select. Each is a child span.

- If non-DB: We do two “fake tasks” (child spans).
</code></pre>
<p>Once we finish, we close the database connection and wait 5 seconds for data flush.</p>
<h4 id="sampleinstructionfile">Sample instruction file</h4>
<p>CMakeLists.txt : This file contains instructions describing the source files and targets.</p>
<pre><code>    cmake_minimum_required(VERSION 3.10)
    project(OtelApp VERSION 1.0)

    set(CMAKE_CXX_STANDARD 11)
    set(CMAKE_CXX_STANDARD_REQUIRED ON)

    # Here we are pointing to use the vcpkg toolchain
    set(CMAKE_TOOLCHAIN_FILE "PATH-TO/vcpkg.cmake" CACHE STRING "Vcpkg toolchain file")

    find_package(opentelemetry-cpp CONFIG REQUIRED)

    add_executable(otel_app main.cpp)

    # Below we are linking the OTLP gRPC exporter, trace library, and sqlite3
    target_link_libraries(otel_app PRIVATE
    &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;opentelemetry-cpp::otlp_grpc_exporter
    &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;opentelemetry-cpp::trace
    &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;sqlite3
    )
</code></pre>
<h4 id="declareenvironmentalvariables">Declare Environmental Variables</h4>
<p>Here we are going to export our Elastic Cloud endpoints as environmental variables</p>
<p>You can get that information by doing the following:</p>
<ol>
<li><p>Login into your elastic cloud</p></li>
<li><p>Go into your deployment</p></li>
<li><p>On the Left hand side, click on the hamburger menu and scroll down to “Integrations”</p></li>
<li><p>Go on the search bar inside the integration and type “APM”</p></li>
</ol>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt45627cb3f8bd9eec/6a7f19071967ea7d50330b4a/APM-Search.png" alt="" /></p>
<ol>
<li><p>Click on the APM integration</p></li>
<li><p>Scroll down and click on the OpenTelemetry Option on the far left side</p></li>
</ol>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5eb12d359510901a/6a7f190afc63ab721064d05a/highlighted.png" alt="" /></p>
<ol>
<li>You should be able to see values similar to the screenshot below. Once you copy the values to export, click on launch APM.</li>
</ol>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt991641419ccdcfb4/6a7f190d3cab1c7d650e4c51/highlighted2.png" alt="" /></p>
<p>As you copy the required values, go ahead and export them.</p>
<pre><code>    export OTEL_EXPORTER_OTLP_ENDPOINT="APM-ENDPOINT"
    export OTEL_EXPORTER_OTLP_HEADERS="KEY"
    export OTEL_RESOURCE_ATTRIBUTES="service.name=my-app,service.version=1.0.0,deployment.environment=dev"
</code></pre>
<p>Note that the elastic OTEL_EXPORTER_OTLP_HEADERS value usually starts with “Authorization=Bearer” make sure that you convert the upper case “A” in authorization to a lower case “a”. This is due to the fact that the otel header exporter expects a lower case “a” for authorization.</p>
<h3 id="buildandrun">Build and Run</h3>
<p>Once we create the two files we then move to building the application.</p>
<pre><code>cd ~/otel-app
mkdir -p build
cd build

cmake -DCMAKE_TOOLCHAIN_FILE=~/vcpkg/scripts/buildsystems/vcpkg.cmake \
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-DCMAKE_PREFIX_PATH=~/vcpkg/installed/x64-linux/share \
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;..
make
</code></pre>
<p>Once make is successful run the the application</p>
<pre><code>./otel-app
</code></pre>
<p>You should be able to see the script execute with a similar console output</p>
<pre><code>    Console outcome:
    === Starting transaction_1 ===
    [TRACE] Doing non-DB work for non_db_task_1...
    [TRACE] Doing non-DB work for non_db_task_2...

    === Starting transaction_2 ===
    [TRACE] Doing DB work for doDb_task_1...
    [TRACE] Doing DB work for doDb_task_2...

    === Starting transaction_3 ===
    [TRACE] Doing non-DB work for non_db_task_1...
    [TRACE] Doing non-DB work for non_db_task_2...

    === Starting transaction_4 ===
    [TRACE] Doing non-DB work for non_db_task_1...
    [TRACE] Doing non-DB work for non_db_task_2...

    === Starting transaction_5 ===
    [TRACE] Doing non-DB work for non_db_task_1...
    [TRACE] Doing non-DB work for non_db_task_2...

    [INFO] Sleeping 5 seconds to allow flush...
    [INFO] Exiting.
</code></pre>
<p>Once the script executes you should be able to observe those traces on Elastic APM similar to the screenshots below.</p>
<h3 id="observeinelasticapm">Observe in Elastic APM</h3>
<p>Go to Elastic Cloud, open your deployment, and navigate to Observability &gt; APM.</p>
<p>Look for the app name in the service list (as defined by OTEL_RESOURCE_ATTRIBUTES).</p>
<p>Inside that service’s Traces tab, you’ll find multiple transactions like “transaction_1”,</p>
<p>“transaction_2”, etc.</p>
<p>Expanding each transaction shows child spans:</p>
<pre><code>- Possibly db_insert_item and db_select_items if random DB path was taken.

- Otherwise, non_db_task_1 and non_db_task_2.
</code></pre>
<p>You can see how some transactions do DB calls, some do not, each with different spans.</p>
<p>This variety demonstrates how your real application might produce multiple different</p>
<p>“routes” or “operations.”</p>
<h4 id="servicemap">Service Map</h4>
<p>If everything runs correctly, you should be able to view your services and see service maps for your application.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2b097f6854507a5b/6a7f1910b6b7346a13e491a0/Service-Map.png" alt="" /></p>
<h4 id="services">Services</h4>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd03422687896996d/6a7f19121967ea32f7330b4e/Services.png" alt="" /></p>
<h4 id="myelasticapp">My Elastic App</h4>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt674df3a1b282a2af/6a7f191633fa8a5c28202b60/Overview-transactions.png" alt="" /></p>
<h4 id="apptransactions">App Transactions</h4>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdaa6307b2d585f39/6a7f191842a117df4295c2db/Transactions2.png" alt="" /></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt77f910a5f0aa9405/6a7f191b5967e583f55dd68d/Trace-db.png" alt="" /></p>
<h4 id="dependencies">Dependencies</h4>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd25a359057e1f0a3/6a7f191d6693f845a8664357/Dependecies.png" alt="" /></p>
<h4 id="logs">Logs</h4>
<p>Navigate to your logs window/Discover to see the incoming application logs</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9807252308e58d22/6a7f19202f00b234edefef0f/Logs.png" alt="" /></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltcf99221e3f2250aa/6a7f1923448e4e993f5c0b3e/Logs2.png" alt="" /></p>
<h4 id="patterns">Patterns</h4>
<p>Log pattern analysis helps you to find patterns in unstructured log messages and makes it easier to examine your data.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt99d01a0dc50b84b8/6a7f1926e02fac4be75d6989/patt2.png" alt="" /></p>
<h2 id="finalrecap">Final Recap</h2>
<p>Here is a quick summary of what we did:</p>
<ul>
<li><p>Provisioned an Ubuntu 22.04 machine.</p></li>
<li><p>Installed build tools for SQLite, dev libs, and vcpkg.</p></li>
<li><p>Installed the client for opentelemetry-cpp via vcpkg.</p></li>
<li><p>Created a minimal C++ project that executes app traces and captures database operations.</p></li>
<li><p>Connected database sqlite3 in CMakeLists.txt.</p></li>
<li><p>Exported the Elastic OTLP endpoint &amp; token as environment variables (with a lowercase authorization=Bearer key!).</p></li>
<li><p>Ran the application and observed DB interactions and app traces in Elastic APM.</p></li>
<li><p>Observed application logs and patterns on Elastic logs and Discover.</p></li>
</ul>
<h2 id="faqcommonissues">FAQ &amp; Common Issues</h2>
<ul>
<li>Getting “Could not find package configuration file provided by opentelemetry-cpp”?</li>
</ul>
<p>Make sure you pass </p>
<pre><code>-DCMAKE_TOOLCHAIN_FILE=... and -DCMAKE_PREFIX_PATH=... 
</code></pre>
<p>to cmake, or embed them in CMakeLists.txt.</p>
<ul>
<li>Crash: “validate_metadata: INTERNAL:Illegal header key”?</li>
</ul>
<p>Use all-lowercase in </p>
<pre><code>OTEL_EXPORTER_OTLP_HEADERS, e.g. authorization=Bearer \&lt;token&gt;.
</code></pre>
<ul>
<li>Missing otlp_grpc_metrics_exporter.h?</li>
</ul>
<p>Your vcpkg version of opentelemetry-cpp (1.18.0) lacks a direct metrics exporter for OTLP. For metrics, either upgrade the library or consider an OpenTelemetry Collector approach.</p>
<ul>
<li>No data in Elastic APM?</li>
</ul>
<p>Double-check your endpoint URL, Bearer token, firewall rules, or service name in the APM</p>
<h2 id="additionalresources">Additional Resources:</h2>
<ul>
<li><a href="https://cloud.elastic.co/registration">Sign up for Elastic Cloud free trial</a></li>
<li><a href="https://www.elastic.co/observability-labs/blog/tag/opentelemetry">More Elastic OpenTelemetry Topics</a></li>
<li><a href="https://www.elastic.co/observability-labs/blog/elastic-distributions-opentelemetry">Introducing Elastic Distributions of OpenTelemetry</a></li>
<li><a href="https://www.elastic.co/observability-labs/blog/elastic-distribution-opentelemetry-collector">Introducing Elastic Distribution of OpenTelemetry Collector</a></li>
<li><a href="https://www.elastic.co/observability-labs/blog/elastic-opentelemetry-openai">Instrumenting your OpenAI- powered Python, Node.js, and Java Applications with EDOT</a></li>
</ul>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/opentelemetry-cpp-elastic</link>
    <guid isPermaLink="false">opentelemetry-cpp-elastic</guid>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[Logs Analytics]]></category>
    <category><![CDATA[APM]]></category>
    <category><![CDATA[Infrastructure Monitoring]]></category>
    <dc:creator><![CDATA[Haidar Braimaanie]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc5b6763bb4d344de/6a7f192aeab5be4b4720aae2/blog-image.png" length="0" type="image/png"/>
    <pubDate>Tue, 11 Feb 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Convert Logstash pipelines to OpenTelemetry Collector Pipelines]]></title>
    <description><![CDATA[This guide helps Logstash users transition to OpenTelemetry by demonstrating how to convert common Logstash pipelines into equivalent OpenTelemetry Collector configurations. We will focus on the log signal.]]></description>
    <content:encoded><![CDATA[<p>Elastic observability strategy is increasingly aligned with OpenTelemetry. With the recent launch of <a href="https://www.elastic.co/observability-labs/blog/elastic-distributions-opentelemetry">Elastic Distributions of OpenTelemetry</a> we’re expanding our offering to make it easier to use OpenTelemetry, the Elastic Agent now offers an <a href="https://www.elastic.co/guide/en/fleet/current/otel-agent.html">"otel" mode</a>, enabling it to run a custom distribution of the OpenTelemetry Collector, seamlessly enhancing your observability onboarding and experience with Elastic.</p>
<p>This post is designed to assist users familiar with Logstash transitioning to OpenTelemetry by demonstrating how to convert some standard Logstash pipelines into corresponding OpenTelemetry Collector configurations. </p>
<h2 id="whatisopentelemetrycollectorandwhyshouldicare">What is OpenTelemetry Collector and why should I care?</h2>
<p><a href="https://opentelemetry.io/">OpenTelemetry</a> is an open-source framework that ensures vendor-agnostic data collection, providing a standardized approach for the collection, processing, and ingestion of observability data. Elastic is fully committed to this principle, aiming to make observability truly vendor-agnostic and eliminating the need for users to re-instrument their observability when switching platforms. </p>
<p>By embracing OpenTelemetry, you have access to  these benefits:</p>
<ul>
<li><strong>Unified Observability</strong>: By using the OpenTelemetry Collector, you can collect and manage logs, metrics, and traces from a single tool, providing holistic observability into your system's performance and behavior. This simplifies monitoring and debugging in complex, distributed environments like microservices.  </li>
<li><strong>Flexibility and Scalability</strong>: Whether you're running a small service or a large distributed system, the OpenTelemetry Collector can be scaled to handle the amount of data generated, offering the flexibility to deploy as an agent (running alongside applications) or as a gateway (a centralized hub).  </li>
<li><strong>Open Standards</strong>: Since OpenTelemetry is an open-source project under the Cloud Native Computing Foundation (CNCF), it ensures that you're working with widely accepted standards, contributing to the long-term sustainability and compatibility of your observability stack.  </li>
<li><strong>Simplified Telemetry Pipelines</strong>: The ability to build pipelines using receivers, processors, and exporters simplifies telemetry management by centralizing data flows and minimizing the need for multiple agents.</li>
</ul>
<p>In the next sections, we will explain how OTEL Collector and Logstash pipelines are structured, and we will clarify how the steps for each option are used.</p>
<h2 id="otelcollectorconfiguration">OTEL Collector Configuration</h2>
<p>An OpenTelemetry Collector <a href="https://opentelemetry.io/docs/collector/configuration/">Configuration</a> has different sections:</p>
<ul>
<li><strong>Receivers</strong>: Collect data from different sources.  </li>
<li><strong>Processors</strong>: Transform the data collected by receivers</li>
<li><strong>Exporters</strong>: Send data to different collectors  </li>
<li><strong>Connectors</strong>: Link two pipelines together  </li>
<li><strong>Service</strong>: defines which components are active  </li>
<li><strong>Pipelines</strong>:  Combine the defined receivers, processors, exporters, and connectors to process the data  </li>
<li><strong>Extensions</strong> are optional components that expand the capabilities of the Collector to accomplish tasks not directly involved with processing telemetry data (e.g., health monitoring)  </li>
<li><strong>Telemetry</strong> where you can set observability for the collector itself (e.g., logging and monitoring)</li>
</ul>
<p>We can visualize it schematically as follows:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7377ed219e680c28/6a7f0d21227b1c0a3a59860a/otel-config-schema.png" alt="otel-config-schema" /></p>
<p>We refer to the official documentation <a href="https://opentelemetry.io/docs/collector/configuration/">Configuration | OpenTelemetry</a> for an in-depth introduction in the components. </p>
<h2 id="logstashpipelinedefinition">Logstash pipeline definition</h2>
<p>A <a href="https://www.elastic.co/guide/en/logstash/current/configuration-file-structure.html">Logstash pipeline</a> is composed of three main components:</p>
<ul>
<li>Input Plugins: Allow us to read data from different sources  </li>
<li>Filters Plugins: Allow us to transform and filter the data  </li>
<li>Output Plugins: Allow us to send the data</li>
</ul>
<p>Logstash also has a special input and a special output that allow the pipeline-to-pipeline communication, we can consider this as a similar concept to an OpenTelemetry connector.</p>
<h2 id="logstashpipelinecomparedtootelcollectorcomponents">Logstash pipeline compared to Otel Collector components</h2>
<p>We can schematize how Logstash Pipeline and OTEL Collector pipeline components can relate to each other as follows:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt621c17a76ed7169b/6a7f0d23eab5becd0520a6f5/logstash-pipeline-to-otel-pipeline.png" alt="logstash-pipeline-to-otel-pipeline" /></p>
<p>Enough theory! Let us dive into some examples.</p>
<h2 id="convertalogstashpipelineintoopentelemetrycollectorpipeline">Convert a Logstash Pipeline into OpenTelemetry Collector Pipeline</h2>
<h3 id="example1parseandtransformlogline">Example 1: Parse and transform log line</h3>
<p>Let's consider the below line:</p>
<pre><code>2024-09-20T08:33:27: user frank accessed from 89.66.167.22:10592 path /blog with error 404
</code></pre>
<p>We will apply the following steps:</p>
<ol>
<li>Read the line from the file <code>/tmp/demo-line.log</code>.</li>
<li>Define the output to be an Elasticsearch datastream <code>logs-access-default</code>.</li>
<li>Extract the <code>@timestamp</code>, <code>user.name</code>, <code>client.ip</code>, <code>client.port</code>, <code>url.path</code> and <code>http.status.code</code>.   </li>
<li>Drop log messages related to the <code>SYSTEM</code> user.  </li>
<li>Parse the date timestamp with the relevant date format and store it in <code>@timestamp</code>.  </li>
<li>Add a code <code>http.status.code_description</code> based on known codes' descriptions.  </li>
<li>Send data to Elasticsearch.</li>
</ol>
<p><strong>Logstash pipeline</strong></p>
<pre><code>input {
    file {
        path =&gt; "/tmp/demo-line.log" #[1]
        start_position =&gt; "beginning"
        add_field =&gt; { #[2]
            "[data_stream][type]" =&gt; "logs"
            "[data_stream][dataset]" =&gt; "access_log"
            "[data_stream][namespace]" =&gt; "default"
        }
    }
}

filter {
    grok { #[3]
        match =&gt; {
            "message" =&gt; "%{TIMESTAMP_ISO8601:[date]}: user %{WORD:[user][name]} accessed from %{IP:[client][ip]}:%{NUMBER:[client][port]:int} path %{URIPATH:[url][path]} with error %{NUMBER:[http][status][code]}"
        }
    }
    if "_grokparsefailure" not in [tags] {
        if [user][name] == "SYSTEM" { #[4]
            drop {}
        }
        date { #[5]
            match =&gt; ["[date]", "ISO8601"]
            target =&gt; "[@timestamp]"
            timezone =&gt; "UTC"
            remove_field =&gt; [ "date" ]
        }
        translate { #[6]
            source =&gt; "[http][status][code]"
            target =&gt; "[http][status][code_description]"
            dictionary =&gt; {
                "200" =&gt; "OK"
                "403" =&gt; "Permission denied"
                "404" =&gt; "Not Found"
                "500" =&gt; "Server Error"
            }
            fallback =&gt; "Unknown error"
        }
    }
}

output {
    elasticsearch { #[7]
        hosts =&gt; "elasticsearch-enpoint:443"
        api_key =&gt; "${ES_API_KEY}"
    }
}
</code></pre>
<p><strong>OpenTelemtry Collector configuration</strong></p>
<pre><code>receivers:
  filelog: #[1]
    start_at: beginning
    include:
      - /tmp/demo-line.log
    include_file_name: false
    include_file_path: true
    storage: file_storage 
    operators:
    # Copy the raw message into event.original (this is done OOTB by Logstash in ECS mode)
    - type: copy
      from: body
      to: attributes['event.original']
    - type: add #[2]
      field: attributes["data_stream.type"]
      value: "logs"
    - type: add #[2]
      field: attributes["data_stream.dataset"]
      value: "access_log_otel" 
    - type: add #[2]
      field: attributes["data_stream.namespace"]
      value: "default"

extensions:
  file_storage:
    directory: /var/lib/otelcol/file_storage

processors:
  # Adding  host.name (this is done OOTB by Logstash)
  resourcedetection/system:
    detectors: ["system"]
    system:
      hostname_sources: ["os"]
      resource_attributes:
        os.type:
          enabled: false

  transform/grok: #[3]
    log_statements:
      - context: log
        statements:
        - 'merge_maps(attributes, ExtractGrokPatterns(attributes["event.original"], "%{TIMESTAMP_ISO8601:date}: user %{WORD:user.name} accessed from %{IP:client.ip}:%{NUMBER:client.port:int} path %{URIPATH:url.path} with error %{NUMBER:http.status.code}", true), "insert")'

  filter/exclude_system_user:  #[4]
    error_mode: ignore
    logs:
      log_record:
        - attributes["user.name"] == "SYSTEM"

  transform/parse_date: #[5]
    log_statements:
      - context: log
        statements:
          - set(time, Time(attributes["date"], "%Y-%m-%dT%H:%M:%S"))
          - delete_key(attributes, "date")
        conditions:
          - attributes["date"] != nil

  transform/translate_status_code:  #[6]
    log_statements:
      - context: log
        conditions:
        - attributes["http.status.code"] != nil
        statements:
        - set(attributes["http.status.code_description"], "OK")                where attributes["http.status.code"] == "200"
        - set(attributes["http.status.code_description"], "Permission Denied") where attributes["http.status.code"] == "403"
        - set(attributes["http.status.code_description"], "Not Found")         where attributes["http.status.code"] == "404"
        - set(attributes["http.status.code_description"], "Server Error")      where attributes["http.status.code"] == "500"
        - set(attributes["http.status.code_description"], "Unknown Error")     where attributes["http.status.code_description"] == nil

exporters:
  elasticsearch: #[7]
    endpoints: ["elasticsearch-enpoint:443"]
    api_key: ${env:ES_API_KEY}
    tls:
    logs_dynamic_index:
      enabled: true
    mapping:
      mode: ecs

service:
  extensions: [file_storage]
  pipelines:
    logs:
      receivers:
        - filelog
      processors:
        - resourcedetection/system
        - transform/grok
        - filter/exclude_system_user
        - transform/parse_date
        - transform/translate_status_code
      exporters:
        - elasticsearch
</code></pre>
<p>These will generate the following document in Elasticsearch</p>
<pre><code>{
    "@timestamp": "2024-09-20T08:33:27.000Z",
    "client": {
        "ip": "89.66.167.22",
        "port": 10592
    },
    "data_stream": {
        "dataset": "access_log",
        "namespace": "default",
        "type": "logs"
    },
    "event": {
        "original": "2024-09-20T08:33:27: user frank accessed from 89.66.167.22:10592 path /blog with error 404"
    },
    "host": {
        "hostname": "my-laptop",
        "name": "my-laptop",
     },
    "http": {
        "status": {
            "code": "404",
            "code_description": "Not Found"
        }
    },
    "log": {
        "file": {
            "path": "/tmp/demo-line.log"
        }
    },
    "message": "2024-09-20T08:33:27: user frank accessed from 89.66.167.22:10592 path /blog with error 404",
    "url": {
        "path": "/blog"
    },
    "user": {
        "name": "frank"
    }
}
</code></pre>
<h3 id="example2parseandtransformandjsonformattedlogfile">Example 2: Parse and transform a NDJSON-formatted log file</h3>
<p>Let's consider the below json line:</p>
<pre><code>{"log_level":"INFO","message":"User login successful","service":"auth-service","timestamp":"2024-10-11 12:34:56.123 +0100","user":{"id":"A1230","name":"john_doe"}}
</code></pre>
<p>We will apply the following steps:</p>
<ol>
<li>Read a line from the file <code>/tmp/demo.ndjson</code>.  </li>
<li>Define the output to be an Elasticsearch datastream <code>logs-json-default</code>   </li>
<li>Parse the JSON and assign relevant keys and values.  </li>
<li>Parse the date.  </li>
<li>Override the message field.  </li>
<li>Rename fields to follow ECS conventions.  </li>
<li>Send data to Elasticsearch.</li>
</ol>
<p><strong>Logstash pipeline</strong></p>
<pre><code>input {
    file {
        path =&gt; "/tmp/demo.ndjson" #[1]
        start_position =&gt; "beginning"
        add_field =&gt; { #[2]
            "[data_stream][type]" =&gt; "logs"
            "[data_stream][dataset]" =&gt; "json"
            "[data_stream][namespace]" =&gt; "default"
        }
    }
}

filter {
  if [message] =~ /^\{.*/ {
    json { #[3] &amp; #[5]
        source =&gt; "message"
    }
  }
  date { #[4]
    match =&gt; ["[timestamp]", "yyyy-MM-dd HH:mm:ss.SSS Z"]
    remove_field =&gt; "[timestamp]"
  }
  mutate {
    rename =&gt; { #[6]
      "service" =&gt; "[service][name]"
      "log_level" =&gt; "[log][level]"
    }
  }
}


output {
    elasticsearch { # [7]
        hosts =&gt; "elasticsearch-enpoint:443"
        api_key =&gt; "${ES_API_KEY}"
    }
}
</code></pre>
<p><strong>OpenTelemtry Collector configuration</strong></p>
<pre><code>receivers:
  filelog/json: # [1]
    include: 
      - /tmp/demo.ndjson
    retry_on_failure:
      enabled: true
    start_at: beginning
    storage: file_storage 
    operators:
     # Copy the raw message into event.original (this is done OOTB by Logstash in ECS mode)
    - type: copy
      from: body
      to: attributes['event.original']
    - type: add #[2]
      field: attributes["data_stream.type"]
      value: "logs"      
    - type: add #[2]
      field: attributes["data_stream.dataset"]
      value: "otel" #[2]
    - type: add
      field: attributes["data_stream.namespace"]
      value: "default"     


extensions:
  file_storage:
    directory: /var/lib/otelcol/file_storage

processors:
  # Adding  host.name (this is done OOTB by Logstash)
  resourcedetection/system:
    detectors: ["system"]
    system:
      hostname_sources: ["os"]
      resource_attributes:
        os.type:
          enabled: false

  transform/json_parse:  #[3]
    error_mode: ignore
    log_statements:
      - context: log
        statements:
          - merge_maps(attributes, ParseJSON(body), "upsert")
        conditions: 
          - IsMatch(body, "^\\{")


  transform/parse_date:  #[4]
    error_mode: ignore
    log_statements:
      - context: log
        statements:
          - set(time, Time(attributes["timestamp"], "%Y-%m-%d %H:%M:%S.%L %z"))
          - delete_key(attributes, "timestamp")
        conditions: 
          - attributes["timestamp"] != nil

  transform/override_message_field: [5]
    error_mode: ignore
    log_statements:
      - context: log
        statements:
          - set(body, attributes["message"])
          - delete_key(attributes, "message")

  transform/set_log_severity: # [6]
    error_mode: ignore
    log_statements:
      - context: log
        statements:
          - set(severity_text, attributes["log_level"])          

  attributes/rename_attributes: #[6]
    actions:
      - key: service.name
        from_attribute: service
        action: insert
      - key: service
        action: delete
      - key: log_level
        action: delete

exporters:
  elasticsearch: #[7]
    endpoints: ["elasticsearch-enpoint:443"]
    api_key: ${env:ES_API_KEY}
    tls:
    logs_dynamic_index:
      enabled: true
    mapping:
      mode: ecs

service:
  extensions: [file_storage]
  pipelines:
    logs/json:
      receivers: 
        - filelog/json
      processors:
        - resourcedetection/system    
        - transform/json_parse
        - transform/parse_date        
        - transform/override_message_field
        - transform/set_log_severity
        - attributes/rename_attributes
      exporters: 
        - elasticsearch
</code></pre>
<p>These will generate the following document in Elasticsearch</p>
<pre><code>{
    "@timestamp": "2024-10-11T12:34:56.123000000Z",
    "data_stream": {
        "dataset": "otel",
        "namespace": "default",
        "type": "logs"
    },
    "event": {
        "original": "{\"log_level\":\"WARNING\",\"message\":\"User login successful\",\"service\":\"auth-service\",\"timestamp\":\"2024-10-11 12:34:56.123 +0100\",\"user\":{\"id\":\"A1230\",\"name\":\"john_doe\"}}"
    },
    "host": {
        "hostname": "my-laptop",
        "name": "my-laptop",
     },
    "log": {
        "file": {
            "name": "json.log"
        },
        "level": "WARNING"
    },
    "message": "User login successful",
    "service": {
        "name": "auth-service"
    },
    "user": {
        "id": "A1230",
        "name": "john_doe"
    }
}
</code></pre>
<h2 id="conclusion">Conclusion</h2>
<p>In this post, we showed examples of how to convert a typical Logstash pipeline into an OpenTelemetry Collector pipeline for logs. While OpenTelemetry provides powerful tools for collecting and exporting logs, if your pipeline relies on complex transformations or scripting, Logstash remains a superior choice. This is because Logstash offers a broader range of built-in features and a more flexible approach to handling advanced data manipulation tasks.</p>
<h2 id="whatsnext">What's Next?</h2>
<p>Now that you've seen basic (but realistic) examples of converting a Logstash pipeline to OpenTelemetry, it's your turn to dive deeper. Depending on your needs, you can explore further and find more detailed resources in the following repositories:</p>
<ul>
<li><a href="https://github.com/open-telemetry/opentelemetry-collector">OpenTelemetry Collector</a>: Learn about the core OpenTelemetry components, from receivers to exporters.  </li>
<li><a href="https://github.com/open-telemetry/opentelemetry-collector-contrib">OpenTelemetry Collector Contrib</a>: Find community-contributed components for a wider range of integrations and features.  </li>
<li><a href="https://github.com/elastic/opentelemetry-collector-components">Elastic's opentelemetry-collector-components</a>: Dive into Elastic's extensions for the OpenTelemetry Collector, offering more tailored features for Elastic Stack users.</li>
</ul>
<p>If you encounter specific challenges or need to handle more advanced use cases, these repositories will be an excellent resource for discovering additional components or integrations that can enhance your pipeline. All these repositories have a similar structure with folders named <code>receiver</code>, <code>processor</code>, <code>exporter</code>, <code>connector</code>, which should be familiar after reading this blog. Whether you are migrating a simple Logstash pipeline or tackling more complex data transformations, these tools and communities will provide the support you need for a successful OpenTelemetry implementation.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/logstash-to-otel</link>
    <guid isPermaLink="false">logstash-to-otel</guid>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[Logs Analytics]]></category>
    <dc:creator><![CDATA[Mirko Bez,Taha Derouiche]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt142ed620f0e6c7a2/6a7f0d26fc63ab58cf64cc5f/logstash-otel.jpg" length="0" type="image/jpeg"/>
    <pubDate>Fri, 25 Oct 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Using NLP and Pattern Matching to Detect, Assess, and Redact PII in Logs - Part 2]]></title>
    <description><![CDATA[How to detect, assess, and redact PII in your logs using Elasticsearch, NLP and Pattern Matching]]></description>
    <content:encoded><![CDATA[<h2 id="introduction">Introduction:</h2>
<p>The prevalence of high-entropy logs in distributed systems has significantly raised the risk of PII (Personally Identifiable Information) seeping into our logs, which can result in security and compliance issues. This 2-part blog delves into the crucial task of identifying and managing this issue using the Elastic Stack. We will explore using NLP (Natural Language Processing) and Pattern matching to detect, assess, and, where feasible, redact PII from logs being ingested into Elasticsearch.</p>
<p>In <a href="https://www.elastic.co/observability-labs/blog/pii-ner-regex-assess-redact-part-1">Part 1 of this blog</a>, we covered the following:</p>
<ul>
<li>Review the techniques and tools we have available to manage PII in our logs</li>
<li>Understand the roles of NLP / NER in PII detection</li>
<li>Build a composable processing pipeline to detect and assess PII</li>
<li>Sample logs and run them through the NER Model</li>
<li>Assess the results of the NER Model </li>
</ul>
<p>In <strong>Part 2</strong> of this blog, we will cover the following:</p>
<ul>
<li>Apply the <code>redact</code> regex pattern processor and assess the results</li>
<li>Create Alerts using ESQL</li>
<li>Apply field-level security to control access to the un-redacted data</li>
<li>Production considerations and scaling</li>
<li>How to run these processes on incoming or historical data</li>
</ul>
<p>Reminder of the overall flow we will construct over the 2 blogs:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb2e3d5e77752e778/6a886127982926638858ace9/pii-overall-flow.png" alt="PII Overall Flow" /></p>
<p>All code for this exercise can be found at:
<a href="https://github.com/bvader/elastic-pii">https://github.com/bvader/elastic-pii</a>. </p>
<h3 id="part1prerequisites">Part 1 Prerequisites</h3>
<p>This blog picks up where <a href="https://www.elastic.co/observability-labs/blog/pii-ner-regex-assess-redact-part-1">Part 1 of this blog</a> left off. You must have the NER model, ingest pipelines, and dashboard from Part 1 installed and working.</p>
<ul>
<li>Loaded and configured NER Model </li>
<li>Installed all the composable ingest pipelines from Part 1 of the blog</li>
<li>Installed dashboard</li>
</ul>
<p>You can access the <a href="https://github.com/bvader/elastic-pii/blob/main/elastic/blog-part-1/logs-sampler-composable-pipelines-blog-1-complete.json">complete solution for Blog 1 here</a>. Don't forget to load the dashboard, found <a href="https://github.com/bvader/elastic-pii/blob/main/elastic/blog-part-1/pii-dashboard-part-1.ndjson">here</a>.</p>
<h3 id="applyingtheredactprocessor">Applying the Redact Processor</h3>
<p>Next, we will apply the <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/redact-processor.html"><code>redact</code> processor</a>. The <code>redact</code> processor is a simple regex-based processor that takes a list of regex patterns and looks for them in a field and replaces them with literals when found. The <code>redact</code> processor is reasonably performant and can run at scale. At the end, we will discuss this in detail in the <a href="https://www.elastic.co/observability-labs/blog/pii-ner-regex-assess-redact-part-2#production-scaling">production scaling</a> section.</p>
<p>Elasticsearch comes packaged with a number of useful predefined <a href="https://github.com/elastic/elasticsearch/blob/8.15/libs/grok/src/main/resources/patterns/ecs-v1">patterns</a> that can be conveniently referenced by the <code>redact</code> processor. If one does not suit your needs, create a new pattern with a custom definition. The Redact processor replaces every occurrence of a match. If there are multiple matches, they will all be replaced with the pattern name.</p>
<p>In the code below, we leveraged some of the predefined patterns as well as constructing several custom patterns.</p>
<pre><code>        "patterns": [
          "%{EMAILADDRESS:EMAIL_REGEX}",      &lt;&lt; Predefined
          "%{IP:IP_ADDRESS_REGEX}",           &lt;&lt; Predefined
          "%{CREDIT_CARD:CREDIT_CARD_REGEX}", &lt;&lt; Custom
          "%{SSN:SSN_REGEX}",                 &lt;&lt; Custom
          "%{PHONE:PHONE_REGEX}"              &lt;&lt; Custom
        ]
</code></pre>
<p>We also replaced the PII with easily identifiable patterns we can use for assessment. </p>
<p>In addition, it is important to note that since the redact processor is a simple regex find and replace, it can be used against many "secrets" patterns, not just PII. There are many references for regex and secrets patterns, so you can reuse this capability to detect secrets in your logs.</p>
<p><a href="https://github.com/bvader/elastic-pii/blob/main/elastic/blog-part-2/pii-redact-composable-pipelines-blog-2-redact-processor-1.json">The code can be found here</a> for the following two sections of code. </p>
<p></p>
  redact processor pipeline code - click to open/close<p></p>
<pre><code># Add the PII redact processor pipeline
DELETE _ingest/pipeline/logs-pii-redact-processor
PUT _ingest/pipeline/logs-pii-redact-processor
{
  "processors": [
    {
      "set": {
        "field": "redact.proc.successful",
        "value": true
      }
    },
    {
      "set": {
        "field": "redact.proc.found",
        "value": false
      }
    },
    {
      "set": {
        "if": "ctx?.redact?.message == null",
        "field": "redact.message",
        "copy_from": "message"
      }
    },
    {
      "redact": {
        "field": "redact.message",
        "prefix": "&lt;REDACTPROC-",
        "suffix": "&gt;",
        "patterns": [
          "%{EMAILADDRESS:EMAIL_REGEX}",
          "%{IP:IP_ADDRESS_REGEX}",
          "%{CREDIT_CARD:CREDIT_CARD_REGEX}",
          "%{SSN:SSN_REGEX}",
          "%{PHONE:PHONE_REGEX}"
        ],
        "pattern_definitions": {
          "CREDIT_CARD": """\d{4}[ -]\d{4}[ -]\d{4}[ -]\d{4}""",
          "SSN": """\d{3}-\d{2}-\d{4}""",
          "PHONE": """(\+\d{1,2}\s?)?1?\-?\.?\s?\(?\d{3}\)?[\s.-]?\d{3}[\s.-]?\d{4}"""
        },
        "on_failure": [
          {
            "set": {
              "description": "Set 'error.message'",
              "field": "failure",
              "value": "REDACT_PROCESSOR_FAILED",
              "override": false
            }
          },
          {
            "set": {
              "field": "redact.proc.successful",
              "value": false
            }
          }
        ]
      }
    },
    {
      "set": {
        "if": "ctx?.redact?.message.contains('REDACTPROC')",
        "field": "redact.proc.found",
        "value": true
      }
    },
    {
      "set": {
        "if": "ctx?.redact?.pii?.found == null",
        "field": "redact.pii.found",
        "value": false
      }
    },
    {
      "set": {
        "if": "ctx?.redact?.proc?.found == true",
        "field": "redact.pii.found",
        "value": true
      }
    }
  ],
  "on_failure": [
    {
      "set": {
        "field": "failure",
        "value": "GENERAL_FAILURE",
        "override": false
      }
    }
  ]
}
</code></pre>
<p></p><p></p>
<p>And now, we will add the <code>logs-pii-redact-processor</code> pipeline to the overall <code>process-pii</code> pipeline 
</p>
  redact processor pipeline code - click to open/close<p></p>
<pre><code># Updated Process PII pipeline that now call the NER and Redact Processor pipeline
DELETE _ingest/pipeline/process-pii
PUT _ingest/pipeline/process-pii
{
  "processors": [
    {
      "set": {
        "description": "Set true if enabling sampling, otherwise false",
        "field": "sample.enabled",
        "value": true
      }
    },
    {
      "set": {
        "description": "Set Sampling Rate 0 None 10000 all allows for 0.01% precision",
        "field": "sample.sample_rate",
        "value": 1000
      }
    },
    {
      "set": {
        "description": "Set to false if you want to drop unsampled data, handy for reindexing hostorical data",
        "field": "sample.keep_unsampled",
        "value": true
      }
    },
    {
      "pipeline": {
        "if": "ctx.sample.enabled == true",
        "name": "logs-sampler",
        "ignore_failure": true
      }
    },
    {
      "pipeline": {
        "if": "ctx.sample.enabled == false || (ctx.sample.enabled == true &amp;&amp; ctx.sample.sampled == true)",
        "name": "logs-ner-pii-processor"
      }
    },
    {
      "pipeline": {
        "if": "ctx.sample.enabled == false || (ctx.sample.enabled == true &amp;&amp;  ctx.sample.sampled == true)",
        "name": "logs-pii-redact-processor"
      }
    }
  ]
}
</code></pre>
<p></p><p></p>
<p>Reload the data as described in the <a href="https://www.elastic.co/observability-labs/blog/pii-ner-regex-assess-redact-part-2#reloading-the-logs">Reloading the logs</a>. If you have not generated the logs the first time, follow the instructions in the <a href="https://www.elastic.co/observability-labs/blog/pii-ner-regex-assess-redact-part-2#data-loading-appendix">Data Loading Appendix</a></p>
<p>Go to Discover and enter the following into the KQL bar
<code>sample.sampled : true and redact.message: REDACTPROC</code> and add the <code>redact.message</code> to the table and you should see something like this.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7f529ae6171d39bf/6a7f19a0de231544dffd8085/pii-discover-1-part-2.png" alt="PII Discover Blog 2 Part 1" /></p>
<p>And if you did not load the dashboard from Blog Part 1 at already, load it, it can be found <a href="https://github.com/bvader/elastic-pii/blob/main/elastic/blog-part-1/pii-dashboard-part-1.ndjson">here</a> using the Kibana -&gt; Stack Management -&gt; Saved Objects -&gt; Import. </p>
<p>It should look something like this now. Note that the REGEX portions of the dashboard are now active.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt19d420e064cc7b13/6a7f19a33cab1ce4a80e4c5d/pii-dashboard-1-part-2.png" alt="PII Dashboards Blog 2 Part 1" /></p>
<h2 id="checkpoint">Checkpoint</h2>
<p>At this point, we have the following capabilities:</p>
<ul>
<li>Ability to sample incoming logs and apply this PII redaction </li>
<li>Detect and Assess PII with the NER/NLP and Pattern Matching</li>
<li>Assess the amount, type and quality of the PII detections</li>
</ul>
<p>This is a great point to stop if you are just running all this once to see how it works, but we have a few more steps to make this useful in production systems.</p>
<ul>
<li>Clean up the working and unredacted data</li>
<li>Update the Dashboard to work with the cleaned-up data</li>
<li>Apply Role Based Access Control to protect the raw  unredacted data</li>
<li>Create Alerts</li>
<li>Production and Scaling Considerations</li>
<li>How to run these processes on incoming or historical data</li>
</ul>
<h2 id="applyingtoproductionsystems">Applying to Production Systems</h2>
<h3 id="cleanupworkingdataandupdatethedashboard">Cleanup working data and update the dashboard</h3>
<p>And now we will add the cleanup code to the overall <code>process-pii</code> pipeline.</p>
<p>In short, we set a flag <code>redact.enable: true</code> that directs the pipeline to move the unredacted <code>message</code> field to <code>raw.message</code> and the move the redacted message field <code>redact.message</code>to the <code>message</code> field. We will "protect" the <code>raw.message</code> in the following section. </p>
<p><strong>NOTE:</strong> Of course you can change this behavior if you want to completely delete the unredacted data. In this exercise we will keep it and protect it. </p>
<p>In addition we set <code>redact.cleanup: true</code> to clean up the NLP working data.</p>
<p>These fields allow a lot of control over what data you decide to keep and analyze. </p>
<p><a href="https://github.com/bvader/elastic-pii/blob/main/elastic/blog-part-2/pii-redact-composable-pipelines-blog-2-redact-processor-2.json">The code can be found here</a> for the following two sections of code. </p>
<p></p>
  redact processor pipeline code - click to open/close<p></p>
<pre><code># Updated Process PII pipeline that now call the NER and Redact Processor pipeline and cleans up 
DELETE _ingest/pipeline/process-pii
PUT _ingest/pipeline/process-pii
{
  "processors": [
    {
      "set": {
        "description": "Set true if enabling sampling, otherwise false",
        "field": "sample.enabled",
        "value": true
      }
    },
    {
      "set": {
        "description": "Set Sampling Rate 0 None 10000 all allows for 0.01% precision",
        "field": "sample.sample_rate",
        "value": 1000
      }
    },
    {
      "set": {
        "description": "Set to false if you want to drop unsampled data, handy for reindexing hostorical data",
        "field": "sample.keep_unsampled",
        "value": true
      }
    },
    {
      "pipeline": {
        "if": "ctx.sample.enabled == true",
        "name": "logs-sampler",
        "ignore_failure": true
      }
    },
    {
      "pipeline": {
        "if": "ctx.sample.enabled == false || (ctx.sample.enabled == true &amp;&amp; ctx.sample.sampled == true)",
        "name": "logs-ner-pii-processor"
      }
    },
    {
      "pipeline": {
        "if": "ctx.sample.enabled == false || (ctx.sample.enabled == true &amp;&amp;  ctx.sample.sampled == true)",
        "name": "logs-pii-redact-processor"
      }
    },
    {
      "set": {
        "description": "Set to true to actually redact, false will run processors but leave original",
        "field": "redact.enable",
        "value": true
      }
    },
    {
      "rename": {
        "if": "ctx?.redact?.pii?.found == true &amp;&amp; ctx?.redact?.enable == true",
        "field": "message",
        "target_field": "raw.message"
      }
    },
    {
      "rename": {
        "if": "ctx?.redact?.pii?.found == true &amp;&amp; ctx?.redact?.enable == true",
        "field": "redact.message",
        "target_field": "message"
      }
    },
    {
      "set": {
        "description": "Set to true to actually to clean up working data",
        "field": "redact.cleanup",
        "value": true
      }
    },
    {
      "remove": {
        "if": "ctx?.redact?.cleanup == true",
        "field": [
          "ml"
        ],
        "ignore_failure": true
      }
    }
  ]
}
</code></pre>
<p></p><p></p>
<p>Reload the data as described here in the <a href="https://www.elastic.co/observability-labs/blog/pii-ner-regex-assess-redact-part-2#reloading-the-logs">Reloading the logs</a>. </p>
<p>Go to Discover and enter the following into the KQL bar
<code>sample.sampled : true and redact.pii.found: true</code> and add the following fields to the table</p>
<p><code>message</code>,<code>raw.message</code>,<code>redact.ner.found</code>,<code>redact.proc.found</code>,<code>redact.pii.found</code></p>
<p>You should see something like this
<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte9e78d6abc37b097/6a7f19a7ead8eca63abaac42/pii-discover-2-part-2.png" alt="PII Discover Part 2 Blog 2" /></p>
<p>We have everything we need to move forward with protecting the PII and Alerting on it. </p>
<p>Load up the new dashboard that works on the cleaned-up data </p>
<p>To load the dashboard, go to Kibana -&gt; Stack Management -&gt; Saved Objects and import the <code>pii-dashboard-part-2.ndjson</code> file that can be found <a href="https://github.com/bvader/elastic-pii/blob/main/elastic/blog-part-2/pii-dashboard-part-2.ndjson">here</a>. </p>
<p>The new dashboard should look like this. Note: It uses different fields under the covers since we have cleaned up the underlying data. </p>
<p>You should see something like this
<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltba60df9557e900b3/6a7f19aa96b5a6370087b86f/pii-dashboard-2-part-2.png" alt="PII Dashboard Part 2 Blog 2" /></p>
<h3 id="applyrolebasedaccesscontroltoprotecttherawunredacteddata">Apply Role Based Access Control to protect the raw unredacted data</h3>
<p>Elasticsearch supports role-based access control, including field and document level access control natively; it dramatically reduces the operational and maintenance complexity required to secure our application.</p>
<p>We will create a Role that does not allow access to the <code>raw.message</code> field and then create a user and assign that user the role. With that role, the user will only be able to see the redacted message, which is now in the <code>message</code> field, but will not be able to access the protected <code>raw.message</code> field.</p>
<p><strong>NOTE:</strong> Since we only sampled 10% of the data in this exercise the non-sampled <code>message</code> fields are not moved to the <code>raw.message</code>, so they are still viewable, but this shows the capability you can apply in a production system.</p>
<p><a href="https://github.com/bvader/elastic-pii/blob/main/elastic/blog-part-2/pii-redact-composable-pipelines-blog-2-rbac.json">The code can be found here</a> for the following section of code. </p>
<p></p>
  RBAC protect-pii role and user code - click to open/close<p></p>
<pre><code># Create role with no access to the raw.message field
GET _security/role/protect-pii
DELETE _security/role/protect-pii
PUT _security/role/protect-pii
{
  "cluster": [],
  "indices": [
    {
      "names": [
        "logs-*"
      ],
      "privileges": [
        "read",
        "view_index_metadata"
      ],
      "field_security": {
        "grant": [
          "*"
        ],
        "except": [
          "raw.message"
        ]
      },
      "allow_restricted_indices": false
    }
  ],
  "applications": [
    {
      "application": "kibana-.kibana",
      "privileges": [
        "all"
      ],
      "resources": [
        "*"
      ]
    }
  ],
  "run_as": [],
  "metadata": {},
  "transient_metadata": {
    "enabled": true
  }
}

# Create user stephen with protect-pii role
GET _security/user/stephen
DELETE /_security/user/stephen
POST /_security/user/stephen
{
  "password" : "mypassword",
  "roles" : [ "protect-pii" ],
  "full_name" : "Stephen Brown"
}
</code></pre>
<p></p><p></p>
<p>Now log into a separate window with the new user <code>stephen</code> with the <code>protect-pii role</code>. Go to Discover and put <code>redact.pii.found : true</code> in the KQL bar and add the <code>message</code> field to the table. Also, notice that the <code>raw.message</code> is not available. </p>
<p>You should see something like this
<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta1d5ca4051cdf880/6a7f19ad3cab1c04430e4c63/pii-discover-3-part-2.png" alt="PII Dashboard Part 2 Blog 2" /></p>
<h3 id="createanalertwhenpiidetected">Create an Alert when PII Detected</h3>
<p>Now, with the processing of the pipelines, creating an alert when PII is detected is easy. To review <a href="https://www.elastic.co/guide/en/kibana/current/alerting-getting-started.html">Alerting in Kibana</a> in detail if needed  </p>
<p>NOTE: <a href="https://www.elastic.co/observability-labs/blog/pii-ner-regex-assess-redact-part-2#reloading-the-logs">Reload</a> the data if needed to have recent data. </p>
<p>First, we will create a simple <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/esql.html">ES|QL query</a> in Discover. </p>
<p><a href="https://github.com/bvader/elastic-pii/blob/main/elastic/blog-part-2/pii-redact-esql-alert-blog-2.txt">The code can be found here.</a></p>
<pre><code>FROM logs-pii-default
| WHERE redact.pii.found == true
| STATS pii_count = count(*)
| WHERE pii_count &gt; 0
</code></pre>
<p>When you run this you should see something like this.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt125ed63dd1353c17/6a7f19b005b7b5771a18bd33/pii-esql-1-part-2.png" alt="PII ESQL Part 1 Blog 2" /></p>
<p>Now click the Alerts menu and select <code>Create search threshold rule</code>, and will create an alert to alert us when PII is found. </p>
<p><strong>Select a time field: @timestamp
Set the time window: 5 minutes</strong></p>
<p>Assuming you loaded the data recently when you run <strong>Test</strong> it should do something like </p>
<p>pii_count : <code>343</code>
Alerts generated <code>query matched</code></p>
<p>Add an action when the alert is Active. </p>
<p><strong>For each alert: <code>On status changes</code>
Run when: <code>Query matched</code></strong></p>
<pre><code>Elasticsearch query rule {{rule.name}} is active:

- PII Found: true
- PII Count: {{#context.hits}} {{_source.pii_count}}{{/context.hits}}
- Conditions Met: {{context.conditions}} over {{rule.params.timeWindowSize}}{{rule.params.timeWindowUnit}}
- Timestamp: {{context.date}}
- Link: {{context.link}}
</code></pre>
<p>Add an Action for when the Alert is Recovered. </p>
<p><strong>For each alert: <code>On status changes</code>
Run when: <code>Recovered</code></strong></p>
<pre><code>Elasticsearch query rule {{rule.name}} is Recovered:

- PII Found: false
- Conditions Not Met: {{context.conditions}} over {{rule.params.timeWindowSize}}{{rule.params.timeWindowUnit}}
- Timestamp: {{context.date}}
- Link: {{context.link}}
</code></pre>
<p>When all setup it should look like this and <code>Save</code></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt23fac59dfe9353b5/6a7f19b43cab1c618c0e4c67/pii-alert-1-part2.png" alt="Alert Setup" />\
<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltabee92bbafa00948/6a7f19b64c4bfb0351ccd8e4/pii-alert-2-part2.png" alt="Action Alert" />\
<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltcade3df9ba1adcb8/6a7f19b9b43770d7974d70ee/pii-alert-3-part2.png" alt="Action Alert" /></p>
<p>You should get an Active alert that looks like this if you have recent data. I sent mine to Slack. </p>
<pre><code>Elasticsearch query rule pii-found-esql is active:
- PII Found: true
- PII Count:  374
- Conditions Met: Query matched documents over 5m
- Timestamp: 2024-10-15T02:44:52.795Z
- Link: https://mydeployment123.aws.found.io:9243/app/management/insightsAndAlerting/triggersActions/rule/7d6faecf-964e-46da-aaba-8a2f89f33989
</code></pre>
<p>And then if you wait you will get a Recovered alert that looks like this. </p>
<pre><code>Elasticsearch query rule pii-found-esql is Recovered:
- PII Found: false
- Conditions Not Met: Query did NOT match documents over 5m
- Timestamp: 2024-10-15T02:49:04.815Z
- Link: https://mydeployment123.kb.us-west-1.aws.found.io:9243/app/management/insightsAndAlerting/triggersActions/rule/7d6faecf-964e-46da-aaba-8a2f89f33989
</code></pre>
<h3 id="productionscaling">Production Scaling</h3>
<h4 id="nerscaling">NER Scaling</h4>
<p>As we mentioned <a href="https://www.elastic.co/observability-labs/blog/pii-ner-regex-assess-redact-part-1#named-entity-recognition-ner-detection">Part 1 of this blog</a> of this blog, NER / NLP Models are CPU-intensive and expensive to run at scale; thus, we employed a sampling technique to understand the risk in our logs without sending the full logs volume through the NER Model.</p>
<p>Please review <a href="https://www.elastic.co/observability-labs/blog/pii-ner-regex-assess-redact-part-1#loading-configuration-and-execution-of-the-ner-pipeline">the setup and configuration of the NER</a> model from Part 1 of the blog.</p>
<p>We chose the base BERT NER model <a href="https://huggingface.co/dslim/bert-base-NER">bert-base-NER</a> for our PII case.</p>
<p>To scale ingest, we will focus on scaling the allocations for the deployed model. More information on this topic is available <a href="https://www.elastic.co/guide/en/machine-learning/current/ml-nlp-deploy-model.html">here</a>. The number of allocations must be less than the available allocated processors (cores, not vCPUs) per node.</p>
<p>The metrics below are related to the model and configuration from Part 1 of the blog.  </p>
<ul>
<li>4 Allocations to allow for more parallel ingestion</li>
<li>1 Thread per Allocation</li>
<li>0 Byes Cache, as we expect a low cache hit rate
<strong>Note</strong> If there are many repeated logs, cache can help, but with timestamps and other variations, cache will not help and can even slow down the process</li>
<li>8192 Queue </li>
</ul>
<pre><code>GET _ml/trained_models/dslim__bert-base-ner/_stats
.....
           "node": {
              "0m4tq7tMRC2H5p5eeZoQig": {
.....
                "attributes": {
                  "xpack.installed": "true",
                  "region": "us-west-1",
                  "ml.allocated_processors": "5", &lt;&lt; HERE 
.....
            },
            "inference_count": 5040,
            "average_inference_time_ms": 138.44285714285715, &lt;&lt; HERE 
            "average_inference_time_ms_excluding_cache_hits": 138.44285714285715,
            "inference_cache_hit_count": 0,
.....
            "threads_per_allocation": 1,
            "number_of_allocations": 4,  &lt;&lt;&lt; HERE
            "peak_throughput_per_minute": 1550,
            "throughput_last_minute": 1373,
            "average_inference_time_ms_last_minute": 137.55280407865988,
            "inference_cache_hit_count_last_minute": 0
          }
        ]
      }
    }
</code></pre>
<p>There are 3 key pieces of information above:</p>
<ul>
<li><p><code>"ml.allocated_processors": "5"</code>
The number of physical cores / processors available </p></li>
<li><p><code>"number_of_allocations": 4</code>
The number of allocations which is maximum 1 per physical core. <strong>Note</strong>: we could have used 5 allocations, but we only allocated 4 for this exercise</p></li>
<li><p><code>"average_inference_time_ms": 138.44285714285715</code>
The averages inference time per document. </p></li>
</ul>
<p>The math is pretty straightforward for throughput for Inferences per Min (IPM) per allocation (1 allocation per physical core), since an inference uses a single core and a single thread.</p>
<p>Then the Inferences per Min per Allocation is simply: </p>
<p><code>IPM per allocation = 60,000 ms (in a minute) / 138ms per inference = 435</code></p>
<p>When then lines up with the Total Inferences per Minute</p>
<p><code>Total IPM = 435 IPM / allocation * 4 Allocations = ~1740</code></p>
<p>Suppose we want to do 10,000 IPMs, how many allocations (cores) would I need? </p>
<p><code>Allocations = 10,000 IPM / 435 IPM per allocation = 23 Allocation (cores rounded up)</code></p>
<p>Or perhaps logs are coming in at 5000 EPS and you want to do 1% Sampling. </p>
<p><code>IPM = 5000 EPS * 60sec * 0.01 sampling = 3000 IPM sampled</code></p>
<p>Then </p>
<p><code>Number of Allocators = 3000 IPM / 435 IPM per allocation = 7 allocations (cores rounded up)</code></p>
<p><strong>Want Faster!</strong> Turns out there is a more lightweight NER Model <a href="https://huggingface.co/dslim/distilbert-NER">
distilbert-NER</a> model that is faster, but the tradeoff is a little less accuracy. </p>
<p>Running the logs through this model results in an inference time nearly twice as fast!</p>
<p><code>"average_inference_time_ms": 66.0263959390863</code></p>
<p>Here is some quick math:
<code>$IPM per allocation = 60,000 ms (in a minute) / 61ms per inference = 983</code></p>
<p>Suppose we want to do 25,000 IPMs, how many allocations (cores) would I need?</p>
<p><code>Allocations = 25,000 IPM / 983 IPM per allocation = 26 Allocation (cores rounded up)</code></p>
<p><strong>Now you can apply this math to determine the correct sampling and NER scaling to support your logging use case.</strong></p>
<h4 id="redactprocessorscaling">Redact Processor Scaling</h4>
<p>In short, the <code>redact</code> processor should scale to production loads as long as you are using appropriately sized and configured nodes and have well-constructed regex patterns. </p>
<h3 id="assessingincominglogs">Assessing incoming logs</h3>
<p>If you want to test on incoming logs data in a data stream. All you need to do is change the conditional in the <code>logs@custom</code> pipeline to apply the <code>process-pii</code> to the dataset you want to. You can use any conditional that fits your condition.</p>
<p>Note: Just make sure that you have accounted for the proper scaling for the NER and Redact processors they were described above in <a href="https://www.elastic.co/observability-labs/blog/pii-ner-regex-assess-redact-part-2#production-scaling">Production Scaling</a></p>
<pre><code>    {
      "pipeline": {
        "description" : "Call the process_pii pipeline on the correct dataset",
        "if": "ctx?.data_stream?.dataset == 'pii'", &lt;&lt;&lt; HERE
        "name": "process-pii"
      }
    }
</code></pre>
<p>So if for example your logs are coming into <code>logs-mycustomapp-default</code> you would just change the conditional to</p>
<pre><code>        "if": "ctx?.data_stream?.dataset == 'mycustomapp'",
</code></pre>
<h3 id="assessinghistoricaldata">Assessing historical data</h3>
<p>If you have a historical (already ingested) data stream or index you can run the assessment over them using the <code>_reindex</code> API&gt; </p>
<p>Note: Just make sure that you have accounted for the proper scaling for the NER and Redact processors they were described above in <a href="https://www.elastic.co/observability-labs/blog/pii-ner-regex-assess-redact-part-2#production-scaling">Production Scaling</a></p>
<p>There are a couple of extra steps: 
<a href="https://github.com/bvader/elastic-pii/blob/main/elastic/blog-part-2/pii-redact-historical-data-blog-2.json">The code can be found here.</a></p>
<p>1) First we can set the parameters to ONLY keep the sampled data as there is no reason to make a copy of all the unsampled data. In the <code>process-pii</code> pipeline, there is a setting <code>sample.keep_unsampled</code>, which we can set to <code>false</code>, which will then only keep the sampled data </p>
<pre><code>    {
      "set": {
        "description": "Set to false if you want to drop unsampled data, handy for reindexing hostorical data",
        "field": "sample.keep_unsampled",
        "value": false &lt;&lt;&lt; SET TO false
      }
    },
</code></pre>
<p>2) Second, we will create a pipeline that will reroute the data to the correct data stream to run through all the PII assessment/detection pipelines. It also sets the correct <code>dataset</code> and <code>namespace</code></p>
<pre><code>DELETE _ingest/pipeline/sendtopii
PUT _ingest/pipeline/sendtopii
{
  "processors": [
    {
      "set": {
        "field": "data_stream.dataset",
        "value": "pii"
      }
    },
    {
      "set": {
        "field": "data_stream.namespace",
        "value": "default"
      }
    },
    {
      "reroute" : 
      {
        "dataset" : "{{data_stream.dataset}}",
        "namespace": "{{data_stream.namespace}}"
      }
    }
  ]
}
</code></pre>
<p>3) Finally, we can run a <code>_reindex</code> to select the data we want to test/assess. It is recommended to review the <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-reindex.html">_reindex</a> documents before trying this. First, select the source data stream you want to assess, in this example, it is the <code>logs-generic-default</code> logs data stream. Note: I also added a <code>range</code> filter to select a specific time range. There is a bit of a "trick" that we need to use since we are re-routing the data to the data stream <code>logs-pii-default</code>. To do this, we just set <code>"index": "logs-tmp-default"</code> in the <code>_reindex</code> as the correct data stream will be set in the pipeline. We must do that because <code>reroute</code> is a <code>noop</code> if it is called from/to the same datastream. </p>
<pre><code>POST _reindex?wait_for_completion=false
{
  "source": {
    "index": "logs-generic-default",
    "query": {
      "bool": {
        "filter": [
          {
            "range": {
              "@timestamp": {
                "gte": "now-1h/h",
                "lt": "now"
              }
            }
          }
        ]
      }
    }
  },
  "dest": {
    "op_type": "create",
    "index": "logs-tmp-default",
    "pipeline": "sendtopii"
  }
}
</code></pre>
<h2 id="summary">Summary</h2>
<p>At this point, you have the tools and processes need to assess, detect, analyze, alert and protect PII in your logs.  </p>
<p><a href="https://github.com/bvader/elastic-pii/tree/main/elastic/blog-complete-end-solution">The end state solution can be found here:</a>. </p>
<p>In <a href="https://www.elastic.co/observability-labs/blog/pii-ner-regex-assess-redact-part-1">Part 1 of this blog</a>, we accomplished the following.</p>
<ul>
<li>Reviewed the techniques and tools we have available for PII detection and assessment</li>
<li>Reviewed NLP / NER role in PII detection and assessment</li>
<li>Built the necessary composable ingest pipelines to sample logs and run them through the NER Model</li>
<li>Reviewed the NER results and are ready to move to the second blog</li>
</ul>
<p>In <strong>Part 2</strong> of this blog, we covered the following:</p>
<ul>
<li>Redact PII using NER and redact processor</li>
<li>Apply field-level security to control access to the un-redacted data</li>
<li>Enhance the dashboards and alerts</li>
<li>Production considerations and scaling</li>
<li>How to run these processes on incoming or historical data</li>
</ul>
<p><strong><em>So get to work and reduce risk in your logs!</em></strong></p>
<h2 id="dataloadingappendix">Data Loading Appendix</h2>
<h4 id="code">Code</h4>
<p>The data loading code can be found here: </p>
<p><a href="https://github.com/bvader/elastic-pii">https://github.com/bvader/elastic-pii</a></p>
<pre><code>$ git clone https://github.com/bvader/elastic-pii.git
</code></pre>
<h4 id="creatingandloadingthesampledataset">Creating and Loading the Sample Data Set</h4>
<pre><code>$ cd elastic-pii
$ cd python
$ python -m venv .env
$ source .env/bin/activate
$ pip install elasticsearch
$ pip install Faker
</code></pre>
<p>Run the log generator </p>
<pre><code>$ python generate_random_logs.py
</code></pre>
<p>If you do not changes any parameters, this will create 10000 random logs in a file named pii.log with a mix of logs that containe and do not contain PII. </p>
<p>Edit <code>load_logs.py</code> and set the following </p>
<pre><code># The Elastic User 
ELASTIC_USER = "elastic"

# Password for the 'elastic' user generated by Elasticsearch
ELASTIC_PASSWORD = "askdjfhasldfkjhasdf"

# Found in the 'Manage Deployment' page
ELASTIC_CLOUD_ID = "deployment:sadfjhasfdlkjsdhf3VuZC5pbzo0NDMkYjA0NmQ0YjFiYzg5NDM3ZDgxM2YxM2RhZjQ3OGE3MzIkZGJmNTE0OGEwODEzNGEwN2E3M2YwYjcyZjljYTliZWQ="
</code></pre>
<p>Then run the following command. </p>
<pre><code>$ python load_logs.py
</code></pre>
<h4 id="reloadingthelogs">Reloading the logs</h4>
<p><strong>Note</strong> To reload the logs, you can simply re-run the above command. You can run the command multiple time during this exercise and the logs will be reloaded (actually loaded again). The new logs will not collide with previous runs as there will be a unique <code>run.id</code> for each run which is displayed at the end of the loading process.</p>
<pre><code>$ python load_logs.py
</code></pre>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/pii-ner-regex-assess-redact-part-2</link>
    <guid isPermaLink="false">pii-ner-regex-assess-redact-part-2</guid>
    <category><![CDATA[Logs Analytics]]></category>
    <dc:creator><![CDATA[Stephen Brown]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0e5726114c49ca9d/6a7f19bde02fac86155d6999/pii-ner-regex-assess-redact-part-2.png" length="0" type="image/png"/>
    <pubDate>Tue, 22 Oct 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Using NLP and Pattern Matching to Detect, Assess, and Redact PII in Logs - Part 1]]></title>
    <description><![CDATA[How to detect and assess PII in your logs using Elasticsearch and NLP]]></description>
    <content:encoded><![CDATA[<h2 id="introduction">Introduction:</h2>
<p>The prevalence of high-entropy logs in distributed systems has significantly raised the risk of PII (Personally Identifiable Information) seeping into our logs, which can result in security and compliance issues. This 2-part blog delves into the crucial task of identifying and managing this issue using the Elastic Stack. We will explore using NLP (Natural Language Processing) and Pattern matching to detect, assess, and, where feasible, redact PII from logs that are being ingested into Elasticsearch.</p>
<p>In <strong>Part 1</strong> of this blog, we will cover the following:</p>
<ul>
<li>Review the techniques and tools we have available to manage PII in our logs</li>
<li>Understand the roles of NLP / NER in PII detection</li>
<li>Build a composable processing pipeline to detect and assess PII</li>
<li>Sample logs and run them through the NER Model</li>
<li>Assess the results of the NER Model </li>
</ul>
<p>In <a href="https://www.elastic.co/observability-labs/blog/pii-ner-regex-assess-redact-part-2">Part 2 of this blog</a> of this blog, we will cover the following:</p>
<ul>
<li>Redact PII using NER and the redact processor</li>
<li>Apply field-level security to control access to the un-redacted data</li>
<li>Enhance the dashboards and alerts</li>
<li>Production considerations and scaling</li>
<li>How to run these processes on incoming or historical data</li>
</ul>
<p>Here is the overall flow we will construct over the 2 blogs:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt13e652a6333de461/6a7f19893ce8e2179bcf5795/pii-overall-flow.png" alt="PII Overall Flow" /></p>
<p>All code for this exercise can be found at:
<a href="https://github.com/bvader/elastic-pii">https://github.com/bvader/elastic-pii</a>. </p>
<h2 id="toolsandtechniques">Tools and Techniques</h2>
<p>There are four general capabilities that we will use for this exercise. </p>
<ul>
<li>Named Entity Recognition Detection (NER)</li>
<li>Pattern Matching Detection</li>
<li>Log Sampling</li>
<li>Ingest Pipelines as Composable Processing </li>
</ul>
<h4 id="namedentityrecognitionnerdetection">Named Entity Recognition (NER) Detection</h4>
<p>NER is a sub-task of Natural Language Processing (NLP) that involves identifying and categorizing named entities in unstructured text into predefined categories such as:</p>
<ul>
<li>Person: Names of individuals, including celebrities, politicians, and historical figures.</li>
<li>Organization: Names of companies, institutions, and organizations.</li>
<li>Location: Geographic locations, including cities, countries, and landmarks.</li>
<li>Event: Names of events, including conferences, meetings, and festivals.</li>
</ul>
<p>For our use PII case, we will choose the base BERT NER model <a href="https://huggingface.co/dslim/bert-base-NER">bert-base-NER</a> that can be downloaded from <a href="https://huggingface.co">Hugging Face</a> and loaded into Elasticsearch as a trained model.</p>
<p><strong>Important Note:</strong>  NER / NLP Models are CPU-intensive and expensive to run at scale; thus, we will want to employ a sampling technique to understand the risk in our logs without sending the full logs volume through the NER Model. We will discuss the performance and scaling of the NER model in part 2 of the blog. </p>
<h4 id="patternmatchingdetection">Pattern Matching Detection</h4>
<p>In addition to using an NER, regex pattern matching is a powerful tool for detecting and redacting PII based on common patterns. The Elasticsearch <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/redact-processor.html">redact</a> processor is built for this use case.</p>
<h4 id="logsampling">Log Sampling</h4>
<p>Considering the performance implications of NER and the fact that we may be ingesting a large volume of logs into Elasticsearch, it makes sense to sample our incoming logs. We will build a simple log sampler to accomplish this. </p>
<h4 id="ingestpipelinesascomposableprocessing">Ingest Pipelines as Composable Processing</h4>
<p>We will create several pipelines, each focusing on a specific capability and a main ingest pipeline to orchestrate the overall process. </p>
<h2 id="buildingtheprocessingflow">Building the Processing Flow</h2>
<h4 id="logssamplingcomposableingestpipelines">Logs Sampling + Composable Ingest Pipelines</h4>
<p>The first thing we will do is set up a sampler to sample our logs. This ingest pipeline simply takes a sampling rate between 0 (no log) and 10000 (all logs), which allows as low as ~0.01% sampling rate and marks the sampled logs with <code>sample.sampled: true</code>. Further processing on the logs will be driven by the value of <code>sample.sampled</code>. The <code>sample.sample_rate</code> can be set here or "passed in" from the orchestration pipeline.</p>
<p>The command should be run from the Kibana -&gt; Dev Tools</p>
<p><a href="https://github.com/bvader/elastic-pii/blob/main/elastic/blog-part-1/logs-sampler-composable-pipelines-part-1.json">The code can be found here</a> for the following three sections of code. </p>
<p></p>
  logs-sampler pipeline code - click to open/close<p></p>
<pre><code># logs-sampler pipeline - part 1
DELETE _ingest/pipeline/logs-sampler
PUT _ingest/pipeline/logs-sampler
{
  "processors": [
    {
      "set": {
        "description": "Set Sampling Rate 0 None 10000 all allows for 0.01% precision",
        "if": "ctx.sample.sample_rate == null",
        "field": "sample.sample_rate",
        "value": 10000
      }
    },
    {
      "set": {
        "description": "Determine if keeping unsampled docs",
        "if": "ctx.sample.keep_unsampled == null",
        "field": "sample.keep_unsampled",
        "value": true
      }
    },
    {
      "set": {
        "field": "sample.sampled",
        "value": false
      }
    },
    {
      "script": {
        "source": """ Random r = new Random();
        ctx.sample.random = r.nextInt(params.max); """,
        "params": {
          "max": 10000
        }
      }
    },
    {
      "set": {
        "if": "ctx.sample.random &lt;= ctx.sample.sample_rate",
        "field": "sample.sampled",
        "value": true
      }
    },
    {
      "drop": {
         "description": "Drop unsampled document if applicable",
        "if": "ctx.sample.keep_unsampled == false &amp;&amp; ctx.sample.sampled == false"
      }
    }
  ]
}
</code></pre>
<p></p><p></p>
<p>Now, let's test the logs sampler. We will build the first part of the composable pipeline. We will be sending logs to the logs-generic-default data stream. With that in mind, we will create the <code>logs@custom</code> ingest pipeline that will be automatically called using the logs <a href="https://www.elastic.co/guide/en/fleet/current/data-streams.html#data-streams-pipelines">data stream framework</a> for customization. We will add one additional level of abstraction so that you can apply this PII processing to other data streams.</p>
<p>Next, we will create the <code>process-pii</code> pipeline. This is the core processing pipeline where we will orchestrate PII processing component pipelines. In this first step, we will simply apply the sampling logic. Note that we are setting the sampling rate to 100, which is equivalent to 10% of the logs.</p>
<p></p>
  process-pii pipeline code - click to open/close<p></p>
<pre><code># Process PII pipeline - part 1
DELETE _ingest/pipeline/process-pii
PUT _ingest/pipeline/process-pii
{
  "processors": [
    {
      "set": {
        "description": "Set true if enabling sampling, otherwise false",
        "field": "sample.enabled",
        "value": true
      }
    },
    {
      "set": {
        "description": "Set Sampling Rate 0 None 10000 all allows for 0.01% precision",
        "field": "sample.sample_rate",
        "value": 1000
      }
    },
    {
      "set": {
        "description": "Set to false if you want to drop unsampled data, handy for reindexing hostorical data",
        "field": "sample.keep_unsampled",
        "value": true
      }
    },
    {
      "pipeline": {
        "if": "ctx.sample.enabled == true",
        "name": "logs-sampler",
        "ignore_failure": true
      }
    }
  ]
}
</code></pre>
<p></p><p></p>
<p>Finally, we create the logs <code>logs@custom</code>, which will simply call our <code>process-pii</code> pipeline based on the correct <code>data_stream.dataset</code></p>
<p></p>
  logs@custom pipeline code - click to open/close<p></p>
<pre><code># logs@custom pipeline - part 1
DELETE _ingest/pipeline/logs@custom
PUT _ingest/pipeline/logs@custom
{
  "processors": [
    {
      "set": {
        "field": "pipelinetoplevel",
        "value": "logs@custom"
      }
    },
        {
      "set": {
        "field": "pipelinetoplevelinfo",
        "value": "{{{data_stream.dataset}}}"
      }
    },
    {
      "pipeline": {
        "description" : "Call the process_pii pipeline on the correct dataset",
        "if": "ctx?.data_stream?.dataset == 'pii'", 
        "name": "process-pii"
      }
    }
  ]
}
</code></pre>
<p></p><p></p>
<p>Now, let's test to see the sampling at work.</p>
<p>Load the data as described here <a href="https://www.elastic.co/observability-labs/blog/pii-ner-regex-assess-redact-part-1#data-loading-appendix">Data Loading Appendix</a>. Let's use the sample data first, and we will talk about how to test with your incoming or historical logs later at the end of this blog. </p>
<p>If you look at Observability -&gt; Logs -&gt; Logs Explorer with KQL filter <code>data_stream.dataset : pii</code> and Breakdown by sample.sampled, you should see the breakdown to be approximately 10%</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0aab62015183d03c/6a7f198d5967e513345dd6a9/pii-discover-1-part-1.png" alt="PII Discover 1" /></p>
<p>At this point we have a composable ingest pipeline that is "sampling" logs. As a bonus, you can use this logs sampler for any other use cases you have as well. </p>
<h4 id="loadingconfigurationandexecutionofthenerpipeline">Loading, Configuration, and Execution of the NER Pipeline</h4>
<h5 id="loadingthenermodel">Loading the NER Model</h5>
<p>You will need a Machine Learning node to run the NER model on. In this exercise, we are using <a href="https://www.elastic.co/guide/en/cloud/current/ec-getting-started.html">Elastic Cloud Hosted Deployment </a>on AWS with the <a href="https://www.elastic.co/guide/en/cloud/current/ec_selecting_the_right_configuration_for_you.html">CPU Optimized (ARM)</a> architecture. The NER inference will run on a Machine Learning AWS c5d node. There will be GPU options in the future, but today, we will stick with CPU architecture.  </p>
<p>This exercise will use a single c5d with 8 GB RAM with 4.2 vCPU up to 8.4 vCPU</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte8ec884fa3092c65/6a7f1990c2cc0903e12499a2/pii-ml-node-part-1.png" alt="ML Node" /></p>
<p>Please refer to the official documentation on <a href="https://www.elastic.co/guide/en/machine-learning/current/ml-nlp-import-model.html">how to import an NLP-trained model into Elasticsearch</a> for complete instructions on uploading, configuring, and deploying the model.</p>
<p>The quickest way to get the model is using the Eland Docker method. </p>
<p>The following command will load the model into Elasticsearch but will not start it. We will do that in the next step.  </p>
<pre><code>docker run -it --rm --network host docker.elastic.co/eland/eland \
  eland_import_hub_model \
  --url https://mydeployment.es.us-west-1.aws.found.io:443/ \
  -u elastic -p password \
  --hub-model-id dslim/bert-base-NER --task-type ner
</code></pre>
<h5 id="deployandstartthenermodel">Deploy and Start the NER Model</h5>
<p>In general, to improve ingest performance, increase throughput by adding more allocations to the deployment. For improved search speed, increase the number of threads per allocation.</p>
<p>To scale ingest, we will focus on scaling the allocations for the deployed model. More information on this topic is available <a href="https://www.elastic.co/guide/en/machine-learning/current/ml-nlp-deploy-model.html">here</a>. The number of allocations must be less than the available allocated processors (cores, not vCPUs) per node.</p>
<p>To deploy and start the NER Model. We will do this using the <a href="https://www.elastic.co/guide/en/elasticsearch/reference/8.15/start-trained-model-deployment.html">Start trained model deployment API</a></p>
<p>We will configure the following:</p>
<ul>
<li>4 Allocations to allow for more parallel ingestion</li>
<li>1 Thread per Allocation</li>
<li>0 Byes Cache, as we expect a low cache hit rate </li>
<li>8192 Queue</li>
</ul>
<pre><code># Start the model with 4 Allocators x 1 Thread, no cache, and 8192 queue
POST _ml/trained_models/dslim__bert-base-ner/deployment/_start?cache_size=0b&amp;number_of_allocations=4&amp;threads_per_allocation=1&amp;queue_capacity=8192
</code></pre>
<p>You should get a response that looks something like this.</p>
<pre><code>{
  "assignment": {
    "task_parameters": {
      "model_id": "dslim__bert-base-ner",
      "deployment_id": "dslim__bert-base-ner",
      "model_bytes": 430974836,
      "threads_per_allocation": 1,
      "number_of_allocations": 4,
      "queue_capacity": 8192,
      "cache_size": "0",
      "priority": "normal",
      "per_deployment_memory_bytes": 430914596,
      "per_allocation_memory_bytes": 629366952
    },
...
    "assignment_state": "started",
    "start_time": "2024-09-23T21:39:18.476066615Z",
    "max_assigned_allocations": 4
  }
}
</code></pre>
<p>The NER model has been deployed and started and is ready to be used.</p>
<p>The following ingest pipeline implements the NER model via the <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/inference-processor.html">inference</a> processor. </p>
<p>There is a significant amount of code here, but only two items of interest now exist. The rest of the code is conditional logic to drive some additional specific behavior that we will look closer at in the future. </p>
<ol>
<li><p>The inference processor calls the NER model by ID, which we loaded previously, and passes the text to be analyzed, which, in this case, is the message field, which is the text_field we want to pass to the NER model to analyze for PII.</p></li>
<li><p>The script processor loops through the message field and uses the data generated by the NER model to replace the identified PII with redacted placeholders. This looks more complex than it really is, as it simply loops through the array of ML predictions and replaces them in the message string with constants, and stores the results in a new field <code>redact.message</code>. We will look at this a little closer in the following steps. </p></li>
</ol>
<p><a href="https://github.com/bvader/elastic-pii/blob/main/elastic/blog-part-1/logs-sampler-composable-pipelines-part-2.json">The code can be found here</a> for the following three sections of code. </p>
<p>The NER PII Pipeline</p>
<p></p>
  logs-ner-pii-processor pipeline code - click to open/close<p></p>
<pre><code># NER Pipeline
DELETE _ingest/pipeline/logs-ner-pii-processor
PUT _ingest/pipeline/logs-ner-pii-processor
{
  "processors": [
    {
      "set": {
        "description": "Set to true to actually redact, false will run processors but leave original",
        "field": "redact.enable",
        "value": true
      }
    },
    {
      "set": {
        "description": "Set to true to keep ml results for debugging",
        "field": "redact.ner.keep_result",
        "value": true
      }
    },
    {
      "set": {
        "description": "Set to PER, LOC, ORG to skip, or NONE to not drop any replacement",
        "field": "redact.ner.skip_entity",
        "value": "NONE"
      }
    },
    {
      "set": {
        "description": "Set to PER, LOC, ORG to skip, or NONE to not drop any replacement",
        "field": "redact.ner.minimum_score",
        "value": 0
      }
    },
    {
      "set": {
        "if": "ctx?.redact?.message == null",
        "field": "redact.message",
        "copy_from": "message"
      }
    },
    {
      "set": {
        "field": "redact.ner.successful",
        "value": true
      }
    },
    {
      "set": {
        "field": "redact.ner.found",
        "value": false
      }
    },
    {
      "inference": {
        "model_id": "dslim__bert-base-ner",
        "field_map": {
          "message": "text_field"
        },
        "on_failure": [
          {
            "set": {
              "description": "Set 'error.message'",
              "field": "failure",
              "value": "REDACT_NER_FAILED"
            }
          },
          {
            "set": {
              "field": "redact.ner.successful",
              "value": false
            }
          }
        ]
      }
    },
    {
      "script": {
        "if": "ctx.failure_ner != 'REDACT_NER_FAILED'",
        "lang": "painless",
        "source": """String msg = ctx['message'];
          for (item in ctx['ml']['inference']['entities']) {
              if ((item['class_name'] != ctx.redact.ner.skip_entity) &amp;&amp; 
                (item['class_probability'] &gt;= ctx.redact.ner.minimum_score)) {  
                    msg = msg.replace(item['entity'], '&lt;' + 
                    'REDACTNER-'+ item['class_name'] + '_NER&gt;')
              }
          }
          ctx.redact.message = msg""",
        "on_failure": [
          {
            "set": {
              "description": "Set 'error.message'",
              "field": "failure",
              "value": "REDACT_REPLACEMENT_SCRIPT_FAILED",
              "override": false
            }
          },
          {
            "set": {
              "field": "redact.successful",
              "value": false
            }
          }
        ]
      }
    },

    {
      "set": {
        "if": "ctx?.ml?.inference?.entities.size() &gt; 0", 
        "field": "redact.ner.found",
        "value": true,
        "ignore_failure": true
      }
    },
    {
      "set": {
        "if": "ctx?.redact?.pii?.found == null",
        "field": "redact.pii.found",
        "value": false
      }
    },
    {
      "set": {
        "if": "ctx?.redact?.ner?.found == true",
        "field": "redact.pii.found",
        "value": true
      }
    },
    {
      "remove": {
        "if": "ctx.redact.ner.keep_result != true",
        "field": [
          "ml"
        ],
        "ignore_missing": true,
        "ignore_failure": true
      }
    }
  ],
  "on_failure": [
    {
      "set": {
        "field": "failure",
        "value": "GENERAL_FAILURE",
        "override": false
      }
    }
  ]
}
</code></pre>
<p></p><p></p>
<p>The updated PII Processor Pipeline, which now calls the NER Pipeline</p>
<p></p>
  process-pii pipeline code - click to open/close<p></p>
<pre><code># Updated Process PII pipeline that now call the NER pipeline
DELETE _ingest/pipeline/process-pii
PUT _ingest/pipeline/process-pii
{
  "processors": [
    {
      "set": {
        "description": "Set true if enabling sampling, otherwise false",
        "field": "sample.enabled",
        "value": true
      }
    },
    {
      "set": {
        "description": "Set Sampling Rate 0 None 10000 all allows for 0.01% precision",
        "field": "sample.sample_rate",
        "value": 1000
      }
    },
    {
      "set": {
        "description": "Set to false if you want to drop unsampled data, handy for reindexing hostorical data",
        "field": "sample.keep_unsampled",
        "value": true
      }
    },
    {
      "pipeline": {
        "if": "ctx.sample.enabled == true",
        "name": "logs-sampler",
        "ignore_failure": true
      }
    },
    {
      "pipeline": {
        "if": "ctx.sample.enabled == false || (ctx.sample.enabled == true &amp;&amp; ctx.sample.sampled == true)",
        "name": "logs-ner-pii-processor"
      }
    }
  ]
}
</code></pre>
<p></p><p></p>
<p>Now reload the data as described here in <a href="https://www.elastic.co/observability-labs/blog/pii-ner-regex-assess-redact-part-1#reloading-the-logs">Reloading the logs</a></p>
<h3 id="results">Results</h3>
<p>Let's take a look at the results with the NER processing in place. In the Logs Explorer with KQL query bar, execute the following query
<code>data_stream.dataset : pii and ml.inference.entities.class_name : ("PER" and "LOC" and "ORG" )</code> </p>
<p>Logs Explorer should look something like this, open the top message to see the details.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt15d140ffc3b719b0/6a7f1993bdcff051cac43293/pii-discover-2-part-1.png" alt="PII Discover 2" /></p>
<h4 id="nermodelresults">NER Model Results</h4>
<p>Lets take a closer look at what these fields mean.</p>
<p><strong>Field:</strong> <code>ml.inference.entities.class_name</code>\
<strong>Sample Value:</strong> <code>[PER, PER, LOC, ORG, ORG]</code>\
<strong>Description:</strong> An array of the named entity classes that the NER model has identified.</p>
<p><strong>Field:</strong> <code>ml.inference.entities.class_probability</code>\
<strong>Sample Value:</strong> <code>[0.999, 0.972, 0.896, 0.506, 0.595]</code>\
<strong>Description:</strong> The class_probability is a value between 0 and 1, which indicates how likely it is that a given data point belongs to a certain class. The higher the number, the higher the probability that the data point belongs to the named class. <strong>This is important as in the next blog we can decide a threshold that we will want to use to alert and redact on.</strong>'
You can see in this example it identified a <code>LOC</code> as an <code>ORG</code>, we can filter this out / find them by setting a threshold. </p>
<p><strong>Field:</strong> <code>ml.inference.entities.entity</code>\
<strong>Sample Value:</strong> <code>[Paul Buck, Steven Glens, South Amyborough, ME, Costco]</code>\
<strong>Description:</strong> The array of entities identified that align positionally with the <code>class_name</code> and <code>class_probability</code>.</p>
<p><strong>Field:</strong> <code>ml.inference.predicted_value</code>\
<strong>Sample Value:</strong> <code>[2024-09-23T14:32:14.608207-07:00Z] log.level=INFO: Payment successful for order #4594 (user: [Paul Buck](PER&amp;Paul+Buck), david59@burgess.net). Phone: 726-632-0527x520, Address: 3713 [Steven Glens](PER&amp;Steven+Glens), [South Amyborough](LOC&amp;South+Amyborough), [ME](ORG&amp;ME) 93580, Ordered from: [Costco](ORG&amp;Costco)</code>\
<strong>Description:</strong> The predicted value of the model.</p>
<h4 id="piiassessmentdashboard">PII Assessment Dashboard</h4>
<p>Lets take a quick look at a dashboard built to assess PII the data. </p>
<p>To load the dashboard, go to Kibana -&gt; Stack Management -&gt; Saved Objects and import the <code>pii-dashboard-part-1.ndjson</code> file that can be found here: </p>
<p>https://github.com/bvader/elastic-pii/blob/main/elastic/blog-part-1/pii-dashboard-part-1.ndjson</p>
<p>More complete instructions on Kibana Saved Objects can be found <a href="https://www.elastic.co/guide/en/kibana/current/managing-saved-objects.html">here</a>.</p>
<p>After loading the dashboard, navigate to it and select the right time range and you should see something like below. It shows metrics such as sample rate, percent of logs with NER, NER Score Trends etc. We will examine the assessment and actions in part 2 of this blog. </p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt70c587367d5fadd9/6a7f199705b7b560a118bd2f/pii-dashboard-1-part-1.png" alt="PII Dashboard 1" /></p>
<h2 id="summaryandnextsteps">Summary and Next Steps</h2>
<p>In this first part of the blog, we have accomplished the following.</p>
<ul>
<li>Reviewed the techniques and tools we have available for PII detection and assement</li>
<li>Reviewed NLP / NER role in PII detection and assessment</li>
<li>Built the necessary composable ingest pipelines to sample logs and run them through the NER Model</li>
<li>Reviewed the NER results and are ready to move to the second blog</li>
</ul>
<p>In the upcoming <a href="https://www.elastic.co/observability-labs/blog/pii-ner-regex-assess-redact-part-2">Part 2 of this blog</a> of this blog, we will cover the following:</p>
<ul>
<li>Redact PII using NER and redact processor</li>
<li>Apply field-level security to control access to the un-redacted data</li>
<li>Enhance the dashboards and alerts</li>
<li>Production considerations and scaling</li>
<li>How to run these processes on incoming or historical data</li>
</ul>
<h2 id="dataloadingappendix">Data Loading Appendix</h2>
<h4 id="code">Code</h4>
<p>The data loading code can be found here: </p>
<p><a href="https://github.com/bvader/elastic-pii">https://github.com/bvader/elastic-pii</a></p>
<pre><code>$ git clone https://github.com/bvader/elastic-pii.git
</code></pre>
<h4 id="creatingandloadingthesampledataset">Creating and Loading the Sample Data Set</h4>
<pre><code>$ cd elastic-pii
$ cd python
$ python -m venv .env
$ source .env/bin/activate
$ pip install elasticsearch
$ pip install Faker
</code></pre>
<p>Run the log generator </p>
<pre><code>$ python generate_random_logs.py
</code></pre>
<p>If you do not changes any parameters, this will create 10000 random logs in a file named pii.log with a mix of logs that containe and do not contain PII. </p>
<p>Edit <code>load_logs.py</code> and set the following </p>
<pre><code># The Elastic User 
ELASTIC_USER = "elastic"

# Password for the 'elastic' user generated by Elasticsearch
ELASTIC_PASSWORD = "askdjfhasldfkjhasdf"

# Found in the 'Manage Deployment' page
ELASTIC_CLOUD_ID = "deployment:sadfjhasfdlkjsdhf3VuZC5pbzo0NDMkYjA0NmQ0YjFiYzg5NDM3ZDgxM2YxM2RhZjQ3OGE3MzIkZGJmNTE0OGEwODEzNGEwN2E3M2YwYjcyZjljYTliZWQ="
</code></pre>
<p>Then run the following command. </p>
<pre><code>$ python load_logs.py
</code></pre>
<h4 id="reloadingthelogs">Reloading the logs</h4>
<p><strong>Note</strong> To reload the logs, you can simply re-run the above command. You can run the command multiple time during this exercise and the logs will be reloaded (actually loaded again). The new logs will not collide with previous runs as there will be a unique <code>run.id</code> for each run which is displayed at the end of the loading process.</p>
<pre><code>$ python load_logs.py
</code></pre>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/pii-ner-regex-assess-redact-part-1</link>
    <guid isPermaLink="false">pii-ner-regex-assess-redact-part-1</guid>
    <category><![CDATA[Logs Analytics]]></category>
    <dc:creator><![CDATA[Stephen Brown]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltce9ee72734da81d6/6a7f199bb6b734381ce491b0/pii-ner-regex-assess-redact-part-1.png" length="0" type="image/png"/>
    <pubDate>Wed, 25 Sep 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Kibana: How to create impactful visualisations with magic formulas]]></title>
    <description><![CDATA[We will see how magic math formulas in the Kibana Lens editor can help to highlight high values.]]></description>
    <content:encoded><![CDATA[<p>In the previous blog post, <a href="https://www.elastic.co/blog/designing-intuitive-kibana-dashboards-as-a-non-designer">Designing Intuitive Kibana Dashboards as a non-designer</a>, we highlighted the importance of creating intuitive dashboards. It demonstrated how simple changes (grouping themes, changing type charts, and more) can make a difference in understanding your data. When delivering courses like<a href="https://www.elastic.co/training/data-analysis-with-kibana"> Data Analysis with Kibana</a> or<a href="https://www.elastic.co/training/elastic-observability-engineer"> Elastic Observability Engineer</a> courses, we emphasize this blog post and how these changes help bring essential information to the surface. I like a complementary approach to reach this goal: using two colors to separate the highest data values from the common ones.</p>
<p>To illustrate this idea, we will use the <em>Sample flight data</em> dataset. Now, let’s compare two visualizations ranking the top 10 destination countries per total number of flights. Which visualization has a higher impact?</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8828b919cb5c544d/6a9fb62c075f974b623e3a1c/blog-1-dbg-excalidraw-flights-teaser-intro-dark.png" alt="Flights: Top 10 destinations" /></p>
<p>If you chose the second one, you may be wondering how this was done with the Kibana Lens editor. While preparing for the certification last year, I found a way to achieve this result. The secret is using two different layers and some magic formulas. This post will explain how math in Lens formulas helps create two data-color visualizations.  </p>
<p>We will start with the first example that emphasizes only the highest value of the dataset we are focusing on. The second example describes how to highlight other high values (as shown in the illustration above).</p>
<p><em>[Note: the tips explained in this blog post can be applied from v 7.15]</em></p>
<h2 id="onlythehighestvalueaidonlythehighestvaluea">Only the highest value<a id="only-the-highest-value"></a></h2>
<p>To understand how math helps to separate high values from common ones, let’s start with this first example: emphasizing only the highest value.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte079be42d799346f/6a7f0b7dc2cc099dce2494e0/blog-1-wbg-flights-1.1-teaser.png" alt="1.1 flights: " /></p>
<p>We start with a bar horizontal chart:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc6d00c95b801b771/6a7f0b8073d9bdf96429da89/blog-1-wbg-flights-1.1-kibana-bar-horizontal-setup.png" alt="1.1 flights: Lens bar horizontal chart" /></p>
<p>We need to identify the highest value of the scope we are currently examining. We will use one proper overall_* function: the <strong>overall_max()</strong>, a pipeline function (equivalent to a pipeline aggregation in Query DSL). </p>
<p>In our example, we group the flights by country(destination). This means we count the number of flights for each DestCountry (= 1 bucket). The <strong>overall_max()</strong> will select which bucket has the highest value. </p>
<p>The math trick here is to divide the number of flights per bucket by the maximum value found among all buckets. Only one bucket will return 1: the bucket matching the max value found by overall_max(). All the other buckets will return a value &lt; 1 and &gt;0. We use <strong>floor()</strong> to ensure any 0.xxx values are rounded to 0. </p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb6b32d7d95edc90c/6a7f0b83c2cc0903c12494e4/blog-1-wbg-flights-1.1-explaination-floor.png" alt="1.1 flights: explaining floor()" /></p>
<p>Now, we can multiple it with a count() and we have our formula for the 1st layer!</p>
<p><strong><em>Layer 1</em></strong>: <code>count()*floor(count()/overall_max(count()))</code></p>
<p>From here, in Lens Editor, we duplicate the layer to adjust the formula of the second layer containing the rest of the data. We need to append another count() followed by the minus operator to the formula. This is the other trick. In this layer, we just need to ensure the highest value is not represented, which will happen only once. It is when count() = overall_max(), which is = 1 when we divide them.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt20522989d7bc223f/6a7f0b86b6b734f2cce48d5a/blog-1-wbg-flights-1.1-explaination-layer1-and-layer2.png" alt="1.1 flights: layer 1 + layer 2" /></p>
<p><strong><em>Layer 2</em></strong>: <code>count() - count()*floor(count()/overall_max(count()))</code></p>
<p>To achieve a nice merge of these two layers, we need to do the following adjustments in both:</p>
<ul>
<li><p>select <strong>bar horizontal stacked</strong></p></li>
<li><p>Vertical axis: change”Rank by” to Custom and ensure Rank function is “Count”</p></li>
</ul>
<p>Here is the final setup of the two layers:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9e00af474f98371d/6a7f0b8b05b7b54ef718b8b8/blog-1-wbg-flights-1.1-kibana-final-2layers-setup.png" alt="1.1 flights: 2layers setup" /></p>
<p><strong><em>Layer 1</em></strong>: <code>count()*floor(count()/overall_max(count()))</code></p>
<p><strong><em>Layer 2</em></strong>: <code>count() - count()*floor(count()/overall_max(count()))</code></p>
<p>This visualization also works well for time series data where you need to quickly highlight which time period (12h in the example below) had the highest number of flights:\
<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt60d2ac3d1eb5c192/6a7f0b8d1967ea25e43306ad/blog-1-wbg-flights-1.1-timeserie-example.png" alt="1.1 flights: timeseries example" /></p>
<h2 id="abovethesurfaceaidabovethesurfacea">Above the surface<a id="above-the-surface"></a></h2>
<p>Building on what we have done earlier, we can extend the approach to get other high values above the surface. Let’s see which formula we used to create the visualization in the introduction:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt21d0e28d38824262/6a9fb68dccc7d12125a15181/blog-1-dbg-excalidraw-flights-teaser-intro-s1-dark.png" alt="2.1 Flights: Top 10 destinations" /></p>
<p>For this visualization, we used a property of the <strong>round()</strong> function. This function brings in only the values greater than 50% of the highest value.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt80275d7b0c0a1b95/6a7f0b93ead8ec2cb6baa7df/blog-1-wbg-flights-2.1-explaination-round.png" alt="2.1 flights: round() &gt; 50% of max explanation" />
</p><p>Let's duplicate our first visualization and swap out the floor() function with round().</p>
<p><strong><em>Layer 1</em></strong>: <code>count()*round(count()/overall_max(count()))</code></p>
<p><strong><em>Layer 2</em></strong>: <code>count() - count()*round(count()/overall_max(count()))</code></p>
<p>It was an easy fix.\
What if we want to extend the first layer further by adding more high values?\
For instance, we would like all the values above the average.</p>
<p>To do this, we use <strong>overall_average</strong>() as a new reference value instead of the overall_max () reference to separate the eligible values in Layer 1.</p>
<p>As we are comparing against the average value among all the buckets, the division might return values greater than 1.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt92d64a7a432beacd/6a7f0b969090b0d4c084e94d/blog-1-wbg-flights-2.2-explaination-floor.png" alt="2.2 flights: round() explanation" /></p>
<p>Here, the <strong>clamp</strong>() function nicely solves this issue. </p>
<p>According to the formula reference, clamp() "limits the value from a minimum to maximum". Combining clamp() and floor() ensures that there are only two possible output values: either the minimum value ( 0 ) or the maximum value ( 1 ) given as parameters.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt225dd2f7ba60125d/6a7f0b99c2cc096da42494ee/blog-1-wbg-flights-2.2-explaination-clamp.png" alt="2.2 flights: clamp() explanation" /></p>
<p>Applied to our flights dataset, it highlights the country destinations that have more flights than the average:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt19fae0da63280f51/6a9fb72236a741330227454a/blog-1-dbg-excalidraw-flights-2.png" alt="2.2 flights: above the overall average " /></p>
<p><strong><em>Layer 1</em></strong>: <code>count()*clamp(floor(count()/overall_average(count())),0,1)</code></p>
<p><strong><em>Layer 2</em></strong>: <code>count() - count()*clamp(floor(count()/overall_average(count())),0,1)</code></p>
<p>It also opens up options for using other dynamic references. For instance, we could place all the values greater than 60% of the highest above the surface ( &gt; <code>0.6*overall_max(count())</code>). 
We can tune our formula as follow: </p>
<pre><code>count()*clamp(floor(count()/(0.6*overall_max(count()) ) ),0,1)
</code></pre>
<h2 id="conclusionaidconclusiona">Conclusion<a id="conclusion"></a></h2>
<p>In the first part, we have seen the main tips allowing us to create a two-color histogram:</p>
<ul>
<li><p>Two layers: one for the highest value and one for the remaining values</p></li>
<li><p>Visualization type: bar horizontal/vertical <strong>stacked</strong></p></li>
<li><p>To separate the data we use a formula where only the highest value return 1 otherwise 0</p></li>
</ul>
<p> </p>
<p>Then in the second part, we have seen how we can extend this principle to embrace more high values above the surface. This approach can be summarized as follows:</p>
<ul>
<li><p>Start with layer 1 focusing on the high value: count()*\</p></li>
<li><p>Duplicate the layer and adjust the formula:\
 ( count() - count()*\)</p></li>
</ul>
<p>Finally, we provide 4 generic formulas that are ready to use to spice up your dashboards:</p>
<p>|                         |                                                         |
| ----------------------- | :-----------------------------------------------------: |
| <strong>1. Only the highest</strong> |                                                         |
| Layer 1                 |      <code>count()*floor(count()/overall_max(count()))</code>      |
| Layer 2                 | <code>count() - count()*floor(count()/overall_max(count()))</code> |</p>
<p>|                                                                       |                                                         |
| --------------------------------------------------------------------- | :-----------------------------------------------------: |
| <strong>2.1. Above the surface :</strong> high values (above 50% of the max value) |                                                         |
| Layer 1                                                               |      <code>count()*floor(count()/overall_max(count()))</code>      |
| Layer 2                                                               | <code>count() - count()*floor(count()/overall_max(count()))</code> |</p>
<p>|                                                                   |                                                                        |
| ----------------------------------------------------------------- | :--------------------------------------------------------------------: |
| <strong>2.2. Above the surface :</strong> all values above the overall average |                                                                        |
| Layer 1                                                           |      <code>count()*clamp(floor(count()/overall_average(count())),0,1)</code>      |
| Layer 2                                                           | <code>count() - count()*clamp(floor(count()/overall_average(count())),0,1)</code> |</p>
<p>|                                                                             |                                                                            |
| --------------------------------------------------------------------------- | :------------------------------------------------------------------------: |
| <strong>2.2. Above the surface :</strong> all the values greater than 60% of the highest |                                                                            |
| Layer 1                                                                     |      <code>count()*clamp(floor(count()/(0.6*overall_max(count()) ) ),0,1)</code>      |
| Layer 2                                                                     | <code>count() - count()*clamp(floor(count()/(0.6*overall_max(count()) ) ),0,1)</code> |</p>
<p>Try these examples out for yourself by signing up for a <a href="https://cloud.elastic.co/registration?elektra=10-common-questions-kibana-blog">free trial of Elastic Cloud</a> or <a href="https://www.elastic.co/downloads/">download</a> the self-managed version of the Elastic Stack for free. If you have additional questions about getting started, head on over to the <a href="https://discuss.elastic.co/c/elastic-stack/kibana/7">Kibana forum</a> or check out the <a href="https://www.elastic.co/guide/en/kibana/current/index.html">Kibana documentation guide</a>.\</p>
<p><em>Note:</em>
Starting from version 8.6, the formulas using <code>clamp()</code> can take advantage of the  function <strong>ifelse</strong>(), this will make it easier to write. An example can be found in <a href="https://discuss.elastic.co/t/lens-if-statements-with-color/366750/2">this discussion</a>.</p>
<p><strong>References</strong>:</p>
<ul>
<li><p><a href="https://www.elastic.co/blog/designing-intuitive-kibana-dashboards-as-a-non-designer">Designing intuitive Kibana dashboards as a non-designer</a></p></li>
<li><p><a href="https://www.elastic.co/guide/en/kibana/current/lens.html#lens-formulas">Kibana: Lens editor - use formula to perform math</a></p></li>
<li><p>Discovering the clamp() function <a href="https://discuss.elastic.co/t/if-condition-in-kibana-table-visualization/305751/5">in this discussion (Thanks Marco!)</a></p></li>
</ul>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/kibana-impactful-visualizations-with-magic-formulas-part1</link>
    <guid isPermaLink="false">kibana-impactful-visualizations-with-magic-formulas-part1</guid>
    <category><![CDATA[Logs Analytics]]></category>
    <dc:creator><![CDATA[Vincent du Sordet]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt304c4355847436f4/6a7f0b9f77b0342b673ff43d/kibana-magic-formulas-p1.png" length="0" type="image/png"/>
    <pubDate>Mon, 09 Sep 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Bringing Your Cloud-Managed Kubernetes Audit Logs into Elasticsearch]]></title>
    <description><![CDATA[How to bring your Cloud-Managed Kubernetes Audit Logs into Elasticsearch]]></description>
    <content:encoded><![CDATA[<h2 id="introduction">Introduction:</h2>
<p>Kubernetes audit logs are essential for ensuring the security, compliance, and transparency of Kubernetes clusters. However, with managed Kubernetes infrastructure, traditional audit file-based log shipping is often not supported, and audit logs are only available via the control plane API or the Cloud Provider logging facility. In this blog, we will show you how to ingest the audit logs from these other sources and still take advantage of the <a href="https://www.elastic.co/docs/current/integrations/kubernetes/audit-logs">Elastic Kubernetes Audit Log Integration</a>.</p>
<p>In this blog we will be focusing on AWS as our cloud provider and when ingesting logs from AWS you have several options:</p>
<ul>
<li><a href="https://www.elastic.co/docs/current/integrations/aws_logs">AWS Custom Logs integration</a> (which we will utilize in this blog)</li>
<li><a href="https://www.elastic.co/observability-labs/blog/aws-kinesis-data-firehose-observability-analytics">AWS Firehose</a> to send logs from Cloudwatch to Elastic</li>
<li><a href="https://www.elastic.co/docs/current/integrations/aws">AWS General integration</a> which supports many AWS sources</li>
</ul>
<p>Kubernetes auditing <a href="https://kubernetes.io/docs/tasks/debug/debug-cluster/audit/">documentation</a> describes the need for auditing in order to get answers to the questions below:</p>
<ul>
<li>What happened?</li>
<li>When did it happen?</li>
<li>Who initiated it?</li>
<li>What resource did it occur on?</li>
<li>Where was it observed?</li>
<li>From where was it initiated (Source IP)?</li>
<li>Where was it going (Destination IP)?</li>
</ul>
<p>Answers to the above questions become important when an incident occurs and an investigation follows. Alternatively, it could just be a log retention use case for a regulated company trying to fulfill compliance requirements. </p>
<p>We are giving special importance to audit logs in Kubernetes because audit logs are not enabled by default. Audit logs can take up a large amount of memory and storage. So, usually, it’s a balance between retaining/investigating audit logs against giving up resources budgeted otherwise for workloads to be hosted on the Kubernetes cluster. Another reason we’re talking about audit logs in Kubernetes is that, unlike usual container logs, after being turned on, these logs are orchestrated to write to the cloud provider’s logging service. This is true for most cloud providers because the Kubernetes control plane is managed by the cloud providers. It makes sense for cloud providers to use their built-in orchestration workflows involving the control plane for a managed service backed by their implementation of a logging framework.</p>
<p>Kubernetes audit logs can be quite verbose by default. Hence, it becomes important to selectively choose how much logging needs to be done so that all the audit requirements are met for the organization. This is done in the <a href="https://kubernetes.io/docs/tasks/debug/debug-cluster/audit/#audit-policy">audit policy</a> file. The audit policy file is submitted against the<code>kube-apiserver.</code> It is not necessary that all flavors of cloud-provider-hosted Kubernetes clusters allow you to play with the <code>kube-apiserver</code> directly. For example, AWS EKS allows for this <a href="https://docs.aws.amazon.com/eks/latest/userguide/control-plane-logs.html">logging</a> to be done only by the control plane.</p>
<p><strong>In this blog we will be using Elastic Kubernetes Service (Amazon EKS) on AWS with the Kubernetes Audit Logs that are automatically shipped to AWS CloudWatch.</strong></p>
<p>A sample audit log for a secret by the name “empty-secret” created by an admin user on EKS  is logged on AWS CloudWatch in the following format: </p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt10648aae438b8d38/6a7f041373d9bd702329d76d/aws-clougwatch-logs.png" alt="Alt text" /></p>
<p>Once the audit logs show up on CloudWatch, it is time to consider how to transfer them to Elasticsearch. Elasticsearch is a great platform for creating dashboards that visualize different audit events recorded in a Kubernetes cluster. It is also a powerful tool for analyzing various audit events. For example, how many secret object creation attempts were made in an hour? </p>
<p>Now that we established the Kubernetes audit logs are being logged in CloudWatch, let’s discuss how to get the logs ingested into Elasticsearch. Elasticsearch has an integration to consume logs written on CloudWatch. Just using this integration by default is going to get the JSON from CloudWatch as is i.e. the real audit log JSON is nested inside the wrapper CloudWatch JSON. When bringing logs to Elasticsearch, it is important that we use the <a href="https://www.elastic.co/guide/en/ecs/current/index.html">Elastic Common Schema</a>(ECS) to get the best search and analytics performance. This means that there needs to  be an ingest pipeline that parses a standard Kubernetes audit JSON message and creates an ECS Compliant document in Elasticsearch. Let’s dive into how to achieve this.</p>
<p>Elasticsearch has a Kubernetes integration using Elastic Agent to consume Kubernetes container logs from the console and audit logs written to a file path. For a cloud-provider use case, as described above, it may not be feasible to write audit logs to a path on the Kubernetes cluster. So, how do we leverage the<a href="https://github.com/elastic/integrations/blob/main/packages/kubernetes/data_stream/audit_logs/fields/fields.yml"> ECS designed for parsing the Kubernetes audit logs</a> already implemented in the Kubernetes integration to work on the CloudWatch audit logs? That is the most exciting plumbing piece! Let’s see how to do it.</p>
<h3 id="whatweregoingtodois">What we’re going to do is:</h3>
<ul>
<li><p>Read the Kubernetes audit logs from the cloud provider’s logging module, in our case, AWS CloudWatch since this is where logs reside. We will use Elastic Agent and <a href="https://www.elastic.co/docs/current/integrations/aws_logs">Elasticsearch AWS Custom Logs integration </a> to read from logs from CloudWatch. <strong>Note:</strong> please be aware, there are several Elastic AWS integration, we are specifically using the AWS Custom Logs integration. </p></li>
<li><p>Create two simple ingest pipelines (we do this for best practices of isolation and composability) </p></li>
<li><p>The first pipeline looks for Kubernetes audit JSON messages and then redirects them to the second pipeline</p></li>
<li><p>The second custom pipeline will associate the JSON <code>message</code> field with the correct field expected by the Elasticsearch Kubernetes Audit managed pipeline (aka the Integration) and then <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/reroute-processor.html"><code>reroute</code></a> the message to the correct data stream, <code>kubernetes.audit_logs-default,</code> which in turn applies all the proper mapping and ingest pipelines for the incoming message</p></li>
<li><p>The overall flow will be </p></li>
</ul>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3d13c601d231abb9/6a7f0416ead8ec386dbaa467/overall-ingestion-flow.png" alt="Alt text" /></p>
<h3 id="1createanawscloudwatchintegration">1. Create an AWS CloudWatch integration:</h3>
<p>a.  Populate the AWS access key and secret pair values</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdde0b180f64347c3/6a7f041a73d9bd059a29d775/aws-custom-logs-integration-1.png" alt="Alt text" /></p>
<p>b. In the logs section, populate the log ARN, Tags and Preserve the original event if you want to, and then Save this integration and exit from the page</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2f3cc63886f6c160/6a7f041d96b5a6e33e87b09f/aws-custom-logs-integration-2.png" alt="Alt text" /></p>
<h3 id="2nextwewillconfigurethecustomingestpipeline">2. Next, we will configure the custom ingest pipeline</h3>
<p>We are doing this because we want to override what the generic managed pipeline does. We will retrieve the custom component name by searching for managed pipeline created as an asset when we install the AWS CloudWatch integration. In this case we will be adding the custom ingest pipeline <code>logs-aws_logs.generic@custom</code> </p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7cfd88e4b022a665/6a7f04201967ea3f0d330350/aws-logs-index-management.png" alt="Alt text" /></p>
<p>From the Dev tools console, run below. Here, we are extracting the message field from the CloudWatch JSON and putting the value in a field called kubernetes.audit. Then, we are rerouting this message to the default Kubernetes audit dataset or ECS that comes with Kubernetes integration</p>
<pre><code>PUT _ingest/pipeline/logs-aws_logs.generic@custom
{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"processors": [
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"pipeline": {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"if": "ctx.message.contains('audit.k8s.io')",
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"name": "logs-aws-process-k8s-audit"
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;]
}

PUT _ingest/pipeline/logs-aws-process-k8s-audit
{
&amp;nbsp;&amp;nbsp;"processors": [
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"json": {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"field": "message",
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"target_field": "kubernetes.audit"
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"remove": {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"field": "message"
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"reroute": {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"dataset": "kubernetes.audit_logs",
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"namespace": "default"
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;]
}
</code></pre>
<p>Let’s understand this further:</p>
<ul>
<li><p>When we create a Kubernetes integration, we get a managed index template called <code>logs-kubernetes.audit_logs</code> that writes to the pipeline called <code>logs-kubernetes.audit_logs-1.62.2</code> by default</p></li>
<li><p>If we look into the pipeline<code>logs-kubernetes.audit_logs-1.62.2</code>, we see that all the processor logic is working against the field <code>kubernetes.audit</code>. This is the reason why our json processor in the above code snippet is creating a field called <code>kubernetes.audit</code>before dropping the original <em>message</em> field and rerouting. Rerouting is directed to the <code>kubernetes.audit_logs</code> dataset that backs the <code>logs-kubernetes.audit_logs-1.62.2</code> pipeline (dataset name is derived from the pipeline name convention that’s in the format <code>logs-&lt;datasetname&gt;-version</code>)</p></li>
</ul>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blteb2dfbb68207030c/6a7f0423c2e914e736016806/ingest-pipelines.png" alt="Alt text" /></p>
<h3 id="3nowletsverifythatthelogsareactuallyflowingthroughandtheauditmessageisbeingparsed">3.  Now let’s verify that the logs are actually flowing through and the audit message is being parsed</h3>
<p>a. We will use Elastic Agent and enroll using Fleet and the integration policy we created in the Step 1. There are a number of ways to <a href="https://www.elastic.co/guide/en/fleet/current/install-fleet-managed-elastic-agent.html">deploy Elastic Agent</a> and for this exercise we will deploy using docker which is quick and easy.</p>
<pre><code>% docker run --env FLEET_ENROLL=1 --env FLEET_URL=&lt;&lt;fleet_URL&gt;&gt; --env FLEET_ENROLLMENT_TOKEN=&lt;&lt;fleet_enrollment_token&gt;&gt;&amp;nbsp; --rm docker.elastic.co/beats/elastic-agent:8.19.13
</code></pre>
<p>b. Check the messages in Discover. In 8.15 there is also a new feature called Logs Explorer which provides an ability to see Kubernetes Audit logs (and container logs) with a few clicks (see image below). Voila! We can see the Kubernetes audit messages parsed!</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta6a09be5a0888012/6a7f0427c2e914fcd601680e/discover.jpg" alt="Alt text" /></p>
<h3 id="4letsdoaquickrecapofwhatwedid">4. Let's do a quick recap of what we did</h3>
<p>We configured CloudWatch integration in Elasticsearch to read Kubernetes audit logs from CloudWatch. Then, we created custom ingest pipelines to reroute the audit messages to the correct data stream and all the OOTB mappings and parsing that come with the Kubernetes Audit Logs integration.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/bringing-your-cloud-managed-kubernetes-audit-logs-into-elasticsearch</link>
    <guid isPermaLink="false">bringing-your-cloud-managed-kubernetes-audit-logs-into-elasticsearch</guid>
    <category><![CDATA[Logs Analytics]]></category>
    <category><![CDATA[Kubernetes]]></category>
    <category><![CDATA[Infrastructure Monitoring]]></category>
    <dc:creator><![CDATA[Rema Subramanian,Stephen Brown]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1dcd94352c8c8fe8/6a7f04295967e525df5dced1/bringing-your-cloud-managed-kubernetes-audit-logs-into-elasticsearch.jpg" length="0" type="image/jpeg"/>
    <pubDate>Mon, 19 Aug 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[NGNIX log analytics with GenAI in Elastic]]></title>
    <description><![CDATA[Elastic has a set of embedded capabilities such as a GenAI RAG-based AI Assistant and a machine learning platform as part of the product baseline. These make analyzing the vast number of logs you get from NGINX easier.]]></description>
    <content:encoded><![CDATA[<p>Elastic Observability provides a full observability solution, supporting metrics, traces, and logs for applications and infrastructure. NGINX, which is highly used for web serving, load balancing, http caching, and reverse proxy, is the key to many applications and outputs a large volume of logs. NGINX’s access logs, which detail all requests made to the NGINX server, and error logs which record server-related issues and problems are key to managing and analyzing NGINX issues along with understanding what is happening to your application. </p>
<p>In managing NGINX Elastic provides several capabilities:</p>
<ol>
<li><p>Easy ingest, parsing, and out-of-the-box dashboards. Check out the simple how-to in our <a href="https://www.elastic.co/guide/en/fleet/current/example-standalone-monitor-nginx.html">docs</a>. Based on logs, these dashboards show several items over time, response codes, errors, top pages, data volume, browsers used, active connections, drop rates, and much more.</p></li>
<li><p>Out-of-the-box ML-based anomaly detection jobs for your NGINX logs. These jobs help pinpoint anomalies against request rates, IP address request rates, URL access, status codes, and visitor rate anomalies.</p></li>
<li><p>ES|QL which helps work through logs and build out charts during analysis.</p></li>
<li><p>Elastic’s GenAI Assistant provides a simple natural language interface that helps analyze all the logs and can pull out issues from ML jobs and even create dashboards. The Elastic AI Assistant also automatically uses ES|QL.</p></li>
<li><p>NGINX SLOs - Finally Elastic provides the ability to define and monitor SLOs for your NGINX logs. While most SLOs are metrics-based, Elastic allows you to create logs-based SLOs. We detailed this in a previous <a href="https://www.elastic.co/observability-labs/blog/service-level-objectives-slos-logs-metrics">blog</a>.</p></li>
</ol>
<p>NGINX logs are another example of why logs are great.  Logging is an important part of Observability, for which we generally think of metrics and tracing. However, the amount of logs an application and the underlying infrastructure output can be significantly daunting and NGINX is usually the starting point for most analyses. </p>
<p>In today’s blog, we’ll cover how the out-of-the-box ML-based anomaly detection jobs can help RCA, and how Elastic’s GenAI Assistant helps easily work through logs to pinpoint issues in minutes. </p>
<h2 id="prerequisitesandconfigaidprerequisitesandconfiga">Prerequisites and config<a id="prerequisites-and-config"></a></h2>
<p>If you plan on following this blog, here are some of the components and details we used to set up this demonstration:</p>
<ul>
<li><p>Ensure you have an account on <a href="http://cloud.elastic.co">Elastic Cloud</a> and a deployed stack (<a href="https://www.elastic.co/guide/en/elastic-stack/current/installing-elastic-stack.html">see instructions here</a>).</p></li>
<li><p>Bring up an <a href="https://docs.nginx.com/nginx/admin-guide/web-server/">NGINX server</a> on a host. OR run an application with NGINX as a front end and drive traffic.</p></li>
<li><p>Install the NGINX integration and assets and review the dashboards as noted in the <a href="https://www.elastic.co/guide/en/fleet/current/example-standalone-monitor-nginx.html">docs</a>.</p></li>
<li><p>Ensure you have an <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-settings.html">ML node configured</a> in your Elastic stack</p></li>
<li><p>To use the AI Assistant you will need a trial or upgrade to Platinum.</p></li>
</ul>
<p>In our scenario, we use data from 3 months from our Elastic environment to help highlight the features. Hence you might need to run your application with traffic for a specific time frame to follow along.</p>
<h2 id="analyzingtheissueswithaiassistantaidanalyzingtheissueswithaiassistanta">Analyzing the issues with AI Assistant<a id="analyzing-the-issues-with-ai-assistant"></a></h2>
<p>As detailed in a previous <a href="https://www.elastic.co/observability-labs/blog/service-level-objectives-slos-logs-metrics">blog</a>, you can get alerted on issues via SLO monitoring against NGINX logs. Let’s assume you have an SLO based on status codes as we outlined in the previous <a href="https://www.elastic.co/observability-labs/blog/service-level-objectives-slos-logs-metrics">blog</a>. You can immediately analyze the issue via the AI Assistant. Because it's a chat interface we simply open the AI Assistant and work through some simple analysis: (See Animated GIF for a demo)</p>
<h3 id="aiassistantanalysisaidaiassistantanalysisa">AI Assistant analysis:<a id="ai-assistant-analysis"></a></h3>
<ul>
<li><p><strong><em>Using lens graph all http response status codes &lt; 400 and &gt; =400 from filebeat-nginx-elasticco-anon-2017. http.response.status.code is not an integer</em></strong> <em>-</em> We wanted to simply understand the amount of requests resulting in status code &gt;= 400 and graph the results. We see that 15% of the requests were not successful, hence an SLO alert being triggered.</p></li>
<li><p><strong>Which ip address (field source.adress) has the highest number of http.response.status.code &gt;= 400 from filebeat-nginx-elasticco-anon-2017. http.response.status.code is not an integer</strong>  - We were curious is there was a specific IP address not having successful requests. 72.57.0.53, with a count of 25,227 occurrences is daily high but not the ensure 2 failed requests.</p></li>
<li><p><strong><em>What country (source.geo.country_iso_code) is source.address=72.57.0.53 coming from. Use filebeat-nginx-elasticco-anon-2017.</em></strong> - Again we were curious if this came from a specific country. And the IP address 72.57.0.53 is coming from the country with the ISO code IN, which corresponds to India. Nothing out of the ordinary.</p></li>
<li><p><strong><em>Did source.address=72.57.0.53 have any (http.response.status.code &lt; 400) from filebeat-nginx-elasticco-anon-2017. http.response.status.code is not an integer -</em></strong>  Oddly the IP address in question only had 4000+ successful responses. Meaning its not malicious, and points to something else.</p></li>
<li><p><strong><em>What are the different status codes (http.response.status.code&gt;=400), from source.address=72.57.0.53. Use filebeat-nginx-elasticco-anon-2017. http.response.status.code is not an integer. Provide counts for each status code -</em></strong> We are curious whether or not we see any 502, which there were none, but most of the failures were 404. </p></li>
<li><p><strong><em>What are the different status codes (http.response.status.code&gt;=400). Use filebeat-nginx-elasticco-anon-2017. http.response.status.code is not an integer. Provide counts for each status code</em></strong> - Regardless of a specific address, what is the largest number of status code occurrences &gt; 400. This also points to 404. </p></li>
<li><p><strong><em>What does a high 404 count from a specific IP address mean from NGINX logs?</em></strong> - Asking this question, we need to understand the potential causes of this from our application. From the answers, we can rule out security probing and web scraping, as we validated that a specific address 72.57.0.53 has a low non-success request status code. It also rules out User error. Hence this points potentially to Broken Links or Missing Resources.</p></li>
</ul>
<h3 id="watchtheflowaidwatchtheflowa">Watch the flow:<a id="watch-the-flow"></a></h3>
<div>
    
</div>
<h3 id="potentialissue">Potential issue:</h3>
<p>It seems that we potentially have an issue with the backend serving specific answers or having issues with resources (database, or broken links). This is cursing the higher-than-normal non-successful status codes&gt;=400.</p>
<h3 id="keyhighlightsfromaiassistant">Key highlights from AI Assistant:</h3>
<p>As you watched this video you will notice a few things:</p>
<ol>
<li><p>We analyzed millions of logs in a matter of minutes using a set of simple natural language queries. </p></li>
<li><p>We didn’t need to know any special query language. The AI Assistant used Elastic’s ES|QL but can similarly use KQL also. </p></li>
<li><p>The AI Assistant easily builds out graphs</p></li>
<li><p>The AI Assistant is accessing and using internal information stored in Elastic’s indices. Vs a simple “google foo” based AI Assistant. This is enabled through RAG, and the AI Assistant can also bring up known issues in github, runbooks, and other useful internal information.</p></li>
</ol>
<p>Check out the following <a href="https://www.elastic.co/observability-labs/blog/elastic-rag-ai-assistant-application-issues-llm-github">blog</a> on how the AI Assistant uses RAG to retrieve internal information. Specifically using github and runbooks.</p>
<h2 id="locatinganomalieswithml">Locating anomalies with ML</h2>
<p>While using the AI Assistant is great for analyzing information, another important aspect of NGINX log management is to ensure you can manage log spikes and anomalies. Elastic has a machine learning platform that allows you to develop jobs to analyze specific metrics or multiple metrics to look for anomalies.When using NGINX, there are several <a href="https://www.elastic.co/guide/en/machine-learning/current/ootb-ml-jobs-nginx.html">out-of-the-box anomaly detection jobs</a>. These work specifically on NGINX access logs.</p>
<ul>
<li><p>Low_request_rate_nginx - Detect low request rates</p></li>
<li><p>Source_ip_request_rate_nginx - Detect unusual source IPs - high request rates</p></li>
<li><p>Source_ip_url_count_nginx - Detect unusual source IPs - high distinct count of URLs</p></li>
<li><p>Status_code_rate_nginx - Detect unusual status code rates</p></li>
<li><p>Visitor_rate_nginx - Detect unusual visitor rates</p></li>
</ul>
<p>Being right out of the box, lets look at the job - Status_code_rate_nginx, which is related to our previous analysis.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt30ec8d10aaf46a17/6a7f0e9073d9bda62429dbcb/nginx-ml-log-analytics.png" alt="NGINX ML Log Analytics" /></p>
<p>With a few simple clicks we immediately get an analysis showing a specific IP address - 72.57.0.53, having higher than normal non-successful requests. Oddly we also found this is using the AI Assistant.</p>
<p>We can take this further with conversations with the AI Assistant, look at the logs, and/or even look at the other ML anomaly jobs.</p>
<h2 id="conclusionaidconclusiona">Conclusion:<a id="conclusion"></a></h2>
<p>You’ve now seen how easily Elastic’s RAG-based AI Assistant can help analyze NGINX logs without even the need to know query syntax, understand where the data is, and understand even the fields. Additionally, you’ve also seen how we can alert you when a potential issue or degradation in service (SLO). </p>
<p>Check out other resources on NGINX logs:</p>
<p><a href="https://www.elastic.co/guide/en/machine-learning/current/ootb-ml-jobs-nginx.html">Out-of-the-box anomaly detection jobs for NGINX</a></p>
<p><a href="https://www.elastic.co/guide/en/fleet/current/example-standalone-monitor-nginx.html">Using the NGINX integration to ingest and analyze NGINX Logs</a></p>
<p><a href="https://www.elastic.co/observability-labs/blog/service-level-objectives-slos-logs-metrics">NGINX Logs based SLOs in Elastic</a></p>
<p><a href="https://www.elastic.co/observability-labs/blog/elastic-rag-ai-assistant-application-issues-llm-github">Using GitHub issues, runbooks, and other internal information for RCAs with Elastic’s RAG based AI Assistant</a></p>
<h2 id="tryitoutaidtryitouta">Try it out<a id="try-it-out"></a></h2>
<p>Existing Elastic Cloud customers can access many of these features directly from the <a href="https://cloud.elastic.co/">Elastic Cloud console</a>. Not taking advantage of Elastic on the cloud? <a href="https://www.elastic.co/cloud/cloud-trial-overview">Start a free trial</a>.</p>
<p>All of this is also possible in your environment. <a href="https://www.elastic.co/observability/universal-profiling">Learn how to get started today</a>.</p>
<p><em>The release and timing of any features or functionality described in this post remain at Elastic's sole discretion. Any features or functionality not currently available may not be delivered on time or at all.</em></p>
<p><em>In this blog post, we may have used or referred to third party generative AI tools, which are owned and operated by their respective owners. Elastic does not have any control over the third party tools and we have no responsibility or liability for their content, operation or use, nor for any loss or damage that may arise from your use of such tools. Please exercise caution when using AI tools with personal, sensitive or confidential information. Any data you submit may be used for AI training or other purposes. There is no guarantee that information you provide will be kept secure or confidential. You should familiarize yourself with the privacy practices and terms of use of any generative AI tools prior to use.</em></p>
<p><em>Elastic, Elasticsearch, ESRE, Elasticsearch Relevance Engine and associated marks are trademarks, logos or registered trademarks of Elasticsearch N.V. in the United States and other countries. All other company and product names are trademarks, logos or registered trademarks of their respective owners.</em></p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/nginx-log-analytics-with-genai-elastic</link>
    <guid isPermaLink="false">nginx-log-analytics-with-genai-elastic</guid>
    <category><![CDATA[Agentic Observability]]></category>
    <category><![CDATA[Logs Analytics]]></category>
    <category><![CDATA[Infrastructure Monitoring]]></category>
    <category><![CDATA[LLM Observability]]></category>
    <dc:creator><![CDATA[Bahubali Shetti]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd89bddfe4a0532b5/6a7f0e936c6eaca022f141b7/blog-thumb-observability-pattern-color.png" length="0" type="image/png"/>
    <pubDate>Fri, 05 Jul 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Smarter log analytics in Elastic Observability]]></title>
    <description><![CDATA[Discover smarter log handling with Kibana's latest features! The new Data Source Selector lets you easily filter logs by integrations like System Logs and Nginx. Smart Fields enhance log analysis by presenting data more intuitively. Simplify your workflow and uncover deeper insights today!]]></description>
    <content:encoded><![CDATA[<p>Discover a smarter way to handle your logs with Kibana's latest features! Our new Data Source selector makes it effortless to zero in on the logs you need, whether they're from System Logs or Application Logs by selecting your integrations or data views. Plus, with the introduction of Smart Fields, your log analysis is now more intuitive and insightful. Get ready to simplify your workflow and uncover deeper insights with these game-changing updates. Dive in and see how easy log exploration can be!</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt81e181c0910eb579/6a7f1af696b5a640f687b895/smart-fields.png" alt="Smart fields" /></p>
<h2 id="findthelogsyourelookingfor">Find the logs you’re looking for</h2>
<h3 id="focusonlogsfromspecificintegrationsordataviews">Focus on logs from specific integrations or data views</h3>
<p>We've added the Data Source selector, a handy new feature for viewing specific logs. Now, you can easily filter your logs based on your integrations, like System Logs, Nginx, or Elastic APM, or switch between different data views, like logs or metrics. This new selector is all about making your data easier to find and helping you focus on what matters most in your analysis.</p>
<h2 id="diveintoyourlogs">Dive into your logs</h2>
<h3 id="analyzelogswithsmartfieldsinkibana">Analyze logs with Smart Fields in Kibana</h3>
<p>Logs in Kibana have undergone a significant transformation, particularly in the way log data is presented. The once-basic table view has evolved with the introduction of Smart Fields, providing users with a more insightful and dynamic log analysis experience.</p>
<h4 id="resourcesmartfieldcentralizinglogsourceinformation">Resource Smart Field - centralizing log source information</h4>
<p>The resource column further elevates the Logs Explorer page by providing users with a single column for exploring the resource that created the log event. This column groups various resource-indicating fields together, streamlining the investigation process. Currently, the following <a href="https://www.elastic.co/guide/en/ecs/current/ecs-reference.html">ECS</a> fields are grouped under this single column and we recommend including them in your logs:</p>
<ul>
<li><a href="https://www.elastic.co/guide/en/ecs/current/ecs-service.html#field-service-name">service.name</a></li>
<li><a href="https://www.elastic.co/guide/en/ecs/current/ecs-container.html#field-container-name">container.name</a></li>
<li><a href="https://www.elastic.co/guide/en/ecs/current/ecs-orchestrator.html#field-orchestrator-namespace">orchestrator.namespace</a></li>
<li><a href="https://www.elastic.co/guide/en/ecs/current/ecs-host.html#field-host-name">host.name</a></li>
<li><a href="https://www.elastic.co/guide/en/ecs/current/ecs-cloud.html#field-cloud-instance-id">cloud.instance.id</a></li>
</ul>
<p>We know this does not include all use cases and would like your feedback on other fields you use/are important for you to help us provide a tailored and user-centric log analysis experience.</p>
<h4 id="contentsmartfieldadeeperdiveintologdata">Content Smart Field - a deeper dive into log data</h4>
<p>The content column revolutionizes log analysis by seamlessly rendering <strong>log.level</strong> and <strong>message</strong> fields. Notably, it automatically handles fallbacks, ensuring a smooth transition when the actual message field is not available. This enhancement simplifies the log exploration process, offering users a more comprehensive understanding of their data.</p>
<h4 id="actionscolumnunleashingadditionalcolumns">Actions column - unleashing additional columns</h4>
<p>As part of our commitment to empowering users, we are introducing the actions column, adding a layer of functionality to the document table. This column includes two powerful actions:</p>
<ul>
<li><strong>Degraded document indicator</strong>: This indicator provides insights about the quality of your data by indicating fields were ignored when the document was indexed and ended up in the <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-ignored-field.html">_ignored</a> property of the document. To help analyze what caused the document to degrade, we suggest reading this blog - <a href="https://www.elastic.co/observability-labs/blog/antidote-index-mapping-exceptions-ignore-malformed">The antidote for index mapping exceptions: ignore_malformed</a>.</li>
<li><strong>Stacktrace indicator</strong>: This indicator informs users of the presence of stack traces in the document. This makes it easy to navigate through logs documents and know if they have additional information.</li>
</ul>
<h3 id="investigateindividuallogsbyexpandinglogdetails">Investigate individual logs by expanding log details</h3>
<p>Now, when you click the expand icon in the actions column, it opens up the <strong>Log details</strong> flyout for any log entry. This new feature gives you a detailed overview of the entry right at your fingertips. Inside the flyout, the <strong>Overview</strong> tab is neatly organized into four sections—Content breakdown, Service &amp; Infrastructure, Cloud, and Others—each offering a snapshot of the most crucial information. Plus, you'll find the same handy controls you're used to in the main table, like filtering in or out, adding or removing columns, and copying data, making it easier than ever to manage your logs directly from the flyout.</p>
<p>The <a href="https://www.elastic.co/guide/en/observability/current/obs-ai-assistant.html">Observability AI Assistant</a> is fully integrated into this view providing contextual insights about the log event and helping to find similar messages.</p>
<h2 id="experienceastreamlinedapproachtologexploration">Experience a streamlined approach to log exploration</h2>
<p>These enhancements simplify the process of finding and focusing on specific logs and offer more intuitive and insightful data presentation. Dive into your logs with these I tools and streamline your workflow, uncovering deeper insights with ease. Try it now and transform your log analysis!</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/smarter-log-analytics-in-elastic-observability</link>
    <guid isPermaLink="false">smarter-log-analytics-in-elastic-observability</guid>
    <category><![CDATA[Logs Analytics]]></category>
    <dc:creator><![CDATA[Achyut Jhunjhunwala,Mike Birnstiehl]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8218d911635eb0c1/6a7f1af963e95941bd73e275/log-monitoring.jpeg" length="0" type="image/jpeg"/>
    <pubDate>Mon, 10 Jun 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[AWS VPC Flow log analysis with GenAI in Elastic]]></title>
    <description><![CDATA[Elastic has a set of embedded capabilities such as a GenAI RAG-based AI Assistant and a machine learning platform as part of the product baseline. These make analyzing the vast number of logs you get from AWS VPC Flows easier.]]></description>
    <content:encoded><![CDATA[<p>Elastic Observability provides a full observability solution, by supporting metrics, traces and logs for applications and infrastructure. In managing AWS deployments, VPC flow logs are critical in managing performance, network visibility, security, compliance, and overall management of your AWS environment. Several examples of :</p>
<ol>
<li><p>Where traffic is coming in from and going out to from the deployment, and within the deployment. This helps identify unusual or unauthorized communications</p></li>
<li><p>Traffic volumes detecting spikes or drops which could indicate service issues in production or an increase in customer traffic</p></li>
<li><p>Latency and Performance bottlenecks - with VPC Flow logs, you can look at latency for a flow (in and outflows), and understand patterns</p></li>
<li><p>Accepted and rejected traffic helps determine where potential security threats and misconfigurations lie. </p></li>
</ol>
<p>AWS VPC Logs is a great example of how logs are great. Logging is an important part of Observability, for which we generally think of metrics and tracing. However, the amount of logs an application and the underlying infrastructure output can be significantly daunting with VPC Logs. However, it also provides a significant amount of insight.</p>
<p>Before we proceed, it is important to understand what Elastic provides in managing AWS and VPC Flow logs:</p>
<ol>
<li><p>A full set of integrations to manage VPC Flows and the <a href="https://www.elastic.co/observability-labs/blog/aws-service-metrics-monitor-observability-easy">entire end-to-end deployment on AWS</a>. </p></li>
<li><p>Elastic has a simple-to-use <a href="https://www.elastic.co/observability-labs/blog/aws-kinesis-data-firehose-observability-analytics">AWS Firehose integration</a>. </p></li>
<li><p>Elastic’s tools such as <a href="https://www.elastic.co/observability-labs/blog/vpc-flow-logs-monitoring-analytics-observability">Discover, spike analysis,  and anomaly detection help provide you with better insights and analysis</a>.</p></li>
<li><p>And a set of simple <a href="https://www.elastic.co/guide/en/observability/current/monitor-amazon-vpc-flow-logs.html#aws-firehose-dashboard">Out-of-the-box dashboards</a></p></li>
</ol>
<p>In today’s blog, we’ll cover how Elastics’ other features can support analyzing and RCA for potential VPC flow logs even more easily. Specifically, we will focus on managing the number of rejects, as this helps ensure there weren’t any unauthorized or unusual activities:</p>
<ol>
<li><p>Set up an easy-to-use SLO (newly released) to detect when things are potentially degrading</p></li>
<li><p>Create an ML job to analyze different fields of the VPC Flow log</p></li>
<li><p>Using our newly released RAG-based AI Assistant to help analyze the logs without needing to know Elastic’s query language nor how to even graph on Elastic</p></li>
<li><p>ES|QL will help understand and analyze add latency for patterns.</p></li>
</ol>
<p>In subsequent blogs, we will use AI Assistant and ESQL to show how to get other insights beyond just REJECT/ACCEPT from VPC Flow log.</p>
<h2 id="prerequisitesandconfig">Prerequisites and config</h2>
<p>If you plan on following this blog, here are some of the components and details we used to set up this demonstration:</p>
<ul>
<li><p>Ensure you have an account on <a href="http://cloud.elastic.co">Elastic Cloud</a> and a deployed stack (<a href="https://www.elastic.co/guide/en/elastic-stack/current/installing-elastic-stack.html">see instructions here</a>).</p></li>
<li><p>Follow the steps in the following blog to get <a href="https://github.com/aws-samples/aws-three-tier-web-architecture-workshop">AWS’s three-tier app</a> installed instructed in git, and bring in the <a href="https://www.elastic.co/observability-labs/blog/aws-kinesis-data-firehose-observability-analytics">AWS VPC Flow logs</a>.</p></li>
<li><p>Ensure you have an <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-settings.html">ML node configured</a> in your Elastic stack</p></li>
<li><p>To use the AI Assistant you will need a trial or upgrade to Platinum.</p></li>
</ul>
<h2 id="slowithvpcflowlogs">SLO with VPC Flow Logs</h2>
<p>Elastic’s SLO capability is based directly on the Google SRE Handbook. All the definitions and semantics are utilized as described in Google’s SRE handbook. Hence users can perform the following on SLOs in Elastic:</p>
<ul>
<li>Define an SLO on Logs not just metrics - Users can use KQL (log-based query), service availability, service latency, custom metric, histogram metric, or a timeslice metric.</li>
<li>Define SLO, SLI, Error budget and burn rates. Users can also use occurrence versus time slice-based budgeting. </li>
<li>Manage, with dashboards, all the SLOs in a singular location.</li>
<li>Trigger alerts from the defined SLO, whether the SLI is off, the burn rate is used up, or the error rate is X.</li>
</ul>
<p>Setting up an SLO for VPC is easy. You simply create a query you want to trigger off. In our case, we look for all the good events where <em>aws.vpcflow.action=ACCEPT</em> and we define the target at 85%. </p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd21d06910b861cd4/6a7f037f33fa8a81e0202287/VPCFlowSLOsetup.png" alt="Setting up SLO for VPC FLow log" /></p>
<p>As the following example shows, over the last 7 days, we have exceeded our budget by 43%. Additionally, we have not complied for the last 7 days.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdce0dcb24898fd24/6a7f038296b5a69c6487b03d/VPCFlowSLOMiss.png" alt="VPC Flow Reject SLO" /></p>
<h2 id="analyzingtheslowithaiassistant">Analyzing the SLO with AI Assistant</h2>
<p>Now that we see that there is an issue with the VPC Flows, we immediately work with the AI Assistant to start analyzing the SLO. Because it's a chat interface we simply open the AI Assistant and work through some simple analysis: (See Animated GIF for a demo below)</p>
<h3 id="aiassistantanalysis">AI Assistant analysis:</h3>
<ul>
<li><p><strong>what were the top 3 source.address that had <em>aws.vpcflow.action=REJECT</em> over the last 7 days, which is causing this SLO issue?</strong> - We wanted to simply see what could be causing the loss in error budget. Were there any particular source.addresses causing a heavy reject rate.</p></li>
<li><p>The answer: A table with the highest count = 42670 and <em>source.address = 79.110.62.185</em></p></li>
<li><p>There is one singular <em>source.address</em> that is causing the loss in SLO. </p></li>
<li><p><strong>What is the largest number of  <em>aws.vpcflow.action=REJECT</em> in a 30 min time frame for the last 3 days where the <em>source.address=79.110.62.185</em>?</strong> - After understanding that a specific source.address is causing the loss in SLO, we want to understand the averages. </p></li>
<li><p>**The answer: ** "The largest number of <em>aws.vpcflow.action=REJECT</em> in a 30-minute time frame for the last 3 days where the <em>source.address</em> is 79.110.62.185 is 229. This occurred on 2024-06-01T04:00:00.000Z.”</p></li>
<li><p>It means there must be a low REJECT rate but fairly consistent vs spiky over the last 7 days. </p></li>
<li><p><strong>for the logs with <em>source.address</em>="79.110.62.185" was there any country code of <em>source.geo.country_iso_code</em> field present. If yes what is the value</strong> - Given the last question showed a low REJECT rate, it only means that this was fairly consistent vs spiky over the last 7 days.</p></li>
<li><p><strong>The answer:</strong> Yes, there is a country code present in the <em>source.geo.country_iso_code</em> field for logs with <em>source.address</em>="79.110.62.185". The value is BG (Bulgaria).</p></li>
<li><p><strong>Is there a specific destination.address where <em>source.address=79.110.62.185</em> is getting a <em>aws.vpcflow.action=REJECT</em>. Give me both the destination.address and the number of REJECTs for that destination.address?</strong></p></li>
<li><p><strong>The answer:</strong> destination.address of 10.0.0.27 is giving a reject number of 53433 in this time frame.</p></li>
<li><p><strong>Graph the number of REJECT vs ACCEPT for <em>source.address</em>="79.110.62.185" over the last 7 days. The graph is on a daily basis in a singular graph</strong> - We asked this question to see what the comparison is between ACCEPT and REJECT. </p></li>
<li><p><strong>The answer:</strong> See the animated GIF to see that the generated graph is fairly stable</p></li>
<li><p><strong>Were there any source.address that had a spike, high reject rate in. a 30min period over the 30 days?</strong> - We wanted to see if there was any other spike </p></li>
<li><p><strong>The answer</strong> - Yes, there was a source.address that had a spike in high reject rates in a 30-minute period over the last 30 days. <em>source.address</em>: 185.244.212.67, Reject Count: 8975, Time Period: 2024-05-22T03:00:00.000Z</p></li>
</ul>
<hr />
<h3 id="watchtheflow">Watch the flow</h3>
<div>
    
</div>
<h3 id="potentialissue">Potential issue:</h3>
<p>he server handling requests from source <strong><em>79.110.62.185</em></strong> is potentially having an issue.</p>
<p>Again using logs, we essentially asked the AI Assistant to give the <em>eni</em> ids where the internal ip address was 10.0.0.27</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2ec3d25d096c3357/6a7f038605b7b5b00a18b519/VPCFlow-findingwebserver.png" alt="Finding the issue - webserver" /></p>
<p>From our AWS console, we know that this is the webserver. Further analysis in Elastic, and with the developers we realized there is a new version that was installed recently causing a problem with connections.</p>
<h2 id="locatinganomalieswithml">Locating anomalies with ML</h2>
<p>While using the AI Assistant is great for analyzing information, another important aspect of VPC flow management is to ensure you can manage log spikes and anomalies. Elastic has a machine learning platform that allows you to develop jobs to analyze specific metrics or multiple metrics to look for anomalies.</p>
<p>VPC Flow logs come with a large amount of information. The full set of fields is listed in <a href="https://docs.aws.amazon.com/vpc/latest/userguide/flow-logs.html#flow-logs-basics">AWS docs</a>. We will use a specific subset to help detect anomalies.</p>
<p>We were setting up anomalies for aws.vpcflow.action=REJECT, which requires us to use multimetric anomaly detection in Elastic.</p>
<p>The config we used utilizes:</p>
<p>Detectors:</p>
<ul>
<li><p>destination.address</p></li>
<li><p>destination.port</p></li>
</ul>
<p>Influencers:</p>
<ul>
<li><p>source.address</p></li>
<li><p>aws.vpcflow.action</p></li>
<li><p>destination.geo.region_iso_code</p></li>
</ul>
<p>The way we set this up will help us understand if there is a large spike in REJECT/ACCEPT against <em>destination.address</em> values from a specific <em>source.address</em> and/or <em>destination.geo.region_iso_code</em> location.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta48754a0753271b1/6a7f03896c6eac6468f13cdd/VPCFlowanomalysetup.png" alt="Anomaly detection job config" /></p>
<p>The job once run reveals something interesting:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9f3aee82193c8e44/6a7f038c05b7b54b1718b51d/VPCFlowAnomalyDetection.png" alt="Anomaly detected" /></p>
<p>Notice that <em>source.address</em> 185.244.212.67 has had a high REJECT rate in the last 30 days. </p>
<p>Notice where we found this before? In the AI Assistant!!!!!</p>
<p>While we can run the AI Assistant and find this sort of anomaly, the ML job can be setup to run continuously and alert us on such spikes. This will help us understand if there are any issues with the webserver like we found above or even potential security attacks.</p>
<h2 id="conclusion">Conclusion:</h2>
<p>You’ve now seen how easily Elastic’s RAG-based AI Assistant can help analyze VPC Flows without even the need to know query syntax, understand where the data is, and understand even the fields. Additionally, you’ve also seen how we can alert you when a potential issue or degradation in service (SLO). Check out our other blogs on AWS VPC Flow analysis in Elastic:</p>
<ol>
<li><p>A full set of integrations to manage VPC Flows and the <a href="https://www.elastic.co/observability-labs/blog/aws-service-metrics-monitor-observability-easy">entire end-to-end deployment on AWS</a>. </p></li>
<li><p>Elastic has a simple-to-use <a href="https://www.elastic.co/observability-labs/blog/aws-kinesis-data-firehose-observability-analytics">AWS Firehose integration</a>. </p></li>
<li><p>Elastic’s tools such as <a href="https://www.elastic.co/observability-labs/blog/vpc-flow-logs-monitoring-analytics-observability">Discover, spike analysis,  and anomaly detection help provide you with better insights and analysis</a>.</p></li>
<li><p>And a set of simple <a href="https://www.elastic.co/guide/en/observability/current/monitor-amazon-vpc-flow-logs.html#aws-firehose-dashboard">Out-of-the-box dashboards</a></p></li>
</ol>
<h2 id="tryitout">Try it out</h2>
<p>Existing Elastic Cloud customers can access many of these features directly from the <a href="https://cloud.elastic.co/">Elastic Cloud console</a>. Not taking advantage of Elastic on the cloud? <a href="https://www.elastic.co/cloud/cloud-trial-overview">Start a free trial</a>.</p>
<p>All of this is also possible in your environment. <a href="https://www.elastic.co/observability/universal-profiling">Learn how to get started today</a>.</p>
<p><em>The release and timing of any features or functionality described in this post remain at Elastic's sole discretion. Any features or functionality not currently available may not be delivered on time or at all.</em></p>
<p><em>In this blog post, we may have used or referred to third party generative AI tools, which are owned and operated by their respective owners. Elastic does not have any control over the third party tools and we have no responsibility or liability for their content, operation or use, nor for any loss or damage that may arise from your use of such tools. Please exercise caution when using AI tools with personal, sensitive or confidential information. Any data you submit may be used for AI training or other purposes. There is no guarantee that information you provide will be kept secure or confidential. You should familiarize yourself with the privacy practices and terms of use of any generative AI tools prior to use.</em></p>
<p><em>Elastic, Elasticsearch, ESRE, Elasticsearch Relevance Engine and associated marks are trademarks, logos or registered trademarks of Elasticsearch N.V. in the United States and other countries. All other company and product names are trademarks, logos or registered trademarks of their respective owners.</em></p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/aws-vpc-flow-log-analysis-with-genai-elastic</link>
    <guid isPermaLink="false">aws-vpc-flow-log-analysis-with-genai-elastic</guid>
    <category><![CDATA[Agentic Observability]]></category>
    <category><![CDATA[Logs Analytics]]></category>
    <category><![CDATA[Infrastructure Monitoring]]></category>
    <category><![CDATA[LLM Observability]]></category>
    <dc:creator><![CDATA[Bahubali Shetti]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5265effb8d313486/6a7f038fde23157404fd7786/21-cubes.jpeg" length="0" type="image/jpeg"/>
    <pubDate>Fri, 07 Jun 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Build better Service Level Objectives (SLOs) from logs and metrics]]></title>
    <description><![CDATA[To help manage operations and business metrics, Elastic Observability's SLO (Service Level Objectives) feature was introduced in 8.12. This blog reviews this feature and how you can use it with Elastic's AI Assistant to meet SLOs.]]></description>
    <content:encoded><![CDATA[<p>In today's digital landscape, applications are at the heart of both our personal and professional lives. We've grown accustomed to these applications being perpetually available and responsive. This expectation places a significant burden on the shoulders of developers and operations teams.</p>
<p>Site reliability engineers (SREs) face the challenging task of sifting through vast quantities of data, not just from the applications themselves but also from the underlying infrastructure. In addition to data analysis, they are responsible for ensuring the effective use and development of operational tools. The growing volume of data, the daily resolution of issues, and the continuous evolution of tools and processes can detract from the focus on business performance.</p>
<p>Elastic Observability offers a solution to this challenge. It enables SREs to integrate and examine all telemetry data (logs, metrics, traces, and profiling) in conjunction with business metrics. This comprehensive approach to data analysis fosters operational excellence, boosts productivity, and yields critical insights, all of which are integral to maintaining high-performing applications in a demanding digital environment.</p>
<p>To help manage operations and business metrics, Elastic Observability's SLO (Service Level Objectives) feature was introduced in <a href="https://www.elastic.co/guide/en/observability/8.12/slo.html">8.12</a>. This feature enables setting measurable performance targets for services, such as <a href="https://sre.google/sre-book/monitoring-distributed-systems/">availability, latency, traffic, errors, and saturation or define your own</a>. Key components include:</p>
<ul>
<li><p>Defining and monitoring SLIs (Service Level Indicators)</p></li>
<li><p>Monitoring error budgets indicating permissible performance shortfalls</p></li>
<li><p>Alerting on burn rates showing error budget consumption</p></li>
</ul>
<p>Users can monitor SLOs in real-time with dashboards, track historical performance, and receive alerts for potential issues. Additionally, SLO dashboard panels offer customized visualizations.</p>
<p>Service Level Objectives (SLOs) are generally available for our Platinum and Enterprise subscription customers.</p>
<div>
    
</div>
<p>In this blog, we will outline the following:</p>
<ul>
<li><p>What are SLOs? A Google SRE perspective</p></li>
<li><p>Several scenarios of defining and managing SLOs</p></li>
</ul>
<h2 id="servicelevelobjectiveoverview">Service Level Objective overview</h2>
<p>Service Level Objectives (SLOs) are a crucial component for Site Reliability Engineering (SRE), as detailed in <a href="https://sre.google/sre-book/table-of-contents/">Google's SRE Handbook</a>. They provide a framework for quantifying and managing the reliability of a service. The key elements of SLOs include:</p>
<ul>
<li><p><strong>Service Level Indicators (SLIs):</strong> These are carefully selected metrics, such as uptime, latency, throughput, error rates, or other important metrics, that represent the aspects of the service and are important from an operations or business perspective. Hence, an SLI is a measure of the service level provided (latency, uptime, etc.), and it is defined as a ratio of good over total events, with a range between 0% and 100%.</p></li>
<li><p><strong>Service Level Objective (SLO):</strong> An SLO is the target value for a service level measured as a percentage by an SLI. Above the threshold, the service is compliant. As an example, if we want to use service availability as an SLI, with the number of successful responses at 99.9%, then any time the number of failed responses is &gt; .1%, the SLO will be out of compliance.</p></li>
<li><p><strong>Error budget:</strong> This represents the threshold of acceptable errors, balancing the need for reliability with practical limits. It is defined as 100% minus the SLO quantity of errors that is tolerated.</p></li>
<li><p><strong>Burn rate:</strong> This concept relates to how quickly the service is consuming its error budget, which is the acceptable threshold for unreliability agreed upon by the service providers and its users.</p></li>
</ul>
<p>Understanding these concepts and effectively implementing them is essential for maintaining a balance between innovation and reliability in service delivery. For more detailed information, you can refer to <a href="https://sre.google/workbook/slo-document/">Google's SRE Handbook</a>.</p>
<p>One main thing to remember is that SLO monitoring is <em>not</em> incident monitoring. SLO monitoring is a proactive, strategic approach designed to ensure that services meet established performance standards and user expectations. It involves tracking Service Level Objectives, error budgets, and the overall reliability of a service over time. This predictive method helps in preventing issues that could impact users and aligns service performance with business objectives.</p>
<p>In contrast, incident monitoring is a reactive process focused on detecting, responding to, and mitigating service incidents as they occur. It aims to address unexpected disruptions or failures in real time, minimizing downtime and impact on service. This includes monitoring system health, errors, and response times during incidents, with a focus on rapid response to minimize disruption and preserve the service's reputation.</p>
<p>Elastic®’s SLO capability is based directly off the Google SRE Handbook. All the definitions and semantics are utilized as described in Google’s SRE handbook. Hence users can perform the following on SLOs in Elastic:</p>
<ul>
<li><p>Define an SLO on an SLI such as KQL (log based query), service availability, service latency, custom metric, histogram metric, or a timeslice metric. Additionally, set the appropriate threshold.</p></li>
<li><p>Utilize occurrence versus time slice based budgeting. Occurrences is the number of good events over the number of total events to compute the SLO. Timeslices break the overall time window into slammer slices of a defined duration and compute the number of good slices over the total slices to compute the SLO. Timeslice targets are more accurate and useful when calculating things like a service’s SLO when trying to meet agreed upon customer targets.</p></li>
<li><p>Manage all the SLOs in a singular location.</p></li>
<li><p>Trigger alerts from the defined SLO, whether the SLI is off, burn rate is used up, or the error rate is X.</p></li>
<li><p>Create unique service level dashboards with SLO information for a more comprehensive view of the service.</p></li>
</ul>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta3810c425fa6d9ef/6a7f1a69b43770d02c4d70fc/1-slo-blog.png" alt="Create alerts" /></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9f526ae1d0618b26/6a7f1a6ce02fac5abb5d69b3/2-slo-blog.png" alt="Create dashboards" /></p>
<p>SREs need to be able to manage business metrics.</p>
<h2 id="slosbasedonlogsnginxavailability">SLOs based on logs: NGINX availability</h2>
<p>Defining SLOs does not always mean metrics need to be used. Logs are a rich form of information, even when they have metrics embedded in them. Hence it’s useful to understand your business and operations status based on logs.</p>
<p>Elastic allows you to create an SLO based on specific fields in the log message, which don’t have to be metrics. A simple example is a simple multi-tier app that has a web server layer (nginx), a processing layer, and a database layer.</p>
<p>Let’s say that your processing layer is managing a significant number of requests. You want to ensure that the service is up properly. The best way is to ensure that all http.response.status_code are less than 500. Anything less ensures the service is up and any errors (like 404) are all user or client errors versus server errors.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte8b306f68814e9fa/6a7f1a6fe02fac7d295d69b7/3-slo-blog.png" alt="expanded document" /></p>
<p>If we use Discover in Elastic, we see that there are close to 2M log messages over a seven-day time frame.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4f8638fe990d421f/6a7f1a72c2e9141e31016ff0/4-slo-blog.png" alt="17k" /></p>
<p>Additionally, the number of messages with http.response.status_code &gt; 500 is minimal, like 17K.</p>
<p>Rather than creating an alert, we can create an SLO with this query:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9f476a5816f7c858/6a7f1a7533fa8a3787202b7e/5-slo-blog.png" alt="edit SLO" /></p>
<p>We chose to use occurrences as the budgeting method to keep things simple.</p>
<p>Once defined, we can see how well our SLO is performing over a seven-day time frame. We can see not only the SLO, but also the burn rate, the historical SLI, and error budget, and any specific alerts against the SLO.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2d749a94c689ccb3/6a7f1a7877b034ab7d3ff907/6-slo-blog.png" alt="SLOs" /></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltde894037a4de1f6d/6a7f1a7bea068d5abaf0a2cb/7-slo-blog.png" alt="nginx server availability " /></p>
<p>Not only do we get information about the violation, but we also get:</p>
<ul>
<li><p>Historical SLI (7 days)</p></li>
<li><p>Error budget burn down</p></li>
<li><p>Good vs. bad events (24 hours)</p></li>
</ul>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt23453245544e2b0d/6a7f1a7f5967e551ff5dd6cf/8-slo-blog.png" alt="Percentages" /></p>
<p>We can see how we’ve easily burned through our error budget.</p>
<p>Hence something must be going on with nginx. To investigate, all we need to do is utilize the <a href="https://www.elastic.co/blog/context-aware-insights-elastic-ai-assistant-observability">AI Assistant</a>, and use its natural language interface to ask questions to help analyze the situation.</p>
<p>Let’s use Elastic’s AI Assistant to analyze the breakdown of http.response.status_code across all the logs from the past seven days. This helps us understand how many 50X errors we are getting.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2e3dad6ad1a36d7f/6a7f1a8233fa8a6c82202b82/9-slo-blog.png" alt="count of http response status code" /></p>
<p>As we can see, the number of 502s is minimal compared to the number of overall messages, but it is affecting our SLO.</p>
<p>However, it seems like Nginx is having an issue. In order to reduce the issue, we also ask the AI Assistant how to work on this error. Specifically, we ask if there is an internal runbook the SRE team has created.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc46a6e3bc18f8d57/6a7f1a8542a117ce0295c305/10-slo-blog.png" alt="ai assistant thread" /></p>
<p>AI Assistant gets a runbook the team has added to its knowledge base. I can now analyze and try to resolve or reduce the issue with nginx.</p>
<p>While this is a simple example, there are an endless number of possibilities that can be defined based on KQL. Some other simple examples:</p>
<ul>
<li><p>99% of requests occur under 200ms</p></li>
<li><p>99% of log message are not errors</p></li>
</ul>
<h2 id="applicationslosopentelemetrydemocartservice">Application SLOs: OpenTelemetry demo cartservice</h2>
<p>A common application developers and SREs use to learn about OpenTelemetry and test out Observability features is the <a href="https://github.com/elastic/opentelemetry-demo">OpenTelemetry demo</a>.</p>
<p>This demo has <a href="https://opentelemetry.io/docs/demo/feature-flags/">feature flags</a> to simulate issues. With Elastic’s alerting and SLO capability, you can also determine how well the entire application is performing and how well your customer experience is holding up when these feature flags are used.</p>
<p><a href="https://www.elastic.co/blog/opentelemetry-observability">Elastic supports OpenTelemetry by taking OTLP directly with no need for an Elastic specific agent</a>. You can send in OpenTelemetry data directly from the application (through OTel libraries) and through the collector.</p>
<p>We’ve brought up the OpenTelemetry demo on a K8S cluster (AWS EKS) and turned on the cartservice feature flag. This inserts errors into the cartservice. We’ve also created two SLOs to monitor the cartservice’s availability and latency.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltfbeda104042da07a/6a7f1a87ead8ec59b3baac54/11-slo-blog.png" alt="SLOs" /></p>
<p>We can see that the cartservice’s availability is violated. As we drill down, we see that there aren’t as many successful transactions, which is affecting the SLO.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltbd22cf26b8ff2180/6a7f1a8a2f00b25cbbefef23/12-slo-blog.png" alt="cartservice-otel" /></p>
<p>As we drill into the service, we can see in Elastic APM that there is a higher than normal failure rate of about 5.5% for the emptyCart service.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8c99cfb53987e5c7/6a7f1a8deab5bee9cd20ab00/13-slo-blog.png" alt="apm" /></p>
<p>We can investigate this further in APM, but that is a discussion for another blog. Stay tuned to see how we can use Elastic’s machine learning, AIOps, and AI Assistant to understand the issue.</p>
<h2 id="conclusion">Conclusion</h2>
<p>SLOs allow you to set clear, measurable targets for your service performance, based on factors like availability, response times, error rates, and other key metrics. Hopefully with the overview we’ve provided in this blog, you can see that:</p>
<ul>
<li><p>SLOs can be based on logs. In Elastic, you can use KQL to essentially find and filter on specific logs and log fields to monitor and trigger SLOs.</p></li>
<li><p>AI Assistant is a valuable, easy-to-use capability to analyze, troubleshoot, and even potentially resolve SLO issues.</p></li>
<li><p>APM Service based SLOs are easy to create and manage with integration to Elastic APM. We also use OTel telemetry to help monitor SLOs.</p></li>
</ul>
<p>For more information on SLOs in Elastic, check out <a href="https://www.elastic.co/guide/en/observability/current/slo.html">Elastic documentation</a> and the following resources:</p>
<ul>
<li><p><a href="https://www.elastic.co/guide/en/observability/8.12/slo.html">What’s new in Elastic Observability 8.12</a></p></li>
<li><p><a href="https://www.elastic.co/blog/context-aware-insights-elastic-ai-assistant-observability">Introducing the Elastic AI Assistant</a></p></li>
<li><p><a href="https://www.elastic.co/blog/opentelemetry-observability">Elastic OpenTelemetry support</a></p></li>
</ul>
<p>Ready to get started? Sign up for <a href="https://cloud.elastic.co/registration">Elastic Cloud</a> and try out the features and capabilities I’ve outlined above to get the most value and visibility out of your SLOs.</p>
<p><em>The release and timing of any features or functionality described in this post remain at Elastic's sole discretion. Any features or functionality not currently available may not be delivered on time or at all.</em></p>
<p><em>In this blog post, we may have used or referred to third party generative AI tools, which are owned and operated by their respective owners. Elastic does not have any control over the third party tools and we have no responsibility or liability for their content, operation or use, nor for any loss or damage that may arise from your use of such tools. Please exercise caution when using AI tools with personal, sensitive or confidential information. Any data you submit may be used for AI training or other purposes. There is no guarantee that information you provide will be kept secure or confidential. You should familiarize yourself with the privacy practices and terms of use of any generative AI tools prior to use.</em></p>
<p><em>Elastic, Elasticsearch, ESRE, Elasticsearch Relevance Engine and associated marks are trademarks, logos or registered trademarks of Elasticsearch N.V. in the United States and other countries. All other company and product names are trademarks, logos or registered trademarks of their respective owners.</em></p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/service-level-objectives-slos-logs-metrics</link>
    <guid isPermaLink="false">service-level-objectives-slos-logs-metrics</guid>
    <category><![CDATA[Incident Management]]></category>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[Logs Analytics]]></category>
    <category><![CDATA[Metrics]]></category>
    <category><![CDATA[Kubernetes]]></category>
    <dc:creator><![CDATA[Bahubali Shetti]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt126c07eb43762792/6a7f1a91b4377020074d7104/139686_-_Elastic_-_Headers_-_V1_3.jpg" length="0" type="image/jpeg"/>
    <pubDate>Fri, 23 Feb 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Optimizing Observability with ES|QL: Streamlining SRE operations and issue resolution for Kubernetes and OTel]]></title>
    <description><![CDATA[ES|QL enhances operational efficiency, data analysis, and issue resolution for SREs. This blog covers the advantages of ES|QL in Elastic Observability and how it can apply to managing issues instrumented with OpenTelemetry and running on Kubernetes.]]></description>
    <content:encoded><![CDATA[<p>As an operations engineer (SRE, IT Operations, DevOps), managing technology and data sprawl is an ongoing challenge. Simply managing the large volumes of high dimensionality and high cardinality data is overwhelming.</p>
<p>As a single platform, Elastic® helps SREs unify and correlate limitless telemetry data, including metrics, logs, traces, and profiling, into a single datastore — Elasticsearch®. By then applying the power of Elastic’s advanced machine learning (ML), AIOps, AI Assistant, and analytics, you can break down silos and turn data into insights. As a full-stack observability solution, everything from infrastructure monitoring to log monitoring and application performance monitoring (APM) can be found in a single, unified experience.</p>
<p>In Elastic 8.11, a technical preview is now available of <a href="https://www.elastic.co/blog/esql-elasticsearch-piped-query-language">Elastic’s new piped query language, ES|QL (Elasticsearch Query Language)</a>, which transforms, enriches, and simplifies data investigations. Powered by a new query engine, ES|QL delivers advanced search capabilities with concurrent processing, improving speed and efficiency, irrespective of data source and structure. Accelerate resolution by creating aggregations and visualizations from one screen, delivering an iterative, uninterrupted workflow.</p>
<h2 id="advantagesofesqlforsres">Advantages of ES|QL for SREs</h2>
<p>SREs using Elastic Observability can leverage ES|QL to analyze logs, metrics, traces, and profiling data, enabling them to pinpoint performance bottlenecks and system issues with a single query. SREs gain the following advantages when managing high dimensionality and high cardinality data with ES|QL in Elastic Observability:</p>
<ul>
<li><strong>Improved operational efficiency:</strong> By using ES|QL, SREs can create more actionable notifications with aggregated values as thresholds from a single query, which can also be managed through the Elastic API and integrated into DevOps processes.</li>
<li><strong>Enhanced analysis with insights:</strong> ES|QL can process diverse observability data, including application, infrastructure, business data, and more, regardless of the source and structure. ES|QL can easily enrich the data with additional fields and context, allowing the creation of visualizations for dashboards or issue analysis with a single query.</li>
<li><strong>Reduced mean time to resolution:</strong> ES|QL, when combined with Elastic Observability's AIOps and AI Assistant, enhances detection accuracy by identifying trends, isolating incidents, and reducing false positives. This improvement in context facilitates troubleshooting and the quick pinpointing and resolution of issues.</li>
</ul>
<p>ES|QL in Elastic Observability not only enhances an SRE's ability to manage the customer experience, an organization's revenue, and SLOs more effectively but also facilitates collaboration with developers and DevOps by providing contextualized aggregated data.</p>
<p>In this blog, we will cover some of the key use cases SREs can leverage with ES|QL:</p>
<ul>
<li>ES|QL integrated with the Elastic AI Assistant, which uses public LLM and private data, enhances the analysis experience anywhere in Elastic Observability.</li>
<li>SREs can, in a single ES|QL query, break down, analyze, and visualize observability data from multiple sources and across any time frame.</li>
<li>Actionable alerts can be easily created from a single ES|QL query, enhancing operations.</li>
</ul>
<p>I will work through these use cases by showcasing how an SRE can solve a problem in an application instrumented with OpenTelemetry and running on Kubernetes. The OpenTelemetry (OTel) demo is on an Amazon EKS cluster, with Elastic Cloud 8.11 configured.</p>
<p>You can also check out our <a href="https://www.youtube.com/watch?v=vm0pBWI2l9c">Elastic Observability ES|QL Demo</a>, which walks through ES|QL functionality for Observability.</p>
<h2 id="esqlwithaiassistant">ES|QL with AI Assistant</h2>
<p>As an SRE, you are monitoring your OTel instrumented application with Elastic Observability, and while in Elastic APM, you notice some issues highlighted in the service map.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt67931374daecc7f2/6a85cdd8eaf2450312a49fab/elastic-blog-1-services.png" alt="1 - services" /></p>
<p>Using Elastic AI Assistant, you can easily ask for analysis, and in particular, we check on what the overall latency is across the application services.</p>
<pre><code>My APM data is in traces-apm*. What's the average latency per service over the last hour? Use ESQL, the data is mapped to ECS
</code></pre>
<div>
    
</div>
<p>The Elastic AI Assistant generates an ES|QL query, which we run in the AI Assistant to get a list of the average latencies across all the application services. We can easily see the top four are:</p>
<ul>
<li>load generator</li>
<li>front-end proxy</li>
<li>frontendservice</li>
<li>checkoutservice</li>
</ul>
<p>With a simple natural language query in the AI Assistant, it generated a single ES|QL query that helped list out the latencies across the services.</p>
<p>Noticing that there is an issue with several services, we decide to start with the frontend proxy. As we work through the details, we see significant failures, and through <strong>Elastic APM failure correlation</strong> , it becomes apparent that the frontend proxy is not properly completing its calls to downstream services.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt497215d42651cc18/6a85cddbd7b2e75e41fe853e/elastic-blog-2-failed-transaction.png" alt="2 - failed transaction" /></p>
<h2 id="esqlinsightfulandcontextualanalysisindiscover">ES|QL insightful and contextual analysis in Discover</h2>
<p>Knowing that the application is running on Kubernetes, we investigate if there are issues in Kubernetes. In particular, we want to see if there are any services having issues.</p>
<p>We use the following query in ES|QL in Elastic Discover:</p>
<pre><code>from metrics-* | where kubernetes.container.status.last_terminated_reason != "" and kubernetes.namespace == "default" | stats reason_count=count(kubernetes.container.status.last_terminated_reason) by kubernetes.container.name, kubernetes.container.status.last_terminated_reason | where reason_count &gt; 0
</code></pre>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt837d9acfc045bf02/6a85cddeeaf245c0cea49faf/elastic-blog-3-two-horizontal-bar-graphs.png" alt="3 - horizontal graph" /></p>
<p>ES|QL helps analyze 1,000s/10,000s of metric events from Kubernetes and highlights two services that are restarting due to OOMKilled.</p>
<p>The Elastic AI Assistant, when asked about OOMKilled, indicates that a container in a pod was killed due to an out-of-memory condition.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4e0b7f31ffb0e6f7/6a85cde1501a854b28fbb38d/elastic-blog-4-understanding-oomkilled.png" alt="4 - understanding oomkilled" /></p>
<p>We run another ES|QL query to understand the memory usage for emailservice and productcatalogservice.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf8feafaa0a4b2fa6/6a85cde4d7b2e78477fe8542/elastic-blog-5-split-bar-graphs.png" alt="5 - split bar graphs" /></p>
<p>ES|QL easily found the average memory usage fairly high.</p>
<p>We can now further investigate both of these services’ logs, metrics, and Kubernetes-related data. However, before we continue, we create an alert to track heavy memory usage.</p>
<h2 id="actionablealertswithesql">Actionable alerts with ES|QL</h2>
<p>Suspecting a specific issue, that might recur, we simply create an alert that brings in the ES|QL query we just ran that will track for any service that exceeds 50% in memory utilization.</p>
<p>We modify the last query to find any service with high memory usage:</p>
<pre><code>FROM metrics*
| WHERE @timestamp &gt;= NOW() - 1 hours
| STATS avg_memory_usage = AVG(kubernetes.pod.memory.usage.limit.pct) BY kubernetes.deployment.name | where avg_memory_usage &gt; .5
</code></pre>
<p>With that query, we create a simple alert. Notice how the ES|QL query is brought into the alert. We simply connect this to pager duty. But we can choose from multiple connectors like ServiceNow, Opsgenie, email, etc.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt16f1d7ecefc9df0f/6a85cde627c5cd53fd5f7450/elastic-blog-6-create-rule.png" alt="6 - create rule" /></p>
<p>With this alert, we can now easily monitor for any services that exceed 50% memory utilization in their pods.</p>
<h2 id="makethemostofyourdatawithesql">Make the most of your data with ES|QL</h2>
<p>In this post, we demonstrated the power ES|QL brings to analysis, operations, and reducing MTTR. In summary, the three use cases with ES|QL in Elastic Observability are as follows:</p>
<ul>
<li>ES|QL integrated with the Elastic AI Assistant, which uses public LLM and private data, enhances the analysis experience anywhere in Elastic Observability.</li>
<li>SREs can, in a single ES|QL query, break down, analyze, and visualize observability data from multiple sources and across any time frame.</li>
<li>Actionable alerts can be easily created from a single ES|QL query, enhancing operations.</li>
</ul>
<p>Elastic invites SREs and developers to experience this transformative language firsthand and unlock new horizons in their data tasks. Try it today at <a href="https://ela.st/free-trial">https://ela.st/free-trial</a> now in technical preview.</p>
<blockquote>
  <ul>
  <li><a href="https://www.elastic.co/demo-gallery/observability">Elastic Observability Tour</a></li>
  <li><a href="https://www.elastic.co/blog/log-management-observability-operations">The power of effective log management</a></li>
  <li><a href="https://www.elastic.co/blog/context-aware-insights-elastic-ai-assistant-observability">Transforming Observability with the AI Assistant</a></li>
  <li><a href="https://www.elastic.co/blog/esql-elasticsearch-piped-query-language">ES|QL announcement blog</a></li>
  </ul>
</blockquote>
<p><em>The release and timing of any features or functionality described in this post remain at Elastic's sole discretion. Any features or functionality not currently available may not be delivered on time or at all.</em></p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/opentelemetry-kubernetes-esql</link>
    <guid isPermaLink="false">opentelemetry-kubernetes-esql</guid>
    <category><![CDATA[Kubernetes]]></category>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[Logs Analytics]]></category>
    <category><![CDATA[Agentic Observability]]></category>
    <category><![CDATA[LLM Observability]]></category>
    <dc:creator><![CDATA[Bahubali Shetti]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd21cab120ad20933/6a85cde980984ce0f666902e/ES_QL_blog-720x420-05.png" length="0" type="image/png"/>
    <pubDate>Wed, 01 Nov 2023 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Migrating 1 billion log lines from OpenSearch to Elasticsearch]]></title>
    <description><![CDATA[Learn how to migrate 1 billion log lines from OpenSearch to Elasticsearch for improved performance and reduced disk usage. Discover the migration strategies, data transfer methods, and optimization techniques used in this guide.]]></description>
    <content:encoded><![CDATA[<p>What are the current options to migrate from OpenSearch to Elasticsearch<sup>®</sup>?</p>
<p>OpenSearch is a fork of Elasticsearch 7.10 that has diverged quite a bit from itself lately, resulting in a different set of features and also different performance, as <a href="https://www.elastic.co/blog/elasticsearch-opensearch-performance-gap">this benchmark</a> shows (hint: it’s currently much slower than Elasticsearch).</p>
<p>Given the differences between the two solutions, restoring a snapshot from OpenSearch is not possible, nor is reindex-from-remote, so our only option is then using something in between that will read from OpenSearch and write to Elasticsearch.</p>
<p>This blog will show you how easy it is to migrate from OpenSearch to Elasticsearch for better performance and less disk usage!</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt96e57e5a7c16168c/6a85cd1e1aa1e11d2aff8d9d/blog-elastic-348gb-disk-space-logs.jpg" alt="1 - arrows" /></p>
<h2 id="1billionloglines">1 billion log lines</h2>
<p>We are going to use part of the data set we used for the benchmark, which takes about half a terabyte on disk, including replicas, and spans over a week ( January 1–7, 2023).</p>
<p>We have in total 1,009,165,775 documents that take <strong>453.5GB</strong> of space in OpenSearch, including the replicas. That’s <strong>241.2KB per document</strong>. This is going to be important later when we enable a couple optimizations in Elasticsearch that will bring this total size way down without sacrificing performance!</p>
<p>This billion log line data set is spread over nine indices that are part of a datastream we are calling logs-myapplication-prod. We have primary shards of about 25GB in size, according to the best practices for optimal shard sizing. A GET _cat/indices show us the indices we are dealing with:</p>
<pre><code>index                              docs.count pri rep pri.store.size store.size
.ds-logs-myapplication-prod-000049  102519334   1   1         22.1gb     44.2gb
.ds-logs-myapplication-prod-000048  114273539   1   1         26.1gb     52.3gb
.ds-logs-myapplication-prod-000044  111093596   1   1         25.4gb     50.8gb
.ds-logs-myapplication-prod-000043  113821016   1   1         25.7gb     51.5gb
.ds-logs-myapplication-prod-000042  113859174   1   1         24.8gb     49.7gb
.ds-logs-myapplication-prod-000041  112400019   1   1         25.7gb     51.4gb
.ds-logs-myapplication-prod-000040  113362823   1   1         25.9gb     51.9gb
.ds-logs-myapplication-prod-000038  110994116   1   1         25.3gb     50.7gb
.ds-logs-myapplication-prod-000037  116842158   1   1         25.4gb     50.8gb
</code></pre>
<p>Both OpenSearch and Elasticsearch clusters have the same configuration: 3 nodes with 64GB RAM and 12 CPU cores. Just like in the <a href="https://www.elastic.co/blog/elasticsearch-opensearch-performance-gap">benchmark</a>, the clusters are running in Kubernetes.</p>
<h2 id="movingdatafromatob">Moving data from A to B</h2>
<p>Typically, moving data from one Elasticsearch cluster to another is easy as a <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/snapshot-restore.html">snapshot and restore</a> if the clusters are compatible versions of each other or a <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-reindex.html#reindex-from-remote">reindex from remote</a> if you need real-time synchronization and minimized downtime. These methods do not apply when migrating data from OpenSearch to Elasticsearch because the projects have significantly diverged from the 7.10 fork. However, there is one method that will work: scrolling.</p>
<h3 id="scrolling">Scrolling</h3>
<p>Scrolling involves using an external tool, such as Logstash<sup>®</sup>, to read data from the source cluster and write it to the destination cluster. This method provides a high degree of customization, allowing us to transform the data during the migration process if needed. Here are a couple advantages of using Logstash:</p>
<ul>
<li><strong>Easy parallelization:</strong> It’s really easy to write concurrent jobs that can read from different “slices” of the indices, essentially maximizing our throughput.</li>
<li><strong>Queuing:</strong> Logstash automatically queues documents before sending.</li>
<li><strong>Automatic retries:</strong> In the event of a failure or an error during data transmission, Logstash will automatically attempt to resend the data; moreover, it will stop querying the source cluster as often, until the connection is re-established, all without manual intervention.</li>
</ul>
<p>Scrolling allows us to do an initial search and to keep pulling batches of results from Elasticsearch until there are no more results left, similar to how a “cursor” works in relational databases.</p>
<p>A <a href="https://www.elastic.co/guide/en/elasticsearch/guide/master/scroll.html">scrolled search</a> takes a snapshot in time by freezing the segments that make the index up until the time the request is made, preventing those segments from merging. As a result, the scroll doesn’t see any changes that are made to the index after the initial search request has been made.</p>
<h3 id="migrationstrategies">Migration strategies</h3>
<p>Reading from A and writing in B in can be slow without optimization because it involves paginating through the results, transferring each batch over the network to Logstash, which will assemble the documents in another batch and then transfer those batches over the network again to Elasticsearch, where the documents will be indexed. So when it comes to such large data sets, we must be very efficient and extract every bit of performance where we can.</p>
<p>Let’s start with the facts — what do we know about the data we need to transfer? We have nine indices in the datastream, each with about 100 million documents. Let’s test with just one of the indices and measure the indexing rate to see how long it takes to migrate. The indexing rate can be seen by activating the <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/monitoring-overview.html">monitoring</a> functionality in Elastic<sup>®</sup> and then navigating to the index you want to inspect.</p>
<p><strong>Scrolling in the deep</strong><br />
The simplest approach for transferring the log lines over would be to make Elasticsearch scroll over the entire data set and check it later when it finishes. Here we will introduce our first two variables: PAGE_SIZE and BATCH_SIZE. The former is how many records we are going to bring from the source every time we query it, and the latter is how many documents are going to be assembled together by Logstash and written to the destination index.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1fd9ccb9272f59ef/6a85cd21342d69693821b133/elastic-blog-2-scrolling-in-the-deep.jpg" alt="Deep scrolling" /></p>
<p>With such a large data set, the scroll slows down as this deep pagination progresses. The indexing rate starts at 6,000 docs/second and steadily descends down to 700 docs/second because the pagination gets very deep. Without any optimization, it would take us 19 days (!) to migrate the 1 billion documents. We can do better than that!</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blteccfb30d71aeda11/6a85cd242d64d563b6081d7e/elastic-blog-3-index-rate.png" alt="Indexing rate for a deep scroll" /></p>
<p><strong>Slice me nice</strong><br />
We can optimize scrolling by using an approach called <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/paginate-search-results.html#slice-scroll">Sliced scroll</a>, where we split the index in different slices to consume them independently.</p>
<p>Here we will introduce our last two variables: SLICES and WORKERS. The amount of slices cannot be too small as the performance decreases drastically over time, and it can’t be too big as the overhead of maintaining the scrolls would counter the benefits of a smaller search.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf4edfae97ce6e5a6/6a85cd27d6cf2990c4bb091d/elastic-blog-4-slice-me-nice.jpg" alt="Sliced scroll" /></p>
<p>Let’s start by migrating a single index (out of the nine we have) with different parameters to see what combination gives us the highest throughput.</p>
<p>|        |           |         |            |                       |
| ------ | --------- | ------- | ---------- | --------------------- |
| SLICES | PAGE_SIZE | WORKERS | BATCH_SIZE | Average Indexing Rate |
| 3      | 500       | 3       | 500        | 13,319 docs/sec       |
| 3      | 1,000     | 3       | 1,000      | 13,048 docs/sec       |
| 4      | 250       | 4       | 250        | 10,199 docs/sec       |
| 4      | 500       | 4       | 500        | 12,692 docs/sec       |
| 4      | 1,000     | 4       | 1,000      | 10,900 docs/sec       |
| 5      | 500       | 5       | 500        | 12,647 docs/sec       |
| 5      | 1,000     | 5       | 1,000      | 10,334 docs/sec       |
| 5      | 2,000     | 5       | 2,000      | 10,405 docs/sec       |
| 10     | 250       | 10      | 250        | 14,083 docs/sec       |
| 10     | 250       | 4       | 1,000      | 12,014 docs/sec       |
| 10     | 500       | 4       | 1,000      | 10,956 docs/sec       |</p>
<p>It looks like we have a good set of candidates for maximizing the throughput for a single index, in between 12K and 14K documents per second. That doesn't mean we have reached our ceiling. Even though search operations are single threaded and every slice will trigger sequential search operations to read data, that does not prevent us from reading several indices in parallel.</p>
<p>By default, the maximum number of open scrolls is 500 — this limit can be updated with the search.max_open_scroll_context cluster setting, but the default value is enough for this particular migration.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb50dfe7c779c3fb5/6a85cd2ad6cf294904bb0921/elastic-blog-5-index-rate-volatile.png" alt="5 - indexing rate" /></p>
<h2 id="letsmigrate">Let’s migrate</h2>
<h3 id="preparingourdestinationindices">Preparing our destination indices</h3>
<p>We are going to create a datastream called logs-myapplication-reindex to write the data to, but before indexing any data, let’s ensure our <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/index-templates.html">index template</a> and <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/ilm-index-lifecycle.html">index lifecycle management</a> configurations are properly set up. An index template acts as a blueprint for creating new indices, allowing you to define various settings that should be applied consistently across your indices.</p>
<p><strong>Index lifecycle management policy</strong><br />
Index lifecycle management (ILM) is equally vital, as it automates the management of indices throughout their lifecycle. With ILM, you can define policies that determine how long data should be retained, when it should be rolled over into new indices, and when old indices should be deleted or archived. Our policy is really straightforward:</p>
<pre><code>PUT _ilm/policy/logs-myapplication-lifecycle-policy
{
  "policy": {
    "phases": {
      "hot": {
        "actions": {
          "rollover": {
            "max_primary_shard_size": "25gb"
          }
        }
      },
      "warm": {
        "min_age": "0d",
        "actions": {
          "forcemerge": {
            "max_num_segments": 1
          }
        }
      }
    }
  }
}
</code></pre>
<p><strong>Index template (and saving 23% in disk space)</strong><br />
Since we are here, we’re going to go ahead and enable <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-source-field.html#synthetic-source">Synthetic Source</a>, a clever feature that allows us to store and discard the original JSON document while still reconstructing it when needed from the stored fields.</p>
<p>For our example, enabling Synthetic Source resulted in a remarkable <strong>23.4% improvement in storage efficiency</strong> , reducing the size required to store a single document from 241.2KB in OpenSearch to just <strong>185KB</strong> in Elasticsearch.</p>
<p>Our full index template is therefore:</p>
<pre><code>PUT _index_template/logs-myapplication-reindex
{
  "index_patterns": [
    "logs-myapplication-reindex"
  ],
  "priority": 500,
  "data_stream": {},
  "template": {
    "settings": {
      "index": {
        "lifecycle.name": "logs-myapplication-lifecycle-policy",
        "codec": "best_compression",
        "number_of_shards": "1",
        "number_of_replicas": "1",
        "query": {
          "default_field": [
            "message"
          ]
        }
      }
    },
    "mappings": {
      "_source": {
        "mode": "synthetic"
      },
      "_data_stream_timestamp": {
        "enabled": true
      },
      "date_detection": false,
      "properties": {
        "@timestamp": {
          "type": "date"
        },
        "agent": {
          "properties": {
            "ephemeral_id": {
              "type": "keyword",
              "ignore_above": 1024
            },
            "id": {
              "type": "keyword",
              "ignore_above": 1024
            },
            "name": {
              "type": "keyword",
              "ignore_above": 1024
            },
            "type": {
              "type": "keyword",
              "ignore_above": 1024
            },
            "version": {
              "type": "keyword",
              "ignore_above": 1024
            }
          }
        },
        "aws": {
          "properties": {
            "cloudwatch": {
              "properties": {
                "ingestion_time": {
                  "type": "keyword",
                  "ignore_above": 1024
                },
                "log_group": {
                  "type": "keyword",
                  "ignore_above": 1024
                },
                "log_stream": {
                  "type": "keyword",
                  "ignore_above": 1024
                }
              }
            }
          }
        },
        "cloud": {
          "properties": {
            "region": {
              "type": "keyword",
              "ignore_above": 1024
            }
          }
        },
        "data_stream": {
          "properties": {
            "dataset": {
              "type": "keyword",
              "ignore_above": 1024
            },
            "namespace": {
              "type": "keyword",
              "ignore_above": 1024
            },
            "type": {
              "type": "keyword",
              "ignore_above": 1024
            }
          }
        },
        "ecs": {
          "properties": {
            "version": {
              "type": "keyword",
              "ignore_above": 1024
            }
          }
        },
        "event": {
          "properties": {
            "dataset": {
              "type": "keyword",
              "ignore_above": 1024
            },
            "id": {
              "type": "keyword",
              "ignore_above": 1024
            },
            "ingested": {
              "type": "date"
            }
          }
        },
        "host": {
          "type": "object"
        },
        "input": {
          "properties": {
            "type": {
              "type": "keyword",
              "ignore_above": 1024
            }
          }
        },
        "log": {
          "properties": {
            "file": {
              "properties": {
                "path": {
                  "type": "keyword",
                  "ignore_above": 1024
                }
              }
            }
          }
        },
        "message": {
          "type": "match_only_text"
        },
        "meta": {
          "properties": {
            "file": {
              "type": "keyword",
              "ignore_above": 1024
            }
          }
        },
        "metrics": {
          "properties": {
            "size": {
              "type": "long"
            },
            "tmin": {
              "type": "long"
            }
          }
        },
        "process": {
          "properties": {
            "name": {
              "type": "keyword",
              "ignore_above": 1024
            }
          }
        },
        "tags": {
          "type": "keyword",
          "ignore_above": 1024
        }
      }
    }
  }
}
</code></pre>
<h3 id="buildingacustomlogstashimage">Building a custom Logstash image</h3>
<p>We are going to use a containerized Logstash for this migration because both clusters are sitting on a Kubernetes infrastructure, so it's easier to just spin up a Pod that will communicate to both clusters.</p>
<p>Since OpenSearch is not an official Logstash input, we must build a custom Logstash image that contains the logstash-input-opensearch plugin. Let’s use the base image from docker.elastic.co/logstash/logstash:9.3.2 and just install the plugin:</p>
<pre><code>FROM docker.elastic.co/logstash/logstash:9.3.2

USER logstash
WORKDIR /usr/share/logstash
RUN bin/logstash-plugin install logstash-input-opensearch
</code></pre>
<h3 id="writingalogstashpipeline">Writing a Logstash pipeline</h3>
<p>Now we have our Logstash Docker image, and we need to write a pipeline that will read from OpenSearch and write to Elasticsearch.</p>
<p><strong>The</strong> <strong>input</strong></p>
<pre><code>input {
    opensearch {
        hosts =&gt; ["os-cluster:9200"]
        ssl =&gt; true
        ca_file =&gt; "/etc/logstash/certificates/opensearch-ca.crt"
        user =&gt; "${OPENSEARCH_USERNAME}"
        password =&gt; "${OPENSEARCH_PASSWORD}"
        index =&gt; "${SOURCE_INDEX_NAME}"
        slices =&gt; "${SOURCE_SLICES}"
        size =&gt; "${SOURCE_PAGE_SIZE}"
        scroll =&gt; "5m"
        docinfo =&gt; true
        docinfo_target =&gt; "[@metadata][doc]"
    }
}
</code></pre>
<p>Let’s break down the most important input parameters. The values are all represented as environment variables here:</p>
<ul>
<li><strong>hosts:</strong> Specifies the host and port of the OpenSearch cluster. In this case, it’s connecting to “os-cluster” on port 9200.</li>
<li><strong>index:</strong> Specifies the index in the OpenSearch cluster from which to retrieve logs. In this case, it’s “logs-myapplication-prod” which is a datastream that contains the actual indices (e.g., .ds-logs-myapplication-prod-000049).</li>
<li><strong>size:</strong> Specifies the maximum number of logs to retrieve in each request.</li>
<li><strong>scroll:</strong> Defines how long a search context will be kept open on the OpenSearch server. In this case, it’s set to “5m,” which means each request must be answered and a new “page” asked within five minutes.</li>
<li><strong>docinfo</strong> and <strong>docinfo_target:</strong> These settings control whether document metadata should be included in the Logstash output and where it should be stored. In this case, document metadata is being stored in the [@metadata][doc] field — this is important because the document’s _id will be used as the destination id as well.</li>
</ul>
<p>The ssl and ca_file are highly recommended if you are migrating from clusters that are in a different infrastructure (separate cloud providers). You don’t need to specify a ca_file if your TLS certificates are signed by a public authority, which is likely the case if you are using a SaaS and your endpoint is reachable over the internet. In this case, only ssl =&gt; true would suffice. In our case, all our TLS certificates are self-signed, so we must also provide the Certificate Authority (CA) certificate.</p>
<p><strong>The (optional)</strong> <strong>filter</strong><br />
We could use this to drop or alter the documents to be written to Elasticsearch if we wanted, but we are not going to, as we want to migrate the documents as is. We are only removing extra metadata fields that Logstash includes in all documents, such as "@version" and "host". We are also removing the original "data_stream" as it contains the source data stream name, which might not be the same in the destination.</p>
<pre><code>filter {
    mutate {
        remove_field =&gt; ["@version", "host", "data_stream"]
    }
}
</code></pre>
<p><strong>The</strong> <strong>output</strong><br />
The output is really simple — we are going to name our datastream logs-myapplication-reindex and we are using the document id of the original documents in document_id, to ensure there are no duplicate documents. In Elasticsearch, datastream names follow a convention \&lt;type&gt;-\&lt;dataset&gt;-\&lt;namespace&gt; so our logs-myapplication-reindex datastream has “myapplication” as dataset and “prod” as namespace.</p>
<pre><code>elasticsearch {
    hosts =&gt; "${ELASTICSEARCH_HOST}"

    user =&gt; "${ELASTICSEARCH_USERNAME}"
    password =&gt; "${ELASTICSEARCH_PASSWORD}"

    document_id =&gt; "%{[@metadata][doc][_id]}"

    data_stream =&gt; "true"
    data_stream_type =&gt; "logs"
    data_stream_dataset =&gt; "myapplication"
    data_stream_namespace =&gt; "prod"
}
</code></pre>
<h3 id="deployinglogstash">Deploying Logstash</h3>
<p>We have a few options to deploy Logstash: it can be deployed <a href="https://www.elastic.co/guide/en/logstash/current/running-logstash-command-line.html">locally from the command line</a>, as a <a href="https://www.elastic.co/guide/en/logstash/current/running-logstash.html">systemd service</a>, via <a href="https://www.elastic.co/guide/en/logstash/current/docker.html">docker</a>, or on <a href="https://www.elastic.co/guide/en/cloud-on-k8s/current/k8s-logstash.html">Kubernetes</a>.</p>
<p>Since both of our clusters are deployed in a Kubernetes environment, we are going to deploy Logstash as a <strong>Pod</strong> referencing our Docker image created earlier. Let’s put our pipeline inside a <strong>ConfigMap</strong> along with some configuration files (pipelines.yml and config.yml).</p>
<p>In the below configuration, we have SOURCE_INDEX_NAME, SOURCE_SLICES, SOURCE_PAGE_SIZE, LOGSTASH_WORKERS, and LOGSTASH_BATCH_SIZE conveniently exposed as environment variables so you just need to fill them out.</p>
<pre><code>apiVersion: v1
kind: Pod
metadata:
  name: logstash-1
spec:
  containers:
    - name: logstash
      image: ugosan/logstash-opensearch-input:8.10.0
      imagePullPolicy: Always
      env:
        - name: SOURCE_INDEX_NAME
          value: ".ds-logs-benchmark-dev-000037"
        - name: SOURCE_SLICES
          value: "10"
        - name: SOURCE_PAGE_SIZE
          value: "500"
        - name: LOGSTASH_WORKERS
          value: "4"
        - name: LOGSTASH_BATCH_SIZE
          value: "1000"
        - name: OPENSEARCH_USERNAME
          valueFrom:
            secretKeyRef:
              name: os-cluster-admin-password
              key: username
        - name: OPENSEARCH_PASSWORD
          valueFrom:
            secretKeyRef:
              name: os-cluster-admin-password
              key: password
        - name: ELASTICSEARCH_USERNAME
          value: "elastic"
        - name: ELASTICSEARCH_PASSWORD
          valueFrom:
            secretKeyRef:
              name: es-cluster-es-elastic-user
              key: elastic
      resources:
        limits:
          memory: "4Gi"
          cpu: "2500m"
        requests:
          memory: "1Gi"
          cpu: "300m"
      volumeMounts:
        - name: config-volume
          mountPath: /usr/share/logstash/config
        - name: etc
          mountPath: /etc/logstash
          readOnly: true
  volumes:
    - name: config-volume
      projected:
        sources:
          - configMap:
              name: logstash-configmap
              items:
                - key: pipelines.yml
                  path: pipelines.yml
                - key: logstash.yml
                  path: logstash.yml
    - name: etc
      projected:
        sources:
          - configMap:
              name: logstash-configmap
              items:
                - key: pipeline.conf
                  path: pipelines/pipeline.conf
          - secret:
              name: os-cluster-http-cert
              items:
                - key: ca.crt
                  path: certificates/opensearch-ca.crt
          - secret:
              name: es-cluster-es-http-ca-internal
              items:
                - key: tls.crt
                  path: certificates/elasticsearch-ca.crt
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: logstash-configmap
data:
  pipelines.yml: |
    - pipeline.id: reindex-os-es
      path.config: "/etc/logstash/pipelines/pipeline.conf"
      pipeline.batch.size: ${LOGSTASH_BATCH_SIZE}
      pipeline.workers: ${LOGSTASH_WORKERS}
  logstash.yml: |
    log.level: info
    pipeline.unsafe_shutdown: true
    pipeline.ordered: false
  pipeline.conf: |
    input {
        opensearch {
          hosts =&gt; ["os-cluster:9200"]
          ssl =&gt; true
          ca_file =&gt; "/etc/logstash/certificates/opensearch-ca.crt"
          user =&gt; "${OPENSEARCH_USERNAME}"
          password =&gt; "${OPENSEARCH_PASSWORD}"
          index =&gt; "${SOURCE_INDEX_NAME}"
          slices =&gt; "${SOURCE_SLICES}"
          size =&gt; "${SOURCE_PAGE_SIZE}"
          scroll =&gt; "5m"
          docinfo =&gt; true
          docinfo_target =&gt; "[@metadata][doc]"
        }
    }

    filter {
        mutate {
            remove_field =&gt; ["@version", "host", "data_stream"]
        }
    }

    output {
        elasticsearch {
            hosts =&gt; "https://es-cluster-es-http:9200"
            ssl =&gt; true
            ssl_certificate_authorities =&gt; ["/etc/logstash/certificates/elasticsearch-ca.crt"]
            ssl_verification_mode =&gt; "full"

            user =&gt; "${ELASTICSEARCH_USERNAME}"
            password =&gt; "${ELASTICSEARCH_PASSWORD}"

            document_id =&gt; "%{[@metadata][doc][_id]}"

            data_stream =&gt; "true"
            data_stream_type =&gt; "logs"
            data_stream_dataset =&gt; "myapplication"
            data_stream_namespace =&gt; "reindex"
        }
    }
</code></pre>
<h2 id="thatsit">That’s it.</h2>
<p>After a couple hours, we successfully migrated 1 billion documents from OpenSearch to Elasticsearch and even saved 23% plus on disk storage! Now that we have the logs in Elasticsearch how about extracting actual business value from them? Logs contain so much valuable information - we can not only do all sorts of interesting things with AIOPS, like <a href="https://www.elastic.co/guide/en/observability/current/categorize-logs.html#analyze-log-categories">Automatically Categorize</a> those logs, but also extract <a href="https://www.youtube.com/watch?v=0E7isxR_FzY&amp;list=PLzPXmNbs8vqUc2bROb1E2gNyj2GynRB5b&amp;index=3&amp;t=1122s">business metrics</a> and <a href="https://www.youtube.com/watch?v=0E7isxR_FzY&amp;list=PLzPXmNbs8vqUc2bROb1E2gNyj2GynRB5b&amp;index=3&amp;t=1906s">detect anomalies</a> on them, give it a try.</p>
<p>|                                    |           |             |                                   |           |             |        |
| ---------------------------------- | --------- | ----------- | --------------------------------- | --------- | ----------- | ------ |
| OpenSearch                         |           |             | Elasticsearch                     |           |             |        |
| Index                              | docs      | size        | Index                             | docs      | size        | Diff.  |
| .ds-logs-myapplication-prod-000037 | 116842158 | 27285520870 | logs-myapplication-reindex-000037 | 116842158 | 21998435329 | 21.46% |
| .ds-logs-myapplication-prod-000038 | 110994116 | 27263291740 | logs-myapplication-reindex-000038 | 110994116 | 21540011082 | 23.45% |
| .ds-logs-myapplication-prod-000040 | 113362823 | 27872438186 | logs-myapplication-reindex-000040 | 113362823 | 22234641932 | 22.50% |
| .ds-logs-myapplication-prod-000041 | 112400019 | 27618801653 | logs-myapplication-reindex-000041 | 112400019 | 22059453868 | 22.38% |
| .ds-logs-myapplication-prod-000042 | 113859174 | 26686723701 | logs-myapplication-reindex-000042 | 113859174 | 21093766108 | 23.41% |
| .ds-logs-myapplication-prod-000043 | 113821016 | 27657006598 | logs-myapplication-reindex-000043 | 113821016 | 22059454752 | 22.52% |
| .ds-logs-myapplication-prod-000044 | 111093596 | 27281936915 | logs-myapplication-reindex-000044 | 111093596 | 21559513422 | 23.43% |
| .ds-logs-myapplication-prod-000048 | 114273539 | 28111420495 | logs-myapplication-reindex-000048 | 114273539 | 22264398939 | 23.21% |
| .ds-logs-myapplication-prod-000049 | 102519334 | 23731274338 | logs-myapplication-reindex-000049 | 102519334 | 19307250001 | 20.56% |</p>
<p>Interested in trying Elasticsearch? <a href="https://cloud.elastic.co/registration?elektra=en-cloud-page">Start our 14-day free trial</a>.</p>
<p><em>The release and timing of any features or functionality described in this post remain at Elastic's sole discretion. Any features or functionality not currently available may not be delivered on time or at all.</em></p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/migrating-billion-log-lines-opensearch-elasticsearch</link>
    <guid isPermaLink="false">migrating-billion-log-lines-opensearch-elasticsearch</guid>
    <category><![CDATA[Logs Analytics]]></category>
    <dc:creator><![CDATA[Ugo Sangiorgi]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6463725fcf9a1b3c/6a85cd2df61d6e13209c2b5b/elastic-blog-header-1-billion-log-lines.png" length="0" type="image/png"/>
    <pubDate>Wed, 11 Oct 2023 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Customize your data ingestion with Elastic input packages]]></title>
    <description><![CDATA[In this post, learn about input packages and how they can provide a flexible solution to advanced users for customizing their ingestion experience in Elastic.]]></description>
    <content:encoded><![CDATA[<p>Elastic<sup>®</sup> has enabled the collection, transformation, and analysis of data flowing between the external data sources and Elastic Observability Solution through <a href="https://www.elastic.co/integrations/">integrations</a>. Integration packages achieve this by encapsulating several components, including <a href="https://www.elastic.co/guide/en/fleet/current/create-standalone-agent-policy.html">agent configuration</a>, inputs for data collection, and assets like <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/ingest.html">ingest pipelines</a>, <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/data-streams.html">data streams</a>, <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/index-templates.html">index templates</a>, and <a href="https://www.elastic.co/guide/en/kibana/current/dashboard.html">visualizations</a>. The breadth of these assets supported in the Elastic Stack increases day by day.</p>
<p>This blog dives into how input packages provide an extremely generic and flexible solution to the advanced users for customizing their ingestion experience in Elastic.</p>
<h2 id="whatareinputpackages">What are input packages?</h2>
<p>An <a href="https://github.com/elastic/elastic-package">Elastic Package</a> is an artifact that contains a collection of assets that extend the Elastic Stack, providing new capabilities to accomplish a specific task like integration with an external data source. The first use of Elastic packages is <a href="https://github.com/elastic/integrations">integration packages</a>, which provide an end-to-end experience — from configuring Elastic Agent, to collecting signals from the data source, to ingesting them correctly and using the data once ingested.</p>
<p>However, advanced users may need to customize data collection, either because an integration does not exist for a specific data source, or even if it does, they want to collect additional signals or in a different way. Input packages are another type of <a href="https://github.com/elastic/elastic-package">Elastic package</a> that provides the capability to configure Elastic Agent to use the provided inputs in a custom way.</p>
<h2 id="letslookatanexample">Let’s look at an example</h2>
<p>Say hello to Julia, who works as an engineer at Ascio Innovation firm. She is currently working with Oracle Weblogic server and wants to get a set of metrics for monitoring it. She goes ahead and installs Elastic <a href="https://docs.elastic.co/integrations/oracle_weblogic">Oracle Weblogic Integration</a>, which uses Jolokia in the backend to fetch the metrics.</p>
<p>Now, her team wants to advance in the monitoring and has the following requirements:</p>
<ol>
<li><p>We should be able to extract metrics other than the default ones, which are not supported by the default Oracle Weblogic Integration.</p></li>
<li><p>We want to have our own bespoke pipelines, visualizations, and experience.</p></li>
<li><p>We should be able to identify the metrics coming in from two different instances of Weblogic Servers by having data mapped to separate <a href="https://www.elastic.co/blog/what-is-an-elasticsearch-index">indices</a>.</p></li>
</ol>
<p>All the above requirements can be met by using the <a href="https://docs.elastic.co/integrations/jolokia">Jolokia input package</a> to get a customized experience. Let's see how.</p>
<p>Julia can add the configuration of Jolokia input package as below, fulfilling the <em>first requirement.</em></p>
<p>hostname, JMX Mappings for the fields you want to fetch for the JVM application, and the <a href="https://www.elastic.co/guide/en/ecs/master/ecs-data_stream.html#field-data-stream-dataset">data set</a> name to which the response fields would get mapped.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltfe3c1c3308b1b952/6a85c86b18249c4a1d18f73b/elastic-blog-1-config-parameters.png" alt="Configuration Parameters for Jolokia Input package" /></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt82c1570a90df3583/6a85c86e5c2790c33bf59ab5/elastic-blog-2-expanded-doc.png" alt="Metrics getting mapped to the index created by the ‘jolokia_first_dataset’" /></p>
<p>Julia can customize her data by writing her own <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/ingest.html">ingest pipelines</a> and providing her customized <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping.html">mappings</a>. Also, she can then build her own bespoke dashboards, hence meeting her <em>second requirement.</em></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt701e15290b92a292/6a85c87143c0b77f5d2f05be/elastic-blog-3-ingest-pipelines.png" alt="Customization of Ingest Pipelines and Mappings" /></p>
<p>Let’s say now Julia wants to use another instance of Oracle Weblogic and get a different set of metrics.</p>
<p>This can be achieved by adding another instance of Jolokia input package and specifying a new <a href="https://www.elastic.co/guide/en/ecs/master/ecs-data_stream.html#field-data-stream-dataset">data set</a> name as shown in the screenshot below. The resultant metrics will be mapped to a different <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-index_.html">index</a>/data set hence fulfilling her <em>third requirement.</em> This will help Julia to differentiate metrics coming in from two different instances of Oracle Weblogic.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8d1bdeef3cb43115/6a85c874d7b2e751abfe846a/elastic-blog-4-jolokia.png" alt="jolokia metrics" /></p>
<p>The resultant metrics of the query will be indexed to the new data set, jolokia_second_dataset in the below example.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta18b2ea1a67c9a46/6a85c877bc5bb33601f81a7b/elastic-blog-5-dataset.png" alt="dataset" /></p>
<p>As we can see above, the Jolokia input package provides the flexibility to get new metrics by specifying different JMX Mappings, which are not supported in the default Oracle Weblogic integration (the user gets metrics from a predetermined set of JMX Mappings).</p>
<p>The Jolokia Input package also can be used for monitoring any Java-based application, which pushes its metrics through JMX. So a single input package can be used to collect metrics from multiple Java applications/services.</p>
<h2 id="elasticinputpackages">Elastic input packages</h2>
<p>Elastic has started supporting input packages from the 8.8.0 release. Some of the input packages are now available in beta and will mature gradually:</p>
<ol>
<li><p><a href="https://docs.elastic.co/integrations/sql">SQL input package</a>: The SQL input package allows you to execute queries against any SQL database and store the results in Elasticsearch<sup>®</sup>.</p></li>
<li><p><a href="https://docs.elastic.co/integrations/prometheus_input">Prometheus input package</a>: This input package can collect metrics from <a href="https://prometheus.io/docs/instrumenting/exporters/">Prometheus Exporters (Collectors)</a>.It can be used by any service exporting its metrics to a Prometheus endpoint.</p></li>
<li><p><a href="https://docs.elastic.co/integrations/jolokia">Jolokia input package</a>: This input package collects metrics from <a href="https://jolokia.org/agent.html">Jolokia agents</a> running on a target JMX server or dedicated proxy server. It can be used for monitoring any Java-based application, which pushes its metrics through JMX.</p></li>
<li><p><a href="https://docs.elastic.co/integrations/statsd_input">Statsd input package</a>: The statsd input package spawns a UDP server and listens for metrics in StatsD compatible format. This input can be used to collect metrics from services that send data over the StatsD protocol.</p></li>
<li><p><a href="https://docs.elastic.co/integrations/gcp_metrics">GCP Metrics input package</a>: The GCP Metrics input package can collect custom metrics for any GCP service.</p></li>
</ol>
<h2 id="tryitout">Try it out!</h2>
<p>Now that you know more about input packages, try building your own customized integration for your service through input packages, and get started with an <a href="https://cloud.elastic.co/registration?fromURI=/home">Elastic Cloud</a> free trial.</p>
<p>We would love to hear from you about your experience with input packages on the Elastic <a href="https://discuss.elastic.co/">Discuss</a> forum or in <a href="https://github.com/elastic/integrations">the Elastic Integrations repository</a>.</p>
<p><em>The release and timing of any features or functionality described in this post remain at Elastic's sole discretion. Any features or functionality not currently available may not be delivered on time or at all.</em></p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/customize-data-ingestion-input-packages</link>
    <guid isPermaLink="false">customize-data-ingestion-input-packages</guid>
    <category><![CDATA[Logs Analytics]]></category>
    <dc:creator><![CDATA[Ishleen Kaur]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdce7cff55bb7e7e5/6a85c87a5c279033e8f59ab9/customize-observability-input-720x420.jpg" length="0" type="image/jpeg"/>
    <pubDate>Tue, 26 Sep 2023 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Elastic SQL inputs: A generic solution for database metrics observability]]></title>
    <description><![CDATA[This blog dives into the functionality of generic SQL and provides various use cases for advanced users to ingest custom metrics to Elastic for database observability. We also introduce the fetch from all database new capability released in 8.10.]]></description>
    <content:encoded><![CDATA[<p>Elastic<sup>®</sup> SQL inputs (<a href="https://www.elastic.co/guide/en/beats/metricbeat/current/metricbeat-module-sql.html">metricbeat</a> module and <a href="https://docs.elastic.co/integrations/sql">input package</a>) allows the user to execute <a href="https://en.wikipedia.org/wiki/SQL">SQL</a> queries against many supported databases in a flexible way and ingest the resulting metrics to Elasticsearch<sup>®</sup>. This blog dives into the functionality of generic SQL and provides various use cases for <em>advanced users</em> to ingest custom metrics to Elastic<sup>®</sup>, for database observability. The blog also introduces the fetch from all database new capability, released in 8.10.</p>
<h2 id="whygenericsql">Why “Generic SQL”?</h2>
<p>Elastic already has metricbeat and integration packages targeted for specific databases. One example is <a href="https://www.elastic.co/guide/en/beats/metricbeat/current/metricbeat-module-mysql.html">metricbeat</a> for MySQL — and the corresponding integration <a href="https://docs.elastic.co/en/integrations/mysql">package</a>. These beats modules and integrations are customized for a specific database, and the metrics are extracted using pre-defined queries from the specific database. The queries used in these integrations and the corresponding metrics are <em>not</em> available for modification.</p>
<p>Whereas the <em>Generic SQL inputs</em> (<a href="https://www.elastic.co/guide/en/beats/metricbeat/current/metricbeat-module-sql.html">metricbeat</a> or <a href="https://docs.elastic.co/integrations/sql">input package</a>) can be used to scrape metrics from any supported database using the user's SQL queries. The queries are provided by the user depending on specific metrics to be extracted. This enables a much more powerful mechanism for metrics ingestion, where users can choose a specific driver and provide the relevant SQL queries and the results get mapped to one or more Elasticsearch documents, using a structured mapping process (table/variable format explained later).</p>
<p>Generic SQL inputs can be used in conjunction with the existing integration packages, which already extract specific database metrics, to extract additional custom metrics dynamically, making this input very powerful. In this blog, <em>Generic SQL input</em> and <em>Generic SQL</em> are used interchangeably.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt86cd15e65734414c/6a85cad543c0b77eb12f0612/elastic-blog-1-genericSQL.png" alt="Generic SQL database metrics collection" /></p>
<h2 id="functionalitiesdetails">Functionalities details</h2>
<p>This section covers some of the features that would help with the metrics extraction. We provide a brief description of the response format configuration. Then we dive into the merge_results functionality, which is used to combine results from multiple SQL queries into a single document.</p>
<p>The next key functionality users may be interested in is to collect metrics from all the custom databases, which is now possible with the fetch_from_all_databases feature.</p>
<p>Now let's dive into the specific functionalities:</p>
<h3 id="differentdriverssupported">Different drivers supported</h3>
<p>The generic SQL can fetch metrics from the different databases. The current version has the capability to fetch metrics from the following drivers: MySQL, PostgreSQL, Oracle, and Microsoft SQL Server(MSSQL).</p>
<h3 id="responseformat">Response format</h3>
<p>The response format in generic SQL is used to manipulate the data in either table or in variable format. Here’s an overview of the formats and syntax for creating and using the table and variables.</p>
<p>Syntax: <code>response_format: table {{or}} variables</code></p>
<p><strong>Response format table</strong><br />
This mode generates a single event for each row. The table format has no restrictions on the number of columns in the response. This format can have any number of columns.</p>
<p>Example:</p>
<pre><code>driver: "mssql"
sql_queries:
 - query: "SELECT counter_name, cntr_value FROM sys.dm_os_performance_counters WHERE counter_name= 'User Connections'"
   response_format: table
</code></pre>
<p>This query returns a response similar to this:</p>
<pre><code>"sql":{
      "metrics":{
         "counter_name":"User Connections ",
         "cntr_value":7
      },
      "driver":"mssql"
}
</code></pre>
<p>The response generated above adds the counter_name as a key in the document.</p>
<p><strong>Response format variables</strong><br />
The variable format supports key:value pairs. This format expects only two columns to fetch in a query.</p>
<p>Example:</p>
<pre><code>driver: "mssql"
sql_queries:
 - query: "SELECT counter_name, cntr_value FROM sys.dm_os_performance_counters WHERE counter_name= 'User Connections'"
   response_format: variables
</code></pre>
<p>The variable format takes the first variable in the query above as the key:</p>
<pre><code>"sql":{
      "metrics":{
         "user connections ":7
      },
      "driver":"mssql"
}
</code></pre>
<p>In the above response, you can see the value of counter_name is used to generate the key in variable format.</p>
<h3 id="responseoptimizationmerge_results">Response optimization: merge_results</h3>
<p>We are now supporting merging multiple query responses into a single event. By enabling <strong>merge_results</strong> , users can significantly optimize the storage space of the metrics ingested to Elasticsearch. This mode enables an efficient compaction of the document generated, where instead of generating multiple documents, a single merged document is generated wherever applicable. The metrics of a similar kind, generated from multiple queries, are combined into a single event.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt658fd39fac11b6c5/6a85cada18249ce22818f7bf/elastic-blog-2-output-merge-results.png" alt="Output of Merge results" /></p>
<p>Syntax: <code>merge_results: true {{or}} false</code></p>
<p>In the below example, you can see how the data is loaded into Elasticsearch for the below query when the merge_results is disabled.</p>
<p>Example:</p>
<p>In this example, we are using two different queries to fetch metrics from the performance counter.</p>
<pre><code>merge_results: false
driver: "mssql"
sql_queries:
  - query: "SELECT cntr_value As 'user_connections' FROM sys.dm_os_performance_counters WHERE counter_name= 'User Connections'"
    response_format: table
  - query: "SELECT cntr_value As 'buffer_cache_hit_ratio' FROM sys.dm_os_performance_counters WHERE counter_name = 'Buffer cache hit ratio' AND object_name like '%Buffer Manager%'"
    response_format: table
</code></pre>
<p>As you can see, the response for the above example generates a single document for each query.</p>
<p>The resulting document from the first query:</p>
<pre><code>"sql":{
      "metrics":{
         "user_connections":7
      },
      "driver":"mssql"
}
</code></pre>
<p>And resulting document from the second query:</p>
<pre><code>"sql":{
      "metrics":{
         "buffer_cache_hit_ratio":87
      },
      "driver":"mssql"
}
</code></pre>
<p>When we enable the merge_results flag in the query, both the above metrics are combined together and the data gets loaded in a single document.</p>
<p>You can see the merged document in the below example:</p>
<pre><code>"sql":{
      "metrics":{
         "user connections ":7,
         “buffer_cache_hit_ratio”:87
      },
      "driver":"mssql"
}
</code></pre>
<p><em>However, such a merge is possible only if the table queries are merged, and each produces a single row. There is no restriction on variable queries being merged.</em></p>
<h3 id="introducinganewcapabilityfetch_from_all_databases">Introducing a new capability: fetch_from_all_databases</h3>
<p>This is a <a href="https://github.com/elastic/beats/pull/35688">new functionality</a> to fetch all the database metrics automatically from the system and user databases of the Microsoft SQL Server, by enabling the fetch_from_all_databases flag.</p>
<p>Keep an eye out for the <a href="https://www.elastic.co/guide/en/beats/metricbeat/8.10/metricbeat-module-sql.html#_example_execute_given_queries_for_all_databases_present_in_a_server">8.10 release version</a> where you can start using the fetch all database feature. Prior to the 8.10 version, users had to provide the database names manually to fetch metrics from custom/user databases.</p>
<p>Syntax: <code>fetch_from_all_databases: true {{or}} false</code></p>
<p>Below is the sample query with fetch all databases flag as disabled:</p>
<pre><code>fetch_from_all_databases: false
driver: "mssql"
sql_queries:
  - query: "SELECT @@servername AS server_name, @@servicename AS instance_name, name As 'database_name', database_id FROM sys.databases WHERE name='master';"
</code></pre>
<p>The above query fetches metrics only for the provided database name. Here the input database is master, so the metrics are fetched only for the master.</p>
<p>Below is the sample query with the fetch all databases flag as enabled:</p>
<pre><code>fetch_from_all_databases: true
driver: "mssql"
sql_queries:
  - query: SELECT @@servername AS server_name, @@servicename AS instance_name, DB_NAME() AS 'database_name', DB_ID() AS database_id;
    response_format: table
</code></pre>
<p>The above query fetches metrics from all available databases. This is useful when the user wants to get data from all the databases.</p>
<p>Please note: currently this feature is supported only for Microsoft SQL Server and will be used by MS SQL integration internally, to support extracting metrics for <a href="https://github.com/elastic/integrations/issues/4108">all user DBs</a> by default.</p>
<h2 id="usinggenericsqlmetricbeat">Using generic SQL: Metricbeat</h2>
<p>The generic <a href="https://www.elastic.co/guide/en/beats/metricbeat/current/metricbeat-module-sql.html">SQL metricbeat module</a> provides flexibility to execute queries against different database drivers. The metricbeat input is available as GA for any production usage. <a href="https://www.elastic.co/guide/en/beats/metricbeat/current/metricbeat-module-sql.html">Here</a>, you can find more information on configuring <a href="https://www.elastic.co/guide/en/beats/metricbeat/current/metricbeat-module-sql.html">the generic SQL</a> for different drivers with various examples.</p>
<h2 id="usinggenericsqlinputpackage">Using generic SQL: Input package</h2>
<p>The input package provides a flexible solution to advanced users for customizing their ingestion experience in Elastic. Generic SQL is now also available as an SQL<a href="https://docs.elastic.co/integrations/sql">input package</a>. The input package is currently available for early users as a <strong>beta release</strong>. Let's take a walk through how users can use generic SQL via the input package.</p>
<h3 id="configurationsofgenericsqlinputpackage">Configurations of generic SQL input package:</h3>
<p>The configuration options for the generic SQL input package are as below:</p>
<ul>
<li><strong>Driver**</strong> :** This is the SQL database for which you want to use the package. In this case, we will take mysql as an example.</li>
<li><strong>Hosts:</strong> Here the user enters the connection string to connect to the database. It would vary depending on which database/driver is being used. Refer <a href="https://docs.elastic.co/integrations/sql#hosts">here</a> for examples.</li>
<li><strong>SQL Queries:</strong> Here the user writes the SQL queries they want to fire and the response_format is specified.</li>
<li><strong>Data set:</strong> The user specifies a <a href="https://www.elastic.co/guide/en/ecs/master/ecs-data_stream.html#_data_stream_field_details">data set</a> name to which the response fields get mapped.</li>
<li><strong>Merge results**</strong> :** This is an advanced setting, used to merge queries into a single event.</li>
</ul>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc36880b217c1a2b7/6a85cadd9829266c605838e4/elastic-blog-3-SQL-metrics-inputpackage.png" alt="Configuration parameters for SQL input package" /></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9f349d24b55e602e/6a85cae333f244f11649f506/elastic-blog-4-expanded-document.png" alt="Metrics getting mapped to the index created by the ‘sql_first_dataset’" /></p>
<h3 id="metricsextensibilitywithcustomizedsqlqueries">Metrics extensibility with customized SQL queries</h3>
<p>Let's say a user is using <a href="https://docs.elastic.co/integrations/mysql">MYSQL Integration</a>, which provides a fixed set of metrics. Their requirement now extends to retrieving more metrics from the MYSQL database by firing new customized SQL queries.</p>
<p>This can be achieved by adding an instance of SQL input package, writing the customized queries and specifying a new <a href="https://www.elastic.co/guide/en/ecs/master/ecs-data_stream.html#field-data-stream-dataset">data set</a> name as shown in the screenshot below.</p>
<p>This way users can get any metrics by executing corresponding queries. The resultant metrics of the query will be indexed to the new data set, sql_second_dataset.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltea0ae6966bbfd68b/6a85cae7ba7acc2b13992146/elastic-blog-5-driver.png" alt="Customization of Ingest Pipelines and Mappings" /></p>
<p>When there are multiple queries, users can club them into a single event by enabling the Merge Results toggle.</p>
<h3 id="customizinguserexperience">Customizing user experience</h3>
<p>Users can customize their data by writing their own <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/ingest.html">ingest pipelines</a> and providing their customized <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping.html">mappings</a>. Users can also build their own bespoke dashboards.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt89913365b437dcf0/6a85caebf61d6e02d29c2b15/elastic-blog-6-ingest-pipeline.png" alt="Customization of Ingest Pipelines and Mappings" /></p>
<p>As we can see above, the SQL input package provides the flexibility to get new metrics by running new queries, which are not supported in the default MYSQL integration (the user gets metrics from a predetermined set of queries).</p>
<p>The SQL input package also supports multiple drivers: mssql, postgresql and oracle. So a single input package can be used to cater to all these databases.</p>
<p>Note: The fetch_from_all_databases feature is not supported in the SQL input package yet.</p>
<h2 id="tryitout">Try it out!</h2>
<p>Now that you know about various use cases and features of generic SQL, get started with <a href="https://cloud.elastic.co/registration?fromURI=/home">Elastic Cloud</a> and try using the <a href="https://docs.elastic.co/integrations/sql">SQL input package</a> for your SQL database and get customized experience and metrics. If you are looking for newer metrics for some of our existing SQL based integrations — like <a href="https://docs.elastic.co/en/integrations/microsoft_sqlserver">Microsoft SQL Server</a>, <a href="https://docs.elastic.co/integrations/oracle">Oracle</a>, and more — go ahead and give the SQL input package a swirl.</p>
<p><em>The release and timing of any features or functionality described in this post remain at Elastic's sole discretion. Any features or functionality not currently available may not be delivered on time or at all.</em></p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/sql-inputs-database-metrics-observability</link>
    <guid isPermaLink="false">sql-inputs-database-metrics-observability</guid>
    <category><![CDATA[Metrics]]></category>
    <category><![CDATA[Logs Analytics]]></category>
    <dc:creator><![CDATA[Lalit Satapathy,Ishleen Kaur,Muthukumar Paramasivam]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt83f89ae1d32f8838/6a85caeeabdc295c6b1224f8/patterns-midnight-background-no-logo-observability.png" length="0" type="image/png"/>
    <pubDate>Mon, 11 Sep 2023 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[3 models for logging with OpenTelemetry and Elastic]]></title>
    <description><![CDATA[Because OpenTelemetry increases usage of tracing and metrics with developers, logging continues to provide flexible, application-specific, and event-driven data. Explore OpenTelemetry logging and how it provides guidance on the available approaches.]]></description>
    <content:encoded><![CDATA[<p>Arguably, <a href="https://www.elastic.co/blog/opentelemetry-observability">OpenTelemetry</a> exists to (greatly) increase usage of tracing and metrics among developers. That said, logging will continue to play a critical role in providing flexible, application-specific, event-driven data. Further, OpenTelemetry has the potential to bring added value to existing application logging flows:</p>
<ol>
<li><p>Common metadata across tracing, metrics, and logging to facilitate contextual correlation, including metadata passed between services as part of REST or RPC APIs; this is a critical element of service observability in the age of distributed, horizontally scaled systems</p></li>
<li><p>An optional unified data path for tracing, metrics, and logging to facilitate common tooling and signal routing to your observability backend</p></li>
</ol>
<p>Adoption of metrics and tracing among developers to date has been relatively small. Further, the number of proprietary vendors and APIs (compared to adoption rate) is relatively large. As such, OpenTelemetry took a greenfield approach to developing new, vendor-agnostic APIs for tracing and metrics. In contrast, most developers have nearly 100% log coverage across their services. Moreover, logging is largely supported by a small number of vendor-agnostic, open-source logging libraries and associated APIs (e.g., <a href="https://logback.qos.ch">Logback</a> and <a href="https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.logging.ilogger">ILogger</a>). As such, <a href="https://opentelemetry.io/docs/specs/otel/logs/#introduction">OpenTelemetry’s approach to logging</a> meets developers where they already are using hooks into existing, popular logging frameworks. In this way, developers can add OpenTelemetry as a log signal output without otherwise altering their code and investment in logging as an observability signal.</p>
<p>Notably, logging is the least mature of OTel supported observability signals. Depending on your service’s <a href="https://opentelemetry.io/docs/instrumentation/#status-and-releases">language</a>, and your appetite for adventure, there exist several options for exporting logs from your services and applications and marrying them together in your observability backend.</p>
<p>The intent of this article is to explore the current state of the art of <a href="https://www.elastic.co/blog/introduction-apm-tracing-logging-customer-experience">OpenTelemetry logging</a> and to provide guidance on the available approaches with the following tenants in mind:</p>
<ul>
<li>Correlation of service logs with OTel-generated tracing where applicable</li>
<li>Proper capture of exceptions</li>
<li>Common context across tracing, metrics, and logging</li>
<li>Support for <a href="https://www.slf4j.org/manual.html#fluent">slf4j key-value pairs</a> (“structured logging”)</li>
<li>Automatic attachment of metadata carried between services via <a href="https://opentelemetry.io/docs/concepts/signals/baggage/">OTel baggage</a></li>
<li>Use of an Elastic<sup>®</sup> Observability backend</li>
<li>Consistent data fidelity in Elastic regardless of the approach taken</li>
</ul>
<h2 id="opentelemetryloggingmodels">OpenTelemetry logging models</h2>
<p>Three models currently exist for getting your application or service logs to Elastic with correlation to OTel tracing and baggage:</p>
<ol>
<li><p>Output logs from your service (alongside traces and metrics) using an embedded <a href="https://opentelemetry.io/docs/instrumentation/#status-and-releases">OpenTelemetry Instrumentation library</a> to Elastic via the OTLP protocol</p></li>
<li><p>Write logs from your service to a file scraped by the <a href="https://opentelemetry.io/docs/collector/">OpenTelemetry Collector</a>, which then forwards to Elastic via the OTLP protocol</p></li>
<li><p>Write logs from your service to a file scraped by <a href="https://www.elastic.co/elastic-agent">Elastic Agent</a> (or <a href="https://www.elastic.co/beats/filebeat">Filebeat</a>), which then forwards to Elastic via an Elastic-defined protocol</p></li>
</ol>
<p>Note that (1), in contrast to (2) and (3), does not involve writing service logs to a file prior to ingestion into Elastic.</p>
<h2 id="loggingvsspanevents">Logging vs. span events</h2>
<p>It is worth noting that most APM systems, including OpenTelemetry, include provisions for <a href="https://opentelemetry.io/docs/instrumentation/ruby/manual/#add-span-events">span events</a>. Like log statements, span events contain arbitrary, textual data. Additionally, span events automatically carry any custom attributes (e.g., a “user ID”) applied to the parent span, which can help with correlation and context. In this regard, it may be advantageous to translate some existing log statements (inside spans) to span events. As the name implies, of course, span events can only be emitted from within a span and thus are not intended to be a general purpose replacement for logging.</p>
<p>Unlike logging, span events do not pass through existing logging frameworks and therefore cannot (practically) be written to a log file. Further, span events are technically emitted as part of trace data and follow the same data path and signal routing as other trace data.</p>
<h2 id="polyfillappender">Polyfill appender</h2>
<p>Some of the demos make use of a custom Logback <a href="https://github.com/ty-elastic/otel-logging/blob/main/java-otel-log/src/main/java/com/tb93/otel/batteries/PolyfillAppender.java">“Polyfill appender”</a> (inspired by OTel’s <a href="https://github.com/open-telemetry/opentelemetry-java-instrumentation/tree/main/instrumentation/logback/logback-mdc-1.0/library">Logback MDC</a>), which provides support for attaching <a href="https://www.slf4j.org/manual.html#fluent">slf4j key-value pairs</a> to log messages for models (2) and (3).</p>
<h2 id="elasticcommonschema">Elastic Common Schema</h2>
<p>For log messages to exhibit full fidelity within Elastic, they eventually need to be formatted in accordance with the <a href="https://www.elastic.co/guide/en/ecs/current/ecs-reference.html">Elastic Common Schema</a> (ECS). In models (1) and (2), log messages remain formatted in OTel log semantics until ingested by the Elastic APM Server. The Elastic APM Server then translates OTel log semantics to ECS. In model (3), ECS is applied at the source.</p>
<p>Notably, OpenTelemetry recently <a href="https://www.elastic.co/blog/ecs-elastic-common-schema-otel-opentelemetry-announcement">adopted the Elastic Common Schema</a> as its standard for semantic conventions going forward! As such, it is anticipated that current OTel log semantics will be updated to align with ECS.</p>
<h2 id="gettingstarted">Getting started</h2>
<p>The included demos center around a “POJO” (no assumed framework) Java project. Java is arguably the most mature of OTel-supported languages, particularly with respect to logging options. Notably, this singular Java project was designed to support the three models of logging discussed here. In practice, you would only implement one of these models (and corresponding project dependencies).</p>
<p>The demos assume you have a working <a href="https://www.docker.com/">Docker</a> environment and an <a href="https://www.elastic.co/cloud/">Elastic Cloud</a> instance.</p>
<ol>
<li><p>git clone https://github.com/ty-elastic/otel-logging</p></li>
<li><p>Create an .env file at the root of otel-logging with the following (appropriately filled-in) environment variables:</p></li>
</ol>
<pre><code># the service name
OTEL_SERVICE_NAME=app4

# Filebeat vars
ELASTIC_CLOUD_ID=(see https://www.elastic.co/guide/en/beats/metricbeat/current/configure-cloud-id.html)
ELASTIC_CLOUD_AUTH=(see https://www.elastic.co/guide/en/beats/metricbeat/current/configure-cloud-id.html)

# apm vars
ELASTIC_APM_SERVER_ENDPOINT=(address of your Elastic Cloud APM server... i.e., https://xyz123.apm.us-central1.gcp.cloud.es.io:443)
ELASTIC_APM_SERVER_SECRET=(see https://www.elastic.co/guide/en/apm/guide/current/secret-token.html)
</code></pre>
<ol>
<li>Start up the demo with the desired model:</li>
</ol>
<ul>
<li>If you want to demo logging via OTel APM Agent, run MODE=apm docker-compose up</li>
<li>If you want to demo logging via OTel filelogreceiver, run MODE=filelogreceiver docker-compose up</li>
<li>If you want to demo logging via Elastic filebeat, run MODE=filebeat docker-compose up</li>
</ul>
<ol>
<li>Validate incoming span and correlated log data in your Elastic Cloud instance</li>
</ol>
<h2 id="model1loggingviaopentelemetryinstrumentation">Model 1: Logging via OpenTelemetry instrumentation</h2>
<p>This model aligns with the long-term goals of OpenTelemetry: <a href="https://opentelemetry.io/docs/specs/otel/logs/#opentelemetry-solution">integrated tracing, metrics, and logging (with common attributes) from your services</a> via the <a href="https://opentelemetry.io/docs/instrumentation/#status-and-releases">OpenTelemetry Instrumentation libraries</a>, without dependency on log files and scrappers.</p>
<p>In this model, your service generates log statements as it always has, using popular logging libraries (e.g., <a href="https://logback.qos.ch">Logback</a> for Java). OTel provides a “Southbound hook” to Logback via the OTel <a href="https://github.com/open-telemetry/opentelemetry-java-instrumentation/tree/main/instrumentation/logback/logback-appender-1.0/library">Logback Appender</a>, which injects ServiceName, SpanID, TraceID, slf4j key-value pairs, and OTel baggage into log records and passes the composed records to the co-resident OpenTelemetry Instrumentation library. We further employ a <a href="https://github.com/ty-elastic/otel-logging/blob/main/java-otel-log/src/main/java/com/tb93/otel/batteries/AddBaggageLogProcessor.java">custom LogRecordProcessor</a> to add baggage to the log record as attributes.</p>
<p>The OTel instrumentation library then formats the log statements per the <a href="https://opentelemetry.io/docs/specs/otel/logs/data-model/">OTel logging spec</a> and ships them via OTLP to either an OTel Collector for further routing and enrichment or directly to Elastic.</p>
<p>Notably, as language support improves, this model can and will be supported by runtime agent binding with auto-instrumentation where available (e.g., no code changes required for runtime languages).</p>
<p>One distinguishing advantage of this model, beyond the simplicity it affords, is the ability to more easily tie together attributes and tracing metadata directly with log statements. This inherently makes logging more useful in the context of other OTel-supported observability signals.</p>
<h3 id="architecture">Architecture</h3>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3567b0084b934a39/6a85c72a27c5cd780e5f733e/elastic-blog-model-1-architecture.png" alt="model 1 architecture" /></p>
<p>Although not explicitly pictured, an <a href="https://opentelemetry.io/docs/collector/">OpenTelemetry Collector</a> can be inserted in between the service and Elastic to facilitate additional enrichment and/or signal routing or duplication across observability backends.</p>
<h3 id="pros">Pros</h3>
<ul>
<li>Simplified signal architecture and fewer “moving parts” (no files, disk utilization, or file rotation concerns)</li>
<li>Aligns with long-term OTel vision</li>
<li>Log statements can be (easily) decorated with OTel metadata</li>
<li>No polyfill adapter required to support structured logging with slf4j</li>
<li>No additional collectors/agents required</li>
<li>Conversion to ECS happens within Elastic keeping log data vendor-agnostic until ingestion</li>
<li>Common wireline protocol (OTLP) across tracing, metrics, and logs</li>
</ul>
<h3 id="cons">Cons</h3>
<ul>
<li>Not available (yet) in many OTel-supported languages</li>
<li>No intermediate log file for ad-hoc, on-node debugging</li>
<li>Immature (alpha/experimental)
Unknown “glare” conditions, which could result in loss of log data if service exits prematurely or if the backend is unable to accept log data for an extended period of time</li>
</ul>
<h3 id="demo">Demo</h3>
<p>MODE=apm docker-compose up</p>
<h2 id="model2loggingviatheopentelemetrycollector">Model 2: Logging via the OpenTelemetry Collector</h2>
<p>Given the cons of Model 1, it may be advantageous to consider a model that continues to leverage an actual log file intermediary between your services and your observability backend. Such a model is possible using an <a href="https://opentelemetry.io/docs/collector/">OpenTelemetry Collector</a> collocated with your services (e.g., on the same host), running the <a href="https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/receiver/filelogreceiver/README.md">filelogreceiver</a> to scrape service log files.</p>
<p>In this model, your service generates log statements as it always has, using popular logging libraries (e.g., <a href="https://logback.qos.ch">Logback</a> for Java). OTel provides a MDC Appender for Logback (<a href="https://github.com/open-telemetry/opentelemetry-java-instrumentation/tree/main/instrumentation/logback/logback-mdc-1.0/library">Logback MDC</a>), which adds SpanID, TraceID, and Baggage to the <a href="https://logback.qos.ch/manual/mdc.html">Logback MDC context</a>.</p>
<p>Notably, no log record structure is assumed by the OTel filelogreceiver. In the example provided, we employ the <a href="https://github.com/logfellow/logstash-logback-encoder">logstash-logback-encoder</a> to JSON-encode log messages. The logstash-logback-encoder will read the OTel SpanID, TraceID, and Baggage off the MDC context and encode it into the JSON structure. Notably, logstash-logback-encoder doesn’t explicitly support <a href="https://www.slf4j.org/manual.html#fluent">slf4j key-value pairs</a>. It does, however, support <a href="https://github.com/logfellow/logstash-logback-encoder#event-specific-custom-fields">Logback structured arguments</a>, and thus I use the <a href="https://github.com/ty-elastic/otel-logging/blob/main/java-otel-log/src/main/java/com/tb93/otel/batteries/PolyfillAppender.java">Polyfill Appender</a> to convert slf4j key-value pairs to Logback structured arguments.</p>
<p>From there, we write the log lines to a log file. If you are using Kubernetes or other container orchestration in your environment, you would more typically write to stdout (console) and let the orchestration log driver write to and manage log files.</p>
<p>We then <a href="https://github.com/ty-elastic/otel-logging/blob/main/collector/filelogreceiver.yml">configure</a> the OTel Collector to scrape this log file (using the filelogreceiver). Because no assumptions are made about the format of the log lines, you need to <a href="https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/pkg/stanza/docs/types/parsers.md#parsers">explicitly map fields</a> from your log schema to the OTel log schema.</p>
<p>From there, the OTel Collector batches and ships the formatted log lines via OTLP to Elastic.</p>
<h3 id="architecture-1">Architecture</h3>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta88cd13cbc10df21/6a85c72e93ffb921bbb9134b/elastic-blog-model-2-architecture.png" alt="model 2 architecture" /></p>
<h3 id="pros-1">Pros</h3>
<ul>
<li>Easy to debug (you can manually read the intermediate log file)</li>
<li>Inherent file-based FIFO buffer</li>
<li>Less susceptible to “glare” conditions when service prematurely exits</li>
<li>Conversion to ECS happens within Elastic keeping log data vendor-agnostic until ingestion</li>
<li>Common wireline protocol (OTLP) across tracing, metrics, and logs</li>
</ul>
<h3 id="cons-1">Cons</h3>
<ul>
<li>All the headaches of file-based logging (rotation, disk overflow)</li>
<li>Beta quality and not yet proven in the field</li>
<li>No support for slf4j key-value pairs</li>
</ul>
<h3 id="demo-1">Demo</h3>
<p>MODE=filelogreceiver docker-compose up</p>
<h2 id="model3loggingviaelasticagentorfilebeat">Model 3: Logging via Elastic Agent (or Filebeat)</h2>
<p>Although the second model described affords some resilience as a function of the backing file, the OTel Collector filelogreceiver module is still decidedly <a href="https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/receiver/filelogreceiver">“beta”</a> in quality. Because of the importance of logs as a debugging tool, today I generally recommend that customers continue to import logs into Elastic using the field-proven <a href="https://www.elastic.co/elastic-agent">Elastic Agent</a> or <a href="https://www.elastic.co/beats/filebeat">Filebeat</a> scrappers. Elastic Agent and Filebeat have many years of field maturity under their collective belt. Further, it is often advantageous to deploy Elastic Agent anyway to capture the multitude of signals outside the purview of OpenTelemetry (e.g., deep Kubernetes and host metrics, security, etc.).</p>
<p>In this model, your service generates log statements as it always has, using popular logging libraries (e.g., <a href="https://logback.qos.ch">Logback</a> for Java). As with model 2, we employ OTel’s <a href="https://github.com/open-telemetry/opentelemetry-java-instrumentation/tree/main/instrumentation/logback/logback-mdc-1.0/library">Logback MDC</a> to add SpanID, TraceID, and Baggage to the <a href="https://logback.qos.ch/manual/mdc.html">Logback MDC context</a>.</p>
<p>From there, we employ the <a href="https://www.elastic.co/guide/en/ecs-logging/java/current/setup.html">Elastic ECS Encoder</a> to encode log statements compliant to the Elastic Common Schema. The Elastic ECS Encoder will read the OTel SpanID, TraceID, and Baggage off the MDC context and encode it into the JSON structure. Similar to model 2, the Elastic ECS Encoder doesn’t support sl4f key-vair arguments. Curiously, the Elastic ECS encoder also doesn’t appear to support Logback structured arguments. Thus, within the Polyfill Appender, I add slf4j key-value pairs as MDC context. This is less than ideal, however, since MDC forces all values to be strings.</p>
<p>From there, we write the log lines to a log file. If you are using Kubernetes or other container orchestration in your environment, you would more typically write to stdout (console) and let the orchestration log driver write to and manage log files.We then configure Elastic Agent or Filebeat to scrape the log file. Notably, the Elastic ECS Encoder does not currently translate incoming OTel SpanID and TraceID variables on the MDC. Thus, we need to perform manual translation of these variables in the <a href="https://github.com/ty-elastic/otel-logging/blob/main/filebeat.yml">Filebeat (or Elastic Agent) configuration</a> to map them to their ECS equivalent.</p>
<h2 id="architecture-2">Architecture</h2>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5aef2e002117c3bc/6a85c7318c29442066b88f70/elastic-blog-model-3-architecture.png" alt="model 3 architecture" /></p>
<h3 id="pros-2">Pros</h3>
<ul>
<li>Robust and field-proven</li>
<li>Easy to debug (you can manually read the intermediate log file)</li>
<li>Inherent file-based FIFO buffer</li>
<li>Less susceptible to “glare” conditions when service prematurely exits</li>
<li>Native ECS format for easy manipulation in Elastic</li>
<li>Fleet-managed via Elastic Agent</li>
</ul>
<h3 id="cons-2">Cons</h3>
<ul>
<li>All the headaches of file-based logging (rotation, disk overflow)</li>
<li>No support for slf4j key-value pairs or Logback structured arguments</li>
<li>Requires translation of OTel SpanID and TraceID in Filebeat config</li>
<li>Disparate data paths for logs versus tracing and metrics</li>
<li>Vendor-specific logging format</li>
</ul>
<h3 id="demo-2">Demo</h3>
<p>MODE=filebeat docker-compose up</p>
<h2 id="recommendations">Recommendations</h2>
<p>For most customers, I currently recommend Model 3 — namely, write to logs in ECS format (with OTel SpanID, TraceID, and Baggage metadata) and collect them with an Elastic Agent installed on the node hosting the application or service. Elastic Agent (or Filebeat) today provides the most field-proven and robust means of capturing log files from applications and services with OpenTelemetry context.</p>
<p>Further, you can leverage this same Elastic Agent instance (ideally running in your <a href="https://www.elastic.co/guide/en/fleet/current/running-on-kubernetes-managed-by-fleet.html">Kubernetes daemonset</a>) to collect rich and robust metrics and logs from <a href="https://docs.elastic.co/en/integrations/kubernetes">Kubernetes</a> and many other supported services via <a href="https://www.elastic.co/integrations/data-integrations">Elastic Integrations</a>. Finally, Elastic Agent facilitates remote management via <a href="https://www.elastic.co/guide/en/fleet/current/fleet-overview.html">Fleet</a>, avoiding bespoke configuration files.</p>
<p>Alternatively, for customers who either wish to keep their nodes vendor-neutral or use a consolidated signal routing system, I recommend Model 2, wherein an OpenTelemetry collector is used to scrape service log files. While workable and practiced by some early adopters in the field today, this model inherently carries some risk given the current beta nature of the OpenTelemetry filelogreceiver.</p>
<p>I generally do not recommend Model 1 given its limited language support, experimental/alpha status (the API could change), and current potential for data loss. That said, in time, with more language support and more thought to resilient designs, it has clear advantages both with regard to simplicity and richness of metadata.</p>
<h2 id="extractingmorevaluefromyourlogs">Extracting more value from your logs</h2>
<p>In contrast to tracing and metrics, most organizations have nearly 100% log coverage over their applications and services. This is an ideal beachhead upon which to build an application observability system. On the other hand, logs are notoriously noisy and unstructured; this is only amplified with the scale enabled by the hyperscalers and Kubernetes. Collecting log lines reliably is the easy part; making them useful at today’s scale is hard.</p>
<p>Given that logs are arguably the most challenging observability signal from which to extract value at scale, one should ideally give thoughtful consideration to a vendor’s support for logging in the context of other observability signals. Can they handle surges in log rates because of unexpected scale or an error or test scenario? Do they have the machine learning tool set to automatically recognize patterns in log lines, sort them into categories, and identify true anomalies? Can they provide cost-effective online searchability of logs over months or years without manual rehydration? Do they provide the tools to extract and analyze business KPIs buried in logs?</p>
<p>As an ardent and early supporter of OpenTelemetry, Elastic, of course, <a href="https://www.elastic.co/guide/en/apm/guide/current/open-telemetry.html">natively ingests OTel traces, metrics, and logs</a>. And just like all logs coming into our system, logs coming from OTel-equipped sources avail themselves of our <a href="https://www.elastic.co/observability/log-monitoring">mature tooling and next-gen AI Ops technologies</a> to enable you to extract their full value.Interested? <a href="https://www.elastic.co/contact?storm=global-header-en">Reach out to our pre-sales team</a> to get started building with Elastic!</p>
<p><em>The release and timing of any features or functionality described in this post remain at Elastic's sole discretion. Any features or functionality not currently available may not be delivered on time or at all.</em></p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/3-models-logging-opentelemetry</link>
    <guid isPermaLink="false">3-models-logging-opentelemetry</guid>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[Logs Analytics]]></category>
    <dc:creator><![CDATA[Ty Bekiares]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt186a218e99f220b3/6a85c73443c0b7d79f2f0550/log_infrastructure_apm_synthetics-monitoring.jpeg" length="0" type="image/jpeg"/>
    <pubDate>Tue, 27 Jun 2023 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Pruning incoming log volumes with Elastic]]></title>
    <description><![CDATA[To drop or not to drop (events) is the question, not only in deciding what events and fields to remove from your logs but also in the various tools used. Learn about using Beats, Logstash, Elastic Agent, Ingest Pipelines, and OTel Collectors.]]></description>
    <content:encoded><![CDATA[<pre><code>filebeat.inputs:
  - type: filestream
    id: my-logging-app
    paths:
      - /var/log/*.log
</code></pre>
<pre><code>filebeat.inputs:
  - type: filestream
    id: my-logging-app
    paths:
      - /var/tmp/other.log
      - /var/log/*.log
processors:
  - drop_event:
      when:
        and:
          - equals:
            url.scheme: http
          - equals:
            url.path: /profile
</code></pre>
<pre><code>filebeat.inputs:
  - type: filestream
    id: my-logging-app
    paths:
      - /var/tmp/other.log
      - /var/log/*.log
processors:
  - drop_fields:
      when:
        and:
          - equals:
            url.scheme: http
          - equals:
            http.response.status_code: 200
        fields: ["event.message"]
        ignore_missing: false
</code></pre>
<pre><code>input {
  file {
    id =&gt; "my-logging-app"
    path =&gt; [ "/var/tmp/other.log", "/var/log/*.log" ]
  }
}
filter {
  if [url.scheme] == "http" &amp;&amp; [url.path] == "/profile" {
    drop {
      percentage =&gt; 80
    }
  }
}
output {
  elasticsearch {
        hosts =&gt; "https://my-elasticsearch:9200"
        data_stream =&gt; "true"
    }
}
</code></pre>
<pre><code># Input configuration omitted
filter {
  if [url.scheme] == "http" &amp;&amp; [http.response.status_code] == 200 {
    drop {
      percentage =&gt; 80
    }
    mutate {
      remove_field: [ "event.message" ]
    }
  }
}
# Output configuration omitted
</code></pre>
<pre><code>PUT _ingest/pipeline/my-logging-app-pipeline
{
  "description": "Event and field dropping for my-logging-app",
  "processors": [
    {
      "drop": {
        "description" : "Drop event",
        "if": "ctx?.url?.scheme == 'http' &amp;&amp; ctx?.url?.path == '/profile'",
        "ignore_failure": true
      }
    },
    {
      "remove": {
        "description" : "Drop field",
        "field" : "event.message",
        "if": "ctx?.url?.scheme == 'http' &amp;&amp; ctx?.http?.response?.status_code == 200",
        "ignore_failure": false
      }
    }
  ]
}
</code></pre>
<pre><code>PUT _ingest/pipeline/my-logging-app-pipeline
{
  "description": "Event and field dropping for my-logging-app with failures",
  "processors": [
    {
      "drop": {
        "description" : "Drop event",
        "if": "ctx?.url?.scheme == 'http' &amp;&amp; ctx?.url?.path == '/profile'",
        "ignore_failure": true
      }
    },
    {
      "remove": {
        "description" : "Drop field",
        "field" : "event.message",
        "if": "ctx?.url?.scheme == 'http' &amp;&amp; ctx?.http?.response?.status_code == 200",
        "ignore_failure": false
      }
    }
  ],
  "on_failure": [
    {
      "set": {
        "description": "Set 'ingest.failure.message'",
        "field": "ingest.failure.message",
        "value": "Ingestion issue"
        }
      }
  ]
}
</code></pre>
<pre><code>receivers:
  filelog:
    include: [/var/tmp/other.log, /var/log/*.log]
processors:
  filter/denylist:
    error_mode: ignore
    logs:
      log_record:
        - 'url.scheme == "info"'
        - 'url.path == "/profile"'
        - "http.response.status_code == 200"
  attributes/errors:
    actions:
      - key: error.message
        action: delete
  memory_limiter:
    check_interval: 1s
    limit_mib: 2000
  batch:
exporters:
  # Exporters configuration omitted
service:
  pipelines:
    # Pipelines configuration omitted
</code></pre>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/pruning-incoming-log-volumes</link>
    <guid isPermaLink="false">pruning-incoming-log-volumes</guid>
    <category><![CDATA[Logs Analytics]]></category>
    <dc:creator><![CDATA[Carly Richmond]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb151937a8dace3fa/6a85cdfbbc5bb37ae5f81b4d/blog-thumb-elastic-on-elastic.png" length="0" type="image/png"/>
    <pubDate>Fri, 23 Jun 2023 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[How to remove PII from your Elastic data in 3 easy steps]]></title>
    <description><![CDATA[Personally Identifiable Information compliance is an ever increasing challenge for any organization. With Elastic's intuitive ML interface and parsing capabilities, sensitive data may be easily redacted from unstructured data with ease.]]></description>
    <content:encoded><![CDATA[<p>Personally identifiable information (PII) compliance is an ever-increasing challenge for any organization. Whether you’re in ecommerce, banking, healthcare, or other fields where data is sensitive, PII may inadvertently be captured and stored. Having structured logs enables quick identification, removal, and protection of sensitive data fields easily; but what about unstructured messages? Or perhaps call center transcriptions?</p>
<p>Elasticsearch, with its long experience in <a href="https://www.elastic.co/what-is/elasticsearch-machine-learning">machine learning</a>, provides various options to bring in custom models, such as large language models (LLMs), and provides its own models. These models will help implement PII redaction.</p>
<p>If you would like to learn more about natural language processing, machine learning, and Elastic, please be sure to check out these related articles:</p>
<ul>
<li><a href="https://www.elastic.co/blog/introduction-to-nlp-with-pytorch-models">Introduction to modern natural language processing with PyTorch in Elasticsearch</a></li>
<li><a href="https://www.elastic.co/blog/how-to-deploy-natural-language-processing-nlp-getting-started">How to deploy natural language processing (NLP): Getting started</a></li>
<li><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/redact-processor.html">Elastic Redact Processor Documentation</a></li>
<li><a href="https://www.elastic.co/blog/may-2023-launch-sparse-encoder-ai-model">Introducing Elastic Learned Sparse Encoder: Elastic’s AI model for semantic search</a></li>
<li><a href="https://www.elastic.co/blog/may-2023-launch-machine-learning-models">Accessing machine learning models in Elastic</a></li>
</ul>
<p>In this blog, we will show you how to set up PII redaction through the use of Elasticsearch’s ability to load a trained model within machine learning and the flexibility of Elastic’s ingest pipelines.</p>
<p>Specifically, we’ll walk through setting up a <a href="https://www.elastic.co/blog/how-to-deploy-nlp-named-entity-recognition-ner-example">named entity recognition (NER)</a> model for person and location identification, as well as deploying the redact processor for custom data identification and removal. All of this will then be combined with an ingest pipeline where we can use Elastic machine learning and data transformations capabilities to remove sensitive information from your data.</p>
<h2 id="loadingthetrainedmodel">Loading the trained model</h2>
<p>Before we begin, we must load our NER model into our Elasticsearch cluster. This may be easily accomplished with Docker and the Elastic Eland client. From a command line, let’s install the Eland client via git:</p>
<pre><code>git clone https://github.com/elastic/eland.git
</code></pre>
<p>Navigate into the recently downloaded client:</p>
<pre><code>cd eland/
</code></pre>
<p>Now let’s build the client:</p>
<pre><code>docker build -t elastic/eland .
</code></pre>
<p>From here, you’re ready to deploy the trained model to an Elastic machine learning node! Be sure to replace your username, password, es-cluster-hostname, and esport.</p>
<p>If you’re using the Elastic Cloud or have signed certificates, simply run this command:</p>
<pre><code>docker run -it --rm --network host elastic/eland eland_import_hub_model --url https://&lt;username&gt;:&lt;password&gt;@&lt;es-cluster-hostname&gt;:&lt;esport&gt;/ --hub-model-id dslim/bert-base-NER --task-type ner --start
</code></pre>
<p>If you’re using self-signed certificates, run this command:</p>
<pre><code>docker run -it --rm --network host elastic/eland eland_import_hub_model --url https://&lt;username&gt;:&lt;password&gt;@&lt;es-cluster-hostname&gt;:&lt;esport&gt;/ --insecure --hub-model-id dslim/bert-base-NER --task-type ner --start
</code></pre>
<p>From here you’ll witness the Eland client in action downloading the trained model from <a href="https://huggingface.co/dslim/bert-base-NER">HuggingFace</a> and automatically deploying it into your cluster!</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc3119d4a20e0f93b/6a85cbd42d64d567c3081d56/blog-elastic-huggingface.png" alt="huggingface code" /></p>
<p>Synchronize your newly loaded trained model by clicking on the blue hyperlink via your Machine Learning Overview UI “Synchronize your jobs and trained models.”</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt256411b1a03cb158/6a85cbd79bf99435330a058b/blog-elastic-Machine-Learning-Overview-UI.png" alt="Machine Learning Overview UI" /></p>
<p>Now click the Synchronize button.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt64d1464700830d19/6a85cbdaf5f1a0f0572ec913/blog-elastic-Synchronize-button.png" alt="Synchronize button" /></p>
<p>That’s it! Congratulations, you just loaded your first trained model into Elastic!</p>
<h2 id="createtheredactprocessorandingestpipeline">Create the redact processor and ingest pipeline</h2>
<p>From DevTools, let’s configure the redact processor along with our inference processor to take advantage of Elastic’s trained model we just loaded. This will create an ingest pipeline named “redact” that we can then use to remove sensitive data from any field we wish. In this example, I’ll be focusing on the “message” field. Note: at the time of this writing, the redact processor is experimental and must be created via DevTools.</p>
<pre><code>PUT _ingest/pipeline/redact
{
  "processors": [
    {
      "set": {
        "field": "redacted",
        "value": "{{{message}}}"
      }
    },
    {
      "inference": {
        "model_id": "dslim__bert-base-ner",
        "field_map": {
          "message": "text_field"
        }
      }
    },
    {
      "script": {
        "lang": "painless",
        "source": "String msg = ctx['message'];\r\n                for (item in ctx['ml']['inference']['entities']) {\r\n                msg = msg.replace(item['entity'], '&lt;' + item['class_name'] + '&gt;')\r\n                }\r\n                ctx['redacted']=msg"
      }
    },
    {
      "redact": {
        "field": "redacted",
        "patterns": [
          "%{EMAILADDRESS:EMAIL}",
          "%{IP:IP_ADDRESS}",
          "%{CREDIT_CARD:CREDIT_CARD}",
          "%{SSN:SSN}",
          "%{PHONE:PHONE}"
        ],
        "pattern_definitions": {
          "CREDIT_CARD": "\d{4}[ -]\d{4}[ -]\d{4}[ -]\d{4}",
          "SSN": "\d{3}-\d{2}-\d{4}",
          "PHONE": "\d{3}-\d{3}-\d{4}"
        }
      }
    },
    {
      "remove": {
        "field": [
          "ml"
        ],
        "ignore_missing": true,
        "ignore_failure": true
      }
    }
  ],
  "on_failure": [
    {
      "set": {
        "field": "failure",
        "value": "pii_script-redact"
      }
    }
  ]
}
</code></pre>
<p>OK, but what does each processor really do? Let’s walk through each processor in detail here:</p>
<ol>
<li><p>The SET processor creates the field “redacted,” which is copied over from the message field and used later on in the pipeline.</p></li>
<li><p>The INFERENCE processor calls the NER model we loaded to be used on the message field for identifying names, locations, and organizations.</p></li>
<li><p>The SCRIPT processor then replaced the detected entities within the redacted field from the message field.</p></li>
<li><p>Our REDACT processor uses Grok patterns to identify any custom set of data we wish to remove from the redacted field (which was copied over from the message field).</p></li>
<li><p>The REMOVE processor deletes the extraneous ml.* fields from being indexed; note we’ll add “message” to this processor once we validate data is being redacted properly.</p></li>
<li><p>The ON_FAILURE / SET processor captures any errors just in case we have them.</p></li>
</ol>
<h2 id="sliceyourpii">Slice your PII</h2>
<p>Now that your ingest pipeline with all the necessary steps has been configured, let’s start testing how well we can remove sensitive data from documents. Navigate over to Stack Management, select Ingest Pipelines and search for “redact”, and then click on the result.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6bd72fd29bb2e13f/6a85cbdd99083fc49740f9e9/blog-elastic-Ingest-Pipelines.png" alt="Ingest Pipelines" /></p>
<p>Click on the Manage button, and then click Edit.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd714b5ef2f5d9084/6a85cbe0d7b2e7851afe84f8/elastic-blog-Manage-button.png" alt="Manage button" /></p>
<p>Here we are going to test our pipeline by adding some documents. Below is a sample you can copy and paste to make sure everything is working correctly.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt69df37d4e0e10869/6a85cbe38c294460d1b89053/elastic-blog-test-pipeline.png" alt="test pipeline" /></p>
<pre><code>{
  "_source":
    {
      "message": "John Smith lives at 123 Main St. Highland Park, CO. His email address is jsmith123@email.com and his phone number is 412-189-9043.  I found his social security number, it is 942-00-1243. Oh btw, his credit card is 1324-8374-0978-2819 and his gateway IP is 192.168.1.2",
    },
}
</code></pre>
<p>Simply press the Run the pipeline button, and you will then see the following output:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7201f608fe3ec634/6a85cbe6e2447a1a268b1416/elastic-blog-pii-output-2.png" alt="pii output code" /></p>
<h2 id="whatsnext">What’s next?</h2>
<p>After you’ve added this ingest pipeline to a data set you’re indexing and validated that it is meeting expectations, you can add the message field to be removed so that no PII data is indexed. Simply update your REMOVE processor to include the message field and simulate again to only see the redacted field.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6d25ef9a7322cf6d/6a85cbe843c0b7af1c2f062c/elastic-blog-manage-processor.png" alt="" /></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt45b3b0c93aec9c67/6a85cbeb43c0b735952f0634/elastic-blog-pii-output.png" alt="pii output code 2" /></p>
<h2 id="conclusion">Conclusion</h2>
<p>With this step-by-step approach, you are now ready and able to detect and redact any sensitive data throughout your indices.</p>
<p>Here’s a quick recap of what we covered:</p>
<ul>
<li>Loading a pre-trained named entity recognition model into an Elastic cluster</li>
<li>Configuring the Redact processor, along with the inference processor, to use the trained model during data ingestion</li>
<li>Testing sample data and modifying the ingest pipeline to safely remove personally identifiable information</li>
</ul>
<p>Ready to get started? Sign up <a href="https://cloud.elastic.co/registration">for Elastic Cloud</a> and try out the features and capabilities I’ve outlined above to get the most value and visibility out of your OpenTelemetry data.</p>
<p><em>The release and timing of any features or functionality described in this post remain at Elastic's sole discretion. Any features or functionality not currently available may not be delivered on time or at all.</em></p>
<p><em>In this blog post, we may have used third party generative AI tools, which are owned and operated by their respective owners. Elastic does not have any control over the third party tools and we have no responsibility or liability for their content, operation or use, nor for any loss or damage that may arise from your use of such tools. Please exercise caution when using AI tools with personal, sensitive or confidential information. Any data you submit may be used for AI training or other purposes. There is no guarantee that information you provide will be kept secure or confidential. You should familiarize yourself with the privacy practices and terms of use of any generative AI tools prior to use.</em></p>
<p><em>Elastic, Elasticsearch and associated marks are trademarks, logos or registered trademarks of Elasticsearch N.V. in the United States and other countries. All other company and product names are trademarks, logos or registered trademarks of their respective owners.</em></p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/remove-pii-data</link>
    <guid isPermaLink="false">remove-pii-data</guid>
    <category><![CDATA[Logs Analytics]]></category>
    <category><![CDATA[Machine Learning]]></category>
    <dc:creator><![CDATA[Peter Titov]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt37a52bf6a3c1aab8/6a85cbee0782904fd0321786/blog-post4-ai-search-B.jpg" length="0" type="image/jpeg"/>
    <pubDate>Tue, 20 Jun 2023 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Simplifying log data management: Harness the power of flexible routing with Elastic]]></title>
    <description><![CDATA[The reroute processor, available as of Elasticsearch 8.8, allows customizable rules for routing documents, such as logs, into data streams for better control of processing, retention, and permissions with examples that you can try on your own.]]></description>
    <content:encoded><![CDATA[<p>In Elasticsearch 8.8, we’re introducing the <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/reroute-processor.html">reroute processor</a> in technical preview that makes it possible to send documents, such as logs, to different <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/data-streams.html">data streams</a>, according to flexible routing rules. When using Elastic Observability, this gives you more granular control over your data with regard to retention, permissions, and processing with all the potential benefits of the <a href="https://www.elastic.co/blog/an-introduction-to-the-elastic-data-stream-naming-scheme">data stream naming scheme</a>. While optimized for data streams, the reroute processor also works with classic indices. This blog post contains examples on how to use the reroute processor that you can try on your own by executing the snippets in the <a href="https://www.elastic.co/guide/en/kibana/current/console-kibana.html">Kibana dev tools</a>.</p>
<p>Elastic Observability offers a wide range of <a href="https://www.elastic.co/integrations/data-integrations?solution=observability">integrations</a> that help you to monitor your applications and infrastructure. These integrations are added as policies to <a href="https://www.elastic.co/guide/en/fleet/current/elastic-agent-installation.html">Elastic agents</a>, which help ingest telemetry into Elastic Observability. Several examples of these integrations include the ability to ingest logs from systems that send a stream of logs from different applications, such as <a href="https://www.elastic.co/guide/en/kinesis/current/aws-firehose-setup-guide.html">Amazon Kinesis Data Firehose</a>, <a href="https://docs.elastic.co/en/integrations/kubernetes">Kubernetes container logs</a>, and <a href="https://docs.elastic.co/integrations/tcp">syslog</a>. One challenge is that these multiplexed log streams are sending data to the same Elasticsearch data stream, such as logs-syslog-default. This makes it difficult to create parsing rules in ingest pipelines and dashboards for specific technologies, such as the ones from the <a href="https://docs.elastic.co/en/integrations/nginx">Nginx</a> and <a href="https://docs.elastic.co/en/integrations/apache">Apache</a> integrations. That’s because in Elasticsearch, in combination with the <a href="https://www.elastic.co/blog/an-introduction-to-the-elastic-data-stream-naming-scheme">data stream naming scheme</a>, the processing and the schema are both encapsulated in a data stream.</p>
<p>The reroute processor helps you tease apart data from a generic data stream and send it to a more specific one. You may use that mechanism to send logs to a data stream that is set up by the Nginx integration, for example, so that the logs are parsed with that integration and you can use the integration’s prebuilt dashboards or create custom ones with the fields, such as the url, the status code, and the response time that the Nginx pipeline has parsed out of the Nginx log message. You can also split out/separate regular Nginx logs and errors with the reroute processor, providing further separation ability and categorization of logs.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt512e74af45a03dab/6a85cdff99083f664140fa2d/blog-elastic-routing-pipeline.png" alt="routing pipeline" /></p>
<h2 id="exampleusecase">Example use case</h2>
<p>To use the reroute processor, first:</p>
<ol>
<li><p>Ensure you are on Elasticsearch 8.8</p></li>
<li><p>Ensure you have permissions to manage indices and data streams</p></li>
<li><p>If you don’t already have an account on <a href="https://cloud.elastic.co/registration?fromURI=/home">Elastic Cloud</a>, sign up for one</p></li>
</ol>
<p>Next, you’ll need to <a href="https://www.elastic.co/guide/en/elasticsearch/reference/master/set-up-a-data-stream.html">set up a data stream</a> and create a custom Elasticsearch <a href="https://www.elastic.co/guide/en/elasticsearch/reference/master/ingest.html">ingest pipeline</a> that is called as the <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/ingest.html#set-default-pipeline">default pipeline</a>. Below we go through this step by step for the “mydata” data set that we’ll simulate ingesting container logs into. We start with a basic example and extend it from there.</p>
<p>The following steps should be utilized in the Elastic console, which is found at <strong>Management -&gt; Dev tools -&gt; Console</strong>. First, we need an an ingest pipeline and a template for the data stream:</p>
<pre><code>PUT _ingest/pipeline/logs-mydata
{
  "description": "Routing for mydata",
  "processors": [
    {
      "reroute": {
      }
    }
  ]
}
</code></pre>
<p>This creates an ingest pipeline with an empty reroute processor. To make use of it, we need an index template:</p>
<pre><code>PUT _index_template/logs-mydata
{
  "index_patterns": [
    "logs-mydata-*"
  ],
  "data_stream": {},
  "priority": 200,
  "template": {
    "settings": {
      "index.default_pipeline": "logs-mydata"
    },
    "mappings": {
      "properties": {
        "container.name": {
          "type": "keyword"
        }
      }
    }
  }
}
</code></pre>
<p>The above template is applied to all data that is shipped to logs-mydata-*. We have mapped container.name as a keyword, as this is the field we will be using for routing later on. Now, we send a document to the data stream and it will be ingested into logs-mydata-default:</p>
<pre><code>POST logs-mydata-default/_doc
{
  "@timestamp": "2023-05-25T12:26:23+00:00",
  "container": {
    "name": "foo"
  }
}
</code></pre>
<p>We can check that it was ingested with the command below, which will show 1 result.</p>
<pre><code>GET logs-mydata-default/_search
</code></pre>
<p>Without modifying the routing processor, this already allows us to route documents. As soon as the reroute processor is specified, it will look for data_stream.dataset and data_stream.namespace fields by default and will send documents to the corresponding data stream, according to the <a href="https://www.elastic.co/blog/an-introduction-to-the-elastic-data-stream-naming-scheme">data stream naming scheme</a> logs-\&lt;dataset&gt;-\&lt;namespace&gt;. Let’s try this out:</p>
<pre><code>POST logs-mydata-default/_doc
{
  "@timestamp": "2023-03-30T12:27:23+00:00",
  "container": {
"name": "foo"
  },
  "data_stream": {
    "dataset": "myotherdata"
  }
}
</code></pre>
<p>As can be seen with the GET logs-mydata-default/_search command, this document ended up in the logs-myotherdata-default data stream. But instead of using default rules, we want to create our own rules for the field container.name. If the field is container.name = foo, we want to send it to logs-foo-default. For this we modify our routing pipeline:</p>
<pre><code>PUT _ingest/pipeline/logs-mydata
{
  "description": "Routing for mydata",
  "processors": [
    {
      "reroute": {
        "tag": "foo",
        "if" : "ctx.container?.name == 'foo'",
        "dataset": "foo"
      }
    }
  ]
}
</code></pre>
<p>Let's test this with a document:</p>
<pre><code>POST logs-mydata-default/_doc
{
  "@timestamp": "2023-05-25T12:26:23+00:00",
  "container": {
    "name": "foo"
  }
}
</code></pre>
<p>While it would be possible to specify a routing rule for each container name, you can also route by the value of a field in the document:</p>
<pre><code>PUT _ingest/pipeline/logs-mydata
{
  "description": "Routing for mydata",
  "processors": [
    {
      "reroute": {
        "tag": "mydata",
        "dataset": [
          "{{container.name}}",
          "mydata"
        ]
      }
    }
  ]
}
</code></pre>
<p>In this example, we are using a field reference as a routing rule. If the container.name field exists in the document, it will be routed — otherwise it falls back to mydata. This can be tested with:</p>
<pre><code>POST logs-mydata-default/_doc
{
  "@timestamp": "2023-05-25T12:26:23+00:00",
  "container": {
    "name": "foo1"
  }
}

POST logs-mydata-default/_doc
{
  "@timestamp": "2023-05-25T12:26:23+00:00",
  "container": {
    "name": "foo2"
  }
}
</code></pre>
<p>This creates the data streams logs-foo1-default and logs-foo2-default.</p>
<p><em>NOTE: There is currently a limitation in the processor that requires the fields specified in a <code>{{field.reference}}</code> to be in a nested object notation. A dotted field name does not currently work. Also, you’ll get errors when the document contains dotted field names for any</em> <em>data_stream.*</em> <em>field. This limitation will be</em> <a href="https://github.com/elastic/elasticsearch/pull/96243"><em>fixed</em></a> <em>in 8.8.2 and 8.9.0.</em></p>
<h2 id="apikeys">API keys</h2>
<p>When using the reroute processor, it is important that the API keys specified have permissions for the source and target indices. For example, if a pattern is used for routing from logs-mydata-default, the API key must have write permissions for <code>logs-*-*</code> as data could end up in any of these indices (see example further down).</p>
<p>We’re currently <a href="https://github.com/elastic/integrations/issues/5989">working</a> <a href="https://github.com/elastic/integrations/issues/6255">on</a> extending the API key permissions for our <a href="https://www.elastic.co/integrations/data-integrations">integrations</a> so that they allow for routing by default if you’re running a Fleet-managed Elastic Agent.</p>
<p>If you’re using a standalone Elastic Agent, or any other shipper, you can use this as a template to create your API key:</p>
<pre><code>POST /_security/api_key
{
  "name": "ingest_logs",
  "role_descriptors": {
    "ingest_logs": {
      "cluster": [
        "monitor"
      ],
      "indices": [
        {
          "names": [
            "logs-*-*"
          ],
          "privileges": [
            "auto_configure",
            "create_doc"
          ]
        }
      ]
    }
  }
}
</code></pre>
<h2 id="futureplans">Future plans</h2>
<p>In Elasticsearch 8.8, the reroute processor was released in technical preview. The plan is to adopt this in our data sink integrations like syslog, k8s, and others. Elastic will provide default routing rules that just work out of the box, but it will also be possible for users to add their own rules. If you are using our integrations, follow <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/ingest.html#pipelines-for-fleet-elastic-agent">this guide</a> on how to add a custom ingest pipeline.</p>
<h2 id="tryitout">Try it out!</h2>
<p>This blog post has shown some sample use cases for document based routing. Try it out on your data by adjusting the commands for index templates and ingest pipelines to your own data, and get started with <a href="https://cloud.elastic.co/registration?fromURI=/home">Elastic Cloud</a> through a 7-day free trial. Let us know via <a href="https://ela.st/reroute-feedback">this feedback form</a> how you’re planning to use the <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/reroute-processor.html">reroute processor</a> and whether you have suggestions for improvement.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/simplifying-log-data-management-flexible-routing</link>
    <guid isPermaLink="false">simplifying-log-data-management-flexible-routing</guid>
    <category><![CDATA[Data Management]]></category>
    <category><![CDATA[Logs Analytics]]></category>
    <dc:creator><![CDATA[Felix Barnsteiner,Nicolas Ruflin]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc40c1bcfa3417a50/6a85ce0293ffb91c1ab91481/observability-digital-transformation-1.jpg" length="0" type="image/jpeg"/>
    <pubDate>Tue, 13 Jun 2023 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Gaining new perspectives beyond logging: An introduction to application performance monitoring]]></title>
    <description><![CDATA[Change is on the horizon for the world of logging. In this post, we’ll outline a recommended journey for moving from just logging to a fully integrated solution with logs, traces, and APM.]]></description>
    <content:encoded><![CDATA[<h2 id="prioritizecustomerexperiencewithapmandtracing">Prioritize customer experience with APM and tracing</h2>
<p>Enterprise software development and operations has become an interesting space. We have some incredibly powerful tools at our disposal, yet as an industry, we have failed to adopt many of these tools that can make our lives easier. One such tool that is currently underutilized is <a href="https://www.elastic.co/blog/adding-free-and-open-elastic-apm-as-part-of-your-elastic-observability-deployment">application performance monitoring</a> (APM) and tracing, despite the fact that OpenTelemetry has made it possible to adopt at low friction.</p>
<p>Logging, however, is ubiquitous. Every software application has logs of some kind, and the default workflow for troubleshooting (even today) is to go from exceptions experienced by customers and systems to the logs and start from there to find a solution.</p>
<p>There are various challenges with this, one of the main ones being that logs often do not give enough information to solve the problem. Many services today return ambiguous 500 errors with little or nothing to go on. What if there isn’t an error or log file at all or the problem is that the system is very slow? Logging alone cannot help solve these problems. This leaves users with half broken systems and poor user experiences. We’ve all been on the wrong side of this, and it can be incredibly frustrating.</p>
<p>The question I find myself asking is why does the customer experience often come second to errors? If the customer experience is a top priority, then a strategy should be in place to adopt tracing and APM and make this as important as logging. Users should stop going to logs by default and thinking primarily in logs, as many are doing today. This will also come with some required changes to mental models.</p>
<p>What’s the path to get there? That’s exactly what we will explore in this blog post. We will start by talking about supporting organizational changes, and then we’ll outline a recommended journey for moving from just logging to a fully integrated solution with logs, traces, and APM.</p>
<h2 id="cultivatinganewmonitoringmindsethowtodriveapmandtracingadoption">Cultivating a new monitoring mindset: How to drive APM and tracing adoption</h2>
<p>To get teams to shift their troubleshooting mindset, what organizational changes need to be made?</p>
<p>Initially, businesses should consider strategic priorities and goals that need to be shared broadly among the teams. One thing that can help drive this in a very large organization is to consider an entire product team devoted to Observability or a CoE (Center of Excellence) with its own roadmap and priorities.</p>
<p>This team (either virtual or permanent) should start with the customer in mind and work backward, starting with key questions like: What do I need to collect? What do I need to observe? How do I act? Once team members understand the answers to these questions, they can start to think about the technology decisions needed to drive those outcomes.</p>
<p>From a tracing and APM perspective, the areas of greatest concern are the customer experience, service level objectives, and service level outcomes. From here, organizations can start to implement programs of work to continuously improve and share knowledge across teams. This will help to align teams around a common framework with shared goals.</p>
<p>In the next few sections, we will go through a four step journey to help you maximize your success with APM and tracing. This journey will take you through the following key steps on your journey to successful APM adoption:</p>
<ol>
<li><strong>Ingest:</strong> What choices do you have to make to get tracing activated and start ingesting trace data into your observability tools?</li>
<li><strong>Integrate:</strong> How does tracing integrate with logs to enable full end-to-end observability, and what else beyond simple tracing can you utilize to get even better resolution on your data?</li>
<li><strong>Analytics and AIOPs:</strong> Improve the customer experience and reduce the noise through machine learning.</li>
<li><strong>Scale and total cost of ownership:</strong> Roll out enterprise-wide tracing and adopt strategies to deal with data volume.</li>
</ol>
<h2 id="1ingest">1. Ingest</h2>
<p>Ingesting data for APM purposes generally involves “instrumenting” the application. In this section, we will explore methods for instrumenting applications, talk a little bit about sampling, and finally wrap up with a note on using common schemas for data representation.</p>
<h3 id="gettingstartedwithinstrumentation">Getting started with instrumentation</h3>
<p>What options do we have for ingesting APM and trace data? There are many, many options we will discuss to help guide you, but first let's take a step back. APM has a deep history — in very first implementations of APM, people were concerned mainly with timing methods, like this below:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1b8ff9b268b8a134/6a85cc2799083f8a0c40f9f1/blog-elastic-timing-methods.png" alt="timing methods" /></p>
<p>Usually you had a configuration file to specify which methods you wanted to time, and the APM implementation would instrument the specified code with method timings.</p>
<p>From here things started to evolve, and one of the first additions to APM was to add in tracing.</p>
<p>For Java, it’s fairly trivial to implement a system to do this by using what's known as a Java agent. You just specify -javagent command line argument, and the agent code gets access to the dynamic compilation routines within Java so it can modify the code before it is compiled into machine code, allowing you to “wrap” specific methods with timing or tracing routines. So, auto instrumenting Java was one of the first things that the original APM vendors did.</p>
<p><a href="https://opentelemetry.io/docs/instrumentation/java/automatic/">OpenTelemetry has agents like this</a>, and most observability vendors that offer APM solutions have their own proprietary ways of doing this, often with more advanced and differing features from the open source tooling.</p>
<p>Things have moved on since then, and Node.JS and Python are now popular.</p>
<p>As a result, ways of auto instrumenting these language runtimes have appeared, which mostly work by injecting the libraries into the code before starting them up. OpenTelemetry has a way of doing this on Kubernetes with an Operator and sidecar <a href="https://github.com/open-telemetry/opentelemetry-operator/blob/main/README.md">here</a>, which supports Python, Node.JS, Java, and DotNet.</p>
<p>The other alternative is to start adding APM and tracing API calls into your own code, which is not dissimilar to adding logging functionality. You may even wish to create an abstraction in your code to deal with this cross-cutting concern, although this is less of a problem now that there are open standards with which you can implement this.</p>
<p>You can see an example of how to add OpenTelemetry spans and attributes to your code for manual instrumentation below and <a href="https://github.com/davidgeorgehope/ChatGPTMonitoringWithOtel/blob/main/monitor.py">here</a>.</p>
<pre><code>from flask import Flask
import monitor  # Import the module
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
import urllib
import os

from opentelemetry import trace
from opentelemetry.sdk.resources import SERVICE_NAME, Resource
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.instrumentation.requests import RequestsInstrumentor


# Service name is required for most backends
resource = Resource(attributes={
    SERVICE_NAME: "your-service-name"
})

provider = TracerProvider(resource=resource)
processor = BatchSpanProcessor(OTLPSpanExporter(endpoint=os.getenv('OTEL_EXPORTER_OTLP_ENDPOINT'),
        headers="Authorization=Bearer%20"+os.getenv('OTEL_EXPORTER_OTLP_AUTH_HEADER')))

provider.add_span_processor(processor)
trace.set_tracer_provider(provider)
tracer = trace.get_tracer(__name__)
RequestsInstrumentor().instrument()

# Initialize Flask app and instrument it
app = Flask(__name__)

@app.route("/completion")
@tracer.start_as_current_span("do_work")
def completion():
        span = trace.get_current_span()
        if span:
            span.set_attribute("completion_count",1)
</code></pre>
<p>By implementing APM in this way, you could even eliminate the need to do any logging by storing all your required logging information within span attributes, exceptions, and metrics. The downside is that you can only do this with code that you own, so you will not be able to remove all logs this way.</p>
<h3 id="sampling">Sampling</h3>
<p>Many people don’t realize that APM is an expensive process. It adds a lot of CPU cycles and memory to your applications, and although there is a lot of value to be had, there are certainly trade-offs to be made.</p>
<p>Should you sample everything 100% and eat the cost? Or should you think about an intelligent trade-off with fewer samples or even tail-based sampling, which many products commonly support? Here, we will talk about the two most common sampling techniques — head-based sampling and tail-based sampling — to help you decide.</p>
<p><strong>Head-based sampling</strong><br />
In this approach, sampling decisions are made at the beginning of a trace, typically at the entry point of a service or application. A fixed rate of traces is sampled, and this decision propagates through all the services involved in a distributed trace.</p>
<p>With head-based sampling, you can control the rate using a configuration, allowing you to control the percentage of requests that are sampled and reported to the APM server. For instance, a sampling rate of 0.5 means that only 50% of requests are sampled and sent to the server. This is useful for reducing the amount of collected data while still maintaining a representative sample of your application's performance.</p>
<p><strong>Tail-based sampling</strong><br />
Unlike head-based sampling, tail-based sampling makes sampling decisions after the entire trace has been completed. This allows for more intelligent sampling decisions based on the actual trace data, such as only reporting traces with errors or traces that exceed a certain latency threshold.</p>
<p>We recommend tail-based sampling because it has the highest likelihood of reducing the noise and helping you focus on the most important issues. It also helps keep costs down on the data store side. A downside of tail-based sampling, however, is that it results in more data being generated from APM agents. This could use more CPU and memory on your application.</p>
<h3 id="opentelemetrysemanticconventionsandelasticcommonschema">OpenTelemetry Semantic Conventions and Elastic Common Schema</h3>
<p>OpenTelemetry prescribes Semantic Conventions, or Semantic Attributes, to establish uniform names for various operations and data types. Adhering to these conventions fosters standardization across codebases, libraries, and platforms, ultimately streamlining the monitoring process.</p>
<p>Creating OpenTelemetry spans for tracing is flexible, allowing implementers to annotate them with operation-specific attributes. These spans represent particular operations within and between systems, often involving widely recognized protocols like HTTP or database calls. To effectively represent and analyze a span in monitoring systems, supplementary information is necessary, contingent upon the protocol and operation type.</p>
<p>Unifying attribution methods across different languages is essential for operators to easily correlate and cross-analyze telemetry from polyglot microservices without needing to grasp language-specific nuances.</p>
<p>Elastic's recent contribution of the Elastic Common Schema to OpenTelemetry enhances Semantic Conventions to encompass logs and security.</p>
<p>Abiding by a shared schema yields considerable benefits, enabling operators to rapidly identify intricate interactions and correlate logs, metrics, and traces, thereby expediting root cause analysis and reducing time spent searching for logs and pinpointing specific time frames.</p>
<p>We advocate for adhering to established schemas such as ECS when defining trace, metrics, and log data in your applications, particularly when developing new code. This practice will conserve time and effort when addressing issues.</p>
<h2 id="2integrate">2. Integrate</h2>
<p>Integrations are very important for APM. How well your solution can integrate with other tools and technologies such as cloud, as well as its ability to integrate logs and metrics into your tracing data, is critical to fully understand the customer experience. In addition, most APM vendors have adjacent solutions for <a href="https://www.elastic.co/observability/synthetic-monitoring">synthetic monitoring</a> and profiling to gain deeper perspectives to supercharge your APM. We will explore these topics in the following section.</p>
<h3 id="apmlogssuperpowers">APM + logs = superpowers!</h3>
<p>Because APM agents can instrument code, they can also instrument code that is being used for logging. This way, you can capture log lines directly within APM. <a href="https://www.elastic.co/guide/en/observability/master/logs-send-application.html">This is normally simple to enable</a>.</p>
<p>With this enabled, you will also get automated injection of useful fields like these:</p>
<ul>
<li>service.name, service.version, service.environment</li>
<li>trace.id, transaction.id, error.id</li>
</ul>
<p>This means log messages will be automatically correlated with transactions as shown below, making it far easier to reduce mean time to resolution (MTTR) and find the needle in the haystack:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltce6112922800419b/6a85cc2af61d6e9f579c2b43/blog-elastic-latency-distribution.png" alt="latency distribution" /></p>
<p>If this is available to you, we highly recommend turning it on.</p>
<h3 id="deployingapminsidekubernetes">Deploying APM inside Kubernetes</h3>
<p>It is common for people to want to deploy APM inside a Kubernetes environment, and tracing is critical for monitoring applications in cloud-native environments. There are three different ways you can tackle this.</p>
<p><strong>1. Auto instrumentation using sidecars</strong><br />
With Kubernetes, it is possible to use an init container and something that will modify Kubernetes manifests on the fly to auto instrument your applications.</p>
<p>The init container will be used simply to copy the required library or jar file into the container at startup that you need to the main Kubernetes pod. Then, you can use <a href="https://kustomize.io/">Kustomize</a> to add the required command line arguments to bootstrap your agents.</p>
<p>If you are not familiar with it, Kustomize adds, removes, or modifies Kubernetes manifests on the fly. It is even available as a flag to the Kubernetes CLI — simply execute kubectl -k.</p>
<p>OpenTelemetry has an <a href="https://github.com/open-telemetry/opentelemetry-operator/blob/main/README.md">operator</a> that does all this for you automatically (without the need for Kustomize) for Java, DotNet, Python, and Node.JS, and many vendors also have their own operator or <a href="https://www.elastic.co/guide/en/apm/attacher/current/apm-attacher.html">helm charts</a> that can achieve the same result.</p>
<p><strong>2. Baking APM into containers or code</strong><br />
A second option for deploying out APM in Kubernetes — and indeed any containerized environment — is using Docker to bake the APM agents and configuration into a dockerfile.</p>
<p>Have a look at an example here using the OpenTelemetry Java Agent:</p>
<pre><code># Use the official OpenJDK image as the base image
FROM openjdk:11-jre-slim

# Set up environment variables
ENV APP_HOME /app
ENV OTEL_VERSION 1.7.0-alpha
ENV OTEL_JAVAAGENT_URL https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/download/v${OTEL_VERSION}/opentelemetry-javaagent-${OTEL_VERSION}-all.jar

# Create the application directory
RUN mkdir $APP_HOME
WORKDIR $APP_HOME

# Download the OpenTelemetry Java agent
ADD ${OTEL_JAVAAGENT_URL} /otel-javaagent.jar

# Add your Java application JAR file
COPY your-java-app.jar $APP_HOME/your-java-app.jar

# Expose the application port (e.g. 8080)
EXPOSE 8080

# Configure the OpenTelemetry Java agent and run the application
CMD java -javaagent:/otel-javaagent.jar \
      -Dotel.resource.attributes=service.name=your-service-name \
      -Dotel.exporter.otlp.endpoint=your-otlp-endpoint:4317 \
      -Dotel.exporter.otlp.insecure=true \
      -jar your-java-app.jar
</code></pre>
<p><strong>3. Tracing using a service mesh (Envoy/Istio)</strong><br />
The final option you have here is if you are using a service mesh. A service mesh is a dedicated infrastructure layer for handling service-to-service communication in a microservices architecture. It provides a transparent, scalable, and efficient way to manage and control the communication between services, enabling developers to focus on building application features without worrying about inter-service communication complexities.</p>
<p>The great thing about this is that we can activate tracing within the proxy and therefore get visibility into requests between services. We don’t have to change any code or even run APM agents for this; we simply turn on the OpenTelemetry collector that exists within the proxy — therefore this is likely the lowest overhead solution. <a href="https://www.envoyproxy.io/docs/envoy/latest/start/sandboxes/opentelemetry">Learn more about this option</a>.</p>
<h3 id="syntheticsuniversalprofiling">Synthetics Universal Profiling</h3>
<p>Most APM vendors have add ons to the primary APM use cases. Typically we see synthetics and <a href="https://www.elastic.co/observability/universal-profiling">continuous profiling</a> being added to APM solutions. APM can integrate with both, and there is some good value in bringing these technologies together to give even more insights into issues.</p>
<p><strong>Synthetics</strong><br />
Synthetic monitoring is a method used to measure the performance, availability, and reliability of web applications, websites, and APIs by simulating user interactions and traffic. It involves creating scripts or automated tests that mimic real user behavior, such as navigating through pages, filling out forms, or clicking buttons, and then running these tests periodically from different locations and devices.</p>
<p>This gives Development and Operations teams the ability to spot problems far earlier than they might otherwise, catching issues before real users do in many cases.</p>
<p>Synthetics can be integrated with APM — inject an APM agent into the website when the script runs, so even if you didn’t put end user monitoring into your website initially, it can be injected at run time. This usually happens without any input from the user. From there, a tracing id for each request can be passed down through the various layers of the system, allowing teams to follow the request all the way from the synthetics script to the lowest levels of the application stack such as the database.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3da4b528c0a61b51/6a85cc2eba7acc5fe499216c/blog-elastic-rainbow-sandals.png" alt="observability rainbow sandals" /></p>
<p><strong>Universal profiling</strong><br />
“Profiling” is a dynamic method of analyzing the complexity of a program, such as CPU utilization or the frequency and duration of function calls. With profiling, you can locate exactly which parts of your application are consuming the most resources. <a href="https://www.elastic.co/observability/universal-profiling">“Continuous profiling”</a> is a more powerful version of profiling that adds the dimension of time. By understanding your system’s resources over time, you can then locate, debug, and fix issues related to performance.</p>
<p>Universal profiling is a further extension of this, which allows you to capture profile information about all of the code running in your system all the time. Using a technology like <a href="https://www.elastic.co/blog/ebpf-observability-security-workload-profiling">eBPF</a> can allow you to see <em>all</em> the function calls in your systems, including into things like the Kubernetes runtime. Doing this gives you the ability to finally see unknown unknowns — things you didn’t know were problems. This is very different from APM, which is really about tracking individual traces and requests and the overall customer experience. Universal profiling is about overcoming those issues you didn’t even know existed and even answering the question “What is my most expensive line of code?”</p>
<p>Universal profiling can be linked into APM, showing you profiles that occurred during a specific customer issue, for example, or by linking profiles directly to traces by looking at the global state that exists at the thread level. These technologies can work wonders when used together.</p>
<p>Typically, profiles are viewed as “flame graphs” shown below. The boxes represent the amount of “on-cpu” time spent executing a particular function.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltab04ef0b036f83a9/6a85cc31d7b2e71203fe8506/blog-elastic-universal-profiling.png" alt="observability universal profiling" /></p>
<h2 id="3analyticsandaiops">3. Analytics and AIOps</h2>
<p>The interesting thing about APM is it opens up a whole new world of analytics versus just logs. All of a sudden, you have access to the information flows from <em>inside</em> applications.</p>
<p>This allows you to easily capture things like the amount of money a specific customer is currently spending on your most critical ecommerce store, or look at failed trades in a brokerage app to see how much lost revenue those failures are impacting. You can even then apply machine learning algorithms to project future spend or look at anomalies occurring in this data, giving you a new window into how your business runs.</p>
<p>In this section, we will look at ways to do this and how to get the most out of this new world, as well as how to apply AIOps practices to this new data. We will also discuss getting SLIs and SLOs setup for APM data.</p>
<h3 id="gettingbusinessdataintoyourtraces">Getting business data into your traces</h3>
<p>There are generally two ways of getting business data into your traces. You can modify code and add in Span attributes, an example of which is available <a href="https://github.com/davidgeorgehope/ChatGPTMonitoringWithOtel/blob/main/monitor.py">here</a> and shown below. Or you can write an extension or a plugin, which has the benefit of avoiding code changes. OpenTelemetry supports <a href="https://opentelemetry.io/docs/instrumentation/java/extensions/">adding extensions in its auto-instrumentation agents</a>. Most other APM vendors usually have something similar.</p>
<pre><code>def count_completion_requests_and_tokens(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        counters['completion_count'] += 1
        response = func(*args, **kwargs)

        token_count = response.usage.total_tokens
        prompt_tokens = response.usage.prompt_tokens
        completion_tokens = response.usage.completion_tokens
        cost = calculate_cost(response)
        strResponse = json.dumps(response)

        # Set OpenTelemetry attributes
        span = trace.get_current_span()
        if span:
            span.set_attribute("completion_count", counters['completion_count'])
            span.set_attribute("token_count", token_count)
            span.set_attribute("prompt_tokens", prompt_tokens)
            span.set_attribute("completion_tokens", completion_tokens)
            span.set_attribute("model", response.model)
            span.set_attribute("cost", cost)
            span.set_attribute("response", strResponse)
        return response
    return wrapper
</code></pre>
<h3 id="usingbusinessdataforfunandprofit">Using business data for fun and profit</h3>
<p>Once you have the business data in your traces, you can start to have some fun with it. Take a look at the example below for a financial services fraud team. Here we are tracking transactions — average transaction value for our larger business customers. Crucially, we can see if there are any unusual transactions.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt968f966ddbe0150e/6a85cc332d64d515ef081d5c/blog-elastic-customer-count.png" alt="customer count" /></p>
<p>A lot of this is powered by machine learning, which can classify transactions or do <a href="https://www.elastic.co/blog/reduce-mttd-ml-machine-learning-observability">anomaly detection</a>. Once you start capturing the data, it is possible to do a lot of useful things like this, and with a flexible platform, integrating machine learning models into this process becomes a breeze.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc046307a571de036/6a85cc3733f244b66149f524/blog-elastic-fraud-12h.png" alt="fraud 12-h" /></p>
<h3 id="slisandslos">SLIs and SLOs</h3>
<p>Service level indicators (SLIs) and service level objectives (SLOs) serve as critical components for maintaining and enhancing application performance. SLIs, which represent key performance metrics such as latency, error rate, and throughput, help quantify an application's performance, while SLOs establish target performance levels to meet user expectations.</p>
<p>By selecting relevant SLIs and setting achievable SLOs, organizations can better monitor their application's performance using APM tools. Continually evaluating and adjusting SLIs and SLOs in response to changes in application requirements, user expectations, or the competitive landscape ensures that the application remains competitive and delivers an exceptional user experience.</p>
<p>In order to define and track SLIs and SLOs, APM becomes a critical perspective that is needed for understanding the user experience. Once APM is implemented, we recommend that organizations perform the following steps.</p>
<ul>
<li>Define SLOs and SLIs required to track them.</li>
<li>Define SLO budgets and how they are calculated. Reflect business’ perspective and set realistic targets.</li>
<li>Define SLIs to be measured from a user experience perspective.</li>
<li>Define different alerting and paging rules, page only on customer facing SLO degradations, record symptomatic alerts, notify on critical symptomatic alerts.</li>
</ul>
<p>Synthetic monitoring and end user monitoring (EUM) can also help with getting even more data required to understand latency, throughput, and error rate from the user’s perspective, where it is critical to get good business focused metrics and data from.</p>
<h2 id="4scaleandtotalcostofownership">4. Scale and total cost of ownership</h2>
<p>With increased perspectives, customers often run into scalability and total cost of ownership issues. All this new data can be overwhelming. Luckily there are various techniques you can use to deal with this. Tracing itself can actually help with volume challenges because you can decompose unstructured logs and combine them with traces, which leads to additional efficiency. You can also use different sampling methods to deal with scale challenges (i.e., both techniques we previously mentioned).</p>
<p>In addition to this, for large enterprise scale, we can use streaming pipelines like Kafka or Pulsar to manage the data volumes. This has an additional benefit that you get for free: if you take down the systems consuming the data or they face outages, it is less likely you will lose data.</p>
<p>With this configuration in place, your “Observability pipeline” architecture would look like this:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd4444d4d2f26f46f/6a85cc3a4710c62b50d3cb73/blog-elastic-opentelemetry-collector.png" alt="opentelemetry collector" /></p>
<p>This completely decouples your sources of data from your chosen observability solution, which will future proof your observability stack going forward, enable you to reach massive scale, and make you less reliant on specific vendor code for collection of data.</p>
<p>Another thing we recommend doing is being intelligent about instrumentation. This will serve two benefits: you will get some CPU cycles back in the instrumented application, and your backend data collection systems will have less data to process. If you know, for example, that you have no interest in tracking calls to a specific endpoint, you can exclude those classes and methods from instrumentation.</p>
<p>And finally, data tiering is a transformative approach for managing data storage that can significantly reduce the total cost of ownership (TCO) for businesses. Primarily, it allows organizations to store data across different types of storage mediums based on their accessibility needs and the value of the data. For instance, frequently accessed, high-value data can be stored in expensive, high-speed storage, while less frequently accessed, lower-value data can be stored in cheaper, slower storage.</p>
<p>This approach, often incorporated in cloud storage solutions, enables cost optimization by ensuring that businesses only pay for the storage they need at any given time. Furthermore, it provides the flexibility to scale up or down based on demand, eliminating the need for large capital expenditures on storage infrastructure. This scalability also reduces the need for costly over-provisioning to handle potential future demand.</p>
<h2 id="conclusion">Conclusion</h2>
<p>In today's highly competitive and fast-paced software development landscape, simply relying on logging is no longer sufficient to ensure top-notch customer experiences. By adopting APM and distributed tracing, organizations can gain deeper insights into their systems, proactively detect and resolve issues, and maintain a robust user experience.</p>
<p>In this blog, we have explored the journey of moving from a logging-only approach to a comprehensive observability strategy that integrates logs, traces, and APM. We discussed the importance of cultivating a new monitoring mindset that prioritizes customer experience, and the necessary organizational changes required to drive APM and tracing adoption. We also delved into the various stages of the journey, including data ingestion, integration, analytics, and scaling.</p>
<p>By understanding and implementing these concepts, organizations can optimize their monitoring efforts, reduce MTTR, and keep their customers satisfied. Ultimately, prioritizing customer experience through APM and tracing can lead to a more successful and resilient enterprise in today's challenging environment.</p>
<p><a href="https://www.elastic.co/observability/application-performance-monitoring">Learn more about APM at Elastic</a>.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/introduction-apm-tracing-logging</link>
    <guid isPermaLink="false">introduction-apm-tracing-logging</guid>
    <category><![CDATA[APM]]></category>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[Logs Analytics]]></category>
    <dc:creator><![CDATA[David Hope]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7bda210049148e3b/6a85cc3dd7b2e756d4fe850a/log-management-720x420_(2).jpeg" length="0" type="image/jpeg"/>
    <pubDate>Tue, 30 May 2023 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Unleash the power of Elastic and Amazon Kinesis Data Firehose to enhance observability and data analytics]]></title>
    <description><![CDATA[AWS users can now leverage the new Amazon Kinesis Firehose Delivery Stream to directly ingest logs into Elastic Cloud in real time for centralized alerting, troubleshooting, and analytics across your cloud and on-premises infrastructure.]]></description>
    <content:encoded><![CDATA[<p>As more organizations leverage the Amazon Web Services (AWS) cloud platform and services to drive operational efficiency and bring products to market, managing logs becomes a critical component of maintaining visibility and safeguarding multi-account AWS environments. Traditionally, logs are stored in Amazon Simple Storage Service (Amazon S3) and then shipped to an external monitoring and analysis solution for further processing.</p>
<p>To simplify this process and reduce management overhead, AWS users can now leverage the new Amazon Kinesis Firehose Delivery Stream to ingest logs into Elastic Cloud in AWS in real time and view them in the Elastic Stack alongside other logs for centralized analytics. This eliminates the necessity for time-consuming and expensive procedures such as VM provisioning or data shipper operations.</p>
<p>Elastic Observability unifies logs, metrics, and application performance monitoring (APM) traces for a full contextual view across your hybrid <a href="https://www.elastic.co/blog/aws-service-metrics-monitor-observability-easy">AWS environments alongside their on-premises data sets</a>. Elastic Observability enables you to track and monitor performance <a href="https://www.elastic.co/observability/aws-monitoring">across a broad range of AWS services</a>, including AWS Lambda, Amazon Elastic Compute Cloud (EC2), Amazon Elastic Container Service (ECS), Amazon Elastic Kubernetes Service (EKS), Amazon Simple Storage Service (S3), Amazon Cloudtrail, Amazon Network Firewall, and more.</p>
<p>In this blog, we will walk you through how to use the Amazon Kinesis Data Firehose integration — <a href="https://aws.amazon.com/blogs/big-data/accelerate-data-insights-with-elastic-and-amazon-kinesis-data-firehose/">Elastic is listed in the Amazon Kinesis Firehose</a> drop-down list — to simplify your architecture and send logs to Elastic, so you can monitor and safeguard your multi-account AWS environments.</p>
<h2 id="announcingthekinesisfirehosemethod">Announcing the Kinesis Firehose method</h2>
<p>Elastic currently provides both agent-based and serverless mechanisms, and we are pleased to announce the addition of the Kinesis Firehose method. This new method enables customers to directly ingest logs from AWS into Elastic, supplementing our existing options.</p>
<ul>
<li><a href="https://www.youtube.com/watch?v=pnGXjljuEnY"><strong>Elastic Agent</strong></a> pulls metrics and logs from CloudWatch and S3 where logs are generally pushed from a service (for example, EC2, ELB, WAF, Route53) and ingests them into Elastic Cloud.</li>
<li><a href="https://www.elastic.co/blog/elastic-and-aws-serverless-application-repository-speed-time-to-actionable-insights-with-frictionless-log-ingestion-from-amazon-s3"><strong>Elastic’s Serverless Forwarder</strong></a> (runs Lambda and available in AWS SAR) sends logs from Kinesis Data Stream, Amazon S3, and AWS Cloudwatch log groups into Elastic. To learn more about this topic, please see this <a href="https://www.elastic.co/blog/elastic-and-aws-serverless-application-repository-speed-time-to-actionable-insights-with-frictionless-log-ingestion-from-amazon-s3">blog post</a>.</li>
<li><a href="https://docs.aws.amazon.com/firehose/latest/dev/what-is-this-service.html"><strong>Amazon Kinesis Firehose</strong></a> directly ingests logs from AWS into Elastic (specifically, if you are running the Elastic Cloud on AWS).</li>
</ul>
<p>In this blog, we will cover the last option since we have recently released the Amazon Kinesis Data Firehose integration. Specifically, we'll review:</p>
<ul>
<li>A general overview of the Amazon Kinesis Data Firehose integration and how it works with AWS</li>
<li>Step-by-step instructions to set up the Amazon Kinesis Data Firehose integration on AWS and on <a href="http://cloud.elastic.co">Elastic Cloud</a></li>
</ul>
<p>By the end of this blog, you'll be equipped with the knowledge and tools to simplify your AWS log management with Elastic Observability and Amazon Kinesis Data Firehose.</p>
<h2 id="prerequisitesandconfigurations">Prerequisites and configurations</h2>
<p>If you intend to follow the steps outlined in this blog post, there are a few prerequisites and configurations that you should have in place beforehand.</p>
<ol>
<li>You will need an account on <a href="http://cloud.elastic.co">Elastic Cloud</a> and a deployed stack on AWS. Instructions for deploying a stack on AWS can be found <a href="https://www.elastic.co/guide/en/elastic-stack/current/installing-elastic-stack.html">here</a>. This is necessary for AWS Firehose Log ingestion.</li>
<li>You will also need an AWS account with the necessary permissions to pull data from AWS. Details on the required permissions can be found in our <a href="https://docs.elastic.co/en/integrations/aws#aws-permissions">documentation</a>.</li>
<li>Finally, be sure to turn on VPC Flow Logs for the VPC where your application is deployed and send them to AWS Firehose.</li>
</ol>
<h2 id="elasticsamazonkinesisdatafirehoseintegration">Elastic’s Amazon Kinesis Data Firehose integration</h2>
<p>Elastic has collaborated with AWS to offer a seamless integration of Amazon Kinesis Data Firehose with Elastic, enabling direct ingestion of data from Amazon Kinesis Data Firehose into Elastic without the need for Agents or Beats. All you need to do is configure the Amazon Kinesis Data Firehose delivery stream to send its data to Elastic's endpoint. In this configuration, we will demonstrate how to ingest VPC Flow logs and Firewall logs into Elastic. You can follow a similar process to ingest other logs from your AWS environment into Elastic.</p>
<p>There are three distinct configurations available for ingesting VPC Flow and Network firewall logs into Elastic. One configuration involves sending logs through CloudWatch, and another uses S3 and Kinesis Firehose; each has its own unique setup. With Cloudwatch and S3 you can store and forward but with Kinesis Firehose you will have to ingest immediately. However, in this blog post, we will focus on this new configuration that involves sending VPC Flow logs and Network Firewall logs directly to Elastic.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt49b9db35922e8fd7/6a85c8192d64d540e0081cd0/image2.png" alt="AWS elastic configuration" /></p>
<p>We will guide you through the configuration of the easiest setup, which involves directly sending VPC Flow logs and Firewalls logs to Amazon Kinesis Data Firehose and then into Elastic Cloud.</p>
<p><strong>Note:</strong> It's important to note that this setup is only compatible with Elastic Cloud on AWS and cannot be used with self-managed or on-premise or other cloud provider Elastic deployments.</p>
<h2 id="settingitallup">Setting it all up</h2>
<p>To begin setting up the integration between Amazon Kinesis Data Firehose and Elastic, let's go through the necessary steps.</p>
<h3 id="step0getanaccountonelasticcloud">Step 0: Get an account on Elastic Cloud</h3>
<p>Create an account on Elastic Cloud by following the instructions provided to <a href="https://cloud.elastic.co/registration?fromURI=/home">get started on Elastic Cloud</a>.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3844d213efe58534/6a85c81c4710c65156d3caed/Screenshot_2023-05-18_at_6.00.28_PM.png" alt="elastic free trial" /></p>
<h3 id="step1deployelasticonaws">Step 1: Deploy Elastic on AWS</h3>
<p>You can deploy Elastic on AWS via two different approaches: through the UI or through Terraform. We’ll start first with the UI option.</p>
<p>After logging into Elastic Cloud, create a deployment on Elastic. It's crucial to make sure that the deployment is on Elastic Cloud on AWS since the Amazon Kinesis Data Firehose connects to a specific endpoint that must be on AWS.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdf563b506826bc9f/6a85c81eabdc29061c122486/blog-elastic-create-a-deployment.png" alt="create a deployment" /></p>
<p>After your deployment is created, it's essential to copy the Elasticsearch endpoint to ensure a seamless configuration process.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2920135cb8ad6097/6a85c82118249c36fe18f735/blog-elastic-O11y-log.png" alt="O11y log" /></p>
<p>The Elasticsearch HTTP endpoint should be copied and used for Amazon Firehose destination configuration purposes, as it will be required. Here's an example of what the endpoint should look like:</p>
<pre><code>https://elastic-O11y-log.es.us-east-1.aws.found.io
</code></pre>
<h3 id="_alternativeapproachusingterraform_"><em>Alternative approach using Terraform</em></h3>
<p>An alternative approach to deploying Elastic Cloud on AWS is by using Terraform. It's also an effective way to automate and streamline the deployment process.</p>
<p>To begin, simply create a Terraform configuration file that outlines the necessary infrastructure. This file should include resources for your Elastic Cloud deployment and any required IAM roles and policies. By using this approach, you can simplify the deployment process and ensure consistency across environments.</p>
<p>One easy way to create your Elastic Cloud deployment with Terraform is to use this Github <a href="https://github.com/aws-ia/terraform-elastic-cloud">repo</a>. This resource lets you specify the region, version, and deployment template for your Elastic Cloud deployment, as well as any additional settings you require.</p>
<h3 id="step2toturnonelasticsawsintegrationsnavigatetotheelasticintegrationsectioninyourdeployment">Step 2: To turn on Elastic's AWS integrations, navigate to the Elastic Integration section in your deployment</h3>
<p>To install AWS assets in your deployment's Elastic Integration section, follow these steps:</p>
<ol>
<li>Log in to your Elastic Cloud deployment and open <strong>Kibana</strong>.</li>
<li>To get started, go to the <strong>management</strong> section of Kibana and click on " <strong>Integrations.</strong>"</li>
<li>Navigate to the <strong>AWS</strong> integration and click on the "Install AWS Assets" button in the <strong>settings</strong>.This step is important as it installs the necessary assets such as <strong>dashboards</strong> and <strong>ingest pipelines</strong> to enable data ingestion from AWS services into Elastic.</li>
</ol>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt67355ec9c39e6455/6a85c8249a32f14240a7df9a/blog-elastic-aws-settings.png" alt="aws settings" /></p>
<h3 id="step3setuptheamazonkinesisdatafirehosedeliverystreamontheawsconsole">Step 3: Set up the Amazon Kinesis Data Firehose delivery stream on the AWS Console</h3>
<p>You can set up the Kinesis Data Firehose delivery stream via two different approaches: through the AWS Management Console or through Terraform. We’ll start first with the console option.</p>
<p>To set up the Kinesis Data Firehose delivery stream on AWS, follow these <a href="https://docs.aws.amazon.com/firehose/latest/dev/create-destination.html#create-destination-elastic">steps</a>:</p>
<ol>
<li><p>Go to the AWS Management Console and select Amazon Kinesis Data Firehose.</p></li>
<li><p>Click on Create delivery stream.</p></li>
<li><p>Choose a delivery stream name and select Direct PUT or other sources as the source.</p></li>
</ol>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt47da0e781330cf8a/6a85c8272d64d5808b081cd4/blog-elastic-create-delivery-stream.png" alt="create delivery stream" /></p>
<ol>
<li><p>Choose Elastic as the destination.</p></li>
<li><p>In the Elastic destination section, enter the Elastic endpoint URL that you copied from your Elastic Cloud deployment.</p></li>
</ol>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2997b8a64bfe401d/6a85c82a342d692d7121b08b/blog-elastic-destination-settings.png" alt="destination settings" /></p>
<ol>
<li><p>Choose the content encoding and retry duration as shown above.</p></li>
<li><p>Enter the appropriate parameter values for your AWS log type. For example, for VPC Flow logs, you would need to specify the  <strong>es_datastream_name</strong>  and  <strong>logs-aws.vpc flow-default</strong> _.</p></li>
<li><p>Configure the Amazon S3 bucket as the source backup for the Amazon Kinesis Data Firehose delivery stream failed data or all data, and configure any required tags for the delivery stream.</p></li>
<li><p>Review the settings and click on Create delivery stream.</p></li>
</ol>
<p>In the example above, we are using the <strong>es_datastream_name</strong> parameter to pull in VPC Flow logs through the <strong>logs-aws.vpcflow-default</strong> datastream. Depending on your use case, this parameter can be configured with one of the following types of logs:</p>
<ul>
<li>logs-aws.cloudfront_logs-default (AWS CloudFront logs)</li>
<li>logs-aws.ec2_logs-default (EC2 logs in AWS CloudWatch)</li>
<li>logs-aws.elb_logs-default (Amazon Elastic Load Balancing logs)</li>
<li>logs-aws.firewall_logs-default (AWS Network Firewall logs)</li>
<li>logs-aws.route53_public_logs-default (Amazon Route 53 public DNS queries logs)</li>
<li>logs-aws.route53_resolver_logs-default (Amazon Route 53 DNS queries &amp; responses logs)</li>
<li>logs-aws.s3access-default (Amazon S3 server access log)</li>
<li>logs-aws.vpcflow-default (AWS VPC flow logs)</li>
<li>logs-aws.waf-default (AWS WAF Logs)</li>
</ul>
<h3 id="_alternativeapproachusingterraform_-1"><em>Alternative approach using Terraform</em></h3>
<p>Using the " <strong>aws_kinesis_firehose_delivery_stream</strong>" resource in <strong>Terraform</strong> is another way to create a Kinesis Firehose delivery stream, allowing you to specify the delivery stream name, data source, and destination - in this case, an Elasticsearch HTTP endpoint. To authenticate, you'll need to provide the endpoint URL and an API key. Leveraging this Terraform resource is a fantastic way to automate and streamline your deployment process, resulting in greater consistency and efficiency.</p>
<p>Here's an example code that shows you how to create a Kinesis Firehose delivery stream with Terraform that sends data to an Elasticsearch HTTP endpoint:</p>
<pre><code>resource "aws_kinesis_firehose_delivery_stream" “Elasticcloud_stream" {
  name        = "terraform-kinesis-firehose-ElasticCloud-stream"
  destination = "http_endpoint”
  s3_configuration {
    role_arn           = aws_iam_role.firehose.arn
    bucket_arn         = aws_s3_bucket.bucket.arn
    buffer_size        = 5
    buffer_interval    = 300
    compression_format = "GZIP"
  }
  http_endpoint_configuration {
    url        = "https://cloud.elastic.co/"
    name       = “ElasticCloudEndpoint"
    access_key = “ElasticApi-key"
    buffering_hints {
      size_in_mb = 5
      interval_in_seconds = 300
    }

   role_arn       = "arn:Elastic_role"
   s3_backup_mode = "FailedDataOnly"
  }
}
</code></pre>
<h3 id="step4configurevpcflowlogstosendtoamazonkinesisdatafirehose">Step 4: Configure VPC Flow Logs to send to Amazon Kinesis Data Firehose</h3>
<p>To complete the setup, you'll need to configure VPC Flow logs in the VPC where your application is deployed and send them to the Amazon Kinesis Data Firehose delivery stream you set up in Step 3.</p>
<p>Enabling VPC flow logs in AWS is a straightforward process that involves several steps. Here's a step-by-step details to enable VPC flow logs in your AWS account:</p>
<ol>
<li><p>Select the VPC for which you want to enable flow logs.</p></li>
<li><p>In the VPC dashboard, click on "Flow Logs" under the "Logs" section.</p></li>
<li><p>Click on the "Create Flow Log" button to create a new flow log.</p></li>
<li><p>In the "Create Flow Log" wizard, provide the following information:</p></li>
</ol>
<p>Choose the target for your flow logs: In this case, Amazon Kinesis Data Firehose in the same AWS account.</p>
<ul>
<li>Provide a name for your flow log.</li>
<li>Choose the VPC and the network interface(s) for which you want to enable flow logs.</li>
<li>Choose the flow log format: either AWS default or Custom format.</li>
</ul>
<ol>
<li><p>Configure the IAM role for the flow logs. If you have an existing IAM role, select it. Otherwise, create a new IAM role that grants the necessary permissions for the flow logs.</p></li>
<li><p>Review the flow log configuration and click "Create."</p></li>
</ol>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb6f869a25b23466b/6a85c82df9373d075096f518/blog-elastic-flow-log-settings.png" alt="flow log settings" /></p>
<p>Create the VPC Flow log.</p>
<h3 id="step5afterafewminutescheckifflowsarecomingintoelastic">Step 5: After a few minutes, check if flows are coming into Elastic</h3>
<p>To confirm that the VPC Flow logs are ingesting into Elastic, you can check the logs in Kibana. You can do this by searching for the index in the Kibana Discover tab and filtering the results by the appropriate index and time range. If VPC Flow logs are flowing in, you should see a list of documents representing the VPC Flow logs.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd978cd1933be3889/6a85c82f99083fa20340f96b/blog-elastic-expanded-document.png" alt="expanded document" /></p>
<h3 id="step6navigatetokibanatoseeyourlogsparsedandvisualizedinthelogsawsvpcflowlogoverviewdashboard">Step 6: Navigate to Kibana to see your logs parsed and visualized in the [Logs AWS] VPC Flow Log Overview dashboard</h3>
<p>Finally, there is an Elastic out-of-the-box (OOTB) VPC Flow logs dashboard that displays the top IP addresses that are hitting your VPC, their geographic location, time series of the flows, and a summary of VPC flow log rejects within the selected time frame. This dashboard can provide valuable insights into your network traffic and potential security threats.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc671e25ad44ecc87/6a85c833ba7acced7b9920ec/blog-elastic-VPC-flow-log-map.png" alt="vpc flow log map" /></p>
<p><em>Note: For additional VPC flow log analysis capabilities, please refer to</em> <a href="https://www.elastic.co/blog/vpc-flow-logs-monitoring-analytics-observability"><em>this blog</em></a><em>.</em></p>
<h3 id="step7configureawsnetworkfirewalllogstosendtokinesisfirehose">Step 7: Configure AWS Network Firewall Logs to send to Kinesis Firehose</h3>
<p>To create a Kinesis Data Firehose delivery stream for AWS Network firewall logs, first log in to the AWS Management Console, navigate to the Kinesis service, select "Data Firehose", and follow the step-by-step instructions as shown in Step 3. Specify the Elasticsearch endpoint, API key, add a parameter (_ <strong>es_datastream_name=logs-aws.firewall_logs-default</strong> _), and create the delivery stream.</p>
<p>Second, to set up a Network Firewall rule group to send logs to the Kinesis Firehose, go to the Network Firewall section of the console, create a rule group, add a rule to allow traffic to the Kinesis endpoint, and attach the rule group to your Network Firewall configuration. Finally, test the configuration by sending traffic through the Network Firewall to the Kinesis Firehose endpoint and verify that logs are being delivered to your S3 bucket.</p>
<p>Kindly follow the instructions below to set up a firewall rule and logging.</p>
<ol>
<li>Set up a Network Firewall rule group to send logs to Amazon Kinesis Data Firehose:</li>
</ol>
<ul>
<li>Go to the AWS Management Console and select Network Firewall.</li>
<li>Click on "Rule groups" in the left menu and then click "Create rule group."</li>
<li>Choose "Stateless" or "Stateful" depending on your requirements, and give your rule group a name. Click "Create rule group."</li>
<li>Add a rule to the rule group to allow traffic to the Kinesis Firehose endpoint. For example, if you are using the us-east-1 region, you would add a rule like this:json</li>
</ul>
<pre><code>{
  "RuleDefinition": {
    "Actions": [
      {
        "Type": "AWS::KinesisFirehose::DeliveryStream",
        "Options": {
          "DeliveryStreamArn": "arn:aws:firehose:us-east-1:12387389012:deliverystream/my-delivery-stream"
        }
      }
    ],
    "MatchAttributes": {
      "Destination": {
        "Addresses": ["api.firehose.us-east-1.amazonaws.com"]
      },
      "Protocol": {
        "Numeric": 6,
        "Type": "TCP"
      },
      "PortRanges": [
        {
          "From": 443,
          "To": 443
        }
      ]
    }
  },
  "RuleOptions": {
    "CustomTCPStarter": {
      "Enabled": true,
      "PortNumber": 443
    }
  }
}
</code></pre>
<ul>
<li>Save the rule group.</li>
</ul>
<ol>
<li>Attach the rule group to your Network Firewall configuration:</li>
</ol>
<ul>
<li>Go to the AWS Management Console and select Network Firewall.</li>
<li>Click on "Firewall configurations" in the left menu and select the configuration you want to attach the rule group to.</li>
<li>Scroll down to "Associations" and click "Edit."</li>
<li>Select the rule group you created in Step 2 and click "Save."</li>
</ul>
<ol>
<li>Test the configuration:</li>
</ol>
<ul>
<li>Send traffic through the Network Firewall to the Kinesis Firehose endpoint and verify that logs are being delivered to your S3 bucket.</li>
</ul>
<h3 id="step8navigatetokibanatoseeyourlogsparsedandvisualizedinthelogsawsfirewalllogdashboard">Step 8: Navigate to Kibana to see your logs parsed and visualized in the [Logs AWS] Firewall Log dashboard</h3>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1f31976122afa373/6a85c83680984c4281668f72/blog-elastic-firewall-log-dashboard.png" alt="firewall log dashboard" /></p>
<h2 id="wrappingup">Wrapping up</h2>
<p>We’re excited to bring you this latest integration for AWS Cloud and Kinesis Data Firehose into production. The ability to consolidate logs and metrics to gain visibility across your cloud and on-premises environment is crucial for today’s distributed environments and applications.</p>
<p>From EC2, Cloudwatch, Lambda, ECS and SAR, <a href="https://www.elastic.co/integrations/data-integrations?solution=all-solutions&amp;category=aws">Elastic Integrations</a> allow you to quickly and easily get started with ingesting your telemetry data for monitoring, analytics, and observability. Elastic is constantly delivering frictionless customer experiences, allowing anytime, anywhere access to all of your telemetry data — this streamlined, native integration with AWS is the latest example of our commitment.</p>
<h2 id="startafreetrialtoday">Start a free trial today</h2>
<p>You can begin with a <a href="https://aws.amazon.com/marketplace/pp/prodview-voru33wi6xs7k">7-day free trial</a> of Elastic Cloud within the AWS Marketplace to start monitoring and improving your users' experience today!</p>
<p><em>The release and timing of any features or functionality described in this post remain at Elastic's sole discretion. Any features or functionality not currently available may not be delivered on time or at all.</em></p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/aws-kinesis-data-firehose-observability-analytics</link>
    <guid isPermaLink="false">aws-kinesis-data-firehose-observability-analytics</guid>
    <category><![CDATA[Infrastructure Monitoring]]></category>
    <category><![CDATA[Logs Analytics]]></category>
    <dc:creator><![CDATA[Udayasimha Theepireddy,Bahubali Shetti]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt49b9db35922e8fd7/6a85c8192d64d540e0081cd0/image2.png" length="0" type="image/png"/>
    <pubDate>Thu, 18 May 2023 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Root cause analysis with logs: Elastic Observability's anomaly detection and log categorization]]></title>
    <description><![CDATA[Elastic Observability provides more than just log aggregation, metrics analysis, APM, and distributed tracing. Elastic’s machine learning capabilities help analyze the root cause of issues, allowing you to focus your time on the most important tasks.]]></description>
    <content:encoded><![CDATA[<p>With more and more applications moving to the cloud, an increasing amount of telemetry data (logs, metrics, traces) is being collected, which can help improve application performance, operational efficiencies, and business KPIs. However, analyzing this data is extremely tedious and time consuming given the tremendous amounts of data being generated. Traditional methods of alerting and simple pattern matching (visual or simple searching etc) are not sufficient for IT Operations teams and SREs. It’s like trying to find a needle in a haystack.</p>
<p>In this blog post, we’ll cover some of Elastic’s artificial intelligence for IT operations (AIOps) and machine learning (ML) capabilities for root cause analysis.</p>
<p>Elastic’s machine learning will help you investigate performance issues by providing anomaly detection and pinpointing potential root causes through time series analysis and log outlier detection. These capabilities will help you reduce time in finding that “needle” in the haystack.</p>
<p>Elastic’s platform enables you to get started on machine learning quickly. You don’t need to have a data science team or design a system architecture. Additionally, there’s no need to move data to a third-party framework for model training.</p>
<p>Preconfigured machine learning models for observability and security are available. If those don't work well enough on your data, in-tool wizards guide you through the few steps needed to configure custom anomaly detection and train your model with supervised learning. To help get you started, there are several key features built into Elastic Observability to aid in analysis, helping bypass the need to run specific ML models. These features help minimize the time and analysis for logs.</p>
<p>Let’s review some of these built-in ML features:</p>
<p><strong>Anomaly detection:</strong> Elastic Observability, when turned on (<a href="https://www.elastic.co/guide/en/kibana/current/xpack-ml-anomalies.html">see documentation</a>), automatically detects anomalies by continuously modeling the normal behavior of your time series data — learning trends, periodicity, and more — in real time to identify anomalies, streamline root cause analysis, and reduce false positives. Anomaly detection runs in and scales with Elasticsearch and includes an intuitive UI.</p>
<p><strong>Log categorization:</strong> Using anomaly detection, Elastic also identifies patterns in your log events quickly. Instead of manually identifying similar logs, the logs categorization view lists log events that have been grouped, based on their messages and formats, so that you can take action quicker.</p>
<p><strong>High-latency or erroneous transactions:</strong> Elastic Observability’s APM capability helps you discover which attributes are contributing to increased transaction latency and identifies which attributes are most influential in distinguishing between transaction failures and successes. An overview of this capability is published here: <a href="https://www.elastic.co/blog/apm-correlations-elastic-observability-root-cause-transactions">APM correlations in Elastic Observability: Automatically identifying probable causes of slow or failed transactions</a>.</p>
<p><strong>AIOps Labs:</strong> AIOps Labs provides two main capabilities using advanced statistical methods:</p>
<ul>
<li><strong>Log spike detector</strong> helps identify reasons for increases in log rates. It makes it easy to find and investigate causes of unusual spikes by using the analysis workflow view. Examine the histogram chart of the log rates for a given data view, and find the reason behind a particular change possibly in millions of log events across multiple fields and values.</li>
<li><strong>Log pattern analysis</strong> helps you find patterns in unstructured log messages and makes it easier to examine your data. It performs categorization analysis on a selected field of a data view, creates categories based on the data, and displays them together with a chart that shows the distribution of each category and an example document that matches the category.</li>
</ul>
<p> <strong>In this blog, we will cover anomaly detection and log categorization against the popular “Hipster Shop app” developed by Google, and modified recently by OpenTelemetry.</strong> </p>
<p>Overviews of high-latency capabilities can be found <a href="https://www.elastic.co/blog/apm-correlations-elastic-observability-root-cause-transactions">here</a>, and an overview of AIOps labs can be found <a href="https://www.youtube.com/watch?v=jgHxzUNzfhM&amp;list=PLhLSfisesZItlRZKgd-DtYukNfpThDAv_&amp;index=5">here</a>.</p>
<p>In this blog, we will examine a scenario where we use anomaly detection and log categorization to help identify a root cause of an issue in Hipster Shop.</p>
<h2 id="prerequisitesandconfig">Prerequisites and config</h2>
<p>If you plan on following this blog, here are some of the components and details we used to set up this demonstration:</p>
<ul>
<li>Ensure you have an account on <a href="http://cloud.elastic.co">Elastic Cloud</a> and a deployed stack (<a href="https://www.elastic.co/guide/en/elastic-stack/current/installing-elastic-stack.html">see instructions here</a>) on AWS. Deploying this on AWS is required for Elastic Serverless Forwarder.</li>
<li>Utilize a version of the ever so popular <a href="https://github.com/GoogleCloudPlatform/microservices-demo">Hipster Shop</a> demo application. It was originally written by Google to showcase Kubernetes across a multitude of variants available, such as the <a href="https://github.com/open-telemetry/opentelemetry-demo">OpenTelemetry Demo App</a>. The Elastic version is found <a href="https://github.com/elastic/opentelemetry-demo">here</a>.</li>
<li>Ensure you have configured the app for either Elastic APM agents or OpenTelemetry agents. For more details, please refer to these two blogs: <a href="https://www.elastic.co/blog/opentelemetry-observability">Independence with OTel in Elastic</a> and <a href="https://www.elastic.co/blog/implementing-kubernetes-observability-security-opentelemetry">Observability and security with OTel in Elastic</a>. Additionally, review the <a href="https://www.elastic.co/guide/en/apm/guide/current/open-telemetry.html">OTel documentation in Elastic</a>.</li>
<li>Look through an overview of <a href="https://www.elastic.co/guide/en/observability/current/apm.html">Elastic Observability APM capabilities</a>.</li>
<li>Look through our <a href="https://www.elastic.co/guide/en/observability/8.5/inspect-log-anomalies.html">Anomaly detection documentation</a> for logs and <a href="https://www.elastic.co/guide/en/observability/8.5/categorize-logs.html">log categorization documentation</a>.</li>
</ul>
<p>Once you’ve instrumented your application with APM (Elastic or OTel) agents and are ingesting metrics and logs into Elastic Observability, you should see a service map for the application as follows:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc051b75831308f32/6a7f1a3c77b03453e73ff8f9/blog-elastic-service-map.png" alt="" /></p>
<p>In our example, we’ve introduced issues to help walk you through the root cause analysis features: anomaly detection and log categorization. You might have a different set of anomalies and log categorization depending on how you load the application and/or introduce specific issues.</p>
<p>As part of the walk-through, we’ll assume we are a DevOps or SRE managing this application in production.</p>
<h2 id="rootcauseanalysis">Root cause analysis</h2>
<p>While the application has been running normally for some time, you get a notification that some of the services are unhealthy. This can occur from the notification setting you’ve set up in Elastic or other external notification platforms (including customer related issues). In this instance, we’re assuming that customer support has called in multiple customer complaints about the website.</p>
<p>How do you as a DevOps or SRE investigate this? We will walk through two avenues in Elastic to investigate the issue:</p>
<ul>
<li>Anomaly detection</li>
<li>Log categorization</li>
</ul>
<p>While we show these two paths separately, they can be used in conjunction and are complementary, as they are both tools Elastic Observability provides to help you troubleshoot and identify a root cause.</p>
<h3 id="machinelearningforanomalydetection">Machine learning for anomaly detection</h3>
<p>Elastic will detect anomalies based on historical patterns and identify a probability of these issues.</p>
<p>Starting with the service map, you can see anomalies identified with red circles and as we select them, Elastic will provide a score for the anomaly.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt38b12944fdbdd0e8/6a7f1a40e3a21944b499f8c2/blog-elastic-service-map-anomaly-detection.png" alt="" /></p>
<p>In this example, we can see that there is a score of 96 for a specific anomaly for the productCatalogService in the Hipster Shop application. An anomaly score indicates the significance of the anomaly compared to previously seen anomalies. More information on anomaly detection results can be found here. We can also dive deeper into the anomaly and analyze the details.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf7a1b60a1109fa57/6a7f1a426693f82b89664375/blog-elastic-single-metric-viewer.png" alt="" /></p>
<p>What you will see for the productCatalogService is that there is a severe spike in average transaction latency time, which is the anomaly that was detected in the service map. Elastic’s machine learning has identified a specific metric anomaly (shown in the single metric view). It’s likely that customers are potentially responding to the slowness of the site and that the company is losing potential transactions.</p>
<p>One step to take next is to review all the other potential anomalies that we saw in the service map in a larger picture. Use an anomaly explorer to view all the anomalies that have been identified.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5f8c2653add063e8/6a7f1a456c6eac6dcbf145a0/blog-elastic-anomaly-explorer.png" alt="" /></p>
<p>Elastic is identifying numerous services with anomalies. productCatalogService has the highest score and a good number or others: frontend, checkoutService, advertService, and others, also have high scores. However, this analysis is looking at just one metric.</p>
<p>Elastic can help detect anomalies across all types of data, such as kubernetes data, metrics, and traces. If we analyze across all these types (via individual jobs we’ve created in Elastic machine learning), we will see a more comprehensive view as to what is potentially causing this latency issue.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd958db4183c3832d/6a7f1a483ce8e28c58cf57a7/blog-elastic-anomaly-explorer-job-selection.png" alt="" /></p>
<p>Once all the potential jobs are selected and we’ve sorted by service.name, we can see that productCatalogService is still showing a high anomaly influencer score.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6756a3a0aadd2dbc/6a7f1a4b42a11732ea95c2fd/blog-elastic-anomaly-explorer-timeline.png" alt="" /></p>
<p>In addition to the chart giving us a visual of the anomalies, we can review all the potential anomalies. As you will notice, Elastic has also categorized these anomalies (see category examples column). As we scroll through the results, we notice a potential postgreSQL issue from the categorization, which also has a high 94 score. Machine learning has identified a “rare mlcategory,” meaning that it has rarely occurred, hence pointing to a potential cause of the issue customers are seeing.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt46c0097b09183fa8/6a7f1a4eeab5be957820aaf8/blog-elastic-machine-learning-service-name.png" alt="" /></p>
<p>We also notice that this issue is potentially caused by pgbench , a popular postgreSQL tool to help benchmark the database. pgbench runs the same sequence of SQL commands over and over, possibly in multiple, concurrent database sessions. While pgbench is definitely a useful tool, it should not be used in a production environment as it causes heavy load on the database host, likely causing the higher latency issues on the site.</p>
<p>While this may or may not be the ultimate root cause, we have rather quickly identified a potentially issue that has a high probability of being the root cause. An engineer likely intended to run pgbench against a staging database to evaluate its performance, and not the production environment.</p>
<h3 id="machinelearningforlogcategorization">Machine learning for log categorization</h3>
<p>Elastic Observability’s service map has detected an anomaly, and in this part of the walk-through, we take a different approach by investigating the service details from the service map versus initially exploring the anomaly. When we explore the service details for productCatalogService, we see the following:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6bcfa46b1ba24f70/6a7f1a523ce8e27f3acf57ab/blog-elastic-product-catalog-service.png" alt="" /></p>
<p>The service details are identifying several things:</p>
<ol>
<li>There is an abnormally high latency compared to expected bounds of the service. We see that recently there was a higher than normal (upward of 1s latency) compared to the average to 275ms on average.</li>
<li>There is also a high failure rate for the same time frame as the high latency (lower left chart “ <strong>Failed transaction rate</strong> ”).</li>
<li>Additionally, we can see the transactions and one in particular /ListProduct has an abnormally high latency, in addition to a high failure rate.</li>
<li>We see productCatalogService has a dependency on postgreSQL.</li>
<li>We also see errors all related to postgreSQL.</li>
</ol>
<p>We have an option to dig through the logs and analyze in Elastic or we can use a capability to identify the logs more easily.</p>
<p>If we go to Categories under Logs in Elastic Observability and search for postgresql.logto help identify postgresql logs that could be causing this error, we see that Elastic’s machine learning has automatically categorized the postgresql logs.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7ef998b88afc1e4d/6a7f1a55c2e9149015016fec/blog-elastic-categories.png" alt="" /></p>
<p>We notice two additional items:</p>
<ul>
<li>There is a high count category (message count of 23,797 with a high anomaly of 70) related to pgbench (which is odd to see in production). Hence we search further for all pgbench related logs in Categories .</li>
<li>We see an odd issue regarding terminating the connection (with a low count).</li>
</ul>
<p>While investigating the second error, which is severe, we can see logs from Categories before and after the error.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt208408824fb1cb51/6a7f1a5877b0346da53ff8ff/blog-elastic-timestamp.png" alt="" /></p>
<p>This troubleshooting shows postgreSQL having a FATAL error, the database shutting down prior to the error, and all connections terminating. Given the two immediate issues we identified, we have an idea that someone was running pgbench and this potentially overloaded the database, causing the latency issue that customers are seeing.</p>
<p>The next steps here could be to investigate anomaly detection and/or work with the developers to review the code and identify pgbench as part of the deployed configuration.</p>
<h2 id="conclusion">Conclusion</h2>
<p>I hope you’ve gotten an appreciation for how Elastic Observability can help you further identify and get closer to pinpointing root cause of issues without having to look for a “needle in a haystack.” Here’s a quick recap of lessons and what you learned:</p>
<ul>
<li>Elastic Observability has numerous capabilities to help you reduce your time to find root cause and improve your MTTR (even MTTD). In particular, we reviewed the following two main capabilities in this blog:</li>
</ul>
<ol>
<li><strong>Anomaly detection:</strong> Elastic Observability, when turned on (<a href="https://www.elastic.co/guide/en/kibana/current/xpack-ml-anomalies.html">see documentation</a>), automatically detects anomalies by continuously modeling the normal behavior of your time series data — learning trends, periodicity, and more — in real time to identify anomalies, streamline root cause analysis, and reduce false positives. Anomaly detection runs in and scales with Elasticsearch and includes an intuitive UI.</li>
<li><strong>Log categorization:</strong> Using anomaly detection, Elastic also identifies patterns in your log events quickly. Instead of manually identifying similar logs, the logs categorization view lists log events that have been grouped based on their messages and formats so that you can take action quicker.</li>
</ol>
<ul>
<li>You learned how easy and simple it is to use Elastic Observability’s log categorization and anomaly detection capabilities without having to understand machine learning (which help drive these features), nor having to do any lengthy setups.
Ready to get started? <a href="https://cloud.elastic.co/registration">Register for Elastic Cloud</a> and try out the features and capabilities I’ve outlined above.</li>
</ul>
<h3 id="additionalloggingresources">Additional logging resources:</h3>
<ul>
<li><a href="https://www.elastic.co/getting-started/observability/collect-and-analyze-logs">Getting started with logging on Elastic (quickstart)</a></li>
<li><a href="https://www.elastic.co/guide/en/observability/current/logs-metrics-get-started.html">Ingesting common known logs via integrations (compute node example)</a></li>
<li><a href="https://docs.elastic.co/integrations">List of integrations</a></li>
<li><a href="https://www.elastic.co/blog/log-monitoring-management-enterprise">Ingesting custom application logs into Elastic</a></li>
<li><a href="https://www.elastic.co/blog/observability-logs-parsing-schema-read-write">Enriching logs in Elastic</a></li>
<li>Analyzing Logs with <a href="https://www.elastic.co/blog/reduce-mttd-ml-machine-learning-observability">Anomaly Detection (ML)</a> and <a href="https://www.elastic.co/blog/observability-logs-machine-learning-aiops">AIOps</a></li>
</ul>
<h3 id="commonusecaseexampleswithlogs">Common use case examples with logs:</h3>
<ul>
<li><a href="https://youtu.be/ax04ZFWqVCg">Nginx log management</a></li>
<li><a href="https://www.elastic.co/blog/vpc-flow-logs-monitoring-analytics-observability">AWS VPC Flow log management</a></li>
<li><a href="https://www.elastic.co/blog/kubernetes-errors-elastic-observability-logs-openai">Using OpenAI to analyze Kubernetes errors</a></li>
<li><a href="https://youtu.be/Li5TJAWbz8Q">PostgreSQL issue analysis with AIOps</a></li>
</ul>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/reduce-mttd-ml-machine-learning-observability</link>
    <guid isPermaLink="false">reduce-mttd-ml-machine-learning-observability</guid>
    <category><![CDATA[Machine Learning]]></category>
    <category><![CDATA[Logs Analytics]]></category>
    <category><![CDATA[Agentic Observability]]></category>
    <dc:creator><![CDATA[Bahubali Shetti]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltcea834289bfca650/6a7f1a5bde23151d7efd809b/illustration-machine-learning-anomaly-1680x980.png" length="0" type="image/png"/>
    <pubDate>Tue, 07 Feb 2023 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Easily analyze AWS VPC Flow Logs with Elastic Observability]]></title>
    <description><![CDATA[Elastic Observability can ingest and help analyze AWS VPC Flow Logs from your application’s VPC. Learn how to ingest AWS VPC Flow Logs through a step-by-step method into Elastic, then analyze it and apply OOTB machine learning for insights.]]></description>
    <content:encoded><![CDATA[<p>Elastic Observability provides a full-stack observability solution, by supporting metrics, traces, and logs for applications and infrastructure. In <a href="https://www.elastic.co/blog/aws-service-metrics-monitor-observability-easy">a previous blog</a>, I showed you an <a href="https://www.elastic.co/observability/aws-monitoring">AWS monitoring</a> infrastructure running a three-tier application. Specifically we reviewed metrics ingest and analysis on Elastic Observability for EC2, VPC, ELB, and RDS. In this blog, we will cover how to ingest logs from AWS, and more specifically, we will review how to get VPC Flow Logs into Elastic and what you can do with this data.</p>
<p>Logging is an important part of observability, for which we generally think of metrics and/or tracing. However, the amount of logs an application or the underlying infrastructure output can be significantly daunting.</p>
<p>With Elastic Observability, there are three main mechanisms to ingest logs:</p>
<ul>
<li>The new Elastic Agent pulls metrics and logs from CloudWatch and S3 where logs are generally pushed from a service (for example, EC2, ELB, WAF, Route53, etc ). We reviewed Elastic agent metrics configuration for EC2, RDS (Aurora), ELB, and NAT metrics in this <a href="https://www.elastic.co/blog/aws-service-metrics-monitor-observability-easy">blog</a>.</li>
<li>Using <a href="https://www.elastic.co/blog/elastic-and-aws-serverless-application-repository-speed-time-to-actionable-insights-with-frictionless-log-ingestion-from-amazon-s3">Elastic’s Serverless Forwarder (runs on Lambda and available in AWS SAR)</a> to send logs from Firehose, S3, CloudWatch, and other AWS services into Elastic.</li>
<li>Beta feature (contact your Elastic account team): Using AWS Firehose to directly insert logs from AWS into Elastic — specifically if you are running the Elastic stack on AWS infrastructure.</li>
</ul>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt864e9aa3b4bf3d2a/6a7f1c302f00b28cbfefef61/Elastic-Observability-VPC-Flow-Logs.jpg" alt="" /></p>
<p>In this blog we will provide an overview of the second option, Elastic’s serverless forwarder collecting VPC Flow Logs from an application deployed on EC2 instances. Here’s what we'll cover:</p>
<ul>
<li>A walk-through on how to analyze VPC Flow Log info with Elastic’s Discover, dashboard, and ML analysis.</li>
<li>A detailed step-by-step overview and setup of the Elastic serverless forwarder on AWS as a pipeline for VPC Flow Logs into <a href="http://cloud.elastic.co">Elastic Cloud</a>.</li>
</ul>
<h2 id="elasticsserverlessforwarderonawslambda">Elastic’s serverless forwarder on AWS Lambda</h2>
<p>AWS users can quickly ingest logs stored in Amazon S3, CloudWatch, or Kinesis with the Elastic serverless forwarder, an AWS Lambda application, and view them in the Elastic Stack alongside other logs and metrics for centralized analytics. Once the AWS serverless forwarder is configured and deployed from AWS, Serverless Application Registry (SAR) logs will be ingested and available in Elastic for analysis. See the following links for further configuration guidance:</p>
<ul>
<li><a href="https://www.elastic.co/blog/elastic-and-aws-serverless-application-repository-speed-time-to-actionable-insights-with-frictionless-log-ingestion-from-amazon-s3">Elastic’s serverless forwarder (runs Lambda and available in AWS SAR)</a></li>
<li><a href="https://github.com/elastic/elastic-serverless-forwarder/blob/main/docs/README-AWS.md#s3_config_file">Serverless forwarder GitHub repo</a></li>
</ul>
<p>In our configuration we will ingest VPC Flow Logs into Elastic for the three-tier app deployed in the previous <a href="https://www.elastic.co/blog/aws-service-metrics-monitor-observability-easy">blog</a>.</p>
<p>There are three different configurations with the Elastic serverless forwarder:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt719dbab9506f0a1e/6a7f1c326c6eacb19af145d1/blog-elastic-vpc-flow-logs-3-configurations.png" alt="" /></p>
<p>Logs can be directly ingested from:</p>
<ul>
<li><strong>Amazon CloudWatch:</strong> Elastic serverless forwarder can pull VPC Flow Logs directly from an Amazon CloudWatch log group, which is a commonly used endpoint to store VPC Flow Logs in AWS.</li>
<li><strong>Amazon Kinesis:</strong> Elastic serverless forwarder can pull VPC Flow Logs directly from Kinesis, which is another location to <a href="https://docs.aws.amazon.com/vpc/latest/userguide/flow-logs-firehose.html">publish VPC Flow Logs</a>.</li>
<li><strong>Amazon S3:</strong> Elastic serverless forwarder can pull VPC Flow Logs from Amazon S3 via SQS event notifications, which is a common endpoint to publish VPC Flow Logs in AWS.</li>
</ul>
<p>We will review how to utilize a common configuration, which is to send VPC Flow Logs to Amazon S3 and into Elastic Cloud in the second half of this blog.</p>
<p>But first let's review how to analyze VPC Flow Logs on Elastic.</p>
<h2 id="analyzingvpcflowlogsinelastic">Analyzing VPC Flow Logs in Elastic</h2>
<p>Now that you have VPC Flow Logs in Elastic Cloud, how can you analyze them?</p>
<p>There are several analyses you can perform on the VPC Flow Log data:</p>
<ol>
<li>Use Elastic’s Analytics Discover capabilities to manually analyze the data.</li>
<li>Use Elastic Observability’s anomaly feature to identify anomalies in the logs.</li>
<li>Use an out-of-the-box (OOTB) dashboard to further analyze data.</li>
</ol>
<h3 id="usingelasticdiscover">Using Elastic Discover</h3>
<p>In Elastic analytics, you can search and filter your data, get information about the structure of the fields, and display your findings in a visualization. You can also customize and save your searches and place them on a dashboard. With Discover, you can:</p>
<ul>
<li>View logs in bulk, within specific time frames</li>
<li>Look at individual details of each entry (document)</li>
<li>Filter for specific values</li>
<li>Analyze fields</li>
<li>Create and save searches</li>
<li>Build visualizations</li>
</ul>
<p>For a complete understanding of Discover and all of Elastic’s analytics capabilities, look at <a href="https://www.elastic.co/guide/en/kibana/current/discover.html#">Elastic documentation</a>.</p>
<p>For VPC Flow Logs, an important stat is to understand:</p>
<ul>
<li>How many logs were accepted/rejected</li>
<li>Where potential security violations are occur (for example, source IPs from outside the VPC)</li>
<li>What port is generally being queried</li>
</ul>
<p>I’ve filtered the logs on the following:</p>
<ul>
<li>Amazon S3: bshettisartest</li>
<li>VPC Flow Log action: REJECT</li>
<li>VPC Network Interface: Webserver 1</li>
</ul>
<p>We want to see what IP addresses are trying to hit our web servers.</p>
<p>From that, we want to understand which IP addresses we are getting the most REJECTS from, and we simply find the <strong>source</strong>.ip field. Then, we can quickly get a breakdown that shows 185.242.53.156 is the most rejected for the last 3+ hours we’ve turned on VPC Flow Logs.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd3966498d22108ca/6a7f1c36bd21989acc7584e1/blog-elastic-vpc-flow-logs-100-hits.png" alt="" /></p>
<p>Additionally, I can see a visualization by selecting the “Visualize” button. We get the following, which we can add to a dashboard:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt805432c53beff1e7/6a7f1c39eab5be37b020ab38/blog-elastic-vpc-flow-logs-add-to-a-dashboard.png" alt="" /></p>
<p>In addition to IP addresses, we want to also see what port is being hit on our web servers.<br />
We select the destination port field, and the quick pop-up shows us a list of ports being targeted. We can see that port 23 is being targeted (this port is generally used for telnet), port 445 is being targeted (used for Microsoft Active Directory), and port 433 (used for https ssl). We also see these are all REJECT.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4a860d66b3ad9a19/6a7f1c3cbd219808a07584e7/blog-elastic-vpc-flow-logs-reject.png" alt="" /></p>
<h3 id="anomalydetectioninelasticobservabilitylogs">Anomaly detection in Elastic Observability logs</h3>
<p>Addition to Discover, Elastic Observability provides the ability to detect anomalies on logs. In Elastic Observability -&gt; logs -&gt; anomalies you can turn on machine learning for:</p>
<ul>
<li>Log rate: automatically detects anomalous log entry rates</li>
<li>Categorization: automatically categorizes log messages</li>
</ul>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3ba725798202a6a9/6a7f1c3f5967e5f51a5dd6f3/blog-elastic-vpc-flow-logs-anomaly-detection-with-machine-learning.png" alt="" /></p>
<p>For our VPC Flow Log, we turned both on. And when we look at what has been detected for anomalous log entry rates, we see:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf2131164c1682ec1/6a7f1c424c4bfb5f14ccd924/blog-elastic-vpc-flow-logs-anomalies.png" alt="" /></p>
<p>Elastic immediately detected a spike in logs when we turned on VPC Flow Logs for our application. The rate change is being detected because we’re also ingesting VPC Flow Logs from another application for a couple of days prior to adding the application in this blog.</p>
<p>We can further drill down into this anomaly with machine learning and analyze further.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltab04d37c363e03e3/6a7f1c455967e599895dd6f9/blog-elastic-vpc-flow-logs-anomaly-explorer.png" alt="" /></p>
<p>There is more machine learning analysis you can utilize with your logs — check out <a href="https://www.elastic.co/guide/en/kibana/8.5/xpack-ml.html">Elastic machine learning documentation</a>.</p>
<p>Since we know that a spike exists, we can also use Elastic AIOps Labs Explain Log Rate Spikes capability in Machine Learning. Additionally, we’ve grouped them to see what is causing some of the spikes.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltac4cc921055c5d85/6a7f1c4877b03481d23ff93d/blog-elastic-vpc-flow-logs-explain-log-rate-spikes.png" alt="" /></p>
<p>As we can see, a specific network interface is sending more VPC log flows than others. We can further drill down into this further in Discover.</p>
<h3 id="vpcflowlogdashboardonelasticobservability">VPC Flow Log dashboard on Elastic Observability</h3>
<p>Finally, Elastic also provides an OOTB dashboard to showing the top IP addresses hitting your VPC, geographically where they are coming from, the time series of the flows, and a summary of VPC Flow Log rejects within the time frame.</p>
<p>This is a baseline dashboard that can be enhanced with visualizations you find in Discover, as we reviewed in option 1 (Using Elastic’s Analytics Discover capabilities) above.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt400e83d9099b4364/6a7f1c4bead8ec4d64baac80/blog-elastic-vpc-flow-logs-action-geolocation.png" alt="" /></p>
<h2 id="settingitallup">Setting it all up</h2>
<p>Let’s walk through the details of configuring Amazon Kinesis Data Firehose and Elastic Observability to ingest data.</p>
<h3 id="prerequisitesandconfig">Prerequisites and config</h3>
<p>If you plan on following steps, here are some of the components and details we used to set up this demonstration:</p>
<ul>
<li>Ensure you have an account on <a href="http://cloud.elastic.co">Elastic Cloud</a> and a deployed stack (<a href="https://www.elastic.co/guide/en/elastic-stack/current/installing-elastic-stack.html">see instructions here</a>) on AWS. Deploying this on AWS is required for Elastic Serverless Forwarder.</li>
<li>Ensure you have an AWS account with permissions to pull the necessary data from AWS. Specifically, ensure you can configure the agent to pull data from AWS as needed. <a href="https://docs.elastic.co/integrations/aws#requirements">Please look at the documentation for details</a>.</li>
<li>We used <a href="https://github.com/aws-samples/aws-three-tier-web-architecture-workshop">AWS’s three-tier app</a> and installed it as instructed in GitHub. (<a href="https://www.elastic.co/blog/aws-service-metrics-monitor-observability-easy">See blog on ingesting metrics from the AWS services supporting this app</a>.)</li>
<li>Configure and install Elastic’s Serverless Forwarder.</li>
<li>Ensure you turn on VPC Flow Logs for the VPC where the application is deployed and send logs to AWS Firehose.</li>
</ul>
<h3 id="step0getanaccountonelasticcloud">Step 0: Get an account on Elastic Cloud</h3>
<p>Follow the instructions to <a href="https://cloud.elastic.co/registration?fromURI=/home">get started on Elastic Cloud</a>.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7dce08a8b90bfacf/6a7f1c4e9090b01f8b84ee5f/blog-elastic-vpc-flow-logs-start-cloud-trial.png" alt="" /></p>
<h3 id="step1deployelasticonaws">Step 1: Deploy Elastic on AWS</h3>
<p>Once logged in to Elastic Cloud, create a deployment on AWS. It’s important to ensure that the deployment is on AWS. The Amazon Kinesis Data Firehose connects specifically to an endpoint that needs to be on AWS.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd7a8f13d7eadf6fb/6a7f1c51ead8ec01fbbaac88/blog-elastic-vpc-flow-logs-create-a-deployment.png" alt="" /></p>
<p>Once your deployment is created, make sure you copy the Elasticsearch endpoint.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltaef67444a94269ce/6a7f1c55ea068d779af0a314/blog-elastic-vpc-flow-logs-aws-logs.png" alt="" /></p>
<p>The endpoint should be an AWS endpoint, such as:</p>
<pre><code>https://aws-logs.es.us-east-1.aws.found.io
</code></pre>
<h3 id="step2turnonelasticsawsintegrationsonaws">Step 2: Turn on Elastic’s AWS Integrations on AWS</h3>
<p>In your deployment’s Elastic Integration section, go to the AWS integration and select Install AWS assets.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt718f23ad192c845a/6a7f1c58448e4ea69d5c0b9b/blog-elastic-vpc-flow-logs-aws-settings.png" alt="" /></p>
<h3 id="step3deployyourapplication">Step 3: Deploy your application</h3>
<p>Follow the instructions listed out in <a href="https://github.com/aws-samples/aws-three-tier-web-architecture-workshop">AWS’s Three-Tier app</a> and instructions in the workshop link on GitHub. The workshop is listed <a href="https://catalog.us-east-1.prod.workshops.aws/workshops/85cd2bb2-7f79-4e96-bdee-8078e469752a/en-US">here</a>.</p>
<p>Once you’ve installed the app, get credentials from AWS. This will be needed for Elastic’s AWS integration.</p>
<p>There are several options for credentials:</p>
<ul>
<li>Use access keys directly</li>
<li>Use temporary security credentials</li>
<li>Use a shared credentials file</li>
<li>Use an IAM role Amazon Resource Name (ARN)</li>
</ul>
<p>View more details on specifics around necessary <a href="https://docs.elastic.co/en/integrations/aws#aws-credentials">credentials</a> and <a href="https://docs.elastic.co/en/integrations/aws#aws-permissions">permissions</a>.</p>
<h3 id="step4sendvpcflowlogstoamazons3andsetupamazonsqs">Step 4: Send VPC Flow Logs to Amazon S3 and set up Amazon SQS</h3>
<p>In the VPC for the application deployed in Step 3, you will need to configure VPC Flow Logs and point them to an Amazon S3 bucket. Specifically, you will want to keep it as AWS default format.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf9a94d515651e20c/6a7f1c5b9090b0415f84ee6b/blog-elastic-vpc-flow-logs-create-flow-log.png" alt="" /></p>
<p>Create the VPC Flow log.</p>
<p>Next:</p>
<ul>
<li><a href="https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-getting-started.html">Set up an Amazon SQS queue</a></li>
<li><a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/ways-to-add-notification-config-to-bucket.html">Configure Amazon S3 event notifications</a></li>
</ul>
<h3 id="step5setupelasticserverlessforwarderonaws">Step 5: Set up Elastic Serverless Forwarder on AWS</h3>
<p>Follow instructions listed in <a href="https://www.elastic.co/guide/en/observability/8.5/aws-deploy-elastic-serverless-forwarder.html">Elastic’s documentation</a> and refer to the <a href="https://www.elastic.co/blog/elastic-and-aws-serverless-application-repository-speed-time-to-actionable-insights-with-frictionless-log-ingestion-from-amazon-s3">previous blog</a> providing an overview. The important bits during the configuration in Lambda’s application repository are to ensure you:</p>
<ul>
<li>Specify the S3 Bucket in ElasticServerlessForwarderS3Buckets where the VPC Flow Logs are being sent. The value is the ARN of the S3 Bucket you created in Step 4.</li>
<li>Specify the configuration file path in ElasticServerlessForwarderS3ConfigFile. The value is the S3 url in the format "s3://bucket-name/config-file-name" pointing to the configuration file (sarconfig.yaml).</li>
<li>Specify the S3 SQS Notifications queue used as the trigger of the Lambda function in ElasticServerlessForwarderS3SQSEvents. The value is the ARN of the SQS Queue you set up in Step 4.</li>
</ul>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt683de353f5f3d00e/6a7f1c5eeab5bea4c420ab44/blog-elastic-vpc-flow-logs-application-settings.png" alt="" /></p>
<p>Once Amazon CloudFormation finishes setting up Elastic serverless forwarder, you should see two Amazon Lambda functions:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0a8541ccc1858f9d/6a7f1c61e02fac26945d69f3/blog-elastic-vpc-flow-logs-functions.png" alt="" /></p>
<p>In order to check if logs are coming in, go to the function with “ <strong>ApplicationElasticServer</strong> ” in the name, and go to monitor and look at <strong>logs</strong>. You should see the logs being pulled from S3.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt57ff998a2a6fea3a/6a7f1c64ead8ec5455baac94/blog-elastic-vpc-flow-logs-function-overview.png" alt="" /></p>
<h3 id="step6checkandensureyouhavelogsinelastic">Step 6: Check and ensure you have logs in Elastic</h3>
<p>Now that steps 1–4 are complete, you can go to Elastic’s Discover capability and you should see VPC Flow Logs coming in. In the image below, we’ve filtered by Amazon S3 bucket <strong>bshettisartest</strong>.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6e7507f7a157ee2a/6a7f1c675967e5b74c5dd705/blog-elastic-vpc-flow-log-dashboard-filter.png" alt="" /></p>
<h2 id="conclusionelasticobservabilityeasilyintegrateswithvpcflowlogsforanalyticsalertingandinsights">Conclusion: Elastic Observability easily integrates with VPC Flow Logs for analytics, alerting, and insights</h2>
<p>I hope you’ve gotten an appreciation for how Elastic Observability can help you manage AWS VPC Flow Logs. Here’s a quick recap of lessons and what you learned:</p>
<ul>
<li>A walk-through of how Elastic Observability provides enhanced analysis for VPC Flow Logs:</li>
<li>Using Elastic’s Analytics Discover capabilities to manually analyze the data</li>
<li>Leveraging Elastic Observability’s anomaly features to:<ul>
<li>Identify anomalies in the VPC flow logs</li>
<li>Detects anomalous log entry rates</li>
<li>Automatically categorizes log messages</li></ul></li>
<li>Using an OOTB dashboard to further analyze data</li>
<li>A more detailed walk-through of how to set up the Elastic Serverless Forwarder</li>
</ul>
<p>Start your own <a href="https://aws.amazon.com/marketplace/pp/prodview-voru33wi6xs7k?trk=5fbc596b-6d2a-433a-8333-0bd1f28e84da%E2%89%BBchannel=el">7-day free trial</a> by signing up via <a href="https://aws.amazon.com/marketplace/pp/prodview-voru33wi6xs7k?trk=d54b31eb-671c-49ba-88bb-7a1106421dfa%E2%89%BBchannel=el">AWS Marketplace</a> and quickly spin up a deployment in minutes on any of the <a href="https://www.elastic.co/guide/en/cloud/current/ec-reference-regions.html#ec_amazon_web_services_aws_regions">Elastic Cloud regions on AWS</a> around the world. Your AWS Marketplace purchase of Elastic will be included in your monthly consolidated billing statement and will draw against your committed spend with AWS.</p>
<h3 id="additionalloggingresources">Additional logging resources:</h3>
<ul>
<li><a href="https://www.elastic.co/getting-started/observability/collect-and-analyze-logs">Getting started with logging on Elastic (quickstart)</a></li>
<li><a href="https://www.elastic.co/guide/en/observability/current/logs-metrics-get-started.html">Ingesting common known logs via integrations (compute node example)</a></li>
<li><a href="https://docs.elastic.co/integrations">List of integrations</a></li>
<li><a href="https://www.elastic.co/blog/log-monitoring-management-enterprise">Ingesting custom application logs into Elastic</a></li>
<li><a href="https://www.elastic.co/blog/observability-logs-parsing-schema-read-write">Enriching logs in Elastic</a></li>
<li>Analyzing Logs with <a href="https://www.elastic.co/blog/reduce-mttd-ml-machine-learning-observability">Anomaly Detection (ML)</a> and <a href="https://www.elastic.co/blog/observability-logs-machine-learning-aiops">AIOps</a></li>
</ul>
<h3 id="commonusecaseexampleswithlogs">Common use case examples with logs:</h3>
<ul>
<li><a href="https://youtu.be/ax04ZFWqVCg">Nginx log management</a></li>
<li><a href="https://www.elastic.co/blog/vpc-flow-logs-monitoring-analytics-observability">AWS VPC Flow log management</a></li>
<li><a href="https://www.elastic.co/blog/kubernetes-errors-elastic-observability-logs-openai">Using OpenAI to analyze Kubernetes errors</a></li>
<li><a href="https://youtu.be/Li5TJAWbz8Q">PostgreSQL issue analysis with AIOps</a></li>
</ul>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/vpc-flow-logs-monitoring-analytics-observability</link>
    <guid isPermaLink="false">vpc-flow-logs-monitoring-analytics-observability</guid>
    <category><![CDATA[Logs Analytics]]></category>
    <category><![CDATA[Metrics]]></category>
    <category><![CDATA[Infrastructure Monitoring]]></category>
    <dc:creator><![CDATA[Bahubali Shetti]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt29d963458983cce0/6a7f1c6ab4377022bc4d7157/patterns-midnight-background-no-logo-observability.png" length="0" type="image/png"/>
    <pubDate>Mon, 23 Jan 2023 00:00:00 GMT</pubDate>
  </item>
  </channel>
</rss>