<?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>Fri, 11 Sep 2026 12:10:29 GMT</lastBuildDate>
  <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[Elastic z/OS ingest: five architectures for mainframe data]]></title>
    <description><![CDATA[This field guide walks through the ingest architectures I've seen work in production, the data quality checks that decide whether your dashboards actually work, and the ECS mapping that makes mainframe data usable to the platform.]]></description>
    <content:encoded><![CDATA[<p>Mainframe teams want what every other observability team already has: anomaly detection, machine learning (ML) on the batch windows, and alerts that fire when something's actually wrong. Most of them have the data for it. What they don't have is data that the platform can recognize as unified, connected, and operationally meaningful.</p>
<p>A customer described it to me this way: A single transaction passes through three products on its way through the mainframe, and each one names the same field differently (system name, program name, user). Getting the data into Elastic isn't the hard part; getting it to correlate across products, so that Elastic's dashboards and ML jobs recognize it as the same data, is where most projects fall short. </p>
<p>Done right, Elastic becomes the speed layer that mainframe environments have never had: a near–real-time view across operational, transactional, and security data, while the authoritative systems of record stay exactly where they are.</p>
<p>This is the onboarding process I use with mainframe customers, built from architectures running in production at large financial institutions. It covers the ingest patterns that actually work, how Elastic Common Schema (ECS) alignment makes the data usable, and whether the data quality holds up or fails quietly.</p>
<h2 id="validateyourmainframesourcedatabeforeyouwriteapipeline">Validate your mainframe source data before you write a pipeline</h2>
<p>The most expensive failures I've seen in mainframe ingest projects are the ones that don't fail loudly. Pipelines run, data lands, dashboards render, and weeks later, someone notices that half the events from one logical partition (LPAR) never parsed or a quiet typing change has been silently corrupting a field.</p>
<p>Two mistakes come up frequently:</p>
<p><strong>1. Format inconsistency across LPARs and time windows:</strong> Log formats vary across LPARs, between batch and online windows, and across shift changes. A format that parses cleanly in a dev LPAR may not match what production emits during peak batch. This is the single most common cause of partial parse failures I run into.</p>
<p><strong>2. Sample configurations treated as production configurations:</strong> A common cause of "it broke overnight" incidents: The upstream collector configuration was based on a sample structure shipped by the vendor and then never replaced with a deliberate production configuration. When the vendor pushed an update, naming and typing changed (fields renamed, types shifted) and the downstream pipeline started rejecting records mid-flight. Treat sample configurations as exactly that, and replace them with a deliberate production configuration that doesn't move under you.</p>
<p>Before any pipeline development begins, walk through 24–48 hours of raw samples from each source with the mainframe team. This review should be treated as a recurring requirement rather than a one-off event, because the conditions that produce format drift (vendor updates, configuration changes, new message types) keep happening after the project goes live.</p>
<p><strong>Worth knowing first:</strong> For mainframe environments, the <a href="https://www.elastic.co/integrations/data-integrations?search=ibm">Elastic integrations catalog</a> is short. The <a href="https://www.elastic.co/docs/reference/integrations/ibmmq">IBM MQ integration</a> is the most complete option (Queue Manager error logs and performance metrics, ECS-aligned, with out-of-the-box dashboards), though the metrics data stream requires the containerized MQ distribution rather than native z/OS MQ. If your architecture includes Customer Information Control System (CICS) workloads or you need end-to-end distributed tracing, assess <a href="https://www.elastic.co/observability-labs/blog/end-to-end-o11y-from-cloud-native-to-mainframe">IBM Z Observability Connect</a> before building custom pipelines: It's the <em>native OpenTelemetry (OTel) path</em> and covers more ground than the architectures below. For everything else, read on.</p>
<h2 id="ecsalignmentfromdatainelastictodataelasticcanuse">ECS alignment: from data in Elastic to data Elastic can use</h2>
<p>Before choosing an ingest strategy, it's worth understanding why ECS alignment comes first in practice, even if the pipeline gets built later. It's the decision that determines whether everything else pays off.</p>
<p>A mainframe team's core mission: Trace a single transaction from a REST call into z/OS Connect, through to an Information Management System (IMS) application, and back. That flow touches three products, each emitting telemetry with its own field names for the same concepts (system name, program name, user, transaction ID). Without normalization, correlating that transaction means writing queries that explicitly union three different field names per concept. That’s expensive to write and fragile when any product changes its schema.</p>
<p>ECS solves this. It defines a consistent target schema (<code>host.name</code>, <code>process.name</code>, <code>user.name</code>, <code>event.code</code>) that every source maps into. Once z/OS Connect, IMS Connect, and IMS data all land in the same ECS fields for the same logical concepts, that cross-product transaction trace becomes a single query.</p>
<p>There's a second reason this matters. Elastic's OOTB dashboards, alerting rules, anomaly detection, and ML jobs are all built against ECS field paths. A <code>job_name</code> field that Logstash extracted from a JES log is invisible to them. A <code>process.name</code> field carrying the same value is immediately recognized and processed. ECS alignment is what makes the platform's built-in capabilities recognize your data.</p>
<p>Skipping this step is the most common reason that ingest projects fall short, despite the data being technically present.</p>
<h3 id="mapwhatfitstocoreecs">Map what fits to core ECS</h3>
<p>The mapping below is a starting point drawn from what I've seen work across customer environments. Field names in your source data will vary, but the ECS targets are stable:</p>
<p>| z/OS concept | ECS field | Notes |
| :---- | :---- | :---- |
| Job name | <code>process.name</code> |  |
| Return code | <code>process.exit_code</code> | Ensure integer type; hex strings are a common mapping mistake |
| Program name | <code>process.executable</code> |  |
| Elapsed time | <code>event.duration</code> | Nanoseconds in ECS; z/OS typically reports in hundredths of a second or milliseconds, so convert at the pipeline stage; unit mismatches silently break ML anomaly detection on latency |
| Message ID | <code>event.code</code> |  |
| Timestamp | <code>@timestamp</code> | Normalize from z/OS format to ISO 8601 in the pipeline |
| LPAR name | <code>host.name</code> |  |
| System ID (SMFID) | <code>host.hostname</code> |  |
| User ID | <code>user.name</code> |  |</p>
<p>Reference: <a href="https://www.elastic.co/docs/reference/ecs/ecs-process">ECS process fields</a> and <a href="https://www.elastic.co/docs/reference/ecs/ecs-event">ECS event fields</a>.</p>
<h3 id="extendstrategicallywithcustomecsfields">Extend strategically with custom ECS fields</h3>
<p>Mainframe-specific concepts have no ECS equivalent: job class, ASID, SMF record type and subtype, sysplex name, WTO routing codes, CICS transaction ID. Flattening these into <code>labels.*</code> as untyped strings destroys type information and makes them effectively unusable for queries and aggregations.</p>
<p>Define a <code>zos.*</code> custom namespace using ECS's documented extension mechanism. It keeps your core telemetry ECS-compliant while retaining the operational context your mainframe team needs for incident response.</p>
<h3 id="useecsmappingstostaycurrent">Use ecs@mappings to stay current</h3>
<p>Include <code>ecs@mappings</code> as a component template in your index template (available from Elasticsearch 8.9 for custom index templates and from 8.13 for Elastic Agent integration templates). It provides Elastic-maintained ECS field definitions automatically and keeps them current with each Elasticsearch release. For custom pipelines, this is what keeps your ECS alignment from drifting over time without manual upkeep.  </p>
<p>One important caveat from the field: <code>ecs@mappings</code> provides the field definitions but doesn't enforce types at ingest. A return code arriving as a string is accepted and mapped as a string. Monitoring these discrepancies is critical, and they can be identified using the Data Quality dashboard. And because Elastic <a href="https://www.elastic.co/blog/ecs-elastic-common-schema-otel-opentelemetry-faq">donated ECS to OpenTelemetry</a>, the <code>zos.*</code> mappings you define here remain valid as OTel semantic conventions and ECS converge. The alignment work is the same whether data arrives via Logstash or OpenTelemetry Protocol (OTLP).</p>
<h2 id="choosetherightarchitectureforthesource">Choose the right architecture for the source</h2>
<p>Most environments I work with run more than one of these ingest architectures, and different data sources have different latency, throughput, and licensing characteristics. A single architecture rarely covers everything. The table below maps common z/OS data sources to the architectures that work well for them.</p>
<p>Kafka is commonly added when there's a network resilience requirement between the mainframe and the Elastic cluster. If Kafka isn't already in your estate, the operational overhead of running Kafka should be weighed against the resilience benefits. From IBM MQ 9.4.3, Kafka Connect can run natively in z/OS UNIX System Services for MQ connector use cases, reducing the need for an off-platform Kafka Connect cluster. </p>
<p>When Kafka is used, Logstash is the recommended downstream consumer for the ingest paths in this guide. The Confluent Elasticsearch sink connector is an alternative; the self-managed <a href="https://docs.confluent.io/kafka-connectors/elasticsearch/current/overview.html">v1 connector</a> supports Elasticsearch 7.x and 8.x but is deprecated with end of life (EOL) in April 2027; and the <a href="https://docs.confluent.io/cloud/current/connectors/cc-elasticsearch-sink-v2/cc-elasticsearch-sink-v2.html#features">v2 connector</a> is Confluent Cloud only, making it unsuitable for on-premises and air-gapped environments.</p>
<p>Reference architecture: <a href="https://www.elastic.co/docs/manage-data/ingest/ingest-reference-architectures/agent-kafka-es">Kafka as middleware</a>.</p>
<p>| Data source | Collector | Notes |
| :---- | :---- | :---- |
| SMF type 30 job accounting | IBM Z Common Data Provider (CDP) | Binary SMF records need preprocessing before ingestion |
| z/OS SYSLOG | IBM Z CDP |  |
| JES job logs | IBM Z CDP | Batch export is an alternative for historical / proof of concept (PoC) work |
| Resource Access Control Facility (RACF) audit events | IBM Z CDP | ECS-aligned RACF data works with Elastic SIEM out of the box |
| RMF performance data | IBM Z CDP | Consider time series data stream (TSDS) for the index template |
| IMS statistical records | IBM Z CDP |  |
| OMEGAMON agent metrics (CICS, IMS, Db2, z/OS, network, storage) | IBM OMEGAMON Data Provider (ODP) | Outputs JSON natively; no binary preprocessing needed |
| IMS transaction data | IMS Connect Extension (Rocket Software) | JSON output bypasses SMF binary parsing; requires Rocket Software licensing |
| CICS transaction traces | IBM Z Observability Connect | Native OTel; covered in detail in the <a href="https://www.elastic.co/observability-labs/blog/end-to-end-o11y-from-cloud-native-to-mainframe">End-to-End Observability from Cloud Native to Mainframe</a> deep -dive |
| Linux on IBM Z (zLinux) | Standard Elastic Agent | Full integration catalog available; different problem from z/OS onboarding |
| Historical analysis / PoC | Batch export (CSV / FTP) | Not suitable as a long-term operational solution |</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt62803cc80ddb77ae/6a7f0d2b448e4eb6415c072b/image1.png" alt="Flow diagram of the ingest paths from z/OS to Elastic" />
<em>Flow diagram of the ingest paths from z/OS to Elastic.</em></p>
<h3 id="ibmzcdptheworkhorseforzosoperationaldata">IBM Z CDP: The workhorse for z/OS operational data</h3>
<p>IBM Z CDP is the most widely deployed first-mile collector for z/OS operational data. It reads from SMF datasets in near-real time and forwards off-platform, handling the genuinely difficult part of getting data off z/OS without burdening performance-critical paths. In the environments I work with, it's the standard path for SMF type 30 job accounting, IMS statistical records, and z/OS SYSLOG.</p>
<p>CDP forwards to Logstash, which handles parsing, field extraction, and routing into Elasticsearch. Kafka is an optional middleware message queue:</p>
<ul>
<li><strong>CDP → (Kafka →) Logstash → Elasticsearch</strong></li>
</ul>
<p>The trade-offs: CDP is a separately licensed IBM product, binary SMF records need preprocessing before Logstash can parse them, and the architecture isn't suited to sub-minute latency requirements.</p>
<p>Worth noting alongside CDP:</p>
<ul>
<li>IBM ODP plays the same collector role for performance and availability metrics from whichever OMEGAMON monitors are in your stack: CICS, IMS, Db2, z/OS, network, and storage. Unlike CDP's binary SMF output, ODP converts to JSON natively, so there's no preprocessing step. ODP consists of two components: OMEGAMON Data Broker (a Zowe cross-memory server plugin running on z/OS that collects attributes from OMEGAMON monitoring agents and forwards them to Data Connect); and OMEGAMON Data Connect (a Java application running on or off z/OS that receives data from Data Broker and forwards it to destinations including Elasticsearch; the destination settings are configured here). If OMEGAMON is already in your monitoring stack, ODP is the natural path for getting that telemetry into Elastic.</li>
<li>IBM Z Operational Log and Data Analytics (IZLDA) packages CDP's data streaming capabilities alongside analytics and dashboarding into a single licensed product — I haven't encountered it in production yet, but it's the direction IBM is heading. If your organization is evaluating or has recently licensed IZLDA, the CDP ingest path described above remains the same — IZLDA uses CDP as its underlying collection engine, with Elastic Stack as one of its supported destinations. <a href="https://www.ibm.com/case-studies/bcc-iccrea-group">Gruppo BCC ICCREA's deployment</a> is a published example of IZLDA feeding an Elasticsearch-based monitoring stack.</li>
</ul>
<p>Reference architecture: <a href="https://www.elastic.co/docs/manage-data/ingest/ingest-reference-architectures/ls-for-input">Logstash to Elasticsearch</a>.</p>
<h3 id="imsconnectextensionforimsworkloadsthatcanbypasscdp">IMS Connect Extension: For IMS workloads that can bypass CDP</h3>
<p>Rocket Software's IMS Connect Extension journals IMS transaction activity directly as JSON, bypassing the SMF layer entirely. Events publish to Kafka, and Logstash consumes and indexes. Some organizations standardize all log streams through Kafka (rsyslog → Kafka → Logstash) as an optional resilience pattern.</p>
<ul>
<li><strong>IMS Connect Extension → (Kafka →) Logstash → Elasticsearch</strong></li>
</ul>
<p>This works well for IMS transaction performance data and application-level event streams. JSON output removes the binary parsing problem. Kafka gives you decoupling, replay, and a buffer for downstream maintenance.</p>
<p>The trade-offs: IMS Connect Extension licensing, Kafka infrastructure to operate, and IMS-specific coverage that doesn't help with z/OS SYSLOG or other SMF types.  </p>
<p>One thing I always validate before committing to this pattern is Kafka topic naming. Banks and regulated environments typically have strict topic naming policies, and IMS Connect Extension's default behavior of creating topics itself can clash with those policies. It’s cheaper to discover this before architecture commitment than after.</p>
<h3 id="batchexportforhistoricalanalysisandpoc">Batch export: For historical analysis and PoC</h3>
<p>Export from IMS Problem Investigator or similar tooling to CSV, transfer off-platform, and ingest via Logstash or Elastic Agent file input. This approach has no real-time capability, and it doesn’t require any new z/OS software.</p>
<ul>
<li><strong>Batch export → CSV/FTP → Logstash/Elastic Agent → Elasticsearch</strong></li>
</ul>
<p>This works well for historical analysis, initial PoC work, and demonstrating value before committing to a real-time pipeline. I also use this to get ECS mapping right before the production architecture is in place. It isn’t suitable as a long-term operational observability solution.</p>
<h3 id="linuxonibmzaseparateandeasierpath">Linux on IBM Z: A separate and easier path</h3>
<p>This path is often overlooked. Linux on IBM Z workloads can run standard Elastic Agent (Elastic Agent doesn’t run on native z/OS), no z/OS-specific tooling, no custom pipeline and the full Elastic integration catalog is available.</p>
<p>If you have Linux on IBM Z workloads in your estate, treat them as a separate (and considerably easier) onboarding path.</p>
<p>For all options, please see the <a href="https://www.elastic.co/docs/manage-data/ingest/ingest-reference-architectures">reference architectures with Elastic Agent</a>.</p>
<h3 id="throughputandairgappedthetwoquestionseverymainframeteamasks">Throughput and air-gapped: The two questions every mainframe team asks</h3>
<p><strong>Throughput impact:</strong> Anything that touches z/OS performance-critical paths is a nonstarter for mainframe teams running thousands of transactions per second. All four architectures above use off-platform collection deliberately: CDP, ODP, Kafka/Logstash, batch export, or standard Linux agent. This is the right design for the environment, not a workaround.</p>
<p><strong>Air-gapped environments:</strong> Most mainframe estates I work with are network-restricted to some degree. Elastic's <a href="https://www.elastic.co/docs/manage-data/ingest/ingest-reference-architectures/airgapped-env">air-gapped reference architecture</a> is a documented, supported deployment path.</p>
<h2 id="buildthepipelineanddontstartfromscratch">Build the pipeline, and don't start from scratch</h2>
<p>For the CDP and IMS Connect Extension architectures, log data lands in Elastic reflecting the limited structure of its source. Mainframe log formats are installation-specific and partially structured at best; no off-the-shelf parser covers them, and writing a pipeline from scratch has historically been the largest time sink in any onboarding project. AI has changed that. For mainframe estates that can't call out to a hosted model (which is most of them), both tools below work with self-managed local large language models (LLMs), so the capability is available in air-gapped and network-restricted environments. See the <a href="https://www.elastic.co/docs/explore-analyze/ai-features/llm-guides/local-llms-overview">local LLMs overview</a> for supported options.</p>
<p><strong>Streams: For data already landing in Elastic (available from 9.2).</strong><br />
Open the <strong>Processing</strong> tab for a stream in Kibana, and click <strong>Suggest pipeline</strong>. Within seconds, you're looking at a complete, validated pipeline (Grok or Dissect pattern, date normalization, type conversions, field cleanup) with a live preview of how your actual documents parse through it. Nothing writes to the stream until you confirm. Under the hood, generation runs in two stages: First, deterministic fingerprinting groups your log formats and picks the best parsing approach; second, a reasoning agent iterates to add normalization and cleanup, validating against hard thresholds before handing control to you. The result is a working pipeline you refine, not a starting point you rewrite. The technical detail is in <a href="https://www.elastic.co/observability-labs/blog/elastic-streams-ai-pipeline-generation">How Streams Generates a Log Pipeline in Seconds</a>.</p>
<p><strong>Automatic Import: For building a new custom integration from the ground up (available from 8.18/9.0).</strong> <a href="https://www.elastic.co/docs/explore-analyze/ai-features/automatic-import">Automatic Import</a> takes a different path. You upload sample data, and it generates a complete, deployable Elastic Agent integration package (ingest pipeline, ECS field mappings, event categorization, and related.* field population), which you review and approve before it installs. Where the Streams Suggest Pipeline structures data already arriving in a stream, Automatic Import builds the entire collection path from scratch. Supported input formats include JSON, NDJSON, CSV, and syslog, which covers z/OS SYSLOG directly. Supported collection methods include Kafka, File Stream, TCP, and HTTP Endpoint, making it a natural fit for shops already routing data through Kafka or receiving ODP output over TCP. For mainframe shops adopting Elastic Agent, this removes what was previously weeks of custom integration work.</p>
<p>A less obvious benefit that applies to both tools is continuity. The engineer who wrote your custom GROK pattern eventually moves to another team. A tool that can regenerate a pipeline or integration from sample data is operational resilience.</p>
<p>In terms of scope, Streams works on text. Binary SMF records need to be converted to text or JSON upstream (via CDP or IBM-supplied utilities) before either tool can do anything with them. That conversion happens before Elastic is involved.</p>
<h2 id="configurethedeadletterqueuefromdayone">Configure the dead-letter queue from day one</h2>
<p>Mainframe teams know the <em>dead-letter queue pattern</em> from MQ: When a message can't be delivered or processed, it goes to a holding queue rather than being silently dropped. Elasticsearch has the same concept for ingest pipelines, called the <a href="https://www.elastic.co/docs/manage-data/data-store/data-streams/failure-store">failure store</a>. Configure it from day one, not after your first production incident.</p>
<p>Format drift is a recurring failure mode: vendor updates, sample-config-as-production, new message types appearing in batch windows. The failure store is how you find out about it before your dashboards lie to you. When a log line arrives in an unexpected format and the pipeline can't parse it, the failure store captures the original document with metadata about why it failed. You can query it, alert on its growth rate, and use the captured documents to fix the pipeline.</p>
<p>Without it, parse failures either fall to default handling (records indexed with raw <code>message</code> fields, expected query fields simply absent) or get dropped entirely. Either way, you don't know it's happening.</p>
<p>Configure retention based on how long it takes your team to triage drift, typically days to a couple of weeks. Pair it with an alert on document count or growth rate so the queue itself is the early warning, not something someone has to remember to check.</p>
<h2 id="verifymainframedataqualitybeforeyoubuildonit">Verify mainframe data quality before you build on it</h2>
<p>Don't build dashboards or alerting rules on data you haven't verified. The Data Quality dashboard tells you whether your ECS alignment is real or aspirational.  </p>
<p>For mainframe data, silent type mismatches are common: return codes in hex mapped as keywords, elapsed times stored as strings, timestamps that never coerced to <code>@timestamp</code>. None of these fail at ingest. All of them silently break queries and alerting conditions.</p>
<p>Run the checker against real production data, not synthetic samples. z/OS log variation across batch windows and message types means edge cases only surface under real conditions. Expect to iterate: Find the mismatch, fix the pipeline, and run again. Two or three passes is normal for a new mainframe data stream.</p>
<p>Source quality validation, the failure store, and the Data Quality dashboard are three points on the same loop. Together they give you confidence that the dashboards reflect what's actually happening on the mainframe, not what you hoped your pipeline was producing.</p>
<h2 id="gettingstartedwithmainframedataonboarding">Getting started with mainframe data onboarding</h2>
<p>The mainframe is a first-class observability target, and the path there is more concrete than it was a few years ago. Managed integrations cover IBM MQ. CDP and Kafka-based architectures have well-understood deployment patterns. Streams and Automatic Import remove the blank-page problem for custom pipelines, including in restricted environments through local LLMs. IBM Z Observability Connect is there when the OTel path is in reach.</p>
<p>Recommended order of operations:</p>
<ol>
<li>Validate the source data.  </li>
<li>Use OOTB integrations where they exist.  </li>
<li>Align to ECS early.  </li>
<li>Choose architectures source by source.  </li>
<li>Generate pipelines rather than write them from scratch.  </li>
<li>Configure the failure store from day one.  </li>
<li>Verify before building anything on top.  </li>
</ol>
<p>If your organization is working through this and you'd like to compare notes, or if you're hitting a specific blocker, reach out to your Elastic account team. For the OTel-native path, the <a href="https://www.elastic.co/observability-labs/blog/end-to-end-o11y-from-cloud-native-to-mainframe">End-to-End Observability from Cloud Native to Mainframe</a> deep dive is the next read. To try the building blocks in your own environment, <a href="http://cloud.elastic.co/registration">start a free Elastic Cloud trial</a>.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/mainframe-data-ingestion</link>
    <guid isPermaLink="false">mainframe-data-ingestion</guid>
    <category><![CDATA[Infrastructure Monitoring]]></category>
    <category><![CDATA[Logs Analytics]]></category>
    <category><![CDATA[Metrics]]></category>
    <dc:creator><![CDATA[Anna Maria Modée]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2cddefcfe7021906/6a7f0d2ee88c65adaf00b6d0/image2.png" length="0" type="image/png"/>
    <pubDate>Tue, 14 Jul 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[Piping Hot: Bringing ES|QL to Your Grafana Dashboards Using the Elasticsearch Plugin]]></title>
    <description><![CDATA[You can now write ES|QL queries in Grafana with the Elasticsearch plugin. Learn how to enable it and write pipe-based queries directly in the Grafana UI.]]></description>
    <content:encoded><![CDATA[<p>The Elasticsearch data source is one of the most popular plugins in the Grafana ecosystem, and it now ships ES|QL support as an experimental feature, available starting in Grafana 13.0. ES|QL is Elasticsearch's modern pipe-based query language that enables querying logs, metrics, and traces, and using Elasticsearch as a native <a href="https://www.elastic.co/observability-labs/blog/elasticsearch-supports-promql">Prometheus PromQL</a> data source, all directly from the Grafana query editor. Built by Elastic in collaboration with Grafana Labs and contributed upstream to the Grafana open source project, this integration is enabled through a single feature flag (<code>elasticsearchESQLQuery = true</code>) that unlocks a Monaco-powered editor with syntax highlighting, autocompletion, and inline error messages. We'll walk through how to enable it and write your first queries for log analysis, time series visualization, and metrics aggregation.</p>
<h2 id="context">Context</h2>
<p>Elasticsearch is one of the top plugins used with the Grafana UI.
Until now, the Elasticsearch plugin only supported Lucene and raw Query DSL for querying.
<a href="https://www.elastic.co/docs/reference/query-languages/esql">ES|QL</a> is Elastic's modern pipe-based query language, designed for analytics on log, metrics, and trace data. Its intuitive syntax makes it easier to filter, aggregate, and transform data compared to Query DSL or Lucene.</p>
<p>This has been tracked as a community request since 2024: <a href="https://github.com/grafana/grafana/issues/81765">grafana/grafana#81765</a>.</p>
<h2 id="howtoenableesqlsupportingrafana">How to enable ES|QL support in Grafana</h2>
<p>The feature is behind the <code>elasticsearchESQLQuery</code> feature flag.
To turn it on, add the following to your <code>grafana.ini</code>:</p>
<pre><code>[feature_toggles]
elasticsearchESQLQuery = true
</code></pre>
<p>Restart Grafana after saving.
The feature is available starting with Grafana 13.0.</p>
<h2 id="esqlinthegrafanaqueryeditor">ES|QL in the Grafana query editor</h2>
<p>Once the flag is enabled, the Elasticsearch query editor gains a <strong>Query language</strong> selector.
You can switch between Lucene, Raw DSL, and ES|QL from the same editor panel.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd096ec3755f0ae85/6a7f0a7a9090b04e8984e8ef/esql-query-language-selector.png" alt="ES|QL query language selector in the Grafana Elasticsearch plugin, showing the dropdown with ES|QL selected" /></p>
<p>When you select ES|QL, the editor switches to a Monaco-powered code editor, the same engine that powers VS Code.
You get syntax highlighting, error highlighting, and basic autocompletion out of the box.</p>
<p><strong>Smart index pre-population</strong> makes getting started quick: if an index pattern is configured in your data source settings, clicking into the ES|QL editor for the first time auto-inserts <code>FROM &lt;index&gt;</code>.
You can change or delete it freely.
If no index is configured, the <code>FROM</code> clause is left blank.</p>
<h2 id="runningyourfirstqueries">Running your first queries</h2>
<h3 id="countlogentriesbyseverity">Count log entries by severity</h3>
<p>This is a good first query to confirm ES|QL is working and to get a feel for the syntax.</p>
<pre><code>FROM logs*
| STATS count = COUNT(*) BY log.level
| SORT count DESC
</code></pre>
<p>Run it in the <strong>Raw Data</strong> panel type to see a table with counts per log level:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltce17c34f78bb7043/6a7f0a7cbd2198629d757fd9/esql-count-by-log-level.png" alt="ES|QL query counting log entries by severity level, displayed as a table in Grafana Raw Data view" /></p>
<h3 id="browsethelatesterrors">Browse the latest errors</h3>
<p><code>WHERE</code>, <code>KEEP</code>, <code>SORT</code>, and <code>LIMIT</code> make it easy to filter down to exactly the fields and rows you care about.</p>
<pre><code>FROM logs*
| WHERE log.level == "ERROR"
| KEEP @timestamp, message, host.name
| SORT @timestamp DESC
| LIMIT 20
</code></pre>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7a502cb8f353dc39/6a7f0a80ead8ec14b8baa771/esql-filter-errors.png" alt="ES|QL query filtering error-level log entries and displaying timestamp, message, and host name columns" /></p>
<h3 id="logvolumeovertime">Log volume over time</h3>
<p>Use <code>BUCKET</code> to group log counts into hourly intervals.
This works well with the <strong>Metrics</strong> panel type, which can render it as a time series graph.</p>
<pre><code>FROM logs*
| STATS count = COUNT(*) BY bucket = BUCKET(@timestamp, 1 hour)
| SORT bucket ASC
</code></pre>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltae0b9968d9979e24/6a7f0a82eab5be6b1a20a5cd/esql-log-volume-time-series.png" alt="ES|QL query aggregating log volume into hourly buckets, rendered as a line graph in Grafana Metrics view" /></p>
<h3 id="tophostsbylogactivity">Top hosts by log activity</h3>
<p>Identifying the most active hosts is a common operations task.
<code>STATS</code> with <code>BY</code> and <code>LIMIT</code> makes it concise.</p>
<pre><code>FROM logs*
| STATS log_count = COUNT(*) BY host.name
| SORT log_count DESC
| LIMIT 10
</code></pre>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3ccdeafa3fb78b09/6a7f0a85e02fac5e545d6488/esql-top-hosts.png" alt="ES|QL query returning top 10 hosts by log count, displayed as a table in Grafana Metrics view" /></p>
<h3 id="thetscommandfortimeseriesmetrics">The TS command for time series metrics</h3>
<p>For metrics data in <a href="https://www.elastic.co/docs/manage-data/data-store/data-streams/time-series-data-stream-tsds">Time Series Data Streams (TSDS)</a>, the <code>TS</code> source command in ES|QL enables metrics analytics and time series aggregation.</p>
<p>Examples:</p>
<ul>
<li><code>RATE()</code>: rate of change over time</li>
<li><code>AVG_OVER_TIME()</code>: average value over a sliding window</li>
<li><code>INCREASE()</code>: total increase over a period</li>
<li><code>DELTA()</code>: difference between first and last value</li>
<li><code>LAST_OVER_TIME()</code>: most recent value in a window</li>
</ul>
<p>The pattern follows a two-level aggregation: an inner function applied per individual time series, then an outer function aggregating across groups (for example, per host or per service).</p>
<pre><code>TS metrics*
  | STATS SUM(RATE(metrics.system.cpu.time)) BY TBUCKET(10 m)
</code></pre>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6da41645da07f6d4/6a7f0a8842a1179b0f95bdb8/ts-command-metrics-graph.png" alt="TS command query in Grafana showing a CPU time rate metric plotted over time" /></p>
<p>You can also use <a href="https://www.elastic.co/docs/reference/query-languages/esql/functions-operators/time-series-aggregation-functions/avg_over_time"><code>AVG_OVER_TIME()</code></a> to compute the average value of a metric over a sliding window, then split the results by host and 10-minute buckets:</p>
<pre><code>TS metrics*
  | STATS MAX(AVG_OVER_TIME(metrics.system.memory.utilization)) BY host.name,TBUCKET(10m)
</code></pre>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf52c64da47a02beb/6a7f0a8b3cab1cdf430e4774/ts-avg-over-time-by-host.png" alt="TS command query using AVG_OVER_TIME showing memory utilization per host in 10-minute buckets" /></p>
<p><code>TS</code> also runs queries through the ES|QL vectorized compute engine.
Internal benchmarks show performance improvements of an order of magnitude or more compared to equivalent Query DSL queries for TSDS-backed data.</p>
<p>Reference:</p>
<ul>
<li><a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/ts">TS command documentation</a></li>
<li><a href="https://www.elastic.co/docs/reference/query-languages/esql/functions-operators/time-series-aggregation-functions">Time series aggregation functions</a></li>
</ul>
<h2 id="inlineerrormessages">Inline error messages</h2>
<p>ES|QL queries run against the <a href="https://www.elastic.co/docs/reference/query-languages/esql/esql-rest"><code>/_query</code> HTTP endpoint</a> on Elasticsearch.
If your query has a syntax error or references a non-existent field, Elasticsearch returns a structured error response.
The plugin surfaces this directly in the query editor as an inline message, so you see exactly what went wrong right in the Grafana UI.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5e8759acb622b412/6a7f0a8e63e959083973dcec/esql-inline-error.png" alt="Grafana Elasticsearch plugin showing an inline ES|QL error message for an unknown column reference" /></p>
<p>In the example above, <code>host.nam</code> is missing the final <code>e</code>.
Elasticsearch catches this as a verification exception and returns the field name that could not be resolved.
That message appears inline, right below the query editor.</p>
<h2 id="technicaldetails">Technical details</h2>
<p>Under the hood, the plugin handles ES|QL and other query types on separate code paths.
ES|QL queries go to the <code>/_query</code> endpoint with <code>Content-Type: application/json</code>.
Lucene and Query DSL queries continue to use <code>/_msearch</code> with <code>Content-Type: application/x-ndjson</code>.</p>
<p>This separation is intentional: <code>/_query</code> returns a different response shape that the plugin parses independently before passing data to Grafana panels.</p>
<h2 id="tryitout">Try it out!</h2>
<p>That also means this is a good moment to try it and give feedback.</p>
<p>The <a href="https://github.com/grafana/grafana/pull/117798">upstream PR</a> and the <a href="https://github.com/grafana/grafana/issues/81765">original tracking issue</a> are public.
If you run into problems or have requests, both are open for comments.</p>
<h2 id="nextsteps">Next steps</h2>
<ul>
<li>Enable the feature in your Grafana 13.0 instance with <code>elasticsearchESQLQuery = true</code></li>
<li>Try the example queries above against your own indices</li>
<li>For metrics data, give the ES|QL <code>TS</code> command a spin, against an Elasticsearch 9.2 or Serverless data source.</li>
<li>Read the full <a href="https://www.elastic.co/docs/reference/query-languages/esql">ES|QL overview</a> to explore what else the language can do</li>
</ul>
<p>If you are not yet on Elasticsearch, you can start a free trial at <a href="https://cloud.elastic.co/registration">Elastic Cloud</a>.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/esql-grafana-elasticsearch-plugin</link>
    <guid isPermaLink="false">esql-grafana-elasticsearch-plugin</guid>
    <category><![CDATA[Metrics]]></category>
    <category><![CDATA[Logs Analytics]]></category>
    <dc:creator><![CDATA[Cauê Marcondes]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7776f74f5ee55ffb/6a7f0a91e88c6580ef00b5a8/header.jpg" length="0" type="image/jpeg"/>
    <pubDate>Tue, 12 May 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Migrate Logstash Pipelines from Azure Event Hubs to OTel Collector Kafka Receiver]]></title>
    <description><![CDATA[Step-by-step guide to migrating Logstash pipelines from the Azure Event Hubs plugin to the OpenTelemetry Collector Kafka receiver.]]></description>
    <content:encoded><![CDATA[<h2 id="introduction">Introduction</h2>
<p>This article is a companion guide to the <a href="https://www.elastic.co/observability-labs/blog/migrate-logstash-pipelines-from-azure-event-hubs-to-kafka-plugin">Logstash Azure Event Hubs to Kafka input plugin migration</a>, covering an alternative path: replacing <code>logstash-input-azure_event_hubs</code> with the OpenTelemetry Collector <code>kafka</code> receiver to consume from the Azure Event Hubs Kafka endpoint. For the reasons to migrate, authentication considerations, and key behavior changes such as offset handling, refer to the original article.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt75cfba706792ba52/6a7f0d796693f89756663f69/amqp-vs-kafka_OTel.png" alt="AMQP vs Kafka protocol path comparison in Otel Collector connected to Azure Event Hubs" /></p>
<blockquote>
  <p><strong>Reference</strong>: For detailed OTel Kafka receiver configuration options or parameter default values, see the <a href="https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/receiver/kafkareceiver">Kafka Receiver README</a>.</p>
</blockquote>
<h2 id="convertingyourconfiguration">Converting your configuration</h2>
<h3 id="tlsconfiguration">TLS configuration</h3>
<p>Azure Event Hubs requires TLS for all Kafka connections on port 9093. The <code>tls: {}</code> block enables TLS with default settings (system CA certificates, no client certificate), which is sufficient for Azure Event Hubs. Omitting this block will cause the connection to fail because the broker expects a TLS handshake.</p>
<h3 id="encoding">Encoding</h3>
<p>The <code>encoding</code> field controls how the receiver interprets each Kafka message payload. For events consumed from Azure Event Hubs, the most common options are:</p>
<ul>
<li><code>text</code>: decodes the payload as text and inserts it as the body of a log record. Uses UTF-8 by default; use <code>text_&lt;ENCODING&gt;</code> (e.g., <code>text_shift_jis</code>) for other character sets.</li>
<li><code>raw</code>: inserts the payload bytes as-is into the log record body.</li>
<li><code>json</code>: decodes the payload as JSON and inserts it as the log record body.</li>
<li><code>azure_resource_logs</code>: converts Azure Resource Logs format to OpenTelemetry format.</li>
</ul>
<p>Additional encodings such as <code>otlp_proto</code>, <code>otlp_json</code>, and trace-specific formats (<code>jaeger_proto</code>, <code>zipkin_json</code>, etc.) are also available. See the <a href="https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/receiver/kafkareceiver">Kafka Receiver README</a> for the full list.</p>
<h3 id="basicconfiguration">Basic configuration</h3>
<p>Minimal configuration to consume logs from one Event Hub with SASL/PLAIN.</p>
<pre><code>receivers:
  kafka:
    brokers:
      - "&lt;NAMESPACE&gt;.servicebus.windows.net:9093"
    group_id: "&lt;CONSUMER_GROUP_NAME&gt;"
    auth:
      sasl:
        username: "$ConnectionString"
        password: "Endpoint=sb://&lt;NAMESPACE&gt;.servicebus.windows.net/;SharedAccessKeyName=&lt;ACCESS_KEY_NAME&gt;;SharedAccessKey=&lt;ACCESS_KEY&gt;"
        mechanism: "PLAIN"
    tls: {}
    logs:
      topics:
        - "&lt;EVENT_HUB_NAME&gt;"
      encoding: text
</code></pre>
<h3 id="advancedconfiguration">Advanced configuration</h3>
<p>Example with multiple Event Hubs.</p>
<pre><code>receivers:
  kafka/eh1:
    brokers:
      - "&lt;NAMESPACE&gt;.servicebus.windows.net:9093"
    group_id: "&lt;CONSUMER_GROUP_1&gt;"
    auth:
      sasl:
        username: "$ConnectionString"
        password: "Endpoint=sb://&lt;NAMESPACE&gt;.servicebus.windows.net/;SharedAccessKeyName=&lt;KEY_1&gt;;SharedAccessKey=&lt;ACCESS_KEY_1&gt;"
        mechanism: "PLAIN"
    tls: {}
    logs:
      topics:
        - "&lt;EVENT_HUB_1&gt;"
      encoding: text

  kafka/eh2:
    brokers:
      - "&lt;NAMESPACE&gt;.servicebus.windows.net:9093"
    group_id: "&lt;CONSUMER_GROUP_2&gt;"
    auth:
      sasl:
        username: "$ConnectionString"
        password: "Endpoint=sb://&lt;NAMESPACE&gt;.servicebus.windows.net/;SharedAccessKeyName=&lt;KEY_2&gt;;SharedAccessKey=&lt;ACCESS_KEY_2&gt;"
        mechanism: "PLAIN"
    tls: {}
    logs:
      topics:
        - "&lt;EVENT_HUB_2&gt;"
      encoding: text
</code></pre>
<h2 id="configurationparametersmapping">Configuration parameters mapping</h2>
<p>The following section maps each <a href="https://www.elastic.co/guide/en/logstash/current/plugins-inputs-azure_event_hubs.html"><code>logstash-input-azure_event_hubs</code></a> parameter to its OpenTelemetry Collector <code>kafka</code> receiver equivalent.</p>
<ol>
<li><p><a href="https://www.elastic.co/guide/en/logstash/current/plugins-inputs-azure_event_hubs.html#plugins-inputs-azure_event_hubs-checkpoint_interval"><code>checkpoint_interval</code></a>: Direct mapping to <code>autocommit.interval</code>.</p>
<p><strong>Units</strong>: Azure <code>checkpoint_interval</code> is in <strong>seconds</strong>. OTel <code>autocommit.interval</code> requires a duration string (e.g., <code>10s</code>, <code>500ms</code>).</p>
<p>Azure config:</p>
<pre><code>input {
    azure_event_hubs {
        # ... other params ...
        checkpoint_interval =&gt; 10 # Default 5
    }
}
</code></pre>
<p>OTel receiver equivalent:</p>
<pre><code>receivers:
  kafka:
    # ... other params ...
    autocommit:
      interval: 10s # Default 1s
</code></pre></li>
<li><p><a href="https://www.elastic.co/guide/en/logstash/current/plugins-inputs-azure_event_hubs.html#plugins-inputs-azure_event_hubs-initial_position"><code>initial_position</code></a>: Maps to <code>initial_offset</code>.</p>
<p>Azure config:</p>
<pre><code>input {
    azure_event_hubs {
        initial_position =&gt; "end"
    }
}
</code></pre>
<p>OTel receiver equivalent:</p>
<pre><code>receivers:
  kafka:
    initial_offset: latest
</code></pre>
<p>Value mapping:</p>
<p>| Azure value | OTel value |
| --- | --- |
| <code>beginning</code> | <code>earliest</code> |
| <code>end</code> | <code>latest</code> (default) |
| <code>look_back</code> | Not directly supported |</p>
<p><strong>Note:</strong> Since the Kafka receiver can't read the old Blob Storage checkpoints, it treats the migration as a first-time connection. To avoid reprocessing data the legacy plugin already handled, set <code>initial_offset: latest</code> for the initial deployment.</p></li>
<li><p><a href="https://www.elastic.co/guide/en/logstash/current/plugins-inputs-azure_event_hubs.html#plugins-inputs-azure_event_hubs-max_batch_size"><code>max_batch_size</code></a>: No direct 1:1 mapping.</p>
<p>In OTel, the maximum batch of events processed cannot be directly controlled by the receiver. The receiver only controls how much data is read per fetch request using <code>min_fetch_size</code>, <code>max_fetch_size</code>, and <code>max_fetch_wait</code>.</p>
<p>The actual event batching happens at the processing layer via the <a href="https://github.com/open-telemetry/opentelemetry-collector/blob/main/processor/batchprocessor/README.md"><code>batch processor</code></a>, which groups telemetry at the configured pipeline stage.</p>
<p><strong>Units</strong>: <code>min_fetch_size</code> and <code>max_fetch_size</code> are in <strong>bytes</strong>. <code>max_fetch_wait</code> uses duration strings (e.g., <code>250ms</code>). <code>send_batch_size</code> is the <strong>number of records</strong>. <code>timeout</code> uses duration strings (e.g., <code>5s</code>).</p>
<p>Azure config:</p>
<pre><code>input {
    azure_event_hubs {
        max_batch_size =&gt; 125
    }
}
</code></pre>
<p>OTel receiver example:</p>
<pre><code>receivers:
  kafka:
    max_fetch_size: 2097152  # bytes (2 MiB)
    max_fetch_wait: 250ms

processors:
  batch:
    send_batch_size: 125  # number of log records
</code></pre></li>
<li><p><a href="https://www.elastic.co/guide/en/logstash/current/plugins-inputs-azure_event_hubs.html#plugins-inputs-azure_event_hubs-threads"><code>threads</code></a>: No direct mapping.</p>
<p>Event Hubs distribute work by partition. A single Collector Kafka client can read from multiple partitions in parallel because the underlying Kafka client (<a href="https://pkg.go.dev/github.com/twmb/franz-go">franz-go</a>) uses internal goroutines to fetch and process partition data concurrently. This concurrency is handled internally and is not configurable via a user-facing <code>threads</code> setting.</p></li>
<li><p><a href="https://www.elastic.co/guide/en/logstash/current/plugins-inputs-azure_event_hubs.html#plugins-inputs-azure_event_hubs-decorate_events"><code>decorate_events</code></a>: Not supported by Kafka receiver.</p></li>
</ol>
<h2 id="performancecomparison">Performance comparison</h2>
<p>These results use the same test environment described in the <a href="https://www.elastic.co/observability-labs/blog/migrate-logstash-pipelines-from-azure-event-hubs-to-kafka-plugin">companion article</a>: same Event Hub namespace, same number of partitions, and same batch/thread configuration. The absolute numbers are environment-specific, but the relative difference is what matters.</p>
<p>| <strong>Component</strong>                      | <strong>Payload</strong> | <strong>Throughput (events/s)</strong> |
| ---------------------------------- | ----------- | ------------------------- |
| Logstash <code>azure_event_hubs</code> plugin | 100B        | ~5700                    |
| OTel Collector <code>kafka</code> receiver    | 100B        | ~10900                   |
| Logstash <code>azure_event_hubs</code> plugin | 1KB         | ~1500                    |
| OTel Collector <code>kafka</code> receiver    | 1KB         | ~1900                    |
| Logstash <code>azure_event_hubs</code> plugin | 10KB        | ~170                     |
| OTel Collector <code>kafka</code> receiver    | 10KB        | ~190                     |</p>
<p>Across all payload sizes, the OTel Collector <code>kafka</code> receiver outperforms the Logstash <code>azure_event_hubs</code> plugin, with the largest gain at small payloads (~1.9x at 100B) where protocol overhead dominates, narrowing at larger sizes (~1.3x at 1KB, ~1.1x at 10KB). It does not reach the throughput of the Logstash <code>kafka</code> plugin from the <a href="https://www.elastic.co/observability-labs/blog/migrate-logstash-pipelines-from-azure-event-hubs-to-kafka-plugin">companion article</a>, but it improves on the legacy plugin across all tested payload sizes. Combined with the removal of the Blob Storage and GPv2 dependencies, the OTel Collector path removes two pieces of infrastructure that need to be provisioned, secured, and monitored.</p>
<h2 id="conclusions">Conclusions</h2>
<p>Both migration paths eliminate the Blob Storage checkpoint dependency and improve throughput over the legacy <code>azure_event_hubs</code> plugin. The Logstash <code>kafka</code> plugin is the lower-friction option: the configuration change is minimal, the offset model carries over, and it delivers the highest throughput of the options tested. The OTel Collector <code>kafka</code> receiver is the better fit if you want to remove Logstash from the pipeline entirely and align with OpenTelemetry. It trades a lower peak throughput and no <code>decorate_events</code> equivalent for a vendor-neutral ingestion layer that can run alongside other OTel Collector pipelines in the same Collector.</p>
<h2 id="nextsteps">Next steps</h2>
<p>With the GPv1 retirement deadline (October 2026) approaching, starting this migration sooner reduces the time spent managing storage infrastructure that is no longer needed.</p>
<p>If any issues arise during migration:</p>
<ul>
<li><p><strong>Usage questions or help with configuration</strong>: Post on the <a href="https://github.com/open-telemetry/opentelemetry-collector/discussions">OpenTelemetry Collector GitHub Discussions</a> or the <a href="https://discuss.elastic.co/c/observability/">Elastic Discuss forum</a>.</p></li>
<li><p><strong>Bugs or unexpected behavior in the Kafka receiver</strong>: Open an issue in the <a href="https://github.com/open-telemetry/opentelemetry-collector-contrib/issues">opentelemetry-collector-contrib</a> repository.</p></li>
</ul>
<h2 id="relatedresources">Related resources</h2>
<ul>
<li><a href="https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/receiver/kafkareceiver">Kafka receiver documentation</a>: Full reference for all OTel Collector <code>kafka</code> receiver configuration parameters.</li>
<li><a href="https://www.elastic.co/guide/en/logstash/current/plugins-inputs-azure_event_hubs.html">Azure Event Hubs input plugin documentation</a>: Full reference for the legacy plugin being replaced.</li>
<li><a href="https://www.elastic.co/observability-labs/blog/migrate-logstash-pipelines-from-azure-event-hubs-to-kafka-plugin">Logstash Azure Event Hubs to Kafka input plugin migration</a>: Companion guide covering the alternative migration path to the <code>logstash-input-kafka</code> plugin.</li>
<li><a href="https://learn.microsoft.com/en-us/azure/event-hubs/azure-event-hubs-kafka-overview">Azure Event Hubs for Apache Kafka overview</a>: Microsoft's documentation on the built-in Kafka endpoint in Event Hubs.</li>
<li><a href="https://learn.microsoft.com/en-us/azure/event-hubs/event-hubs-quotas#basic-vs-standard-vs-premium-vs-dedicated-tiers">Event Hubs quotas and tier comparison</a>: Tier requirements for Kafka protocol support.</li>
</ul>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/migrate-logstash-pipelines-from-azure-event-hubs-to-otel-collector-kafka-receiver</link>
    <guid isPermaLink="false">migrate-logstash-pipelines-from-azure-event-hubs-to-otel-collector-kafka-receiver</guid>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[Logs Analytics]]></category>
    <category><![CDATA[Infrastructure Monitoring]]></category>
    <dc:creator><![CDATA[Álex Cámara]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5714aa83da345966/6a7f0d7ce88c65c3c200b6ec/elastic-blog-otel-kafka.jpeg" length="0" type="image/jpeg"/>
    <pubDate>Fri, 08 May 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[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/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[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/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/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/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/blog/logsdb-index-mode-generally-available">LogsDB GA announcement</a></li>
<li><a href="https://www.elastic.co/blog/elasticsearch-logsdb-tsds-benchmarks">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[Migrate Logstash Pipelines from Azure Event Hubs to Kafka Input Plugin]]></title>
    <description><![CDATA[Step-by-step guide to migrating Logstash pipelines from the Azure Event Hubs plugin to the Kafka input plugin to eliminate offset storage costs and improve performance.]]></description>
    <content:encoded><![CDATA[<h2 id="introduction">Introduction</h2>
<p>Azure Event Hubs natively supports the Apache Kafka protocol, which means you no longer need the <code>logstash-input-azure_event_hubs</code> plugin or an external Blob Storage account for offset checkpointing. Switching to <code>logstash-input-kafka</code> removes that storage dependency, reduces costs, and delivers up to 2.5x higher throughput.</p>
<p>This guide walks you through the migration: why it matters, how to convert your existing configuration, parameter mapping between the two plugins, and how to adapt proxy setups.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd8b3f0a2ba2bc771/6a7f0d71b6b73409d4e48e22/amqp-vs-kafka.png" alt="AMQP vs Kafka protocol path comparison for Logstash with Azure Event Hubs" /></p>
<h2 id="whymigrate">Why migrate?</h2>
<p>The migration from the Azure Event Hubs plugin to the Kafka input plugin is motivated by several factors:</p>
<ol>
<li><p><strong>Azure Event Hubs already speaks Kafka natively.</strong> Event Hubs exposes a <a href="https://learn.microsoft.com/en-us/azure/event-hubs/azure-event-hubs-kafka-overview">built-in Apache Kafka endpoint</a> on Standard, Premium, and Dedicated tiers. This means the <code>logstash-input-azure_event_hubs</code> plugin is no longer necessary. The standard <code>logstash-integration-kafka</code> (input) plugin connects directly to the same service with no extra Azure-side configuration.</p></li>
<li><p><strong>No more Blob Storage for offset checkpointing.</strong> The AMQP-based plugin requires an <a href="https://learn.microsoft.com/en-us/azure/event-hubs/event-processor-balance-partition-load#checkpoint">external Azure Blob Storage account</a> to track consumer offsets. This means provisioning and maintaining a storage account, plus paying for every checkpoint write. With the Kafka protocol, <a href="https://learn.microsoft.com/en-us/azure/event-hubs/apache-kafka-frequently-asked-questions#event-hubs-consumer-group-vs--kafka-consumer-group">offset tracking is handled internally by Azure Event Hubs at no extra cost</a>, removing the need for external storage.</p></li>
<li><p><strong>GPv1 storage retirement is coming, and GPv2 costs more.</strong> Microsoft will <a href="https://learn.microsoft.com/en-us/azure/storage/common/general-purpose-version-1-account-migration-overview">retire general-purpose v1 storage accounts in October 2026</a>. Accounts not manually <a href="https://learn.microsoft.com/en-us/azure/storage/common/storage-account-upgrade">upgraded to GPv2</a> by then will be migrated automatically. The <code>logstash-input-azure_event_hubs</code> plugin works correctly with GPv2, so existing pipelines will not break. However, GPv2 can bring <a href="https://learn.microsoft.com/en-us/azure/storage/common/storage-account-upgrade#billing-impact-of-upgrading">higher transactional costs</a>, especially for checkpoint-heavy workloads. By switching to the Kafka input plugin, this concern is eliminated: no storage account means nothing to upgrade and nothing to pay for.</p>
<p><strong>Not ready to migrate yet? Reducing GPv2 costs in the meantime is possible.</strong> GPv2 transaction pricing is significantly more expensive than GPv1's flat rate. Increasing the <a href="https://www.elastic.co/guide/en/logstash/current/plugins-inputs-azure_event_hubs.html#plugins-inputs-azure_event_hubs-checkpoint_interval"><code>checkpoint_interval</code></a> setting above its default of 5 seconds reduces write operations and lowers the cost impact. The cost difference can be estimated using the <a href="https://azure.microsoft.com/en-us/pricing/calculator/">Azure Pricing Calculator</a>.</p>
<p>Example for East US and Local Retention Storage. Write operation cost comparison (per 10,000 write operations):</p>
<ul>
<li><p><strong>GPv1 (flat):</strong> $0.00036</p></li>
<li><p><strong>GPv2 (Hot tier):</strong> $0.050</p></li></ul>
<p>That's roughly a 140x increase in write operation costs.</p></li>
<li><p><strong>Broader community and active maintenance.</strong> The Kafka input plugin is more widely used across Logstash deployments and receives regular updates aligned with the Kafka ecosystem. Moving to it reduces long-term operational risk and keeps your pipeline on a well-supported path.</p></li>
<li><p><strong>Better throughput.</strong> The Kafka input plugin consistently outperforms the Azure Event Hubs plugin when consuming from the same namespace. See the <a href="https://www.elastic.co/observability-labs/blog/migrate-logstash-pipelines-from-azure-event-hubs-to-kafka-plugin#performance-comparison">Performance Comparison</a> section for measured results.</p></li>
</ol>
<h2 id="requirementstoenablethekafkainterface">Requirements to enable the Kafka interface</h2>
<p>The Kafka interface is built into Azure Event Hubs. You don't need to enable or configure anything in the Azure portal.</p>
<p>The only requirement is that your Event Hubs namespace is on the <strong>Standard</strong>, <strong>Premium</strong>, or <strong>Dedicated</strong> tier. The Basic tier does not support the Kafka protocol.</p>
<p>See the <a href="https://learn.microsoft.com/en-us/azure/event-hubs/event-hubs-quotas#basic-vs-standard-vs-premium-vs-dedicated-tiers">Tiers comparison table</a> for details.</p>
<h2 id="convertingyourconfiguration">Converting your configuration</h2>
<p>This section walks through converting an existing <code>logstash-input-azure_event_hubs</code> configuration to <code>logstash-input-kafka</code>, starting with the simplest single-hub scenario and building up to multi-hub and advanced use cases.</p>
<h3 id="keybehaviorchanges">Key behavior changes</h3>
<p>Before changing any configuration, be aware of two important differences:</p>
<ol>
<li><p><strong>No more Blob Storage for offsets.</strong> The Kafka input plugin tracks offsets internally through the Azure Event Hubs service at no extra cost. The <code>storage_connection</code> and <code>storage_container</code> parameters have no equivalent. There is nothing to provision, maintain, or pay for.</p></li>
<li><p><strong>Consumer offsets don't carry over.</strong> AMQP consumer groups and Kafka consumer groups are completely separate, even if they share the same name. When the Kafka input plugin connects for the first time, Azure auto-creates the Kafka consumer group specified in <a href="https://www.elastic.co/guide/en/logstash/current/plugins-inputs-kafka.html#plugins-inputs-kafka-group_id"><code>group_id</code></a> (default: <code>logstash</code>). <strong>It will not read the old Blob Storage checkpoints or resume from where the legacy plugin left off.</strong> It starts fresh.</p></li>
</ol>
<p>|                     | Event Hubs (AMQP) consumer groups       | Kafka consumer groups              |
| ------------------- | --------------------------------------- | ---------------------------------- |
| <strong>Protocol</strong>        | AMQP                                    | Kafka                              |
| <strong>Offset storage</strong>  | External Azure Blob Storage             | Internal to the Event Hubs service |
| <strong>Creation</strong>        | Must be created via portal, SDK, or ARM | Auto-created on first connection   |
| <strong>Namespace scope</strong> | Scoped to a single Event Hub            | Span the entire namespace          |</p>
<p><strong>Limit:</strong> A maximum of 1,000 simultaneous Kafka consumer groups per namespace is allowed. See the <a href="https://learn.microsoft.com/en-us/azure/event-hubs/apache-kafka-frequently-asked-questions#event-hubs-consumer-group-vs--kafka-consumer-group">Event Hubs vs. Kafka Consumer Groups FAQ</a>.</p>
<h3 id="authentication">Authentication</h3>
<p>The <code>logstash-input-azure_event_hubs</code> plugin only supports <strong>SAS (Shared Access Signature)</strong> authentication via connection strings. The same SAS credentials work with the Kafka plugin through SASL PLAIN, as shown in the <a href="https://www.elastic.co/observability-labs/blog/migrate-logstash-pipelines-from-azure-event-hubs-to-kafka-plugin#single-event-hub-basic-migration">Single Event Hub (basic migration)</a> example below.</p>
<h3 id="singleeventhubbasicmigration">Single Event Hub (basic migration)</h3>
<p>Most pipelines start with a single Event Hub, SAS authentication, and Blob Storage checkpointing. The following example shows the baseline <code>azure_event_hubs</code> configuration and its direct Kafka equivalent.</p>
<p><strong>Before</strong> (legacy Azure Event Hubs input):</p>
<pre><code>input {
  azure_event_hubs {
    event_hub_connections =&gt; ["Endpoint=sb://&lt;NAMESPACE&gt;.servicebus.windows.net/;SharedAccessKeyName=&lt;ACCESS_KEY_NAME&gt;;SharedAccessKey=&lt;ACCESS_KEY&gt;;EntityPath=&lt;EVENT_HUB_NAME&gt;"]
    storage_connection =&gt; "DefaultEndpointsProtocol=https;AccountName=&lt;STORAGE_ACCOUNT_NAME&gt;;AccountKey=&lt;STORAGE_ACCOUNT_KEY&gt;;EndpointSuffix=core.windows.net"
    consumer_group =&gt; "&lt;CONSUMER_GROUP_NAME&gt;"
    storage_container =&gt; "&lt;STORAGE_NAME&gt;"
  }
}
</code></pre>
<p><strong>After</strong> (Kafka input):</p>
<pre><code>input {
  kafka {
    # The Namespace name and the mandatory Kafka SSL port
    bootstrap_servers =&gt; "&lt;NAMESPACE&gt;.servicebus.windows.net:9093"

    topics =&gt; ["&lt;EVENT_HUB_NAME&gt;"]
    group_id =&gt; "&lt;KAFKA_CONSUMER_GROUP_NAME&gt;"
    security_protocol =&gt; "SASL_SSL"
    sasl_mechanism =&gt; "PLAIN"

    # Need to create a 'jaas.conf' file storing Username and Password (username is always '$ConnectionString')
    jaas_path =&gt; "path/to/jaas.conf"
  }
}
</code></pre>
<pre><code>KafkaClient {
    org.apache.kafka.common.security.plain.PlainLoginModule required
    username="$ConnectionString" 
    password="Endpoint=sb://&lt;NAMESPACE&gt;.servicebus.windows.net/;SharedAccessKeyName=&lt;ACCESS_KEY_NAME&gt;;SharedAccessKey=&lt;ACCESS_KEY&gt;";
};
</code></pre>
<pre><code># Inline JAAS configuration (substitutes jaas_path)
    sasl_jaas_config =&gt; "org.apache.kafka.common.security.plain.PlainLoginModule required username='$ConnectionString' password='Endpoint=sb://&lt;NAMESPACE&gt;.servicebus.windows.net/;SharedAccessKeyName=&lt;ACCESS_KEY_NAME&gt;;SharedAccessKey=&lt;ACCESS_KEY&gt;';"
</code></pre>
<h3 id="multipleeventhubswithasinglekafkainput">Multiple Event Hubs with a single Kafka input</h3>
<p>If your SAS policy has <strong>namespace-level read rights</strong> (not just a single Event Hub), you can consume from multiple Event Hubs with a single <code>kafka</code> input by listing multiple topics:</p>
<pre><code>input {
  kafka {
    bootstrap_servers =&gt; "&lt;NAMESPACE&gt;.servicebus.windows.net:9093"
    topics =&gt; ["&lt;EVENT_HUB_1&gt;", "&lt;EVENT_HUB_2&gt;", "&lt;EVENT_HUB_3&gt;"]
    group_id =&gt; "&lt;KAFKA_CONSUMER_GROUP_NAME&gt;"
    security_protocol =&gt; "SASL_SSL"
    sasl_mechanism =&gt; "PLAIN"
    jaas_path =&gt; "path/to/jaas.conf"
  }
}
</code></pre>
<h2 id="configurationparametersmapping">Configuration parameters mapping</h2>
<p>The following section maps each <code>logstash-input-azure_event_hubs</code> parameter to its <code>logstash-input-kafka</code> equivalent, with usage notes and example configurations.</p>
<ol>
<li><p><a href="https://www.elastic.co/guide/en/logstash/current/plugins-inputs-azure_event_hubs.html#plugins-inputs-azure_event_hubs-config_mode"><code>config_mode</code></a>: No direct equivalent. Kafka doesn't have "basic" vs "advanced" modes. To consume from multiple hubs with different settings, define multiple <code>kafka {}</code> input blocks or list multiple topics. The basic mode conversion is covered in <a href="https://www.elastic.co/observability-labs/blog/migrate-logstash-pipelines-from-azure-event-hubs-to-kafka-plugin#single-event-hub-basic-migration">Single Event Hub (basic migration)</a>.</p>
<p>Here is an advanced-mode example with two Event Hubs in the same namespace:</p>
<pre><code>input {
    azure_event_hubs {
        config_mode =&gt; "advanced"
        storage_connection =&gt; "DefaultEndpointsProtocol=https;AccountName=&lt;STORAGE_ACCOUNT&gt;;..."
        event_hubs =&gt; [
            {"&lt;EVENT_HUB_1&gt;" =&gt; {
                event_hub_connection =&gt; "Endpoint=sb://&lt;NAMESPACE&gt;.servicebus.windows.net/;SharedAccessKeyName=&lt;KEY_1&gt;;SharedAccessKey=&lt;ACCESS_KEY&gt;;EntityPath=&lt;EVENT_HUB_1&gt;"
                consumer_group =&gt; "&lt;CONSUMER_GROUP_1&gt;"
            }},
            {"&lt;EVENT_HUB_2&gt;" =&gt; {
                event_hub_connection =&gt; "Endpoint=sb://&lt;NAMESPACE&gt;.servicebus.windows.net/;SharedAccessKeyName=&lt;KEY_2&gt;;SharedAccessKey=&lt;ACCESS_KEY&gt;;EntityPath=&lt;EVENT_HUB_2&gt;"
                consumer_group =&gt; "&lt;CONSUMER_GROUP_2&gt;"
            }}
        ]
    }
}
</code></pre>
<pre><code>input {
    kafka {
        bootstrap_servers =&gt; "&lt;NAMESPACE&gt;.servicebus.windows.net:9093"
        topics =&gt; ["&lt;EVENT_HUB_1&gt;"]
        group_id =&gt; "&lt;KAFKA_CONSUMER_GROUP_1&gt;"
        security_protocol =&gt; "SASL_SSL"
        sasl_mechanism =&gt; "PLAIN"
        sasl_jaas_config =&gt; "...&lt;KEY_1&gt;..."
    }
    kafka {
        bootstrap_servers =&gt; "&lt;NAMESPACE&gt;.servicebus.windows.net:9093"
        topics =&gt; ["&lt;EVENT_HUB_2&gt;"]
        group_id =&gt; "&lt;KAFKA_CONSUMER_GROUP_2&gt;"
        security_protocol =&gt; "SASL_SSL"
        sasl_mechanism =&gt; "PLAIN"
        sasl_jaas_config =&gt; "...&lt;KEY_2&gt;..."
    }
}
</code></pre></li>
<li><p><a href="https://www.elastic.co/guide/en/logstash/current/plugins-inputs-azure_event_hubs.html#plugins-inputs-azure_event_hubs-checkpoint_interval"><code>checkpoint_interval</code></a>: This corresponds to <a href="https://www.elastic.co/guide/en/logstash/current/plugins-inputs-kafka.html#plugins-inputs-kafka-auto_commit_interval_ms"><code>auto_commit_interval_ms</code></a>.</p>
<p>In the Azure plugin, this controls how often a write operation hits the Blob Storage container to save the reading offset. In the Kafka plugin, it controls how often the consumer commits its offset to the Event Hubs service.</p>
<p><strong>Note</strong> Keep <code>enable_auto_commit</code> set to <code>true</code> (default) while configuring <code>auto_commit_interval_ms</code> parameter.</p>
<p>Azure config:</p>
<pre><code>input {
    azure_event_hubs {
        # ... other params ...
        checkpoint_interval =&gt; 10 # in seconds
    }
}
</code></pre>
<p>Kafka equivalent:</p>
<pre><code>input {
    kafka {
        # ... other params ...
        auto_commit_interval_ms =&gt; 10000 # in milliseconds 
    }
}
</code></pre></li>
<li><p><a href="https://www.elastic.co/guide/en/logstash/current/plugins-inputs-azure_event_hubs.html#plugins-inputs-azure_event_hubs-decorate_events"><code>decorate_events</code></a>: This parameter exists in both plugins with the same name and behavior.</p></li>
<li><p><a href="https://www.elastic.co/guide/en/logstash/current/plugins-inputs-azure_event_hubs.html#plugins-inputs-azure_event_hubs-initial_position"><code>initial_position</code></a>: This corresponds to <a href="https://www.elastic.co/guide/en/logstash/current/plugins-inputs-kafka.html#plugins-inputs-kafka-auto_offset_reset"><code>auto_offset_reset</code></a>.</p>
<p>Both parameters control where to start reading when no prior offset is found at checkpoint storage. Options differ slightly:</p>
<ul>
<li><p>Azure: <code>beginning</code>, <code>end</code>, <code>look_back</code></p></li>
<li><p>Kafka: <code>earliest</code>, <code>latest</code>, <code>by_duration:&lt;duration&gt;</code>, <code>none</code></p></li></ul>
<p>The difference between beginning-end and earliest-latest is purely terminology.</p>
<p>Azure config:</p>
<pre><code>input {
    azure_event_hubs {
        initial_position =&gt; "beginning"
    }
}
</code></pre>
<p>Kafka equivalent:</p>
<pre><code>input {
    kafka {
        auto_offset_reset =&gt; "earliest"
    }
}
</code></pre>
<p>| Azure Value | Kafka Value                | Notes                                                        |
| ----------- | -------------------------- | ------------------------------------------------------------ |
| <code>beginning</code> | <code>earliest</code>                 |                                                              |
| <code>end</code>       | <code>latest</code>                   |                                                              |
| <code>look_back</code> | <code>by_duration:&lt;duration&gt;</code>   | Duration in ISO 8601 format (e.g., <code>by_duration:PT1H</code> for 1 hour). Requires <code>logstash-integration-kafka</code> 12.1.0+.|</p>
<p>The <code>by_duration</code> option was introduced in Apache Kafka client 4.0.0 and is available in <code>logstash-integration-kafka</code> version 12.1.0 and later. The version bundled with the latest Logstash release is older than 12.1.0, so a manual gem update is needed:</p>
<pre><code>&lt;LOGSTASH_HOME&gt;/bin/logstash-plugin install --version 12.1.0 logstash-integration-kafka
</code></pre>
<p>Replace <code>&lt;LOGSTASH_HOME&gt;</code> with the Logstash installation directory (e.g., <code>/usr/share/logstash</code> for DEB/RPM packages).</p>
<p><strong>Note:</strong> Since Kafka can't read the old Blob Storage checkpoints, it treats the migration as a first-time connection. To avoid reprocessing data the legacy plugin already handled, set <code>auto_offset_reset =&gt; "latest"</code> for the initial deployment.</p></li>
<li><p><a href="https://www.elastic.co/guide/en/logstash/current/plugins-inputs-azure_event_hubs.html#plugins-inputs-azure_event_hubs-max_batch_size"><code>max_batch_size</code></a>: This corresponds to <a href="https://www.elastic.co/guide/en/logstash/current/plugins-inputs-kafka.html#plugins-inputs-kafka-max_poll_records"><code>max_poll_records</code></a>.</p>
<p>Both parameters define the maximum number of messages to fetch in a single poll/batch operation.</p>
<p>Azure config:</p>
<pre><code>input {
    azure_event_hubs {
        max_batch_size =&gt; 125
    }
}
</code></pre>
<p>Kafka equivalent:</p>
<pre><code>input {
    kafka {
        max_poll_records =&gt; "125"
    }
}
</code></pre></li>
<li><p><a href="https://www.elastic.co/guide/en/logstash/current/plugins-inputs-azure_event_hubs.html#plugins-inputs-azure_event_hubs-threads"><code>threads</code></a>: This corresponds to <a href="https://www.elastic.co/guide/en/logstash/current/plugins-inputs-kafka.html#plugins-inputs-kafka-consumer_threads"><code>consumer_threads</code></a>.</p>
<p>Both parameters control the number of threads used to consume messages concurrently. In Azure, the minimum is 2 (with 1 Event Hub + 1), while in Kafka the default is 1 thread.</p>
<p>Azure config:</p>
<pre><code>input {
    azure_event_hubs {
        threads =&gt; 8
    }
}
</code></pre>
<p>Kafka equivalent:</p>
<pre><code>input {
    kafka {
        consumer_threads =&gt; 8
    }
}
</code></pre></li>
</ol>
<h2 id="performancecomparison">Performance Comparison</h2>
<p>We tested both plugins under identical conditions: same Logstash instance, same Event Hub namespace, same number of partitions, and same batch/thread configuration. The absolute numbers are environment-specific, but the relative difference is what matters.</p>
<p>| <strong>Plugin</strong>         | <strong>Payload</strong> | <strong>Throughput (events/s)</strong> |
| ------------------ | ----------- | ------------------------- |
| <code>azure_event_hubs</code> | 100B        | ~5700                    |
| <code>kafka</code>            | 100B        | ~14500                   |
| <code>azure_event_hubs</code> | 1KB         | ~1500                    |
| <code>kafka</code>            | 1KB         | ~3200                    |
| <code>azure_event_hubs</code> | 10KB        | ~170                     |
| <code>kafka</code>            | 10KB        | ~290                     |</p>
<p>Across all payload sizes, the Kafka input plugin delivers 1.7x to 2.5x higher throughput. The gain is most noticeable with small payloads, where protocol overhead dominates. Beyond the infrastructure simplification (no Blob Storage, no GPv2 concerns), you also get a clear performance win.</p>
<h2 id="proxyconnectionconfiguration">Proxy connection configuration</h2>
<blockquote>
  <p>If the Logstash instance connects directly to Azure Event Hubs without a proxy, this section can be skipped.</p>
</blockquote>
<p>Proxy setups require special attention during this migration because the two plugins use fundamentally different protocols.</p>
<h3 id="azureeventhubspluginsetupreference">Azure Event Hubs plugin setup (reference)</h3>
<p>The <code>logstash-input-azure_event_hubs</code> plugin supports HTTPS proxies. The setup involves:</p>
<ol>
<li><p>Set the proxy environment variable:</p>
<pre><code>export https_proxy="https://my_proxy:8080"
</code></pre></li>
<li><p>Add the WebSockets transport flag to the Event Hubs connection string:</p>
<pre><code>;TransportType=AmqpWebSockets
</code></pre></li>
<li><p>Add the following JVM options (Logstash <code>jvm.options</code>):</p>
<pre><code>-Dhttp.proxyHost=my_proxy
-Dhttp.proxyPort=8080
-Dhttps.proxyHost=my_proxy
-Dhttps.proxyPort=8443
-Dhttp.nonProxyHosts=localhost|127.0.0.1
</code></pre></li>
</ol>
<h3 id="migratingtoatcplayer4proxy">Migrating to a TCP (Layer 4) proxy</h3>
<p>The proxy setup from the Azure plugin is not compatible with the Kafka client. The Azure plugin communicates over AMQP/WebSockets (HTTP layer), which is why JVM proxy settings and <code>TransportType=AmqpWebSockets</code> work. The Kafka plugin opens a raw TCP socket to the broker. It never makes an HTTP request, so <strong>JVM HTTP proxy settings are ignored entirely</strong>. If the environment requires a proxy, the HTTP proxy needs to be replaced with a TCP (Layer 4) proxy.</p>
<h4 id="step1configureetchosts">Step 1: Configure <code>/etc/hosts</code></h4>
<p>The Kafka client verifies that the TLS certificate matches the hostname in <code>bootstrap_servers</code>. Since the certificate is issued for <code>*.servicebus.windows.net</code>, <code>bootstrap_servers</code> must use the real Event Hubs FQDN, not the proxy address. A DNS override routes the FQDN to the proxy IP:</p>
<pre><code># /etc/hosts
&lt;PROXY_HOST_IP&gt;  &lt;NAMESPACE&gt;.servicebus.windows.net
</code></pre>
<h4 id="step2logstashconfiguration">Step 2: Logstash configuration</h4>
<p>The Logstash configuration is identical to a non-proxied setup. The <code>/etc/hosts</code> override transparently routes traffic through the proxy, so <code>bootstrap_servers</code> still uses the Event Hubs FQDN:</p>
<pre><code>input {
  kafka {
    bootstrap_servers =&gt; "&lt;NAMESPACE&gt;.servicebus.windows.net:9093"
    topics =&gt; ["&lt;EVENT_HUB_NAME&gt;"]
    security_protocol =&gt; "SASL_SSL"
    sasl_mechanism =&gt; "PLAIN"
    group_id =&gt; "&lt;GROUP_ID&gt;"
    jaas_path =&gt; "&lt;PATH_TO_JAAS_FILE&gt;"
  }
}
</code></pre>
<p>If the TCP proxy runs on the same host as Logstash or within a trusted network segment, the DNS override is not needed. Instead, point <code>bootstrap_servers</code> directly to the proxy IP (e.g., <code>&lt;PROXY_HOST_IP&gt;:9093</code>) and change <code>security_protocol</code> to <code>SASL_PLAINTEXT</code>. This delegates the TLS handshake to the proxy, while the link between Logstash and the proxy stays unencrypted. Only use this configuration when the Logstash-to-proxy path is secure.</p>
<pre><code>input {
  kafka {
    bootstrap_servers =&gt; "&lt;PROXY_HOST_IP&gt;:9093"
    security_protocol =&gt; "SASL_PLAINTEXT"
  }
}
</code></pre>
<h2 id="frequentlyaskedquestions">Frequently asked questions</h2>
<p><strong>Are events lost when switching from the Azure Event Hubs plugin to the Kafka plugin?</strong></p>
<p>No. Events remain available within the configured retention period regardless of which protocol reads them. What changes is where the consumer starts reading. Since the Kafka plugin cannot access the AMQP plugin's Blob Storage checkpoints, it starts from scratch. Set <code>auto_offset_reset =&gt; "earliest"</code> to reprocess all retained events, or <code>auto_offset_reset =&gt; "latest"</code> to consume only new ones from the switchover point. See the <a href="https://www.elastic.co/observability-labs/blog/migrate-logstash-pipelines-from-azure-event-hubs-to-kafka-plugin#configuration-parameters-mapping"><code>initial_position</code> mapping</a> for details.</p>
<p><strong>What happens to the Azure Blob Storage account after migration?</strong></p>
<p>It is no longer needed for offset checkpointing. Once the Kafka plugin is confirmed to be consuming correctly and the <code>azure_event_hubs</code> input has been decommissioned, the storage account (or at least the checkpoint container) can be safely deleted. If the storage account is used for other purposes, only remove the specific container referenced in <code>storage_container</code>.</p>
<p><strong>Can the same consumer group name be reused?</strong></p>
<p>Yes, but it has no practical effect. AMQP and Kafka consumer groups are completely independent even if they share the same name. They use different protocols, different offset storage, and different scoping rules. Reusing the name will not cause the Kafka plugin to resume from the AMQP plugin's last checkpoint.</p>
<p><strong>Are other authentication methods supported?</strong></p>
<p>The <code>logstash-input-azure_event_hubs</code> plugin only supports SAS connection strings, so SAS is the only credential that needs to be carried over. There is no Entra ID, OAUTHBEARER, or managed identity configuration to migrate. The <code>logstash-input-kafka</code> plugin does support SASL OAUTHBEARER, so adopting token-based authentication becomes possible after migration.</p>
<p><strong>What if the proxy only allows traffic on port 443?</strong></p>
<p>The Kafka endpoint on Azure Event Hubs requires port 9093. If the TCP proxy only forwards port 443, it must be reconfigured to also allow port 9093 for the Event Hubs FQDN (<code>*.servicebus.windows.net</code>). Azure Event Hubs does not expose a Kafka listener on port 443.</p>
<h2 id="nextsteps">Next steps</h2>
<p>With the GPv1 retirement deadline (October 2026) approaching, starting this migration sooner reduces the time spent managing storage infrastructure that is no longer needed.</p>
<p>If any issues arise during migration:</p>
<ul>
<li><p><strong>Usage questions or help with configuration</strong>: Post on the <a href="https://discuss.elastic.co/c/logstash/">Elastic Discuss forum</a>.</p></li>
<li><p><strong>Bugs or unexpected behavior in the Kafka plugin</strong>: Open an issue in the <a href="https://github.com/logstash-plugins/logstash-integration-kafka/issues">logstash-integration-kafka</a>.</p></li>
</ul>
<h2 id="relatedresources">Related resources</h2>
<ul>
<li><a href="https://www.elastic.co/guide/en/logstash/current/plugins-inputs-kafka.html">Kafka input plugin documentation</a>: Full reference for all <code>logstash-input-kafka</code> configuration parameters.</li>
<li><a href="https://www.elastic.co/guide/en/logstash/current/plugins-inputs-azure_event_hubs.html">Azure Event Hubs input plugin documentation</a>: Full reference for the legacy plugin being replaced.</li>
<li><a href="https://learn.microsoft.com/en-us/azure/event-hubs/azure-event-hubs-kafka-overview">Azure Event Hubs for Apache Kafka overview</a>: Microsoft's documentation on the built-in Kafka endpoint in Event Hubs.</li>
<li><a href="https://learn.microsoft.com/en-us/azure/event-hubs/event-hubs-quotas#basic-vs-standard-vs-premium-vs-dedicated-tiers">Event Hubs quotas and tier comparison</a>: Tier requirements for Kafka protocol support.</li>
</ul>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/migrate-logstash-pipelines-from-azure-event-hubs-to-kafka-plugin</link>
    <guid isPermaLink="false">migrate-logstash-pipelines-from-azure-event-hubs-to-kafka-plugin</guid>
    <category><![CDATA[Logs Analytics]]></category>
    <category><![CDATA[Infrastructure Monitoring]]></category>
    <dc:creator><![CDATA[Álex Cámara]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt53abdbbdd62c1960/6a7f0d74bd219830967580e3/elastic-blog-logstash-kafka.jpeg" length="0" type="image/jpeg"/>
    <pubDate>Mon, 06 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[Logstash Pipeline Management & Configuration with GitOps]]></title>
    <description><![CDATA[Stop treating Logstash like a black box. This guide shows you how to use GitOps to create auditable, automated, and resilient data pipelines. Eliminate config drift and boost security with this GitHub and Jenkins blueprint.]]></description>
    <content:encoded><![CDATA[<p>Is your Logstash environment a 'black box'? Are manual configuration changes leading to unexpected outages, security gaps, and countless hours spent on troubleshooting? It's time to stop treating observability infrastructure like a fragile art project. This blog post delivers a strategic blueprint for taming your Logstash pipelines, transforming them into a version-controlled, automated, and auditable asset. By adopting a GitOps approach, you can eliminate configuration drift, empower your teams to collaborate securely and ensure your observability platform is as resilient as the systems it monitors.</p>
<h2 id="fromfragileartprojecttoauditableassethowtotameyourlogstashconfigurationswithversioncontrolandautomation">From Fragile Art Project to Auditable Asset: How to Tame Your Logstash Configurations with Version Control and Automation</h2>
<p>Observability ensures system health, performance, and security. Logstash drives this by processing and routing your data. But as you scale, manual configuration management becomes a bottleneck. It leads to errors, outages, and security gaps. You need a better way.</p>
<p>This blog post shows you how to manage Logstash pipelines using GitOps. You will use Git as your single source of truth and automate deployments to increase stability, security, and efficiency of your enterprise organisation’s observability infrastructure. </p>
<p>This blog post details the benefits of this methodology and provides a practical implementation model using <strong>GitHub</strong> for version control and <strong>Jenkins</strong> for Continuous Integration and Continuous Deployment (CI/CD).</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt38e7b0cad8c807ba/6a7f0d19fc63ab62d864cc51/ls-pipeline-gitops-flow.png" alt="Logstash Central Pipeline GitOps flow" /></p>
<h2 id="theunsungherowhylogstashremainsacornerstoneofenterprisedatastrategy">The Unsung Hero: Why Logstash Remains a Cornerstone of Enterprise Data Strategy</h2>
<p>In the evolving landscape of observability and data pipelines, <strong>Logstash</strong> remains one of the most powerful and reliable components in the Elastic ecosystem. While it may not always take the spotlight, its depth of capability, flexibility, and resilience make it essential for enterprises managing complex, varied data streams. Logstash offers four main benefits:</p>
<ul>
<li><p><strong>Extensive Integration Support:</strong> Logstash supports a wide array of input and output plugins — including Kafka, syslog, Beats, cloud services, and databases — making it ideal for ingesting data from diverse environments and routing it across your architecture.</p></li>
<li><p><strong>Advanced Data Transformation:</strong> With rich filtering capabilities and optional Ruby scripting, Logstash enables complex enrichment, field manipulation, and conditional routing — allowing teams to standardise and prepare data early in the pipeline.</p></li>
<li><p><strong>Offloading Elasticsearch Ingest Load:</strong> The <a href="https://www.elastic.co/docs/reference/logstash/using-logstash-with-elastic-integrations"><code>elastic_integration</code></a> filter replicates ingest pipeline logic in Logstash, enabling upstream transformations that reduce processing overhead on Elasticsearch and streamline the indexing path.</p></li>
<li><p><strong>Operational Resilience with Persistent Queues:</strong> Logstash’s persistent-queue buffers data during downstream slowdowns or outages, helping smooth ingestion spikes, prevent data loss, and maintain stability under load.</p></li>
</ul>
<p>In modern CI/CD workflows, where automation and rapid iteration are standard, Logstash’s maturity and flexibility continue to make it a dependable choice — quietly powering the data flows that keep observability pipelines running strong.</p>
<h2 id="thecaseforagitopsdrivenobservabilitystrategy">The Case for a GitOps-Driven Observability Strategy</h2>
<p>GitOps is a paradigm that applies proven DevOps best practices such as version control, collaboration, compliance, and CI/CD to infrastructure and configuration management. When applied to Logstash, this means that every pipeline configuration is treated as code—defined, versioned, reviewed, and deployed from a Git repository.</p>
<p>For enterprise environments, the adoption of a GitOps model for Logstash pipelines offers compelling advantages:</p>
<ul>
<li><p><strong>Enhanced Auditability and Compliance:</strong> Every pipeline modification is captured as a Git commit, creating an immutable, chronological audit trail. This provides unparalleled visibility into who made what change, when, and why, which is indispensable for meeting regulatory compliance requirements and conducting security audits.</p></li>
<li><p><strong>Improved System Stability and Reliability:</strong> The risk of deploying faulty configurations is drastically reduced. By enforcing a pull request (PR) workflow, all changes undergo peer review and automated validation <em>before</em> they are merged and deployed. In the event of an incident caused by a new configuration, a rollback is as fast and straightforward as reverting a Git commit.</p></li>
<li><p><strong>Increased Automation and Operational Efficiency:</strong> Automating the deployment lifecycle eliminates manual, error-prone configuration tasks. This frees up skilled engineers from routine operational duties, allowing them to focus on higher-value activities such as optimising data flows, improving analytics, and strengthening security postures.</p></li>
<li><p><strong>Fostered Cross-Team Collaboration:</strong> Git provides a universal and well-understood platform for collaboration. Development, Security, and Operations (DevSecOps) teams can work together seamlessly on a unified codebase. This shared ownership breaks down silos and ensures that pipeline configurations are robust, secure, and fit for purpose across the organization.</p></li>
</ul>
<h2 id="implementationmodelgithubandjenkins">Implementation Model: GitHub and Jenkins</h2>
<p>This section details a practical framework for implementing a GitOps workflow for Logstash.</p>
<h3 id="1prerequisites">1. Prerequisites</h3>
<ul>
<li><p>An established <strong>GitHub</strong> organisation or account.</p></li>
<li><p>A running <strong>Jenkins</strong> instance with the necessary plugins installed (e.g., Git, GitHub Integration).</p></li>
<li><p>A target <strong>Logstash</strong> environment where configurations will be deployed.</p></li>
<li><p>Working knowledge of Git, Jenkins pipelines, and Logstash configuration syntax.</p></li>
</ul>
<h3 id="2step1establishacentralisedgitrepository">2. Step 1: Establish a Centralised Git Repository</h3>
<p>The foundation of a GitOps workflow is a version-controlled repository.</p>
<ol>
<li><p><strong>Create a Repository:</strong> In GitHub, create a new repository (e.g., logstash-configurations). This will serve as the single source of truth for all pipeline configurations.</p></li>
<li><p><strong>Define a Directory Structure:</strong> A logical directory structure is crucial for managing configurations across different environments. A recommended structure is:</p></li>
</ol>
<pre><code>    /
    ├── pipelines/
    │ &amp;nbsp; ├── development/
    │ &amp;nbsp; │ &amp;nbsp; ├── 01-input-beats.conf
    │ &amp;nbsp; │ &amp;nbsp; ├── 10-filter-nginx.conf
    │ &amp;nbsp; │ &amp;nbsp; └── 99-output-elasticsearch.conf
    │ &amp;nbsp; ├── staging/
    │ &amp;nbsp; │ &amp;nbsp; └── ...
    │ &amp;nbsp; └── production/
    │ &amp;nbsp; &amp;nbsp; &amp;nbsp; └── ...
    └── Jenkinsfile
</code></pre>
<p>This structure clearly separates configurations by environment and allows for a modular and maintainable pipeline design.</p>
<h3 id="3step2automatedeploymentwithajenkinscicdpipeline">3. Step 2: Automate Deployment with a Jenkins CI/CD Pipeline</h3>
<p>The Jenkins pipeline automates validation and deployment of the configurations from Git to your Logstash instances.</p>
<ol>
<li><p><strong>Create a</strong> <code>Jenkinsfile</code><strong>:</strong> Add a <code>Jenkinsfile</code> to the root of your repository to define the automation pipeline. This pipeline-as-code approach ensures the deployment process itself is version-controlled.</p></li>
<li><p><strong>Define the Pipeline Stages:</strong> The pipeline should include distinct stages for checking out code, validating configurations, and deploying to the target environment.</p>
<p>A sample <code>Jenkinsfile</code> could look as follows:</p>
<pre><code>pipeline {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;agent any

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// Trigger the pipeline on every push to the main branch
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;triggers {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;githubPush()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;stages {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;stage('Checkout') {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;steps {
&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;// Clone the repository
&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;git 'https://github.com/your-org/logstash-configurations.git'
&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;stage('Validate Staging Configs') {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;steps {
&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;// Run Logstash's built-in config test
&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;// This prevents syntax errors from reaching production
&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;sh 'docker run --rm -v ${WORKSPACE}/pipelines/staging:/usr/share/logstash/pipeline/ docker.elastic.co/logstash/logstash:9.3.2 logstash --config.test_and_exit'
&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;sh 'docker run --rm -v ${WORKSPACE}/pipelines/staging:/usr/share/logstash/pipeline/ docker.elastic.co/logstash/logstash:9.3.2 logstash --config.test_and_exit'
&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;stage('Deploy to Staging') {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// This stage requires Jenkins to have credentials to access the Staging server
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;steps {
&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;withCredentials([sshUserPrivateKey(credentialsId: 'staging-server-creds', keyFileVariable: 'KEY_FILE')]) {
&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;sh '''
&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;scp -i ${KEY_FILE} ${WORKSPACE}/pipelines/staging/*.conf user@staging-logstash-host:/etc/logstash/conf.d/
&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;ssh -i ${KEY_FILE} user@staging-logstash-host 'sudo systemctl reload logstash'
&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;&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;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// Optional: Add a manual approval step before deploying to production
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;stage('Approval for Production') {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;steps {
&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;input 'Deploy to Production?'
&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;stage('Deploy to Production') {
&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;steps {
&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;// Similar deployment steps for the production environment
&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;// using production credentials
&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></li>
</ol>
<h3 id="4thegitopsworkflowinpractice">4. The GitOps Workflow in Practice</h3>
<p>This setup enables a controlled, auditable, and automated workflow:</p>
<ol>
<li><p><strong>Branch Creation:</strong> An engineer creates a feature branch in Git to propose a change (e.g., <code>feature/add-syslog-input</code>).</p></li>
<li><p><strong>Configuration Change:</strong> The engineer modifies or adds a pipeline configuration file in their branch.</p></li>
<li><p><strong>Pull Request:</strong> A pull request is created in GitHub. This action can trigger automated checks in Jenkins to validate the syntax of the proposed changes.</p></li>
<li><p><strong>Peer Review:</strong> Team members review the changes for logic, security, and adherence to standards.</p></li>
<li><p><strong>Merge and Deploy:</strong> Upon approval, the PR is merged into the <code>main</code> branch. This merge automatically triggers the Jenkins pipeline, which deploys the validated configuration to the corresponding Logstash environment.</p></li>
</ol>
<h2 id="bestpracticesforenterpriseadoption">Best Practices for Enterprise Adoption</h2>
<p>To successfully implement this model at an enterprise scale, consider the following best practices:</p>
<ul>
<li><p><strong>Branching Strategy:</strong> Adopt a consistent branching strategy, such as GitFlow, to manage features, releases, and hotfixes in an orderly manner. Protect your <code>main</code> or <code>production</code> branches with rules that require PR reviews and passing status checks before merging.</p></li>
<li><p><strong>Scalability:</strong> For large-scale deployments with many Logstash nodes, use configuration management tools like Ansible, Puppet, or Chef within your Jenkins pipeline to orchestrate the deployment across your entire fleet.</p></li>
<li><p><strong>Fostering a GitOps Culture:</strong> Successful adoption is as much about people and processes as it is about tools. Provide training and documentation to ensure all stakeholders understand the workflow and their role within it. Emphasise the collaborative benefits and the shared responsibility for maintaining a stable and secure observability platform.</p></li>
<li><p><strong>Pipeline Observability</strong> (<em>Optional</em>): Monitoring the health and performance of your CI/CD pipelines is crucial and recommended for early detection of issues, visibility into bottlenecks, and auditability. Elastic Observability provides native support for monitoring Jenkins pipelines using the Elastic <a href="https://plugins.jenkins.io/opentelemetry/">CI/CD Observability plugin</a>.</p></li>
<li><p><strong>Secrets Management:</strong> Never hardcode sensitive information (passwords, API keys) in your configuration files. Use a secrets management tool like HashiCorp Vault or AWS Secrets Manager, and have Logstash retrieve these secrets at runtime.</p></li>
</ul>
<p>A sample <em>snippet</em> to retrieve the secret and store it securely in a <a href="https://www.elastic.co/docs/reference/logstash/keystore">Logstash keystore</a> could look as follows:</p>
<pre><code>```
...

    stage('Update Logstash Secret') {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// Define the secret path and key in Vault
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;def secretPath = 'secret/logstash/production'
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;def secretKey = 'elasticsearch_password'
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;def keystoreKey = 'ES_PWD' // The key name to be used in the Logstash keystore

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// Wrap the steps in withVault to get access to the secrets
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;withVault(configuration: [url: 'http://your-vault-server:8200',
&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;credentialsId: 'vault-approle-creds']) {
&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;// Retrieve the secret from Vault. The plugin makes it available as an environment variable.
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;def secrets = readVault(path: secretPath, key: secretKey)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;def esPassword = secrets[secretKey]

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// Use SSH credentials to access the Logstash server
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;withCredentials() {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;sh """
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ssh -i ${KEY_FILE} user@logstash-host &lt;&lt;'ENDSSH'
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# Pipe the secret directly into the logstash-keystore command
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# This avoids writing the secret to disk or exposing it in the process list
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;echo "${esPassword}" | sudo -u logstash /usr/share/logstash/bin/logstash-keystore add ${keystoreKey} --stdin

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;# After updating the keystore, reload Logstash to apply the change
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;sudo systemctl reload logstash
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ENDSSH
&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>
<h2 id="conclusion">Conclusion</h2>
<p>Adopting a GitOps approach for managing Logstash pipelines is a strategic move that aligns observability with modern DevSecOps principles. It replaces manual, opaque processes with an automated, transparent, and collaborative framework. For enterprise organisations, this leads to a more secure, resilient, and efficient observability infrastructure, empowering teams to derive maximum value from their data while minimising operational overhead and risk.</p>
<p>The above example is just a start; there’s a lot more you can do once you lay the foundation—GitOps is just the beginning. From branching automation to pipeline promotion workflows to building self-service deployment portals, the possibilities are limited only by your creativity (and maybe your CI minutes).</p>
<p>GitOps lays the foundation. To see the whole picture, you need to monitor your pipelines. <a href="https://cloud.elastic.co/registration">Start a trial</a> and try out Elastic’s <a href="https://www.elastic.co/docs/solutions/observability/cicd">CI/CD Observability solution</a> to track build health and deployment trends. It connects code changes to production behavior, giving you deep visibility into your new automated workflow.</p>
<p>Build smarter pipelines. Monitor what matters. And let your GitOps-powered observability stack become the quiet hero of your DevSecOps story. See how <a href="https://www.elastic.co/elasticsearch/streams">Streams</a> can supercharge your data engineering with the next generation of AI-powered log management &amp; log processing.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/logstash-pipeline-management-configuration-gitops</link>
    <guid isPermaLink="false">logstash-pipeline-management-configuration-gitops</guid>
    <category><![CDATA[Logs Analytics]]></category>
    <dc:creator><![CDATA[Adrian Chen,Vu Pham]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9a85d4a7c7330d47/6a7f0d1c5967e50d575dd2ef/continuous-improvement.png" length="0" type="image/png"/>
    <pubDate>Mon, 09 Feb 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[The observability gap: Why your monitoring strategy isn't ready for what's coming next]]></title>
    <description><![CDATA[The increasing complexity of distributed applications and the observability data they generate creates challenges for SREs and IT Operations teams. Take a look at how you can close this observability gap with OpenTelemetry and the right strategy.]]></description>
    <content:encoded><![CDATA[<p>Anyone that’s been to London knows the announcements at the Tube to “Mind the gap” but what about the gap that’s developing in our monitoring and observability strategies? I’ve been through this toil before, and have run a distributed system that was humming along perfectly. My alerts were manageable, my dashboards made sense, and when things broke, I could usually track down the issue in a reasonable amount of time. </p>
<p>Fast forward 3-5 years and things have changed, we added Kubernetes, embraced microservices, maybe these days you might have even sprinkled in some AI-powered features. Suddenly, you're drowning in telemetry data, your alert fatigue is real, and correlating issues across your distributed architecture feels stressful.</p>
<p>You're experiencing what I call the "observability gap", where system complexity rockets ahead while our monitoring maturity crawls behind. Today, we're going to explore why this gap exists, what's driving it wider, and most importantly, how to close it using modern observability practices.</p>
<h2 id="thecomplexityrocketshiphasleftthestation">The complexity rocket ship has left the station</h2>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4dfb1949be1b3004/6a7f0dd1b6b7346bd2e48e4a/ObservabilityGapBlog-Image2.jpg" alt="Observability Gap" /></p>
<p>Let's be honest about what we're dealing with. The scale and complexity of our infrastructure isn't growing linearly, it's exponential. We've gone from monolithic applications running on physical servers to container orchestration platforms managing hundreds of microservices, with AI algorithms now starting to make scaling decisions autonomously.</p>
<p>This trajectory shows no signs of slowing down. With AI-assisted coding accelerating development cycles and intelligent orchestration systems like Kubernetes evolving toward predictive scaling, we're looking at infrastructure that's not just complex, but dynamically complex.</p>
<p>Meanwhile, our observability tooling? It's stuck in the past, designed for a world where you knew exactly how many servers you had and could manually correlate logs with metrics by cross-referencing timestamps.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8fa2bca48e8d082d/6a7f0dd46c6eac494ef14183/ObservabilityGapBlog-Image3.jpg" alt="Observability Gap part 2" /></p>
<h2 id="thetelemetrydataexplosionandwhysamplingisnttheanswer">The telemetry data explosion (and why sampling isn't the answer)</h2>
<p>One of the first things teams notice as they scale is their observability bill climbing faster than their infrastructure costs. The knee-jerk reaction is often to start sampling data downsample metrics, head-sample traces, deduplicate logs. While these techniques have their place, they're fundamentally at odds with where we're heading.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blteb610051de00eee0/6a7f0dd7c2e914712f016c42/ObservabilityGapBlog-Image4.jpg" alt="Data Management: Reduce fidelity of data" /></p>
<p>Here's the thing: ML and AI systems thrive on rich, contextual data. When you sample away the "noise," you're often discarding the very signals that could help you understand system behavior patterns or predict failures. Instead of asking "how can we collect less data?", the better question is "how can we store and process all this data cost-effectively?"</p>
<p>Modern storage architectures, particularly those leveraging object storage and advanced compression techniques like ZStandard, can achieve remarkable cost-to-value ratios. The secret is organizing related data together and moving it to cheaper storage tiers quickly. This approach lets you have your cake and eat it too, full fidelity data retention without breaking the bank.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt495ce5512c1f01b6/6a7f0ddaead8ec9612baa8e8/ObservabilityGapBlog-Image5.jpg" alt="Data Management: Make Storage Cheaper" /></p>
<p>Now of course there is a balance to this and not all your applications are equal, so as a first step you should look at all your most critical flows and applications and ensure that they have the richest telemetry. Do not use a sledge hammer approach and sample all your data just to reduce bills when a scalpel is best. </p>
<h2 id="opentelemetryotelthefoundationeverythingelsebuildson">OpenTelemetry (OTel): the foundation everything else builds on</h2>
<p>If I had to pick the single most transformative change in observability during my career, it would be OpenTelemetry. Not because it's flashy or revolutionary in concept, but because it solves fundamental problems that have plagued us for years.</p>
<p>Before OTel, instrumenting applications meant vendor lock-in. Want to switch from vendor A to vendor B? Good luck re-instrumenting your entire codebase. Want to send the same telemetry to multiple backends? Hope you enjoy maintaining multiple agent configurations.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt73b2fa6ffdc6a577/6a7f0ddd3ce8e21185cf53e1/ObservabilityGapBlog-Image6.jpg" alt="What is OpenTelemetry" /></p>
<p>OpenTelemetry changes things completely. Here's the three main reasons why.</p>
<p><strong>Vendor Neutrality:</strong> Your instrumentation code becomes portable. The same OTEL SDK can send data to any compliant backend.</p>
<p><strong>OpenTelemetry Semantic Conventions:</strong> All your telemetry (logs, metrics, traces, profiles, wide-events) shares common metadata like service names, resource attributes, and trace context.</p>
<p><strong>Auto-Instrumentation:</strong> For most popular languages and frameworks, you get rich telemetry with zero code changes.</p>
<p>OTEL also makes manual instrumentation incredibly valuable with minimal effort. Adding a single line like this</p>
<p><code>baggage.set_baggage("customer.id", "alice123")</code></p>
<p>In your authentication service means that customer ID automatically flows through every downstream service call, every database query, every log message. Suddenly, you can search all your telemetry data by customer ID across your entire distributed system.</p>
<p>The trajectory is clear: within a few years, OTel will be as ubiquitous and invisible as Kubernetes is becoming today. Runtimes will include it by default, cloud providers will offer OTel collectors at the edge, and frameworks will come pre-instrumented.</p>
<h2 id="correlationthesecretsaucethatmakeseverythingclick">Correlation: the secret sauce that makes everything click</h2>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte3d2151ee8113d0d/6a7f0de0e3a219329f99f510/ObservabilityGapBlog-Image7.jpg" alt="Why do we need correlation?" /></p>
<p>You get an alert about high latency. You check your metrics dashboard yep, 95th percentile is spiking. You switch to your tracing system and you can see some slow requests. You hop over to your logging system and there are some error messages around the same time. Now comes the fun part: figuring out which logs correspond to which traces and whether they're related to the metric that alerted you.</p>
<p>This context-switching nightmare is exactly what proper correlation eliminates. When your telemetry data shares common identifiers for example, trace IDs in logs, consistent service names, synchronized timestamps or even customer IDs you can seamlessly pivot between different signal types without losing context.</p>
<p>But correlation goes beyond just technical convenience. When you can search all your logs by customer.id and immediately see the traces and metrics for that customer's journey through your system, you transform how you approach support and debugging. When you can filter your entire observability stack by deployment version and instantly understand the impact of a release, you change how you think about deployments. </p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt98aa8a4169ff243b/6a7f0de35967e539c65dd337/ObservabilityGapBlog-Image8.jpg" alt="How does this work?" /></p>
<p>Metrics? Yes, even metrics can be correlated by using OpenTelemetry exemplars, for example using python you would turn on exemplars as follows.</p>
<pre><code># Setup metrics with exemplars enabled

exemplar_filter = ExemplarFilter(trace_based=True)  

exemplar_reservoir = ExemplarReservoir(

    exemplar_filter=exemplar_filter,`

    max_exemplars=5
)
</code></pre>
<p>This would then associate metrics with a trace that happens to be occurring so you get some metrics correlated to your traces.</p>
<h2 id="thenagainwhycorrelateatall">Then again, why correlate at all?</h2>
<p>So you may be thinking, this is great and I can see this being a useful strategy. It is especially useful when you have metrics, logs and traces in separate systems, however, pretty soon you realize that it's a lot of effort when you could just combine all this data together in a single data structure and avoid the need to correlate at all. The observability industry agrees and has recently been espousing the benefits of a new signal type called wide-events. </p>
<p>Wide-events are just really structured logs, the idea is to put metric data, trace data and log data all into the same wide data structure which can make analysis much easier. Think about it, if you have a single data structure you can very quickly run queries and aggregations without having to join any data which can get pretty expensive. </p>
<p>Additionally you are increasing the information density per log record which is particularly great for AI applications.  AI gets a context-rich dataset to do analysis on with minimal latency, a single record with enough descriptive capability to quickly find the root cause of your issue without having to dig around in other data stores and try to figure out whatever schema those data stores are using. </p>
<p>LLMs especially LOVE context and if you can give them all the context they need without having them try to find it, your investigation time will significantly reduce. </p>
<p>This isn't just about making SRE life easier (though it does that). It's about creating the rich, interconnected dataset that AI and ML systems need to understand your infrastructure's behavior patterns.</p>
<h2 id="aidriveninvestigations">AI-driven investigations</h2>
<p>Observability tools today have been pretty good at solving the alerting fatigue and dashboarding problems, things have gotten quite mature there. Alert correlation and other techniques drastically reduce the noise in these domains, not to mention a focus on being alerted by SLOs instead of pure technical metrics. Life has gotten better over the past few years for SREs here. </p>
<p>Now alerts are one piece of the puzzle but the latest AI techniques using LLMs and agentic AI can unlock time savings in a different spot, during investigations. Think about it, investigations are typically what drags on when you have an outage, the cognitive overload while the pressure is on is very real and pretty stressful for SREs. </p>
<p>The good news is that when we get our data in good shape with correlation, enrichment and adopting wide-events and we store the data in full fidelity we now have the tools to help us drive faster investigations. </p>
<p>LLMs can take all that rich data and do some very powerful analysis that can cut down your investigation time. Let's walk through an example.</p>
<p>Imagine we have the following basic log. We only have a limited amount of data for an LLM to reason about. All it can tell is that a database failed. </p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltbb49613601ff570b/6a7f0de6fc63abacbc64ccc7/ObservabilityGapBlog-Image9.jpg" alt="What is a basic log" /></p>
<p>Let's see what this looks like when we use a wide-event, notice that already we can see some significant benefits, firstly we only had to visit the log from a single node, the node that serviced the request. We didn’t have to dig into downstream logs. This already makes life easier for the LLM; it doesn't have to figure out how to correlate multiple log lines and traces and metrics though we do still have correlation IDs if we desperately need to look in downstream systems.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltcbe9c352d4cc7afa/6a7f0de9bdcff02561c42ebf/ObservabilityGapBlog-Image10.jpg" alt="App Log" /></p>
<p>Next we have all this additional rich data that an LLM can use to reason about what happened. LLMs work best with context and if you can feed them as much context as possible they will work more effectively to reduce your investigation time.</p>
<p>| Field | How an LLM uses it |
| ----- | ----- |
| <code>trace_id</code>, <code>parent_span_id</code> | Thread every hop together without parsing free-text |
| <code>status.code</code>, <code>error.*</code> | Precise failure class; no NLP guess-work |
| <code>db.*</code> | Root-cause surface ("postgres isn't provisioned") |
| <code>user.id</code>, <code>cloud.region</code> | Instant blast-radius queries |
| <code>deployment.version</code> | Correlation with new releases |</p>
<p>Notice that we didn’t get rid of the unstructured error message, this is still useful context! LLMs are great at processing unstructured text so this textual description helps it understand the problem even further. </p>
<p>Large language models shine when they’re handed complete, context-rich evidence, exactly what wide-event logging supplies. Invest once in richer logs, and every downstream AI workflow (summaries, anomaly detection, natural-language queries) becomes simpler, cheaper, and far more reliable.</p>
<h2 id="buildingtowardthefuture">Building toward the future</h2>
<p>As I look ahead, three trends seem inevitable:</p>
<ol>
<li><p><strong>OpenTelemetry semantic conventions powers wide-events:</strong> OTel semantic conventions will become as standard as logging is today to create wide-events. Cloud providers, runtimes, and frameworks will use it by default.</p></li>
<li><p><strong>Making sense of logs with LLMs:</strong> Both improving the richness of your data and having LLMs automatically improve the richness of your existing logs will become essential for shortening investigation times.</p></li>
<li><p><strong>AI will be essential</strong>: As system complexity outpaces human cognitive ability to understand it, AI assistance will become necessary for maintaining reasonable investigation times.</p></li>
</ol>
<p>The organizations that start building toward this future now, adopting OpenTelemetry, investing in richer observability, and beginning to experiment with AI-assisted debugging will have a significant advantage as these trends accelerate.</p>
<h2 id="yournextsteps">Your next steps</h2>
<p>If you're dealing with the observability gap in your own environment, here's where I'd start</p>
<ol>
<li><p><strong>Evaluate your logs:</strong> Do your logs have the richness of data you need to shorten investigation times? Can LLMs help provide additional context?</p></li>
<li><p><strong>Start experimenting with OpenTelemetry:</strong> Even if you can't migrate everything immediately, instrumenting new services with OTel and using semantic conventions to produce wide-events gives you experience with the technology and starts building your enriched dataset.</p></li>
<li><p><strong>Add high-value context:</strong> Customer IDs, session IDs, deployment versions even small amounts of contextual metadata can dramatically improve your debugging capabilities.</p></li>
<li><p><strong>Think beyond storage costs:</strong> Instead of sampling data away, investigate modern storage architectures that let you keep everything at a reasonable cost for your most critical services.</p></li>
</ol>
<p>The complexity rocket ship has left the station, and it's not slowing down. The question isn't whether your observability strategy needs to evolve; it's whether you'll evolve it proactively or reactively. I know which approach leads to better sleep at night.</p>
<h2 id="additionalresources">Additional resources</h2>
<ul>
<li><a href="https://www.elastic.co/virtual-events/getting-started-logging">Getting started with logging on the ELK Stack webinar</a>  </li>
<li><a href="https://www.elastic.co/observability-labs/blog/the-next-evolution-of-observability-unifying-data-with-opentelemetry-and-generative-ai">The next evolution of observability: unifying data with OpenTelemetry and generative AI blog</a>  </li>
<li><a href="https://www.elastic.co/observability-labs/blog/elastic-agent-pivot-opentelemetry">Pivoting Elastic's Data Ingestion to OpenTelemetry blog</a></li>
</ul>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/modern-observability-opentelemetry-correlation-ai</link>
    <guid isPermaLink="false">modern-observability-opentelemetry-correlation-ai</guid>
    <category><![CDATA[Logs Analytics]]></category>
    <dc:creator><![CDATA[David Hope]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt73b0c0a781e473b3/6a7f0deb2f00b26088efebd0/ObservabilityGapBlog-Image1.jpg" length="0" type="image/jpeg"/>
    <pubDate>Mon, 25 Aug 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 ? (part 1)]]></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[<h2 id="kibanahowtocreateimpactfulvisualizationswithmagicformulaspart1">Kibana: How to create impactful visualizations with magic formulas? (part 1)</h2>
<h3 id="introduction">Introduction</h3>
<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>.\
In the next blog post, we will see how the new function <strong>ifelse</strong>() (introduced in version 8.6) will greatly simplify the creation of visualizations with more advanced formulas.</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[Monitor your Python data pipelines with OTEL]]></title>
    <description><![CDATA[Learn how to configure OTEL for your data pipelines, detect any anomalies, analyze performance, and set up corresponding alerts with Elastic.]]></description>
    <content:encoded><![CDATA[<p>This article delves into how to implement observability practices, particularly using <a href="https://opentelemetry.io/">OpenTelemetry (OTEL)</a> in Python, to enhance the monitoring and quality control of data pipelines using Elastic. While the primary focus of the examples presented in the article is ETL (Extract, Transform, Load) processes to ensure the accuracy and reliability of data pipelines that is crucial for Business Intelligence (BI), the strategies and tools discussed are equally applicable to Python processes used for Machine Learning (ML) models or other data processing tasks.</p>
<h2 id="introduction">Introduction</h2>
<p>Data pipelines, particularly ETL processes, form the backbone of modern data architectures. These pipelines are responsible for extracting raw data from various sources, transforming it into meaningful information, and loading it into data warehouses or data lakes for analysis and reporting.</p>
<p>In our organization, we have Python-based ETL scripts that play a pivotal role in exporting and processing data from Elasticsearch (ES) clusters and loading it into <a href="https://cloud.google.com/bigquery">Google BigQuery (BQ)</a>. This processed data then feeds into <a href="https://www.getdbt.com">DBT (Data Build Tool)</a> models, which further refine the data and make it available for analytics and reporting. To see the full architecture and learn how we monitor our DBT pipelines with Elastic see <a href="https://www.elastic.co/observability-labs/blog/monitor-dbt-pipelines-with-elastic-observability">Monitor your DBT pipelines with Elastic Observability</a>. In this article we focus on the ETL scripts. Given the critical nature of these scripts, it is imperative to set up mechanisms to control and ensure the quality of the data they generate.</p>
<p>The strategies discussed here can be extended to any script or application that handles data processing or machine learning models, regardless of the programming language used as long as there exists a corresponding agent that supports OTEL instrumentation. </p>
<h2 id="motivation">Motivation</h2>
<p>Observability in data pipelines involves monitoring the entire lifecycle of data processing to ensure that everything works as expected. It includes:</p>
<ol>
<li>Data Quality Control:</li>
</ol>
<ul>
<li>Detecting anomalies in the data, such as unexpected drops in record counts.</li>
<li>Verifying that data transformations are applied correctly and consistently.</li>
<li>Ensuring the integrity and accuracy of the data loaded into the data warehouse.</li>
</ul>
<ol>
<li>Performance Monitoring:</li>
</ol>
<ul>
<li>Tracking the execution time of ETL scripts to identify bottlenecks and optimize performance.</li>
<li>Monitoring resource usage, such as memory and CPU consumption, to ensure efficient use of infrastructure.</li>
</ul>
<ol>
<li>Real-time Alerting:</li>
</ol>
<ul>
<li>Setting up alerts for immediate notification of issues such as failed ETL jobs, data quality issues, or performance degradation.</li>
<li>Identify the root case of such incidents</li>
<li>Proactively addressing incidents to minimize downtime and impact on business operations</li>
</ul>
<p>Issues such as failed ETL jobs, can even point to larger infrastructure or data source data quality issues.</p>
<h2 id="stepsforinstrumentation">Steps for Instrumentation</h2>
<p>Here are the steps to automatically instrument your Python script for exporting OTEL traces, metrics, and logs.</p>
<h3 id="step1importrequiredlibraries">Step 1: Import Required Libraries</h3>
<p>We first need to install the following libraries.</p>
<pre><code>pip install elastic-opentelemetry google-cloud-bigquery[opentelemetry]
</code></pre>
<p>You can also them to your project's <code>requirements.txt</code> file and install them with <code>pip install -r requirements.txt</code>.</p>
<h4 id="explanationofdependencies">Explanation of Dependencies</h4>
<ol>
<li><p><strong>elastic-opentelemetry</strong>: This package is the Elastic Distribution for OpenTelemetry Python. Under the hood it will install the following packages: </p>
<ul>
<li><p><strong>opentelemetry-distro</strong>: This package is a convenience distribution of OpenTelemetry, which includes the OpenTelemetry SDK, APIs, and various instrumentation packages. It simplifies the setup and configuration of OpenTelemetry in your application.</p></li>
<li><p><strong>opentelemetry-exporter-otlp</strong>: This package provides an exporter that sends telemetry data to the OpenTelemetry Collector or any other endpoint that supports the OpenTelemetry Protocol (OTLP). This includes traces, metrics, and logs.</p></li>
<li><p><strong>opentelemetry-instrumentation-system-metrics</strong>: This package provides instrumentation for collecting system metrics, such as CPU usage, memory usage, and other system-level metrics.</p></li></ul></li>
<li><p><strong>google-cloud-bigquery[opentelemetry]</strong>: This package integrates Google Cloud BigQuery with OpenTelemetry, allowing you to trace and monitor BigQuery operations.</p></li>
</ol>
<h3 id="step2exportotelvariables">Step 2: Export OTEL Variables</h3>
<p>Set the necessary OpenTelemetry (OTEL) variables by getting the configuration from APM OTEL from Elastic.</p>
<p>Go to APM -&gt; Services -&gt; Add data (top left corner).</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2e703cfea77bc4fb/6a7f0e189090b08f6a84ea5f/otel-variables-1.png" alt="1 - Get OTEL variables step 1" /></p>
<p>In this section you will find the steps how to configure various APM agents. Navigate to OpenTelemetry to find the variables that you need to export. </p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb4a4b1336cbb8a5f/6a7f0e1be3a219604299f528/otel-variables-2.png" alt="2 - Get OTEL variables step 2" /></p>
<p><strong>Find OTLP Endpoint</strong>:</p>
<ul>
<li><p>Look for the section related to OpenTelemetry or OTLP configuration.</p></li>
<li><p>The <code>OTEL_EXPORTER_OTLP_ENDPOINT</code> is typically provided as part of the setup instructions for integrating OpenTelemetry with Elastic APM. It might look something like <code>https://&lt;your-apm-server&gt;/otlp</code>.</p>
<p><strong>Obtain OTLP Headers</strong>:</p></li>
<li><p>In the same section, you should find instructions or a field for OTLP headers. These headers are often used for authentication purposes.</p></li>
<li><p>Copy the necessary headers provided by the interface. They might look like <code>Authorization: Bearer &lt;your-token&gt;</code>.</p></li>
</ul>
<p>Note: Notice you need to replace the whitespace between <code>Bearer</code> and your token with <code>%20</code> in the <code>OTEL_EXPORTER_OTLP_HEADERS</code> variable when using Python.</p>
<p>Alternatively you can use a different approach for authentication using API keys (see <a href="https://github.com/elastic/elastic-otel-python?tab=readme-ov-file#authentication">instructions</a>). If you are using our <a href="https://www.elastic.co/docs/current/serverless/general/what-is-serverless-elastic">serverless offering</a> you will need to use this approach instead.  </p>
<p><strong>Set up the variables</strong>:</p>
<ul>
<li>Replace the placeholders in your script with the actual values obtained from the Elastic APM interface and execute it in your shell via the source command <code>source env.sh</code>.</li>
</ul>
<p>Below is a script to set these variables:</p>
<pre><code>#!/bin/bash
echo "--- :otel: Setting OTEL variables"
export OTEL_EXPORTER_OTLP_ENDPOINT='https://your-apm-server/otlp:443'
export OTEL_EXPORTER_OTLP_HEADERS='Authorization=Bearer%20your-token'
export OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED=true
export OTEL_PYTHON_LOG_CORRELATION=true
export ELASTIC_OTEL_SYSTEM_METRICS_ENABLED=true
export OTEL_METRIC_EXPORT_INTERVAL=5000
export OTEL_LOGS_EXPORTER="otlp,console"
</code></pre>
<p>With these variables set, we are ready for auto-instrumentation without needing to add anything to the code.</p>
<h4 id="explanationofvariables">Explanation of Variables</h4>
<ul>
<li><p><strong>OTEL_EXPORTER_OTLP_ENDPOINT</strong>: This variable specifies the endpoint to which OTLP data (traces, metrics, logs) will be sent. Replace <code>placeholder</code> with your actual OTLP endpoint.</p></li>
<li><p><strong>OTEL_EXPORTER_OTLP_HEADERS</strong>: This variable specifies any headers required for authentication or other purposes when sending OTLP data. Replace <code>placeholder</code> with your actual OTLP headers.</p></li>
<li><p><strong>OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED</strong>: This variable enables auto-instrumentation for logging in Python, allowing logs to be automatically enriched with trace context.</p></li>
<li><p><strong>OTEL_PYTHON_LOG_CORRELATION</strong>: This variable enables log correlation, which includes trace context in log entries to correlate logs with traces.</p></li>
<li><p><strong>OTEL_METRIC_EXPORT_INTERVAL</strong>: This variable specifies the metric export interval in milliseconds, in this case 5s. </p></li>
<li><p><strong>OTEL_LOGS_EXPORTER</strong>: This variable specifies the exporter to use for logs. Setting it to "otlp" means that logs will be exported using the OTLP protocol. Adding "console" specifies that logs should be exported to both the OTLP endpoint and the console. In our case for better visibility on the infa side, we choose to export to console as well.</p></li>
<li><p><strong>ELASTIC_OTEL_SYSTEM_METRICS_ENABLED</strong>: It is needed to use this variable when using the Elastic distribution as by default it is set to false. </p></li>
</ul>
<p>Note: <strong>OTEL_METRICS_EXPORTER</strong> and <strong>OTEL_TRACES_EXPORTER</strong>: This variables specify the exporter to use for metrics/traces, and are set to "otlp" by default, which means that metrics and traces will be exported using the OTLP protocol.</p>
<h3 id="runningpythonetls">Running Python ETLs</h3>
<p>We run Python ETLs with the following command:</p>
<pre><code>OTEL_RESOURCE_ATTRIBUTES="service.name=x-ETL,service.version=1.0,deployment.environment=production" &amp;&amp; opentelemetry-instrument python3 X_ETL.py 
</code></pre>
<h4 id="explanationofthecommand">Explanation of the Command</h4>
<ul>
<li><p><strong>OTEL_RESOURCE_ATTRIBUTES</strong>: This variable specifies additional resource attributes, such as <a href="https://www.elastic.co/guide/en/observability/current/apm.html">service name</a>, service version and deployment environment, that will be included in all telemetry data, you can customize these values per your needs. You can use a different service name for each script.</p></li>
<li><p><strong>opentelemetry-instrument</strong>: This command auto-instruments the specified Python script for OpenTelemetry. It sets up the necessary hooks to collect traces, metrics, and logs.</p></li>
<li><p><strong>python3 X_ETL.py</strong>: This runs the specified Python script (<code>X_ETL.py</code>).</p></li>
</ul>
<h3 id="tracing">Tracing</h3>
<p>We export the traces via the default OTLP protocol.</p>
<p>Tracing is a key aspect of monitoring and understanding the performance of applications. <a href="https://www.elastic.co/guide/en/observability/current/apm-data-model-spans.html">Spans</a> form the building blocks of tracing. They encapsulate detailed information about the execution of specific code paths. They record the start and end times of activities and can have hierarchical relationships with other spans, forming a parent/child structure.</p>
<p>Spans include essential attributes such as transaction IDs, parent IDs, start times, durations, names, types, subtypes, and actions. Additionally, spans may contain stack traces, which provide a detailed view of function calls, including attributes like function name, file path, and line number, which is especially useful for debugging. These attributes help us analyze the script's execution flow, identify performance issues, and enhance optimization efforts.</p>
<p>With the default instrumentation, the whole Python script would be a single span. In our case we have decided to manually add specific spans per the different phases of the Python process, to be able to measure their latency, throughput, error rate, etc individually. This is how we define spans manually: </p>
<pre><code>from opentelemetry import trace

if __name__ == "__main__":

    tracer = trace.get_tracer("main")
    with tracer.start_as_current_span("initialization") as span:
            # Init code
            … 
    with tracer.start_as_current_span("search") as span:
            # Step 1 - Search code
            …
   with tracer.start_as_current_span("transform") as span:
           # Step 2 - Transform code
           …
   with tracer.start_as_current_span("load") as span:
           # Step 3 - Load code
           …
</code></pre>
<p>You can explore traces in the APM interface as shown below. </p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt96c41c3496c22185/6a7f0e1e1967eaf8d13307db/Traces-APM-Observability-Elastic.png" alt="3 - APM Traces view" /></p>
<h3 id="metrics">Metrics</h3>
<p>We export metrics via the default OTLP protocol as well, such as CPU usage and memory. No extra code needs to be added in the script itself. </p>
<p>Note: Remember to set <code>ELASTIC_OTEL_SYSTEM_METRICS_ENABLED</code> to true. </p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt18fce421bc3081e2/6a7f0e21c2cc09cf812495fe/otel-metrics-apm-view.png" alt="4 - APM Metrics view" /></p>
<h3 id="logging">Logging</h3>
<p>We export logs via the default OTLP protocol as well.</p>
<p>For logging, we modify the logging calls to add extra fields using a dictionary structure (bq_fields) as shown below:</p>
<pre><code>        job.result()  # Waits for table load to complete
        job_details = client.get_job(job.job_id)  # Get job details

        # Extract job information
        bq_fields = {
            # "slot_time_ms": job_details.slot_ms,
            "job_id": job_details.job_id,
            "job_type": job_details.job_type,
            "state": job_details.state,
            "path": job_details.path,
            "job_created": job_details.created.isoformat(),
            "job_ended": job_details.ended.isoformat(),
            "execution_time_ms": (
                job_details.ended - job_details.created
            ).total_seconds()
            * 1000,
            "bytes_processed": job_details.output_bytes,
            "rows_affected": job_details.output_rows,
            "destination_table": job_details.destination.table_id,
            "event": "BigQuery Load Job", # Custom event type
            "status": "success", # Status of the step (success/error)
            "category": category # ETL category tag 
        }

        logging.info("BigQuery load operation successful", extra=bq_fields)
</code></pre>
<p>This code shows how to extract BQ job stats, execution time, bytes processed, rows affected and destination table among them. You can add other metadata like we do such as custom event type, status, and category. </p>
<p>Any calls to logging (of all levels above the set threshold, in this case INFO <code>logging.getLogger().setLevel(logging.INFO)</code>) will create a log that will be exported to Elastic. This means that in Python scripts that already use <code>logging</code> there is no need to make any changes to export logs to Elastic.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb6446286112e0ddf/6a7f0e24b43770fd594d6cfb/otel-logs-apm-view.png" alt="5 - APM Logs view" /></p>
<p>For each of the log messages, you can go into the details view (click on the <code>…</code> when you hover over the log line and go into <code>View details</code>) to examine the metadata attached to the log message. You can also explore the logs in <a href="https://www.elastic.co/guide/en/kibana/8.14/discover.html">Discover</a>.</p>
<h4 id="explanationofloggingmodification">Explanation of Logging Modification</h4>
<ul>
<li><p><strong>logging.info</strong>: This logs an informational message. The message "BigQuery load operation successful" is logged.</p></li>
<li><p><strong>extra=bq_fields</strong>: This adds additional context to the log entry using the <code>bq_fields</code> dictionary. This context can include details making the log entries more informative and easier to analyze. This data will be later used to set up alerts and data anomaly detection jobs. </p></li>
</ul>
<h2 id="monitoringinelasticsapm">Monitoring in Elastic's APM</h2>
<p>As shown, we can examine traces, metrics, and logs in the APM interface. To make the most out of this data, we make use on top of nearly the whole suit of features in Elastic Observability alongside Elastic Analytic's ML capabilities.</p>
<h3 id="rulesandalerts">Rules and Alerts</h3>
<p>We can set up rules and alerts to detect anomalies, errors, and performance issues in our scripts.</p>
<p>The <a href="https://www.elastic.co/guide/en/kibana/current/apm-alerts.html#apm-create-error-alert"><code>error count threshold</code> rule</a> is used to create a trigger when the number of errors in a service exceeds a defined threshold.</p>
<p>To create the rule go to Alerts and Insights -&gt; Rules -&gt; Create Rule -&gt; Error count threshold, set the error count threshold, the service or environment you want to monitor (you can also set an error grouping key across services), how often to run the check, and choose a connector.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9ba2519ba5f3fa2d/6a7f0e27ea068dc2d9f09f00/error-count-threshold.png" alt="6 - ETL Status Error Rule" /></p>
<p>Next, we create a rule of type <code>custom threshold</code> on a given ETL logs <a href="https://www.elastic.co/guide/en/kibana/current/data-views.html">data view</a> (create one for your index) filtering on "labels.status: error" to get all the logs with status error from any of the steps of the ETL which have failed. The rule condition is set to document count &gt; 0. In our case, in the last section of the rule config, we also set up Slack <a href="https://www.elastic.co/guide/en/kibana/current/alerting-getting-started.html">alerts</a> every time the rule is activated. You can pick from a long list of <a href="https://www.elastic.co/guide/en/kibana/current/action-types.html">connectors</a> Elastic supports. </p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7f27471820068122/6a7f0e2a3cab1c6e0b0e48e6/etl-fail-status-rule.png" alt="7 - ETL Status Error Rule" /></p>
<p>Then we can set up alerts for failures. We add status to the logs metadata as shown in the code sample below for each of the steps in the ETLs. It then becomes available in ES via <code>labels.status</code>.</p>
<pre><code>logging.info(
            "Elasticsearch search operation successful",
            extra={
                "event": "Elasticsearch Search",
                "status": "success",
                "category": category,
                "index": index,
            },
        )
</code></pre>
<h3 id="morerules">More Rules</h3>
<p>We could also add rules to detect anomalies in the execution time of the different spans we define. This is done by selecting transaction/span -&gt; Alerts and rules -&gt; Custom threshold rule -&gt; Latency. In the example below, we want to generate an alert whenever the search step takes more than 25s. </p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt238521f3b6880f17/6a7f0e2d42a1175c6895bf24/apm_custom_threshold_latency.png" alt="8 - APM Custom Threshold - Latency" /></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1da604ca4d0ab23c/6a7f0e30c2cc097c7e24960a/apm_custom_threshold_latency_2.png" alt="9 - APM Custom Threshold - Config" /></p>
<p>Alternatively, for finer-grained control, you can go with Alerts and rules -&gt; Anomaly rule, set up an anomaly job, and pick a threshold severity level. </p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte3ac0d0af99a213b/6a7f0e3342a117206695bf28/apm_anomaly_rule_config.png" alt="10 - APM Anomaly Rule - Config" /></p>
<h3 id="anomalydetectionjob">Anomaly detection job</h3>
<p>In this example we set an anomaly detection job on the number of documents before transform. </p>
<p>We set up an <a href="https://www.elastic.co/guide/en/machine-learning/current/ml-ad-run-jobs.html">Anomaly Detection jobs</a> on the number of document before the transform using the <a href="https://www.elastic.co/guide/en/machine-learning/current/ml-anomaly-detection-job-types.html#multi-metric-jobs">Single metric job</a> to detect any anomalies with the incoming data source.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltaf263d1acbb97333/6a7f0e376693f817ce663fbd/single-metrics.png" alt="11 - Single Metrics" /></p>
<p>In the last step, you can create alerting similarly to what we did before to receive alerts whenever there is an anomaly detected, by setting up a severity level threshold. Using the anomaly score which is assigned to every anomaly, every anomaly is characterized by a severity level. </p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta7cd8ba4e4eb7176/6a7f0e3ade2315ec2cfd7cb5/anomaly-detection-alerting-1.png" alt="12 - Anomaly detection Alerting - Severity" /></p>
<p>Similarly to the previous example, we set up a Slack connector to receive alerts whenever an anomaly is detected.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6ef0c217964808ad/6a7f0e3dbd2198cabb758135/anomaly-detection-alerting-connectors.png" alt="13 - Anomaly detection Alerting - Connectors" /></p>
<p>You can go to your custom dashboard by going to Add Panel -&gt; ML -&gt; Anomaly Swim Lane -&gt; Pick your job. </p>
<p>Similarly, we add jobs for the number of documents after the transform, and a Multi-Metric one on the <code>execution_time_ms</code>, <code>bytes_processed</code> and <code>rows_affected</code> similarly to how it was done in <a href="https://www.elastic.co/observability-labs/blog/monitor-dbt-pipelines-with-elastic-observability">Monitor your DBT pipelines with Elastic Observability</a>.</p>
<h2 id="customdashboard">Custom Dashboard</h2>
<p>Now that your logs, metrics, and traces are in Elastic, you can use the full potential of our Kibana dashboards to extract the most from them. We can create a custom dashboard like the following one: a pie chart based on <code>labels.event</code> (category field for every type of step in the ETLs), a chart for every type of step broken down by status, a timeline of steps broken down by status, BQ stats for the ETL, and anomaly detection swim lane panels for the various anomaly jobs. </p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5e6b6efe442d487f/6a7f0e41b4377082224d6d07/custom_dashboard.png" alt="14 - Custom Dashboard" /></p>
<h2 id="conclusion">Conclusion</h2>
<p>Elastic’s APM, in combination with other Observability and ML features, provides a unified view of our data pipelines, allowing us to bring a lot of value with minimal code changes:</p>
<ul>
<li>Logging of new logs (no need to add custom logging) alongside their execution context</li>
<li>Monitor the runtime behavior of our models</li>
<li>Track data quality issues</li>
<li>Identify and troubleshoot real-time incidents</li>
<li>Optimize performance bottlenecks and resource usage</li>
<li>Identify dependencies on other services and their latency</li>
<li>Optimize data transformation processes</li>
<li>Set up alerts on latency, data quality issues, error rates of transactions or CPU usage)</li>
</ul>
<p>With these capabilities, we can ensure the resilience and reliability of our data pipelines, leading to more robust and accurate BI system and reporting.</p>
<p>In conclusion, setting up OpenTelemetry (OTEL) in Python for data pipeline observability has significantly improved our ability to monitor, detect, and resolve issues proactively. This has led to more reliable data transformations, better resource management, and enhanced overall performance of our data transformation, BI and Machine Learning systems.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/monitor-your-python-data-pipelines-with-otel</link>
    <guid isPermaLink="false">monitor-your-python-data-pipelines-with-otel</guid>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[Logs Analytics]]></category>
    <category><![CDATA[APM]]></category>
    <category><![CDATA[Machine Learning]]></category>
    <dc:creator><![CDATA[Tamara Dancheva,Almudena Sanz Olivé]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt62d1e1b03ea2e67c/6a7f0e43448e4e3e0c5c079f/main_image.jpg" length="0" type="image/jpeg"/>
    <pubDate>Thu, 08 Aug 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Monitor dbt pipelines with Elastic Observability]]></title>
    <description><![CDATA[Learn how to set up a dbt monitoring system with Elastic that proactively alerts on data processing cost spikes, anomalies in rows per table, and data quality test failures]]></description>
    <content:encoded><![CDATA[<p>In the Data Analytics team within the Observability organization in Elastic, we use <a href="https://www.getdbt.com/product/what-is-dbt">dbt (dbt™, data build tool)</a> to execute our SQL data transformation pipelines. dbt is a SQL-first transformation workflow that lets teams quickly and collaboratively deploy analytics code. In particular, we use <a href="https://docs.getdbt.com/docs/core/installation-overview">dbt core</a>, the <a href="https://github.com/dbt-labs/dbt-core">open-source project</a>, where you can develop from the command line and run your dbt project.</p>
<p>Our data transformation pipelines run daily and process the data that feed our internal dashboards, reports, analyses, and Machine Learning (ML) models.</p>
<p>There have been incidents in the past when the pipelines have failed, the source tables contained wrong data or we have introduced a change into our SQL code that has caused data quality issues, and we only realized once we saw it in a weekly report that was showing an anomalous number of records. That’s why we have built a monitoring system that proactively alerts us about these types of incidents as soon as they happen and helps us with visualizations and analyses to understand their root cause, saving us several hours or days of manual investigations.</p>
<p>We have leveraged our own Observability Solution to help solve this challenge, monitoring the entire lifecycle of our dbt implementation. This setup enables us to track the behavior of our models and conduct data quality testing on the final tables. We export dbt process logs from run jobs and tests into Elasticsearch and utilize Kibana to create dashboards, set up alerts, and configure Machine Learning jobs to monitor and assess issues.</p>
<p>The following diagram shows our complete architecture. In a follow-up article, we’ll also cover how we observe our python data processing and ML model processes using OTEL and Elastic - stay tuned.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blted013fc2f4985545/6a7f0df0e02fac34585d65ec/architecture.png" alt="1 - architecture" /></p>
<h2 id="whymonitordbtpipelineswithelastic">Why monitor dbt pipelines with Elastic?</h2>
<p>With every invocation, dbt generates and saves one or more JSON files called <a href="https://docs.getdbt.com/reference/artifacts/dbt-artifacts">artifacts</a> containing log data on the invocation results. <code>dbt run</code> and <code>dbt test</code> invocation logs are <a href="https://docs.getdbt.com/reference/artifacts/run-results-json">stored in the file <code>run_results.json</code></a>, as per the dbt documentation:</p>
<blockquote>
  <p>This file contains information about a completed invocation of dbt, including timing and status info for each node (model, test, etc) that was executed. In aggregate, many <code>run_results.json</code> can be combined to calculate average model runtime, test failure rates, the number of record changes captured by snapshots, etc.</p>
</blockquote>
<p>Monitoring <code>dbt run</code> invocation logs can help solve several issues, including tracking and alerting about table volumes, detecting excessive slot time from resource-intensive models, identifying cost spikes due to slot time or volume, and pinpointing slow execution times that may indicate scheduling issues. This system was crucial when we merged a PR with a change in our code that had an issue, producing a sudden drop in the number of daily rows in upstream Table A. By ingesting the <code>dbt run</code> logs into Elastic, our anomaly detection job quickly identified anomalies in the daily row counts for Table A and its downstream tables, B, C, and D. The Data Analytics team received an alert notification about the issue, allowing us to promptly troubleshoot, fix and backfill the tables before it affected the weekly dashboards and downstream ML models.</p>
<p>Monitoring <code>dbt test</code> invocation logs can also address several issues, such as identifying duplicates in tables, detecting unnoticed alterations in allowed values for specific fields through validation of all enum fields, and resolving various other data processing and quality concerns. With dashboards and alerts on data quality tests, we proactively identify issues like duplicate keys, unexpected category values, and increased nulls, ensuring data integrity. In our team, we had an issue where a change in one of our raw lookup tables produced duplicated rows in our user table, doubling the number of users reported. By ingesting the <code>dbt test</code> logs into Elastic, our rules detected that some duplicate tests had failed. The team received an alert notification about the issue, allowing us to troubleshoot it right away by finding the upstream table that was the root cause. These duplicates meant that downstream tables had to process 2x the amount of data, creating a spike in the bytes processed and slot time. The anomaly detection and alerts on the <code>dbt run</code> logs also helped us spot these spikes for individual tables and allowed us to quantify the impact on our billing.</p>
<p>Processing our dbt logs with Elastic and Kibana allows us to obtain real-time insights, helps us quickly troubleshoot potential issues, and keeps our data transformation processes running smoothly. We set up anomaly detection jobs and alerts in Kibana to monitor the number of rows processed by dbt, the slot time, and the results of the tests. This lets us catch real-time incidents, and by promptly identifying and fixing these issues, Elastic makes our data pipeline more resilient and our models more cost-effective, helping us stay on top of cost spikes or data quality issues.</p>
<p>We can also correlate this information with other events ingested into Elastic, for example using the <a href="https://www.elastic.co/guide/en/enterprise-search/current/connectors-github.html">Elastic Github connector</a>, we can correlate data quality test failures or other anomalies with code changes to find the root cause of the commit or PR that caused the issues. By ingesting application logs into Elastic, we can also analyze if these issues in our pipelines have affected downstream applications, increasing latency, throughput or error rates using APM. Ingesting billing, revenue data or web traffic, we could also see the impact in business metrics.</p>
<h2 id="howtoexportdbtinvocationlogstoelasticsearch">How to export dbt invocation logs to Elasticsearch</h2>
<p>We use the <a href="https://elasticsearch-py.readthedocs.io/en">Python Elasticsearch client</a> to send the dbt invocation logs to Elastic after we run our <code>dbt run</code> and <code>dbt test</code> processes daily in production. The setup just requires you to install the <a href="https://elasticsearch-py.readthedocs.io/en/v8.14.0/quickstart.html#installation">Elasticsearch Python client</a> and obtain your Elastic Cloud ID (go to https://cloud.elastic.co/deployments/, select your deployment and find the <code>Cloud ID</code>) and Elastic Cloud API Key <a href="https://elasticsearch-py.readthedocs.io/en/v8.14.0/quickstart.html#connecting">(following this guide)</a></p>
<p>This python helper function will index the results from your <code>run_results.json</code> file to the specified index. You just need to export the variables to the environment:</p>
<ul>
<li><code>RESULTS_FILE</code>: path to your <code>run_results.json</code> file</li>
<li><code>DBT_RUN_LOGS_INDEX</code>: the name you want to give to dbt run logs index in Elastic, e.g. <code>dbt_run_logs</code></li>
<li><code>DBT_TEST_LOGS_INDEX</code>: the name you want to give to the dbt test logs index in Elastic, e.g. <code>dbt_test_logs</code></li>
<li><code>ES_CLUSTER_CLOUD_ID</code></li>
<li><code>ES_CLUSTER_API_KEY</code></li>
</ul>
<p>Then call the function <code>log_dbt_es</code> from your python code or save this code as a python script and run it after executing your <code>dbt run</code> or <code>dbt test</code> commands:</p>
<pre><code>from elasticsearch import Elasticsearch, helpers
import os
import sys
import json

def log_dbt_es():
   RESULTS_FILE = os.environ["RESULTS_FILE"]
   DBT_RUN_LOGS_INDEX = os.environ["DBT_RUN_LOGS_INDEX"]
   DBT_TEST_LOGS_INDEX = os.environ["DBT_TEST_LOGS_INDEX"]
   es_cluster_cloud_id = os.environ["ES_CLUSTER_CLOUD_ID"]
   es_cluster_api_key = os.environ["ES_CLUSTER_API_KEY"]


   es_client = Elasticsearch(
       cloud_id=es_cluster_cloud_id,
       api_key=es_cluster_api_key,
       request_timeout=120,
   )


   if not os.path.exists(RESULTS_FILE):
       print(f"ERROR: {RESULTS_FILE} No dbt run results found.")
       sys.exit(1)


   with open(RESULTS_FILE, "r") as json_file:
       results = json.load(json_file)
       timestamp = results["metadata"]["generated_at"]
       metadata = results["metadata"]
       elapsed_time = results["elapsed_time"]
       args = results["args"]
       docs = []
       for result in results["results"]:
           if result["unique_id"].split(".")[0] == "test":
               result["_index"] = DBT_TEST_LOGS_INDEX
           else:
               result["_index"] = DBT_RUN_LOGS_INDEX
           result["@timestamp"] = timestamp
           result["metadata"] = metadata
           result["elapsed_time"] = elapsed_time
           result["args"] = args
           docs.append(result)
        = helpers.bulk(es_client, docs)
   return "Done"

# Call the function
log_dbt_es()
</code></pre>
<p>If you want to add/remove any other fields from <code>run_results.json</code>, you can modify the above function to do it.</p>
<p>Once the results are indexed, you can use Kibana to create Data Views for both indexes and start exploring them in Discover.</p>
<p>Go to Discover, click on the data view selector on the top left and “Create a data view”.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt86858215f11dfac8/6a7f0df24c4bfb0553ccd595/discover-create-dataview.png" alt="2 - discover create a data view" /></p>
<p>Now you can create a data view with your preferred name. Do this for both dbt run (<code>DBT_RUN_LOGS_INDEX</code> in your code) and dbt test (<code>DBT_TEST_LOGS_INDEX</code> in your code) indices:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta4284fecea8b3f0b/6a7f0df5e3a219e42799f51a/create-dataview.png" alt="3 - create a data view" /></p>
<p>Going back to Discover, you’ll be able to select the Data Views and explore the data.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd49dbf639fdef6b3/6a7f0df8448e4e20545c0781/discover-logs-explorer.png" alt="4 - discover logs explorer" /></p>
<h2 id="dbtrunalertsdashboardsandmljobs">dbt run alerts, dashboards and ML jobs</h2>
<p>The invocation of <a href="https://docs.getdbt.com/reference/commands/run"><code>dbt run</code></a> executes compiled SQL model files against the current database. <code>dbt run</code> invocation logs contain the <a href="https://docs.getdbt.com/reference/artifacts/run-results-json">following fields</a>:</p>
<ul>
<li><code>unique_id</code>: Unique model identifier</li>
<li><code>execution_time</code>: Total time spent executing this model run</li>
</ul>
<p>The logs also contain the following metrics about the job execution from the adapter:</p>
<ul>
<li><code>adapter_response.bytes_processed</code></li>
<li><code>adapter_response.bytes_billed</code></li>
<li><code>adapter_response.slot_ms</code></li>
<li><code>adapter_response.rows_affected</code></li>
</ul>
<p>We have used Kibana to set up <a href="https://www.elastic.co/guide/en/machine-learning/current/ml-ad-run-jobs.html">Anomaly Detection jobs</a> on the above-mentioned metrics. You can configure a <a href="https://www.elastic.co/guide/en/machine-learning/current/ml-anomaly-detection-job-types.html#multi-metric-jobs">multi-metric job</a> split by <code>unique_id</code> to be alerted when the sum of rows affected, slot time consumed, or bytes billed is anomalous per table. You can track one job per metric. If you have built a dashboard of the metrics per table, you can use <a href="https://www.elastic.co/guide/en/machine-learning/8.14/ml-jobs-from-lens.html">this shortcut</a> to create the Anomaly Detection job directly from the visualization. After the jobs are created and are running on incoming data, you can <a href="https://www.elastic.co/guide/en/machine-learning/current/ml-ad-view-results.html">view the jobs</a> and add them to a dashboard using the three dots button in the anomaly timeline:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt15b36788c551c5df/6a7f0dfb73d9bd41df29db95/ml-job-add-to-dashboard.png" alt="5 - add ML job to dashboard" /></p>
<p>We have used the <a href="https://www.elastic.co/guide/en/machine-learning/current/ml-configuring-alerts.html">ML job to set up alerts</a> that send us emails/slack messages when anomalies are detected. Alerts can be created directly from the Jobs (Machine Learning &gt; Anomaly Detection Jobs) page, by clicking on the three dots at the end of the ML job row:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt06a4d48c8c462c07/6a7f0dfe96b5a6b37687b4cf/ml-job-create-alert.png" alt="6 - create alert from ML job" /></p>
<p>We also use <a href="https://www.elastic.co/guide/en/kibana/current/dashboard.html">Kibana dashboards</a> to visualize the anomaly detection job results and related metrics per table, to identify which tables consume most of our resources, to have visibility on their temporal evolution, and to measure aggregated metrics that can help us understand month over month changes.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt792d7ad77ab8b974/6a7f0e02b437704d0b4d6cf1/ml-job-dashboard.png" alt="7 - ML job in dashboard" />
<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt995625988df0518b/6a7f0e041967ea82403307cd/dashboard-slot-time.png" alt="8 - dashboard slot time chart" />
<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1bd2f5b6a0aab3fc/6a7f0e07fc63ab1c4364ccd9/dashboard-aggregated-metrics.png" alt="9 - dashboard aggregated metrics" /></p>
<h2 id="dbttestalertsanddashboards">dbt test alerts and dashboards</h2>
<p>You may already be familiar with <a href="https://docs.getdbt.com/docs/build/data-tests">tests in dbt</a>, but if you’re not, dbt data tests are assertions you make about your models. Using the command <a href="https://docs.getdbt.com/reference/commands/test"><code>dbt test</code></a>, dbt will tell you if each test in your project passes or fails. <a href="https://docs.getdbt.com/docs/build/data-tests#example">Here is an example of how to set them up</a>. In our team, we use out-of-the-box dbt tests (<code>unique</code>, <code>not_null</code>, <code>accepted_values</code>, and <code>relationships</code>) and the packages <a href="https://hub.getdbt.com/dbt-labs/dbt_utils/latest/">dbt_utils</a> and <a href="https://hub.getdbt.com/calogica/dbt_expectations/latest/">dbt_expectations</a> for some extra tests. When the command <code>dbt test</code> is run, it generates logs that are stored in <code>run_results.json</code>.</p>
<p>dbt test logs contain the <a href="https://docs.getdbt.com/reference/artifacts/run-results-json">following fields</a>:</p>
<ul>
<li><code>unique_id</code>: Unique test identifier, tests contain the “test” prefix in their unique identifier</li>
<li><code>status</code>: result of the test, <code>pass</code> or <code>fail</code></li>
<li><code>execution_time</code>: Total time spent executing this test</li>
<li><code>failures</code>: will be 0 if the test passes and 1 if the test fails</li>
<li><code>message</code>: If the test fails, reason why it failed</li>
</ul>
<p>The logs also contain the metrics about the job execution from the adapter.</p>
<p>We have set up alerts on document count (see <a href="https://www.elastic.co/guide/en/observability/8.14/custom-threshold-alert.html">guide</a>) that will send us an email / slack message when there are any failed tests. The rule for the alerts is set up on the dbt test Data View that we have created before, the query filtering on <code>status:fail</code> to obtain the logs for the tests that have failed, and the rule condition is document count bigger than 0.
Whenever there is a failure in any test in production, we get an alert with links to the alert details and dashboards to be able to troubleshoot them:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2ff19480a02d48c6/6a7f0e0a6693f8c2fe663fa5/email-alert.png" alt="10 - alert" /></p>
<p>We have also built a dashboard to visualize the tests run, tests failed, and their execution time and slot time to have a historical view of the test run:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5950dccdea7e0424/6a7f0e0d4c4bfb2ba0ccd5a1/dashboard-tests.png" alt="11 - dashboard dbt tests" /></p>
<h2 id="findingrootcauseswiththeaiassistant">Finding Root Causes with the AI Assistant</h2>
<p>The most effective way for us to analyze these multiple sources of information is using the AI Assistant to help us troubleshoot the incidents. In our case, we got an alert about a test failure, and we used the AI Assistant to give us context on what happened. Then we asked if there were any downstream consequences, and the AI Assistant interpreted the results of the Anomaly Detection job, which indicated a spike in slot time for one of our downstream tables and the increase of the slot time vs. the baseline. Then, we asked for the root cause, and the AI Assistant was able to find and provide us a link to a PR from our Github changelog that matched the start of the incident and was the most probable cause.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte69b3d0db1c5f71a/6a7f0e10227b1c608e59865a/ai-assistant.png" alt="12 - ai assistant troubleshoot" /></p>
<h2 id="conclusion">Conclusion</h2>
<p>As a Data Analytics team, we are responsible for guaranteeing that the tables, charts, models, reports, and dashboards we provide to stakeholders are accurate and contain the right sources of information. As teams grow, the number of models we own becomes larger and more interconnected, and it isn’t easy to guarantee that everything is running smoothly and providing accurate results. Having a monitoring system that proactively alerts us on cost spikes, anomalies in row counts, or data quality test failures is like having a trusted companion that will alert you in advance if something goes wrong and help you get to the root cause of the issue.</p>
<p>dbt invocation logs are a crucial source of information about the status of our data pipelines, and Elastic is the perfect tool to extract the maximum potential out of them. Use this blog post as a starting point for utilizing your dbt logs to help your team achieve greater reliability and peace of mind, allowing them to focus on more strategic tasks rather than worrying about potential data issues.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/monitor-dbt-pipelines-with-elastic-observability</link>
    <guid isPermaLink="false">monitor-dbt-pipelines-with-elastic-observability</guid>
    <category><![CDATA[Data Management]]></category>
    <category><![CDATA[Logs Analytics]]></category>
    <category><![CDATA[LLM Observability]]></category>
    <dc:creator><![CDATA[Almudena Sanz Olivé,Tamara Dancheva]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7b9a1fc65967a172/6a7f0e13c2e914c297016c54/monitoring-dbt-with-elastic.png" length="0" type="image/png"/>
    <pubDate>Fri, 26 Jul 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[Collecting OpenShift container logs using Red Hat’s OpenShift Logging Operator]]></title>
    <description><![CDATA[Learn how to optimize OpenShift logs collected with Red Hat OpenShift Logging Operator, as well as format and route them efficiently in Elasticsearch.]]></description>
    <content:encoded><![CDATA[<p>This blog explores a possible approach to collecting and formatting OpenShift Container Platform logs and audit logs with Red Hat OpenShift Logging Operator. We recommend using Elastic® Agent for the best possible experience! We will also show how to format the logs to Elastic Common Schema (<a href="https://www.elastic.co/guide/en/ecs/current/index.html">ECS</a>) for the best experience viewing, searching, and visualizing your logs. All examples in this blog are based on OpenShift 4.14.</p>
<h2 id="whyuseopenshiftloggingoperator">Why use OpenShift Logging Operator?</h2>
<p>A lot of enterprise customers use OpenShift as their orchestrating solution. The advantages of this approach are:</p>
<ul>
<li><p>It is developed and supported by Red Hat</p></li>
<li><p>It can automatically update the OpenShift cluster along with the Operating system to make sure that they are and remain compatible</p></li>
<li><p>It can speed up developing life cycles with features like source to image</p></li>
<li><p>It uses enhanced security</p></li>
</ul>
<p>In our consulting experience, this latter aspect poses challenges and frictions with OpenShift administrators when we try to install an Elastic Agent to collect the logs of the pods. Indeed, Elastic Agent requires the files of the host to be mounted in the pod, and it also needs to be run in privileged mode. (Read more about the permissions required by Elastic Agent in the <a href="https://www.elastic.co/guide/en/fleet/current/running-on-kubernetes-standalone.html#_red_hat_openshift_configuration">official Elasticsearch® Documentation</a>). While the solution we explore in this post requires similar privileges under the hood, it is managed by the OpenShift Logging Operator, which is developed and supported by Red Hat.</p>
<h2 id="whichlogsarewegoingtocollect">Which logs are we going to collect?</h2>
<p>In OpenShift Container Platform, we distinguish <a href="https://docs.openshift.com/container-platform/4.14/logging/cluster-logging.html#logging-architecture-overview_cluster-logging">three broad categories of logs</a>: audit, application, and infrastructure logs:</p>
<ul>
<li><p><strong>Audit logs</strong> describe the list of activities that affected the system by users, administrators, and other components.</p></li>
<li><p><strong>Application logs</strong> are composed of the container logs of the pods running in non-reserved namespaces.</p></li>
<li><p><strong>Infrastructure logs</strong> are composed of container logs of the pods running in reserved namespaces like openshift*, kube*, and default along with journald messages from the nodes.</p></li>
</ul>
<p>In the following, we will consider only audit and application logs for the sake of simplicity. In this post, we will describe how to format audit and application Logs in the format expected by the Kubernetes integration to take the most out of Elastic Observability.</p>
<h2 id="gettingstarted">Getting started</h2>
<p>To collect the logs from OpenShift, we must perform some preparation steps in Elasticsearch and OpenShift.</p>
<h3 id="insideelasticsearch">Inside Elasticsearch</h3>
<p>We first <a href="https://www.elastic.co/guide/en/fleet/8.11/install-uninstall-integration-assets.html#install-integration-assets">install the Kubernetes integration assets</a>. We are mainly interested in the index templates and ingest pipelines for the logs-kubernetes.container_logs and logs-kubernetes.audit_logs.</p>
<p>To format the logs received from the ClusterLogForwarder in <a href="https://www.elastic.co/guide/en/ecs/current/index.html">ECS</a> format, we will define a pipeline to normalize the container logs. The field naming convention used by OpenShift is slightly different from that used by ECS. To get a list of exported fields from OpenShift, refer to <a href="https://docs.openshift.com/container-platform/4.14/logging/cluster-logging-exported-fields.html">Exported fields | Logging | OpenShift Container Platform 4.14</a>. To get a list of exported fields of the Kubernetes integration, you can refer to <a href="https://www.elastic.co/guide/en/beats/filebeat/current/exported-fields-kubernetes-processor.html">Kubernetes fields | Filebeat Reference [8.11] | Elastic</a> and <a href="https://www.elastic.co/guide/en/observability/current/logs-app-fields.html">Logs app fields | Elastic Observability [8.11]</a>. Further, specific fields like kubernetes.annotations must be normalized by replacing dots with underscores. This operation is usually done automatically by Elastic Agent.</p>
<pre><code>PUT _ingest/pipeline/openshift-2-ecs
{
  "processors": [
    {
      "rename": {
        "field": "kubernetes.pod_id",
        "target_field": "kubernetes.pod.uid",
        "ignore_missing": true
      }
    },
    {
      "rename": {
        "field": "kubernetes.pod_ip",
        "target_field": "kubernetes.pod.ip",
        "ignore_missing": true
      }
    },
    {
      "rename": {
        "field": "kubernetes.pod_name",
        "target_field": "kubernetes.pod.name",
        "ignore_missing": true
      }
    },
    {
      "rename": {
        "field": "kubernetes.namespace_name",
        "target_field": "kubernetes.namespace",
        "ignore_missing": true
      }
    },
    {
      "rename": {
        "field": "kubernetes.namespace_id",
        "target_field": "kubernetes.namespace_uid",
        "ignore_missing": true
      }
    },
    {
      "rename": {
        "field": "kubernetes.container_id",
        "target_field": "container.id",
        "ignore_missing": true
      }
    },
    {
      "dissect": {
        "field": "container.id",
        "pattern": "%{container.runtime}://%{container.id}",
        "ignore_failure": true
      }
    },
    {
      "rename": {
        "field": "kubernetes.container_image",
        "target_field": "container.image.name",
        "ignore_missing": true
      }
    },
    {
      "set": {
        "field": "kubernetes.container.image",
        "copy_from": "container.image.name",
        "ignore_failure": true
      }
    },
    {
      "set": {
        "copy_from": "kubernetes.container_name",
        "field": "container.name",
        "ignore_failure": true
      }
    },
    {
      "rename": {
        "field": "kubernetes.container_name",
        "target_field": "kubernetes.container.name",
        "ignore_missing": true
      }
    },
    {
      "set": {
        "field": "kubernetes.node.name",
        "copy_from": "hostname",
        "ignore_failure": true
      }
    },
    {
      "rename": {
        "field": "hostname",
        "target_field": "host.name",
        "ignore_missing": true
      }
    },
    {
      "rename": {
        "field": "level",
        "target_field": "log.level",
        "ignore_missing": true
      }
    },
    {
      "rename": {
        "field": "file",
        "target_field": "log.file.path",
        "ignore_missing": true
      }
    },
    {
      "set": {
        "copy_from": "openshift.cluster_id",
        "field": "orchestrator.cluster.name",
        "ignore_failure": true
      }
    },
    {
      "dissect": {
        "field": "kubernetes.pod_owner",
        "pattern": "%{_tmp.parent_type}/%{_tmp.parent_name}",
        "ignore_missing": true
      }
    },
    {
      "lowercase": {
        "field": "_tmp.parent_type",
        "ignore_missing": true
      }
    },
    {
      "set": {
        "field": "kubernetes.pod.{{_tmp.parent_type}}.name",
        "value": "{{_tmp.parent_name}}",
        "if": "ctx?._tmp?.parent_type != null",
        "ignore_failure": true
      }
    },
    {
      "remove": {
        "field": [
          "_tmp",
          "kubernetes.pod_owner"
          ],
          "ignore_missing": true
      }
    },
    {
      "script": {
        "description": "Normalize kubernetes annotations",
        "if": "ctx?.kubernetes?.annotations != null",
        "source": """
        def keys = new ArrayList(ctx.kubernetes.annotations.keySet());
        for(k in keys) {
          if (k.indexOf(".") &gt;= 0) {
            def sanitizedKey = k.replace(".", "_");
            ctx.kubernetes.annotations[sanitizedKey] = ctx.kubernetes.annotations[k];
            ctx.kubernetes.annotations.remove(k);
          }
        }
        """
      }
    },
    {
      "script": {
        "description": "Normalize kubernetes namespace_labels",
        "if": "ctx?.kubernetes?.namespace_labels != null",
        "source": """
        def keys = new ArrayList(ctx.kubernetes.namespace_labels.keySet());
        for(k in keys) {
          if (k.indexOf(".") &gt;= 0) {
            def sanitizedKey = k.replace(".", "_");
            ctx.kubernetes.namespace_labels[sanitizedKey] = ctx.kubernetes.namespace_labels[k];
            ctx.kubernetes.namespace_labels.remove(k);
          }
        }
        """
      }
    },
    {
      "script": {
        "description": "Normalize special Kubernetes Labels used in logs-kubernetes.container_logs to determine service.name and service.version",
        "if": "ctx?.kubernetes?.labels != null",
        "source": """
        def keys = new ArrayList(ctx.kubernetes.labels.keySet());
        for(k in keys) {
          if (k.startsWith("app_kubernetes_io_component_")) {
            def sanitizedKey = k.replace("app_kubernetes_io_component_", "app_kubernetes_io_component/");
            ctx.kubernetes.labels[sanitizedKey] = ctx.kubernetes.labels[k];
            ctx.kubernetes.labels.remove(k);
          }
        }
        """
      }
    }
    ]
}
</code></pre>
<p>Similarly, to handle the audit logs like the ones collected by Kubernetes, we define an ingest pipeline:</p>
<pre><code>PUT _ingest/pipeline/openshift-audit-2-ecs
{
  "processors": [
    {
      "script": {
        "source": """
        def audit = [:];
        def keyToRemove = [];
        for(k in ctx.keySet()) {
          if (k.indexOf('_') != 0 &amp;&amp; !['@timestamp', 'data_stream', 'openshift', 'event', 'hostname'].contains(k)) {
            audit[k] = ctx[k];
            keyToRemove.add(k);
          }
        }
        for(k in keyToRemove) {
          ctx.remove(k);
        }
        ctx.kubernetes=["audit":audit];
        """,
        "description": "Move all the 'kubernetes.audit' fields under 'kubernetes.audit' object"
      }
    },
    {
      "set": {
        "copy_from": "openshift.cluster_id",
        "field": "orchestrator.cluster.name",
        "ignore_failure": true
      }
    },
    {
      "set": {
        "field": "kubernetes.node.name",
        "copy_from": "hostname",
        "ignore_failure": true
      }
    },
    {
      "rename": {
        "field": "hostname",
        "target_field": "host.name",
        "ignore_missing": true
      }
    },
    {
      "script": {
        "if": "ctx?.kubernetes?.audit?.annotations != null",
        "source": """
          def keys = new ArrayList(ctx.kubernetes.audit.annotations.keySet());
          for(k in keys) {
            if (k.indexOf(".") &gt;= 0) {
              def sanitizedKey = k.replace(".", "_");
              ctx.kubernetes.audit.annotations[sanitizedKey] = ctx.kubernetes.audit.annotations[k];
              ctx.kubernetes.audit.annotations.remove(k);
            }
          }
          """,
        "description": "Normalize kubernetes audit annotations field as expected by the Integration"
      }
    }
  ]
}
</code></pre>
<p>The main objective of the pipeline is to mimic what Elastic Agent is doing: storing all audit fields under the kubernetes.audit object.</p>
<p>We are not going to use the conventional @custom pipeline approach because the fields must be normalized before invoking the logs-kubernetes.container_logs integration pipeline that uses fields like kubernetes.container.name and kubernetes.labels to determine the fields service.name and service.version. Read more about custom pipelines in <a href="https://www.elastic.co/guide/en/fleet/8.11/data-streams-pipeline-tutorial.html#data-streams-pipeline-one">Tutorial: Transform data with custom ingest pipelines | Fleet and Elastic Agent Guide [8.11]</a>.</p>
<p>The OpenShift Cluster Log Forwarder writes the data in the indices app-write and audit-write by default. It is possible to change this behavior, but it still tries to prepend the prefix “app” and the suffix “write”, so we opted to send the data to the default destination and use the reroute processor to send it to the right data streams. Read more about the Reroute Processor in our blog <a href="https://www.elastic.co/blog/simplifying-log-data-management-flexible-routing-elastic">Simplifying log data management: Harness the power of flexible routing with Elastic</a> and our documentation <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/reroute-processor.html">Reroute processor | Elasticsearch Guide [8.11] | Elastic</a>.</p>
<p>In this case, we want to redirect the container logs (app-write index) to logs-kubernetes.container_logs and the Audit logs (audit-write) to logs-kubernetes.audit_logs:</p>
<pre><code>PUT _ingest/pipeline/app-write-reroute-pipeline
{
  "processors": [
    {
      "pipeline": {
        "name": "openshift-2-ecs",
        "description": "Format the Openshift data in ECS"
      }
    },
    {
      "set": {
        "field": "event.dataset",
        "value": "kubernetes.container_logs"
      }
    },
    {
      "reroute": {
        "destination": "logs-kubernetes.container_logs-openshift"
      }
    }
  ]
}



PUT _ingest/pipeline/audit-write-reroute-pipeline
{
  "processors": [
    {
      "pipeline": {
        "name": "openshift-audit-2-ecs",
        "description": "Format the Openshift data in ECS"
      }
    },
    {
      "set": {
        "field": "event.dataset",
        "value": "kubernetes.audit_logs"
      }
    },
    {
      "reroute": {
        "destination": "logs-kubernetes.audit_logs-openshift"
      }
    }
  ]
}
</code></pre>
<p>Please note that given that app-write and audit-write do not follow the data stream naming convention, we are forced to add the destination field in the reroute processor. The reroute processor will also fill up the <a href="https://www.elastic.co/guide/en/ecs/8.11/ecs-data_stream.html">data_stream fields</a> for us. Note that this step is done automatically by Elastic Agent at source.</p>
<p>Further, we create the indices with the default pipelines we created to reroute the logs according to our needs.</p>
<pre><code>PUT app-write
{
  "settings": {
      "index.default_pipeline": "app-write-reroute-pipeline"
   }
}


PUT audit-write
{
  "settings": {
    "index.default_pipeline": "audit-write-reroute-pipeline"
  }
}
</code></pre>
<p>Basically, what we did can be summarized in this picture:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf80ce5b83551cdde/6a7f0f616693f891e9664031/openshift-summary-blog.png" alt="openshift-summary-blog" /></p>
<p>Let us take the container logs. When the operator attempts to write in the app-write index, it will invoke the default_pipeline “app-write-reroute-pipeline” that formats the logs into ECS format and reroutes the logs to logs-kubernetes.container_logs-openshift datastreams. This calls the integration pipeline that invokes, if it exists, the logs-kubernetes.container_logs@custom pipeline. Finally, the logs-kubernetes_container_logs pipeline may reroute the logs to another data set and namespace utilizing the elastic.co/dataset and elastic.co/namespace annotations as described in the Kubernetes <a href="https://docs.elastic.co/integrations/kubernetes/container-logs#rerouting-based-on-pod-annotations">integration documentation</a>, which in turn can lead to the execution of an another integration pipeline.</p>
<h3 id="createauserforsendingthelogs">Create a user for sending the logs</h3>
<p>We are going to use basic authentication because, at the time of writing, it is the only supported authentication method for Elasticsearch in OpenShift logging. Thus, we need a role that allows the user to write and read the app-write, and audit-write logs (required by the OpenShift agent) and auto_configure access to logs-*-* to allow custom Kubernetes rerouting:</p>
<pre><code>PUT _security/role/YOURROLE
{
    "cluster": [
      "monitor"
    ],
    "indices": [
      {
        "names": [
          "logs-*-*"
        ],
        "privileges": [
          "auto_configure",
          "create_doc"
        ],
        "allow_restricted_indices": false
      },
      {
        "names": [
          "app-write",
          "audit-write",
        ],
        "privileges": [
          "create_doc",
          "read"
        ],
        "allow_restricted_indices": false
      }
    ],
    "applications": [],
    "run_as": [],
    "metadata": {},
    "transient_metadata": {
      "enabled": true
    }

}



PUT _security/user/YOUR_USERNAME
{
  "password": "YOUR_PASSWORD",
  "roles": ["YOURROLE"]
}
</code></pre>
<h3 id="onopenshift">On OpenShift</h3>
<p>On the OpenShift Cluster, we need to follow the <a href="https://docs.openshift.com/container-platform/4.14/logging/log_collection_forwarding/log-forwarding.html">official documentation</a> of Red Hat on how to install the Red Hat OpenShift Logging and configure Cluster Logging and the Cluster Log Forwarder.</p>
<p>We need to install the Red Hat OpenShift Logging Operator, which defines the ClusterLogging and ClusterLogForwarder Resources. Afterward, we can define the Cluster Logging resource:</p>
<pre><code>apiVersion: logging.openshift.io/v1
kind: ClusterLogging
metadata:
  name: instance
  namespace: openshift-logging
spec:
  collection:
    logs:
      type: vector
      vector: {}
</code></pre>
<p>The Cluster Log Forwarder is the resource responsible for defining a daemon set that will forward the logs to the remote Elasticsearch. Before creating it, we need to create in the same namespace as the ClusterLogForwarder a secret containing the Elasticsearch credentials for the user we created previously in the namespace, where the ClusterLogForwarder will be deployed:</p>
<pre><code>apiVersion: v1
kind: Secret
metadata:
  name: elasticsearch-password
  namespace: openshift-logging
type: Opaque
stringData:
  username: YOUR_USERNAME
  password: YOUR_PASSWORD
</code></pre>
<p>Finally, we create the ClusterLogForwarder resource:</p>
<pre><code>kind: ClusterLogForwarder
apiVersion: logging.openshift.io/v1
metadata:
  name: instance
  namespace: openshift-logging
spec:
  outputs:
    - name: remote-elasticsearch
      secret:
        name: elasticsearch-password
      type: elasticsearch
      url: "https://YOUR_ELASTICSEARCH_URL:443"
      elasticsearch:
        version: 8 # The default is version 6 with the _type field
  pipelines:
    - inputRefs:
        - application
        - audit
      name: enable-default-log-store
      outputRefs:
        - remote-elasticsearch
</code></pre>
<p>Note that we explicitly defined the version of Elasticsearch to be 8, otherwise the ClusterLogForwarder will send the _type field, which is not compatible with Elasticsearch 8 and that we collect only application and audit logs.</p>
<h2 id="result">Result</h2>
<p>Once the logs are collected and passed through all the pipelines, the result is very close to the out-of-the-box Kubernetes integration. There are important differences, like the lack of host and cloud metadata information that don’t seem to be collected (at least without an additional configuration). We can view the Kubernetes container logs in the logs explorer:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt71059bb9bce109a8/6a7f0f64c2cc09a7662496be/openshift-summary-blog-graphs.png" alt="openshift-summary-blog-graphs" /></p>
<p>In this post, we described how you can use the OpenShift Logging Operator to collect the logs of containers and audit logs. We still recommend leveraging Elastic Agent to collect all your logs. It is the best user experience you can get. No need to maintain or transform the logs yourself to ECS formatting. Additionally, Elastic Agent uses API keys as the authentication method and collects metadata like cloud information that allow you in the long run to do <a href="https://www.elastic.co/blog/optimize-cloud-resources-cost-apm-metadata-elastic-observability">more</a>.</p>
<p><a href="https://www.elastic.co/observability/log-monitoring">Learn more about log monitoring with the Elastic Stack</a>.</p>
<p><em>Have feedback on this blog?</em> <a href="https://github.com/herrBez/elastic-blog-openshift-logging/issues"><em>Share it here</em></a><em>.</em></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/openshift-container-logs-red-hat-logging-operator</link>
    <guid isPermaLink="false">openshift-container-logs-red-hat-logging-operator</guid>
    <category><![CDATA[Logs Analytics]]></category>
    <category><![CDATA[Kubernetes]]></category>
    <dc:creator><![CDATA[Mirko Bez,David Ricordel,Philipp Kahr]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt72834ffb0a4604c9/6a7f0f6773d9bdff3c29dc3b/139687_-_Blog_Header_Banner_V1.jpg" length="0" type="image/jpeg"/>
    <pubDate>Tue, 16 Jan 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 AIOps Labs]]></title>
    <description><![CDATA[Elastic Observability provides more than just log aggregation, metrics analysis, APM, and distributed tracing. Our machine learning-based AIOps capabilities help you analyze the root cause of issues allowing you to focus on the most important tasks.]]></description>
    <content:encoded><![CDATA[<p>In the <a href="https://www.elastic.co/blog/reduce-mttd-ml-machine-learning-observability">previous blog</a> in our root cause analysis with logs series, we explored how to analyze logs in Elastic Observability with Elastic’s anomaly detection and log categorization capabilities. Elastic’s platform enables you to get started on machine learning (ML) 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 <a href="https://www.elastic.co/blog/may-2023-launch-machine-learning-models">machine learning models</a> 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 get you started, there are several key features built into Elastic Observability to aid in analysis, bypassing the need to run specific ML models. These features help minimize the time and analysis of logs.</p>
<p>Let’s review the set of machine learning-based observability features in Elastic:</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 more quickly.</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. Read <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> for an overview of this capability.</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 the 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>As we showed in the <a href="https://www.elastic.co/blog/reduce-mttd-ml-machine-learning-observability">last blog</a>, using machine learning-based features helps minimize the extremely tedious and time-consuming process of analyzing data using traditional methods, such as alerting and simple pattern matching (visual or simple searching, etc.). Trying to find the needle in the haystack requires the use of some level of artificial intelligence due to the increasing amounts of telemetry data (logs, metrics, and traces) being collected across ever-growing applications.</p>
<p>In this blog post, we’ll cover two capabilities found in Elastic’s AIOps Labs: log spike detector and log pattern analysis. We’ll use the same data from the previous blog and analyze it using these two capabilities.</p>
<p> <strong>We will cover log spike detector and log pattern analysis 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>Below, 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 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/blta9d36b30928e7224/6a7f0eb5de23157ee8fd7cdd/blog-elastic-observability-service-map.png" alt="observability service map" /></p>
<p>In our example, we’ve introduced issues to help walk you through the root cause analysis features. You might have a different set of issues depending on how you load the application and/or introduce specific feature flags.</p>
<p>As part of the walk-through, we’ll assume we are 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>Log spike analysis</li>
<li>Log pattern analysis</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>
<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/bltd43a5fdcc58e77ae/6a7f0eb8b43770411d4d6d4f/blog-elastic-observability-service-map-service-details.png" alt="observability service map service details" /></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. Rather than jump into anomaly detection (see previous <a href="https://www.elastic.co/blog/reduce-mttd-ml-machine-learning-observability">blog</a>), let’s look at some of the potential issues by reviewing the service details in APM.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt146757fbf179e467/6a7f0ebbfc63abce9164cd2f/blog-elastic-observability-product-catalog-service-overview.png" alt="observability product catalog service overview" /></p>
<p>What we see for the productCatalogService is that there are latency issues, failed transactions, a large number of issues, and a dependency to PostgreSQL. When we look at the errors in more detail and drill down, we see they are all coming from <a href="https://pkg.go.dev/github.com/lib/pq">PQ - which is a PostgreSQL driver in Go</a>.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3d30199bf7ae5c6a/6a7f0ebe73d9bd166229dbd5/blog-elastic-observability-product-catalog-service-errors.png" alt="observability product catalog service errors" /></p>
<p>As we drill further, we still can’t tell why the productCatalogService is not able to pull information from the PostgreSQL database.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2ea03c73a1b9776d/6a7f0ec16693f804b3663ff3/blog-elastic-observability-product-catalog-service-error-group.png" alt="observability product catalog service error group" /></p>
<p>We see that there is a spike in errors, so let's see if we can gleam further insight using one of our two options:</p>
<ul>
<li>Log rate spikes</li>
<li>Log pattern analysis</li>
</ul>
<h3 id="logratespikes">Log rate spikes</h3>
<p>Let’s start with the <strong>log rate spikes</strong> detector capability from Elastic’s AIOps Labs section of Elastic’s machine learning capabilities. We also pre-select analyzing the spike against a baseline history.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0bc6a360acca0efa/6a7f0ec49090b024f184ea9f/blog-elastic-observability-explain-log-rate-spikes-postgres.png" alt="explain log rate spikes postgres" /></p>
<p>The log rate spikes detector has looked at all the logs from the spike and compared them to the baseline, and it's seeing higher-than-normal counts in specific log messages. From a visual inspection, we see that PostgreSQL log messages are high. We further filter this with postgres.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte922b9077ca384e5/6a7f0ec7c2cc09fa92249652/blog-elastic-observability-explain-log-rate-spikes-pgbench.png" alt="explain log rates spikes pgbench" /></p>
<p>We immediately 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 a heavy load on the database host, likely causing 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 potential 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="logpatternanalysis">Log pattern analysis</h3>
<p>Instead of log rate spikes, let’s use log pattern analysis to investigate the spike in errors we saw in productCatalogService. In AIOps Labs, we simply select Log Pattern Analysis, use Logs data, filter the results with postgres (since we know it's related to PostgreSQL), and look at information from the message field of the logs we are processing. We see the following:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte965362e068db55a/6a7f0eca1967ea593b330813/blog-elastic-observability-explain-log-pattern-analysis.png" alt="observability explain log pattern analysis" /></p>
<p>Almost immediately we see the biggest pattern it finds is a log message where pgbench is updating the database. We can further directly drill into this log message from log pattern analysis into Discover and review the details and further analyze the messages.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt80d6079ba85313db/6a7f0ecdbd2198cc1b758169/blog-elastic-observability-expanded-document.png" alt="expanded document" /></p>
<p>As we mentioned in the previous section, while it may or may not be the root cause, it quickly gives us a place to start and a potential root cause. A developer likely intended to run pgbench against a staging database to evaluate its performance, and not the production environment.</p>
<h2 id="conclusion">Conclusion</h2>
<p>Between the <a href="https://www.elastic.co/blog/reduce-mttd-ml-machine-learning-observability">first blog</a> and this one, we’ve shown how Elastic Observability can help you further identify and get closer to pinpointing the root cause of issues without having to look for a “needle in a haystack.” Here’s a quick recap of what you learned in this blog.</p>
<ul>
<li>Elastic Observability has numerous capabilities to help you reduce your time to find the root cause and improve your MTTR (even MTTD). In particular, we reviewed the following two main capabilities (found in AIOps Labs in Elastic) in this blog:</li>
</ul>
<ol>
<li><strong>Log rate spikes</strong> detector helps identify reasons for increases in log rates. It makes it easy to find and investigate the 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>
</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 helps drive these features) or having to do any lengthy setups.</li>
</ul>
<p>Ready to get started? <a href="https://cloud.elastic.co/registration">Register for Elastic Cloud</a> and try out the features and capabilities outlined above.</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>
<p><em>Elastic and Elasticsearch are trademarks, logos or registered trademarks of Elasticsearch B.V. in the United States and other countries.</em></p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/observability-logs-machine-learning-aiops</link>
    <guid isPermaLink="false">observability-logs-machine-learning-aiops</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/blt01dcd709335ca52d/6a7f0ed03ce8e276b1cf5437/illustration-machine-learning-anomaly-1680x980.png" length="0" type="image/png"/>
    <pubDate>Thu, 27 Apr 2023 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Monitoring service performance: An overview of SLA calculation for Elastic Observability]]></title>
    <description><![CDATA[Elastic Stack provides many valuable insights for different users, such as reports on service performance and if the service level agreement (SLA) is met. In this post, we’ll provide an overview of calculating an SLA for Elastic Observability.]]></description>
    <content:encoded><![CDATA[<p>Elastic Stack provides many valuable insights for different users. Developers are interested in low-level metrics and debugging information. <a href="https://www.elastic.co/blog/elastic-observability-sre-incident-response">SREs</a> are interested in seeing everything at once and identifying where the root cause is. Managers want reports that tell them how good service performance is and if the service level agreement (SLA) is met. In this post, we’ll focus on the service perspective and provide an overview of calculating an SLA.</p>
<p><em>Since version 8.8, we have a built in functionality to calculate SLOs —</em> <a href="https://www.elastic.co/guide/en/observability/current/slo.html"><em>check out our guide</em></a><em>!</em></p>
<h2 id="foundationsofcalculatingansla">Foundations of calculating an SLA</h2>
<p>There are many ways to calculate and measure an SLA. The most important part is the definition of the SLA, and as a consultant, I’ve seen many different ways. Some examples include:</p>
<ul>
<li>Count of HTTP 2xx must be above 98% of all HTTP status</li>
<li>Response time of successful HTTP 2xx requests must be below x milliseconds</li>
<li>Synthetic monitor must be up at least 99%</li>
<li>95% of all batch transactions from the billing service need to complete within 4 seconds</li>
</ul>
<p>Depending on the origin of the data, calculating the SLA can be easier or more difficult. For uptime (Synthetic Monitoring), we automatically provide SLA values and offer out-of-the-box alerts to simply define alert when availability below 98% for the last 1 hour.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltbc2d548426b2de11/6a7f0ed563e95958ae73dea0/blog-elastic-overview-monitor-details.png" alt="overview monitor details" /></p>
<p>I personally recommend using <a href="https://www.elastic.co/blog/new-synthetic-monitoring-observability">Elastic Synthetic Monitoring</a> whenever possible to monitor service performance. Running HTTP requests and verifying the answers from the service, or doing fully fledged browser monitors and clicking through the website as a real user does, ensures a better understanding of the health of your service.</p>
<p>Sometimes this is impossible because you want to calculate the uptime of a specific Windows Service that does not offer any TCP port or HTTP interaction. Here the caveat applies that just because the service is running, it does not necessarily imply that the service is working fine.</p>
<h2 id="transformstotherescue">Transforms to the rescue</h2>
<p>We have identified our important service. In our case, it is the Steam Client Helper. There are two ways to solve this.</p>
<h3 id="lensformula">Lens formula</h3>
<p>You can use Lens and formula (for a deep dive into formulas, <a href="https://www.elastic.co/blog/how-tough-was-your-workout-take-a-closer-look-at-strava-data-through-kibana-lens">check out this blog</a>). Use the Search bar to filter down the data you want. Then use the formula option in Lens. We are dividing all counts of records with Running as a state and dividing it by the overall count of records. This is a nice solution when there is a need to calculate quickly and on the fly.</p>
<pre><code>count(kql='windows.service.state: "Running" ')/count()
</code></pre>
<p>Using the formula posted above as the bar chart's vertical axis calculates the uptime percentage. We use an annotation to mark why there is a dip and why this service was below the threshold. The annotation is set to reboot, which indicates a reboot happening, and thus, the service was down for a moment. Lastly, we add a reference line and set this to our defined threshold at 98%. This ensures that a quick look at the visualization allows our eyes to gauge if we are above or below the threshold.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt34a650fc757a72e6/6a7f0ed86693f826b8664001/blog-elastic-visualization.png" alt="visualization" /></p>
<h3 id="transform">Transform</h3>
<p>What if I am not interested in just one service, but there are multiple services needed for your SLA? That is where Transforms can solve this problem. Furthermore, the second issue is that this data is only available inside the Lens. Therefore, we cannot create any alerts on this.</p>
<p>Go to Transforms and create a pivot transform.</p>
<ol>
<li><p>Add the following filter to narrow it to only services data sets: data_stream.dataset: "windows.service". If you are interested in a specific service, you can always add it to the search bar if you want to know if a specific remote management service is up in your entire fleet!</p></li>
<li><p>Select data histogram(@timestamp) and set it to your chosen unit. By default, the Elastic Agent only collects service states every 60 seconds. I am going with 1 hour.</p></li>
<li><p>Select agent.name and windows.service.name as well.</p></li>
</ol>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte97db39dd71386e5/6a7f0edbeab5be71a920a7b3/blog-elastic-transform-configuration.png" alt="transform configuration" /></p>
<ol>
<li>Now we need to define an aggregation type. We will use a value_count of windows.service.state. That just counts how many records have this value.</li>
</ol>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt49501f71d3d6f782/6a7f0edd448e4e547f5c07e3/blog-elastic-aggregations.png" alt="aggregations" /></p>
<ol>
<li><p>Rename the value_count to total_count.</p></li>
<li><p>Add value_count for windows.service.state a second time and use the pencil icon to edit it to terms, which aggregates for running.</p></li>
</ol>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt798455134fc46058/6a7f0ee01967ea4fa233081f/blog-elastic-aggregations-apply.png" alt="aggregations apply" /></p>
<ol>
<li><p>This opens up a sub-aggregation. Once again, select value_count(windows.service.state) and rename it to values.</p></li>
<li><p>Now, the preview shows us the count of records with any states and the count of running.</p></li>
</ol>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte9f3761c68fa183d/6a7f0ee333fa8aaa772027ba/blog-elastic-transform-configuration-next.png" alt="transform configuration" /></p>
<ol>
<li><p>Here comes the tricky part. We need to write some custom aggregations to calculate the percentage of uptime. Click on the copy icon next to the edit JSON config.</p></li>
<li><p>In a new tab, go to Dev Tools. Paste what you have in the clipboard.</p></li>
<li><p>Press the play button or use the keyboard shortcut ctrl+enter/cmd+enter and run it. This will create a preview of what the data looks like. It should give you the same information as in the table preview.</p></li>
<li><p>Now, we need to calculate the percentage of up, which means doing a bucket script where we divide running.values by total_count, just like we did in the Lens visualization. Suppose you name the columns differently or use more than a single value. In that case, you will need to adapt accordingly.</p></li>
</ol>
<pre><code>"availability": {
        "bucket_script": {
          "buckets_path": {
            "up": "running&gt;values",
            "total": "total_count"
          },
          "script": "params.up/params.total"
        }
      }
</code></pre>
<ol>
<li>This is the entire transform for me:</li>
</ol>
<pre><code>POST _transform/_preview
{
  "source": {
    "index": [
      "metrics-*"
    ]
  },
  "pivot": {
    "group_by": {
      "@timestamp": {
        "date_histogram": {
          "field": "@timestamp",
          "calendar_interval": "1h"
        }
      },
      "agent.name": {
        "terms": {
          "field": "agent.name"
        }
      },
      "windows.service.name": {
        "terms": {
          "field": "windows.service.name"
        }
      }
    },
    "aggregations": {
      "total_count": {
        "value_count": {
          "field": "windows.service.state"
        }
      },
      "running": {
        "filter": {
          "term": {
            "windows.service.state": "Running"
          }
        },
        "aggs": {
          "values": {
            "value_count": {
              "field": "windows.service.state"
            }
          }
        }
      },
      "availability": {
        "bucket_script": {
          "buckets_path": {
            "up": "running&gt;values",
            "total": "total_count"
          },
          "script": "params.up/params.total"
        }
      }
    }
  }
}
</code></pre>
<ol>
<li>The preview in Dev Tools should work and be complete. Otherwise, you must debug any errors. Most of the time, it is the bucket script and the path to the values. You might have called it up instead of running. This is what the preview looks like for me.</li>
</ol>
<pre><code>{
  "running": {
    "values": 1
  },
  "agent": {
    "name": "AnnalenasMac"
  },
  "@timestamp": "2021-12-07T19:00:00.000Z",
  "total_count": 1,
  "availability": 1,
  "windows": {
    "service": {
      "name": "InstallService"
    }
  }
},
</code></pre>
<ol>
<li>Now we only paste the bucket script into the transform creation UI after selecting Edit JSON. It looks like this:</li>
</ol>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7d7e576bbffe943b/6a7f0ee7c2cc091689249666/blog-elastic-transform-configuration-pivot-configuration-object.png" alt="transform configuration pivot configuration object" /></p>
<ol>
<li>Give your transform a name, set the destination index, and run it continuously. When selecting this, please also make sure not to use @timestamp. Instead, opt for event.ingested. <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/transform-checkpoints.html">Our documentation explains this in detail</a>.</li>
</ol>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf553c02bfe0a169c/6a7f0eeaeab5be10b120a7b9/blog-elastic-transform-details.png" alt="transform details" /></p>
<ol>
<li>Click next and create and start. This can take a bit, so don’t worry.</li>
</ol>
<p>To summarize, we have now created a pivot transform using a bucket script aggregation to calculate the running time of a service in percentage. There is a caveat because Elastic Agent, per default, only collects the every 60 seconds the services state. It can be that a service is up exactly when collected and down a few seconds later. If it is that important and no other monitoring possibilities, such as <a href="https://www.elastic.co/blog/what-can-elastic-synthetics-tell-us-about-kibana-dashboards">Elastic Synthetics</a> are possible, you might want to reduce the collection time on the Agent side to get the services state every 30 seconds, 45 seconds. Depending on how important your thresholds are, you can create multiple policies having different collection times. This ensures that a super important server might collect the services state every 10 seconds because you need as much granularity and insurance for the correctness of the metric. For normal workstations where you just want to know if your remote access solution is up the majority of the time, you might not mind having a single metric every 60 seconds.</p>
<p>After you have created the transform, one additional feature you get is that the data is stored in an index, similar to in Elasticsearch. When you just do the visualization, the metric is calculated for this visualization only and not available anywhere else. Since this is now data, you can create a threshold alert to your favorite connection (Slack, Teams, Service Now, Mail, and so <a href="https://www.elastic.co/guide/en/kibana/current/action-types.html">many more to choose from</a>).</p>
<h2 id="visualizingthetransformeddata">Visualizing the transformed data</h2>
<p>The transform created a data view called windows-service. The first thing we want to do is change the format of the availability field to a percentage. This automatically tells Lens that this needs to be formatted as a percentage field, so you don’t need to select it manually as well as do calculations. Furthermore, in Discover, instead of seeing 0.5 you see 50%. Isn’t that cool? This is also possible for durations, like event.duration if you have it as nanoseconds! No more calculations on the fly and thinking if you need to divide by 1,000 or 1,000,000.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt65c1a7d0833a6894/6a7f0eecbdcff02544c42f1b/blog-elastic-edit-field-availability.png" alt="edit field availability" /></p>
<p>We get this view by using a simple Lens visualization with a timestamp on the vertical axis with the minimum interval for 1 day and an average of availability. Don’t worry — the other data will be populated once the transformation finishes. We can add a reference line using the value 0.98 because our target is 98% uptime of the service.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb5831bb81c97d83f/6a7f0eefea068d7688f09f54/blog-elastic-line.png" alt="line" /></p>
<h2 id="summary">Summary</h2>
<p>This blog post covered the steps needed to calculate the SLA for a specific data set in Elastic Observability, as well as how to visualize it. Using this calculation method opens the door to a lot of interesting use cases. You can change the bucket script and start calculating the number of sales, and the average basket size. Interested in learning more about Elastic Synthetics? Read <a href="https://www.elastic.co/guide/en/observability/current/monitor-uptime-synthetics.html">our documentation</a> or check out our free <a href="https://www.elastic.co/training/synthetics-quick-start">Synthetic Monitoring Quick Start training</a>.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/observability-sla-calculations-transforms</link>
    <guid isPermaLink="false">observability-sla-calculations-transforms</guid>
    <category><![CDATA[Incident Management]]></category>
    <category><![CDATA[Logs Analytics]]></category>
    <category><![CDATA[Metrics]]></category>
    <dc:creator><![CDATA[Philipp Kahr]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd00efad84954bdc1/6a7f0ef2ea068d6a81f09f5a/illustration-analytics-report-1680x980.png" length="0" type="image/png"/>
    <pubDate>Mon, 24 Apr 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>