<?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[OpenTelemetry - 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[OpenTelemetry - 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/opentelemetry</link>
    </image>
    <link>https://www.elastic.co/observability-labs/blog/category/opentelemetry</link>
    <atom:link href="https://www.elastic.co/observability-labs/rss/category/opentelemetry.xml" rel="self" type="application/rss+xml"/>
    <language><![CDATA[en]]></language>
    <lastBuildDate>Mon, 14 Sep 2026 18:33:53 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[Drain Vercel into Elastic: serverless observability with nothing to install]]></title>
    <description><![CDATA[A drain and an API key put Vercel logs, traces, and Speed Insights into Elastic Cloud, where you can follow a slow request from the edge to the Lambda function behind it.]]></description>
    <content:encoded><![CDATA[<p>Something breaks in production. Your Lambda error rate is climbing, users are bouncing, and you're staring at the Vercel dashboard trying to understand whether this is a code problem, a cold start problem, or something happening in a specific region. The logs tell you <em>something</em> happened. They don't tell you why, where it started, or what the user actually experienced.</p>
<p>This is the gap that Elastic's <a href="https://www.elastic.co/docs/reference/integrations/vercel">Vercel Integration</a> is designed to close. Not just logs, but the full picture. Real-user performance, server health, traces showing the complete request journey, governance events, traffic, and engagement. All of it is queryable in one place.</p>
<p>And this is the part that's easy to miss: there's nothing to deploy on your side. No OpenTelemetry Collector, no Elastic Agent, no agent policy. Vercel has a feature called Drains that streams your data as HTTP webhooks. Elastic operates a managed endpoint on Elastic Cloud that receives those payloads and writes them into Elasticsearch. You point a drain at a URL with an API key, and data starts flowing. That's the entirety of the infrastructure story.</p>
<h2 id="whatverceldrainssendtoelasticcloud">What Vercel drains send to Elastic Cloud</h2>
<p>The integration pulls in five signal types from Vercel, and they're more complementary than they might seem at first.</p>
<p>| Signal | What it carries | SDK required | Plan | Endpoint |
|---|---|---|---|---|
| Web Analytics | Page views and custom events, with traffic patterns and geography | <code>@vercel/analytics</code> | Pro or Enterprise | Vercel endpoint |
| Speed Insights | Real user Web Vitals: LCP, INP, CLS, TTFB, tagged by route, device type, and country | <code>@vercel/speed-insights</code> | Pro or Enterprise | Vercel endpoint |
| Logs | Build output, static asset requests, Lambda function output, edge function output | None | Pro or Enterprise | Vercel endpoint |
| Audit Logs | Team governance events: environment variable changes, drain edits, project transfers | None | Enterprise | Vercel endpoint |
| Traces | Infrastructure spans and outbound HTTP fetch calls, plus framework and custom spans | <code>@vercel/otel</code> (optional) | Pro or Enterprise | Managed OTLP endpoint, <code>/v1/traces</code> |</p>
<h4 id="vercelwebanalyticstrafficandcustomevents">Vercel Web Analytics: traffic and custom events</h4>
<p><a href="https://vercel.com/docs/analytics"><strong>Web Analytics</strong></a> captures page views and custom events from the <code>@vercel/analytics</code> SDK (traffic patterns, geography, what pages users are actually hitting). The SDK itself can be installed on any Vercel plan. Forwarding that data to Elastic requires a drain, which is available on Pro and Enterprise plans.</p>
<h4 id="vercelspeedinsightsrealuserwebvitals">Vercel Speed Insights: real user Web Vitals</h4>
<p><a href="https://vercel.com/docs/speed-insights"><strong>Speed Insights</strong></a> carries real-user Web Vitals, including LCP, INP, CLS, and TTFB. These are measured in the browser and tagged with route, device type, and country. Install the <code>@vercel/speed-insights</code> SDK in your app, then create a drain on a Pro or Enterprise plan to send Web Vitals to Elastic. When you see a Lambda error spike and want to know whether users actually noticed, this data can help answer that question.</p>
<h4 id="vercellogsbuildlambdaandedgeoutput">Vercel logs: build, Lambda and edge output</h4>
<p><a href="https://vercel.com/docs/logs"><strong>Logs</strong></a> cover the full server-side runtime picture, including build output, static asset requests, Lambda function output, and edge function output. When something fails, this is where you start digging. You can configure sampling rates and narrow by environment directly in the Vercel drain settings. Logs require a Pro or Enterprise plan.</p>
<h4 id="vercelauditlogsteamgovernanceevents">Vercel audit logs: team governance events</h4>
<p><a href="https://vercel.com/docs/audit-log"><strong>Audit Logs</strong></a> cover team-level governance events (who changed an environment variable, who modified a drain, and who transferred a project). It is surprisingly useful when you're trying to understand whether a configuration change preceded an incident. Audit Logs require an Enterprise plan.</p>
<h4 id="verceltracesopentelemetryspansendtoend">Vercel traces: OpenTelemetry spans end to end</h4>
<p><a href="https://vercel.com/docs/tracing"><strong>Traces</strong></a> are where the integration gets genuinely powerful for debugging. When you configure a Trace Drain, Vercel automatically instruments infrastructure spans and outbound HTTP fetch calls. You can add the <code>@vercel/otel</code> package for framework and custom spans. These traces surface in Kibana's Service Inventory: trace waterfalls, service maps, and the ability to jump directly from a trace to the related log lines. A Pro or Enterprise plan is required for traces. Traces use a separate endpoint from the other signals; they go to <code>/v1/traces</code> on the Managed OTLP endpoint.</p>
<p>Each signal has its own drain in Vercel and lands in its own data stream in Elasticsearch.</p>
<h2 id="howtosendvercellogsspeedinsightswebanalyticsandtracestoelasticcloud">How to send Vercel logs, speed insights, web analytics and traces to Elastic Cloud</h2>
<p>In Elastic Cloud, go to <strong>Add data → Connect directly to the endpoint</strong>. You'll see the Vercel endpoint listed alongside an option to create an API key. Grab both before heading to Vercel.</p>
<p><em>The Vercel endpoint URL and API key are both available here; everything you need before touching Vercel's settings.</em></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt14978e1c44f6af6b/6a9feac83481c24af68b03f0/elastic-vercel-endpoint.png" alt="The Vercel endpoint URL and API key are both available here – everything you need before touching Vercel's settings." /></p>
<p>Over in Vercel, open your team settings and go to <strong>Drains → Add Drain</strong>. Pick the signal type you want to collect, hit Next, paste in the Elastic endpoint URL, and add one custom header:</p>
<pre><code>Authorization: ApiKey &lt;your-key&gt;
</code></pre>
<p><em>Vercel lets you drain Logs, Traces, Speed Insights, Web Analytics, Audit Logs and more, with each as a separate drain. Logs, Speed Insights, Web Analytics, and Audit Logs share the Vercel endpoint, while Traces use the Managed OTLP traces URL.</em></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9c5150f088a6bb84/6a9feadfee57e50167051f3a/vercel-add-drain.png" alt="Vercel lets you drain Logs, Traces, Speed Insights, Web Analytics, and Audit Logs – each as a separate drain. Logs, Speed Insights, Web Analytics, and Audit Logs share the Vercel endpoint; traces use the Managed OTLP traces URL." /></p>
<p><em>The destination URL and Authorization header are all it takes – Elastic handles everything from here.</em></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltbf653521d5329de4/6a9feaf93eabd09e1444292c/vercel-drain-destination.png" alt="The destination URL and Authorization header are all it takes – Elastic handles everything from here." /></p>
<p>Repeat for each signal type. For Traces, use the <strong>Managed OTLP endpoint</strong> with <code>/v1/traces</code> appended, and not the standard Vercel drain URL.</p>
<p>For the full setup walkthrough and configuration options, refer to the <a href="https://www.elastic.co/docs/reference/integrations/vercel">Vercel (OpenTelemetry) Integration docs</a>.</p>
<h2 id="serverlessmonitoringinkibanadashboardsalertsandslos">Serverless monitoring in Kibana: dashboards, alerts and SLOs</h2>
<p>Once data is flowing, all the assets (dashboards, alert and SLO templates) install automatically. But the real value isn't in isolation, it's having everything in the same place when something goes wrong.</p>
<h3 id="dashboards">Dashboards</h3>
<p>The <strong>Logs dashboard</strong> is where most investigations start: request volume, error rates, HTTP status distribution, top routes, and regional breakdowns. If errors are concentrated in a single region or a specific route, you can spot it right away.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf37ec33fdacf8ee7/6a9feb1032b53009566d2d25/logs_1.png" alt="Request volume and error rates broken down by route and region – the starting point for any server-side investigation." /></p>
<p>The <strong>Speed Insights dashboard</strong> shows Core Web Vitals at p75, over time and per page, using the same "good / needs improvement / poor" thresholds Vercel uses. When a deployment ships and LCP starts drifting, this is where you'll see it first.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9092920a6ac3682a/6a9feb2689368100b43df91d/speed_insights.png" alt="p75 Web Vitals per page – spot a performance regression the moment it starts affecting real users." /></p>
<p>The <strong>Web Analytics dashboard</strong> covers the client-side traffic story: page views, geography, devices, top pages, and custom events. It is useful on its own, but especially alongside the Logs dashboard when you're trying to understand whether a server-side problem is actually reaching users.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltaadf19c3370ab370/6a9fec443481c24a798b0400/web_insights.png" alt="The Web Analytics dashboard covers the client-side traffic story. Page views, geography, devices, top pages, custom events" /></p>
<h3 id="tenalertrulesforvercelfailuremodes">Ten alert rules for Vercel failure modes</h3>
<p>Beyond dashboards, ten pre-built alert rules cover the failure modes that actually matter for Vercel workloads:</p>
<ul>
<li>5xx spikes.</li>
<li>Lambda and edge function hard crashes (the kind that never return an HTTP response and disappear from standard error-rate math).</li>
<li>Sudden traffic drops on both server and client side.</li>
<li>Regional error concentration.</li>
<li>TTFB degradation as an early warning for LCP regressions.</li>
<li>WAF deny spikes.</li>
<li>Suspicious audit activity from a single actor.</li>
</ul>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9e0eb84ddf4517e3/6a9feb51ee57e537cc051f3e/alert-page-view-drop.png" alt="Page view drops are the client-side alarm bell — by the time your error rate looks bad, users have already stopped arriving." /></p>
<h3 id="fiveslotemplatesforerrorrateandwebvitals">Five SLO templates for error rate and Web Vitals</h3>
<p>Five SLO templates round things out, all on a rolling 30-day window. Server-side error rate at 99% is the core target. The remaining four cover Core Web Vitals and TTFB at p75, so real-user performance becomes something you can formally commit to, not just a metric you check occasionally.</p>
<h3 id="tracewaterfallsandservicemapsinkibana">Trace waterfalls and service maps in Kibana</h3>
<p>For traces, once data is flowing you get the full experience in Kibana's Service inventory: end-to-end trace waterfalls, service maps, and log lines correlated right alongside the spans.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf31a9f1ab571e89f/6a9feb6bee57e5d73d051f44/trace-waterfall.png" alt="A full trace waterfall with correlated logs,follow a slow request from the edge all the way through your Lambda function." /></p>
<h2 id="setupserverlessobservabilityforyourvercelproject">Set up serverless observability for your Vercel project</h2>
<p>If you're already on <a href="https://cloud.elastic.co">Elastic Cloud</a>, you're closer than you think. The endpoint is already there; you just need to point Vercel at it. Start with Web Analytics or Speed Insights if you want a focused first step: install the SDK Vercel already recommends, then create a drain on your Pro or Enterprise team. You'll have real-user performance data in Kibana within minutes. From there, layer in logs, then traces when you're ready for the full debugging story.</p>
<p>The integration is available on Elastic Cloud Serverless and Elastic Cloud Hosted. Head to <strong>Add data → Connect directly to the endpoint</strong> in your Elastic Cloud project to grab your endpoint URL and get going.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/serverless-observability-vercel-elastic</link>
    <guid isPermaLink="false">serverless-observability-vercel-elastic</guid>
    <category><![CDATA[OpenTelemetry]]></category>
    <dc:creator><![CDATA[Ishleen Kaur]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt02b31e693f55f91c/6a9febcd805755b3ce706182/title_final.png" length="0" type="image/png"/>
    <pubDate>Tue, 08 Sep 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[LLM tracing in Elastic APM: prompts, responses, and token counts in the span view]]></title>
    <description><![CDATA[In a twenty-call agentic trace, you can see which span is using the most tokens and read the prompt that caused it. Both live in Elastic APM, so there is no second tool to run.]]></description>
    <content:encoded><![CDATA[<p>Elastic APM now does LLM tracing in the trace view. The GenAI tab in the span flyout has the whole conversation, so you can read the system prompt, the user messages and the model response, and copy any of them. Every GenAI span row in the waterfall shows input and output token counts, so in an agentic trace with twenty LLM calls you can find the span using the most tokens without opening any of them. Your LLM calls are now in the same waterfall as your database queries and HTTP spans.</p>
<p>Both features follow the <a href="https://github.com/open-telemetry/semantic-conventions-genai/tree/main/docs/gen-ai">OTel GenAI semantic conventions</a> and work with any OTel-instrumented provider. If your framework already emits OTel GenAI span attributes, there is nothing to change.</p>
<h2 id="howotelgenaispansarestructured">How OTel GenAI spans are structured</h2>
<p>A GenAI span stores everything as span attributes. A typical chat span includes:</p>
<ul>
<li><code>gen_ai.provider.name</code>: the provider (<code>openai</code>, <code>anthropic</code>, <code>aws.bedrock</code>, etc.); <code>gen_ai.system</code> is supported as a fallback for older instrumentation.</li>
<li><code>gen_ai.operation.name</code>: the operation type (<code>chat</code>, <code>embeddings</code>, etc.).</li>
<li><code>gen_ai.request.model</code>: the model being called.</li>
<li><code>gen_ai.usage.input_tokens</code>: tokens consumed by the prompt.</li>
<li><code>gen_ai.usage.output_tokens</code>: tokens generated in the response.</li>
<li><code>gen_ai.input.messages</code>, <code>gen_ai.output.messages</code>: conversation messages.</li>
<li><code>gen_ai.system_instructions</code>: the system prompt.</li>
</ul>
<p>Both features read from these attributes:</p>
<p>| Feature | What it shows | Where it appears | Attributes it reads |
| --- | --- | --- | --- |
| <strong>GenAI tab</strong> | Details (operation type, request model, provider, input and output token counts, response model, response ID) and Conversation (system prompt, user messages, model response) | Span flyout in the APM trace view, and the span flyout in Discover | Appears with any <code>gen_ai.*</code> attribute. Conversation needs <code>gen_ai.system_instructions</code>, <code>gen_ai.input.messages</code>, and <code>gen_ai.output.messages</code> |
| <strong>Token count badges</strong> | Input and output token counts for each GenAI span | Every GenAI span row in the trace waterfall | <code>gen_ai.usage.input_tokens</code>, <code>gen_ai.usage.output_tokens</code> |</p>
<h2 id="howtoreadllmpromptsandresponsesinthegenaitab">How to read LLM prompts and responses in the GenAI tab</h2>
<p>When any <code>gen_ai.*</code> attribute is present on a span, the span flyout shows a dedicated <strong>GenAI</strong> tab next to <strong>Metadata</strong>.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltaeedbb89fd9c6871/6a968cd2144a15728fde3efd/genai-tab-span-details.png" alt="GenAI tab in span details flyout" /></p>
<p>The Details section shows model metadata from the span attributes: operation type, request model, provider, input and output token counts, response model, and response ID. The Conversation section shows the full exchange, populated from <code>gen_ai.system_instructions</code> (system prompt), <code>gen_ai.input.messages</code> (user messages), and <code>gen_ai.output.messages</code> (model response), each with a copy button so you can pull the exact prompt or response out of the trace without scraping text from a formatted table.</p>
<p>All raw span attributes remain accessible on the <strong>Metadata</strong> tab.</p>
<p>The <strong>GenAI</strong> tab is also available in the span flyout in <strong>Discover</strong>, so you can inspect LLM prompts and responses directly alongside your log and trace data without switching to the APM view.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd1ccc4e30c48f8ed/6a968ced36a7416fdf27288b/genai-tab-discover.png" alt="GenAI tab in Discover span flyout" /></p>
<h2 id="whatinstrumentationdoesllmtracingrequire">What instrumentation does LLM tracing require?</h2>
<p>No Kibana-side configuration is needed. The GenAI tab appears automatically when any <code>gen_ai.*</code> attribute is present on a span. Full Conversation support requires the <a href="https://github.com/open-telemetry/semantic-conventions-genai/blob/main/docs/gen-ai/gen-ai-spans.md">OTel GenAI v1.37.0 span-attribute model</a>: <code>gen_ai.input.messages</code>, <code>gen_ai.output.messages</code>, and <code>gen_ai.system_instructions</code>.</p>
<p>Frameworks that emit the older span-events model (<code>gen_ai.user.message</code>, <code>gen_ai.assistant.message</code>, <code>gen_ai.choice</code>) will show the Details metadata section but will not populate the Conversation section. For a current list of compatible instrumentations, see the <a href="https://github.com/open-telemetry/opentelemetry-python-genai/#released-instrumentations">OTel GenAI semantic conventions</a>.</p>
<p>To verify, open the span in Discover, check that <code>gen_ai.input.messages</code> and <code>gen_ai.output.messages</code> are present, and confirm the Conversation section renders.</p>
<p>If your application already sends APM data to Elastic from a GenAI workload, open any GenAI span in the trace view and check for the GenAI tab.</p>
<h2 id="llmtokenusageinthetracewaterfall">LLM token usage in the trace waterfall</h2>
<p>Token count badges now appear on each GenAI span row in the waterfall, so you can scan the full trace without drilling in. In agentic traces with ten or twenty LLM calls, this lets you identify which span is driving token consumption before opening any span.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2d47691c65ef8799/6a968d055f9db7338e560d85/genai-waterfall-tokens.png" alt="Trace waterfall with token counts on GenAI spans" /></p>
<p>Each row shows input and output token count badges sourced from <code>gen_ai.usage.input_tokens</code> and <code>gen_ai.usage.output_tokens</code>. The row label is the span name, which instrumentation frameworks typically set to something like <code>chat gpt-4o-mini</code>.</p>
<p>Waterfall-level token counts are most useful in agentic traces where a chain of LLM calls uses different models or the same model with varying context sizes.</p>
<h2 id="whichllmprovidersdoeselasticapmsupportforgenaitracing">Which LLM providers does Elastic APM support for GenAI tracing?</h2>
<p>Elastic APM's LLM tracing works with any OTel-instrumented provider: the GenAI tab and waterfall token counts use the same OTel attribute schema regardless of which provider your application uses. Provider is read from <code>gen_ai.provider.name</code>, falling back to <code>gen_ai.system</code> for older instrumentation.</p>
<p>The <a href="https://github.com/open-telemetry/semantic-conventions-genai/tree/main/docs/gen-ai">OTel GenAI semantic conventions</a> that enable this provider detection are currently in a <code>Development</code> lifecycle. Check the <a href="https://github.com/open-telemetry/semantic-conventions-genai/releases">release notes</a> before upgrading instrumentation.</p>
<h2 id="howtoenablellmtracinginelasticapm">How to enable LLM tracing in Elastic APM</h2>
<blockquote>
  <p><strong>Availability:</strong> Both features are available as a Technical Preview on Elastic Serverless and will be available as a Technical Preview in Elastic Stack 9.6.</p>
</blockquote>
<p>To try these features:</p>
<ol>
<li>Instrument your GenAI application with an OTel SDK that follows the <a href="https://github.com/open-telemetry/semantic-conventions-genai/tree/main/docs/gen-ai">OTel GenAI semantic conventions</a> (v1.37.0 or later for full Conversation support).</li>
<li>Send traces to <a href="https://www.elastic.co/observability">Elastic Observability</a> using OTLP, the Elastic APM agent, or an EDOT SDK.</li>
<li>Open the <strong>APM</strong> section in Kibana, navigate to a service that makes LLM calls, and open the trace waterfall for any transaction.</li>
</ol>
<p>The GenAI tab appears on any span with at least one <code>gen_ai.*</code> attribute set; token count badges appear when <code>gen_ai.usage.input_tokens</code> or <code>gen_ai.usage.output_tokens</code> are present.</p>
<p>If you don't have a GenAI application to test with, the <a href="https://github.com/jennypavlova/otel-genai-chat-app">otel-genai-chat-app</a> repository is a minimal OpenAI chat app pre-instrumented with EDOT. Set <code>OPENAI_API_KEY</code> and follow the EDOT commands in the <a href="https://github.com/jennypavlova/otel-genai-chat-app#otel-genai-chat-app">README</a> to send traces to Elastic and see both features in action.</p>
<h2 id="whatsnextforllmobservabilityinelasticapm">What's next for LLM observability in Elastic APM</h2>
<p>We're exploring cost estimation per span (estimated spend based on model pricing and token counts, surfaced in the waterfall) and tool call rendering (structured display of tool/function call inputs and outputs for agentic spans).</p>
<p>If you are building GenAI applications and want early access or to share feedback, reach out through the <a href="https://discuss.elastic.co/c/observability">Elastic community forums</a> or open an issue in the <a href="https://github.com/elastic/kibana/issues">kibana repository</a>.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/llm-tracing-elastic-apm-genai-spans</link>
    <guid isPermaLink="false">llm-tracing-elastic-apm-genai-spans</guid>
    <category><![CDATA[AI]]></category>
    <category><![CDATA[APM]]></category>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[What's New]]></category>
    <dc:creator><![CDATA[Jenny Pavlova,Miriam Aparicio,Costas Pipilas]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7b5da648bcacf4aa/6a968bfc5c312610fa43eee4/header.png" length="0" type="image/png"/>
    <pubDate>Tue, 01 Sep 2026 15:22:01 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Your AI agent needs an alibi: Observability and audit trails for Agent Builder in Elastic]]></title>
    <description><![CDATA[Elastic 9.5 traces every Agent Builder run as OpenTelemetry spans in your own cluster, so tool calls and token counts are queryable with ES|QL. One workflow step adds the approval record, in a data stream the pipeline cannot rewrite.]]></description>
    <content:encoded><![CDATA[<p>One question to an Elastic Agent Builder agent produced 24 spans, 10 model calls across two models, and roughly 160,000 input tokens. Elastic 9.5 records AI agent observability data without a collector or a scraper. Every run lands as OpenTelemetry traces in your own cluster, on by default, writing to <code>traces-agent_builder.otel-&lt;space-id&gt;</code> down to each ES|QL query the agent generated and each index it looked up.</p>
<p>Those traces show how the agent reached its recommendation and what it cost. They do not record who approved it. Below: how to read the traces, scope the three identities a run touches, and append the approval decision to a data stream the pipeline cannot rewrite.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8b462c91ad11f060/6a95560048c299343ec688fd/02-architecture.png" alt="Agent Builder investigates, a workflow gate takes the human approval, and each stage writes to a different Elasticsearch data stream" /></p>
<h2 id="prerequisites">Prerequisites</h2>
<ul>
<li>Elastic Stack 9.5 or Serverless  </li>
<li>Privileges to manage Kibana advanced settings, needed to install the traces dashboard.</li>
</ul>
<h2 id="whereelasticobservabilityrecordseachpartofanaiagentaction">Where Elastic Observability records each part of an AI agent action</h2>
<p>Four questions come up in every review of an agentic operations pipeline, and each one is answered by a different record.</p>
<p>| Question | Where the answer lives | Who creates it |
| :---- | :---- | :---- |
| How did the agent reach its recommendation? | <code>traces-agent_builder.otel-*</code> spans | Agent Builder, automatically |
| Which tools did it call, and did they fail? | <code>execute_tool</code> spans in the same data stream | Agent Builder, automatically |
| Whose privileges did the run execute with? | Workflow execution record and Elasticsearch security audit logs | Kibana, partly |
| What did a human decide, and did the action run? | An index you write to yourself | You |</p>
<p>The first two are new in 9.5 and cost nothing but a toggle. The last one has no automatic source, so it is the one most pipelines are missing.</p>
<h2 id="thescenarioastalepricingcacheincheckout">The scenario: a stale pricing cache in checkout</h2>
<p>Three <code>checkout-service</code> workers serve production traffic. One of them, <code>checkout-worker-1</code>, was rolled to version <code>2026.07.26.1</code> and now returns HTTP 500 on every quote because its pricing cache stopped refreshing. The other two stay on <code>2026.07.25.3</code> and serve normally.</p>
<p>The <a href="https://www.elastic.co/observability-labs/blog/sre-control-plane-agent-builder-workflows">SRE control plane</a> pattern behind this setup connects telemetry, an Agent Builder agent that reasons over it, and Elastic Workflows that run known actions, with a <a href="https://www.elastic.co/observability-labs/blog/human-approval-sre-automation-elastic-workflows">human approval gate</a> before the first step that changes production.</p>
<p>Telemetry arrives through the documented OpenTelemetry path, so the agent reads standard OTel fields. Elasticsearch 9.5 exposes a native OTLP endpoint, which lets an OTel SDK write directly to the cluster with no collector in between:</p>
<pre><code>from opentelemetry.exporter.otlp.proto.http._log_exporter import OTLPLogExporter
from opentelemetry.sdk._logs import LoggerProvider
from opentelemetry.sdk._logs.export import BatchLogRecordProcessor
from opentelemetry.sdk.resources import Resource

provider = LoggerProvider(
    resource=Resource.create({
        "service.name": "checkout-service",
        "deployment.environment": "production",
    })
)
provider.add_log_record_processor(
    BatchLogRecordProcessor(
        OTLPLogExporter(
            endpoint=f"{ES_URL}/_otlp/v1/logs",
            headers={"Authorization": f"ApiKey {API_KEY}"},
        )
    )
)
</code></pre>
<p>The endpoint speaks OTLP over protobuf and rejects <code>application/json</code> with HTTP 406, so send it through an SDK or collector rather than hand-built JSON. That writes 450 records to <code>logs-generic.otel-default</code>: 360 healthy events across the three workers and 90 <code>PricingCacheStaleError</code> events from the broken one. The agent is given none of that context and has to find it by querying.</p>
<h2 id="readingaiagentobservabilitytracesinelastic">Reading AI agent observability traces in Elastic</h2>
<p>Everything Agent Builder records about a run lives in two data streams, and all of it is queryable with ES|QL.</p>
<h3 id="howtoturnonaiagenttracingingenaisettings">How to turn on AI agent tracing in GenAI Settings</h3>
<p>Open <strong>Stack Management</strong>, then <strong>GenAI Settings</strong>, and find the <strong>Agent Builder Traces</strong> section. <strong>Collect conversation traces</strong> is on by default in 9.5.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt907aa26526dec518/6a9555e8893681fd923dd487/03-genai-settings.png" alt="Agent Builder Traces section in GenAI Settings with Collect conversa" /></p>
<p>Two details on that panel matter more than the toggle. Traces are written to <code>traces-agent_builder.otel-&lt;space-id&gt;</code>, one data stream per Kibana space, with a companion <code>logs-agent_builder.otel-&lt;space-id&gt;</code> for agent-side events. These are ordinary data streams on the standard OTel index templates, not hidden system indices, so Discover, Lens, and ES|QL query them directly.</p>
<p>The callout states the access model plainly: anyone who can read the index can read every trace in it. Trace access is not scoped per user, so restrict the index pattern through a role before granting access to a space with sensitive conversations.</p>
<h3 id="readingthellmtracewaterfallforoneagentrun">Reading the LLM trace waterfall for one agent run</h3>
<p>A single question through the <a href="https://www.elastic.co/docs/explore-analyze/ai-features/agent-builder/kibana-api">converse API</a> asks the built-in Elastic AI Agent to investigate <code>logs-generic.otel-default</code>, find which pod and version are returning HTTP 500, and propose one bounded action. It answers correctly, naming <code>checkout-worker-1</code> on <code>2026.07.26.1</code> with a 43% error rate against zero errors on the two workers still on <code>2026.07.25.3</code>.</p>
<p>Select the trace icon under any agent response to open the waterfall.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0a0275868ed6d61a/6a95566e9b940a2b62cb3c62/04-trace-waterfall.png" alt="Trace waterfall for one agent run showing 24 spans across invoke_agent, chat, and execute_tool" /></p>
<p>One question through the converse API produced 24 spans over 43.7 seconds. The structure is an <code>invoke_agent</code> root, a <code>generate_title</code> side branch, then alternating <code>chat</code> and <code>execute_tool</code> spans as the agent queries, reads the result, and picks the next query.</p>
<p>Three span families carry everything you will aggregate on.</p>
<p>| Span name prefix | What it represents | Key attributes |
| :---- | :---- | :---- |
| <code>invoke_agent</code> | A conversation round (<code>CHAIN</code>) or an agent execution (<code>AGENT</code>) | <code>elastic.inference.span.kind</code>, <code>gen_ai.agent.id</code> |
| <code>chat</code> | One model call | <code>gen_ai.request.model</code>, <code>gen_ai.provider.name</code>, <code>gen_ai.usage.input_tokens</code>, <code>gen_ai.usage.output_tokens</code> |
| <code>execute_tool</code> | One tool invocation | <code>gen_ai.tool.name</code>, <code>gen_ai.tool.call.id</code>, <code>status.code</code> |</p>
<p>The token breakdown for that run:</p>
<p>| Model | Calls | Input tokens | Output tokens |
| :---- | ----: | ----: | ----: |
| <code>anthropic-claude-4.6-sonnet</code> | 5 | 113,315 | 2,039 |
| <code>anthropic-claude-4.5-haiku</code> | 5 | 47,211 | 651 |</p>
<p>Half the model calls went to the smaller model. That is <a href="https://www.elastic.co/docs/explore-analyze/ai-features/agent-builder/models">fast model routing</a>, which sends low-effort steps to a cheaper model, and the split is only visible in the trace.</p>
<p>The single agent investigation consumed roughly 160,000 input tokens in total. Each round replays the accumulated context, so cost scales with conversation length rather than with the length of the question.</p>
<h3 id="howdoyouqueryagenttoolcallswithesql">How do you query agent tool calls with ES|QL?</h3>
<p>Tool invocations are the part of agent behavior most worth watching, because that is where the agent touches your data. Every call is one <code>execute_tool</code> span, and the documented query aggregates them directly:</p>
<pre><code>FROM traces-agent_builder.otel-*
| WHERE span.name LIKE "execute_tool *"
| STATS calls = COUNT(*),
        errors = COUNT(*) WHERE status.code == "Error",
        avg_ms = ROUND(AVG(duration) / 1000000.0, 1)
  BY tool = attributes.gen_ai.tool.name
| SORT calls DESC
</code></pre>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte3954942227269d2/6a955510d04dac38a16c9c15/05-tool-calls-esql.png" alt="ES|QL aggregation of execute_tool spans grouped by tool name" /></p>
<p>Across that run the agent leaned on <code>platform.core.execute_esql</code>, with <code>platform.core.generate_esql</code> and <code>load_skill</code> behind it at two calls each. <code>duration</code> is in nanoseconds on the root of the document, which is why the query divides by a million for milliseconds.</p>
<p>Check the companion logs data stream as well. During a different run in the same session, the agent tried to call a tool that was not in its available set, and the attempt was recorded as an exception event in <code>logs-agent_builder.otel-default</code>, correlated to the trace by <code>trace_id</code>:</p>
<pre><code>{
  "trace_id": "3f8b9722dbd371ac4b7ad75e4bed13b6",
  "event_name": "exception",
  "attributes": {
    "exception.type": "toolNotFoundError",
    "exception.message": "Tool \"platform.streams.query_documents\" called but was not available"
  }
}
</code></pre>
<p>A blocked tool attempt is audit-relevant, and it does not appear in the trace waterfall. Querying only the traces data stream will miss it.</p>
<p>For aggregate views there is a managed dashboard, installed per space from the same settings panel.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt84e139b24e474a43/6a9556211ade64a39142b72a/06-traces-dashboard.png" alt="Agent Builder traces overview dashboard showing token usage, conversation latency, and tool call frequency" /></p>
<p>Over a fifteen-minute window covering these runs, it reported 903,914 input tokens, 11,734 output tokens, and 44 LLM requests, with 35 tool spans at a 100% success rate and 0.42 seconds average duration. The dashboard is managed and read-only, so duplicate it to change a panel, and Elastic can still ship improvements to the original.</p>
<h3 id="whatopentelemetryllmtracesdonotcapturebydefault">What OpenTelemetry LLM traces do not capture by default</h3>
<p>By default, a trace records structure and cost, not content. Six toggles under <strong>Advanced privacy settings</strong> control prompts, responses, tool call details, system prompts, real tool and agent names, and real conversation and workflow IDs, and all six are off.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6c873c449fc27e07/6a9554a860f7958318d76b3c/07-privacy-settings.png" alt="Advanced privacy settings with six toggles, all off by default" /></p>
<p>By default, a trace records structure and cost, not content. Select any span in the waterfall and the detail panel says so directly: "No input/output data available for this span."</p>
<p>Identifiers are hashed rather than dropped. The run with the blocked tool call returned conversation <code>be30fb53-d351-4fa1-b5e1-a569816f85d9</code> from the API, but its spans carry <code>gen_ai.conversation.id: b1141340d0851a46</code>. That lets you group every span belonging to one conversation and compare conversations against each other, without exposing an identifier that ties back to a user's session.</p>
<p>The consequence is that you cannot join traces to conversations on the conversation ID unless you enable real IDs, and you rarely need to. The converse API hands you the correlation key directly:</p>
<pre><code>{
  "conversation_id": "be30fb53-d351-4fa1-b5e1-a569816f85d9",
  "trace_id": "3f8b9722dbd371ac4b7ad75e4bed13b6",
  "model_usage": {
    "llm_calls": 22,
    "input_tokens": 518251,
    "output_tokens": 6423,
    "model": "anthropic-claude-4.6-sonnet"
  }
}
</code></pre>
<p>Store that <code>trace_id</code> in your own decision record, and the join works without weakening the privacy defaults. Enable real IDs only when exact response-to-decision attribution is required, and restrict the trace index in the same change.</p>
<h2 id="auditingaiagentactionsbeyondthetrace">Auditing AI agent actions beyond the trace</h2>
<p>The traces stop at what the agent did, so the records that show who authorised it have to come from somewhere else.</p>
<h3 id="whoseprivilegesdoesanaiagentactionrunwith">Whose privileges does an AI agent action run with?</h3>
<p>Three identities are involved in an agentic pipeline, and each has its own boundary.</p>
<p>| Identity | Runs with | Determined by | How to scope |
| :---- | :---- | :---- | :---- |
| Agent Builder tools | The privileges of whoever is chatting | The current user, so two people can get different data from the same question | Roles, as described in <a href="https://www.elastic.co/docs/explore-analyze/ai-features/agent-builder/permissions">Agent Builder permissions</a> |
| Workflow steps | One stored API key shared by every <code>elasticsearch.*</code> and <code>kibana.*</code> step | The trigger: manual runs use the person who started them, scheduled runs use whoever last saved the workflow | <a href="https://www.elastic.co/docs/explore-analyze/workflows/authorization">Workflow authorization</a> |
| Trace readers | Index-level access, all or nothing | A role grant on <code>traces-agent_builder.otel-*</code> | A role boundary on the trace index pattern |</p>
<p>One consequence of the stored key belongs in any review. Deactivating a user or changing their role does not refresh it, and the workflow keeps running with the privileges it captured until someone saves it again or toggles <strong>Enabled</strong> off and back on. Revoking an engineer's access does not, by itself, stop workflows that still run as them.</p>
<p>Scope the investigation role to reads only:</p>
<pre><code>POST /_security/role/agent-builder-observability-investigator
{
  "cluster": ["monitor_inference"],
  "indices": [
    {
      "names": ["logs-*", "metrics-*", "traces-*"],
      "privileges": ["read", "view_index_metadata"]
    }
  ]
}
</code></pre>
<p>Reading the agent's own traces is a separate grant, needing <code>read</code> and <code>view_index_metadata</code> on <code>traces-agent_builder.otel-*</code>. Keep the two roles apart, because the people who investigate incidents and the people who audit the agent are not always the same people.</p>
<h3 id="recordingwhoapprovedanagentactioninelasticsearch">Recording who approved an agent action in Elasticsearch</h3>
<p>Run the workflow, and it stops at the approval gate, where the reviewer sees the agent's structured output rendered into the request rather than a bare confirmation prompt.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt38b441b0cded6d2c/6a9554bc0897901dfdefb3ee/08-approval-gate.png" alt="Workflow approval gate showing the agent structured output and the decision input form" /></p>
<p>The execution record is detailed. It captures <code>resumedAt</code>, <code>resumedBy</code>, the full <code>resumeInput</code> payload, per-step token usage, and a deep link back to itself.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc05efc447ca49698/6a9554d2e657a3f7b475b008/09-execution-record.png" alt="Workflow execution record showing resumedBy, resumeInput, and per-step token usage" /></p>
<p>That execution history is an operational view, not an audit store. The underlying <code>.workflows-events</code> data stream is reserved for system operations and rejects user queries outright, so you cannot run ES|QL across a quarter of decisions, and execution history is subject to retention rather than to your compliance policy.</p>
<p>Write the decision to an index you control:</p>
<pre><code>- name: review
  type: waitForInput
  with:
    message: |
      ## Approve the proposed checkout remediation?

      The evidence query matched {{ steps.collect_evidence.output.hits.total.value }} error events in the last hour.

      Agent classification: {{ steps.investigate.output.structured_output.incident_class }}
      Affected pod: {{ steps.investigate.output.structured_output.affected_pod }}
      Proposed action: {{ steps.investigate.output.structured_output.recommended_action }}
    schema:
      type: object
      properties:
        decision:
          type: string
          enum: ["approve", "decline"]
        reason:
          type: string
          enum: ["supported-by-evidence", "insufficient-evidence", "wrong-target", "unsafe-action"]
        notes:
          type: string
      required: ["decision", "reason"]

- name: record_decision
  type: elasticsearch.index
  with:
    index: "agent-action-audit"
    document:
      "@timestamp": "{{ now | date: '%Y-%m-%dT%H:%M:%S.%LZ' }}"
      "event.action": "agent_recommendation_reviewed"
      "incident.id": "{{ consts.incident_id }}"
      "agent.conversation_id": "{{ steps.investigate.output.conversation_id }}"
      "agent.incident_class": "{{ steps.investigate.output.structured_output.incident_class }}"
      "agent.affected_pod": "{{ steps.investigate.output.structured_output.affected_pod }}"
      "agent.recommended_action": "{{ steps.investigate.output.structured_output.recommended_action }}"
      "agent.evidence_count": "{{ steps.collect_evidence.output.hits.total.value }}"
      "review.decision": "{{ steps.review.output.response.decision }}"
      "review.reason": "{{ steps.review.output.response.reason }}"
      "review.notes": "{{ steps.review.output.response.notes }}"
      "review.responded_by": "{{ steps.review.output.respondedBy }}"
      "workflow.execution_id": "{{ execution.id }}"
      "workflow.executed_by": "{{ execution.executedBy }}"
      "workflow.execution_url": "{{ execution.url }}"
</code></pre>
<p>Two details in the workflow snippet above differ from the reference page.</p>
<p>The reviewer payload is nested one level deeper. The docs describe <code>steps.&lt;name&gt;.output.&lt;field&gt;</code>, but the running build returns the submitted values under <code>response</code>, alongside a <code>respondedBy</code> field:</p>
<pre><code>{
  "response": { "decision": "approve", "reason": "supported-by-evidence" },
  "respondedBy": "1506416774"
}
</code></pre>
<p><code>execution.executedBy</code> records who started the run, and <code>respondedBy</code> records who approved the action, which in a human-in-the-loop pipeline are usually different people.</p>
<p>The second detail is the timestamp. <code>{{ now }}</code> renders a JavaScript date string like <code>Sun Jul 26 2026 07:37:11 GMT+0000 (Coordinated Universal Time)</code>, which Elasticsearch rejects with <code>failed to parse date field</code>, and <code>execution.startedAt</code> has the same problem. The Liquid <code>date</code> filter fixes it.</p>
<p>The workflow editor also flags <code>steps.review.output.*</code> as an invalid variable before the first run, because the reviewer payload shape is only known once someone responds. The warning clears after the step has real output, and the templates resolve correctly at runtime.</p>
<h3 id="makingtheauditdatastreamappendonly">Making the audit data stream append-only</h3>
<p>An audit trail the agent's own pipeline can rewrite is not an audit trail. Elasticsearch provides two independent controls, and they compose.</p>
<p>First, write to a data stream rather than an index, because data streams accept appends and nothing else:</p>
<pre><code>PUT _index_template/agent-action-audit
{
  "index_patterns": ["agent-action-audit"],
  "data_stream": {},
  "priority": 500,
  "template": {
    "mappings": {
      "properties": {
        "@timestamp":            { "type": "date" },
        "event.action":          { "type": "keyword" },
        "incident.id":           { "type": "keyword" },
        "agent.conversation_id": { "type": "keyword" },
        "agent.evidence_count":  { "type": "long" },
        "agent.recommended_action": { "type": "keyword" },
        "review.decision":       { "type": "keyword" },
        "review.reason":         { "type": "keyword" },
        "review.responded_by":   { "type": "keyword" },
        "workflow.execution_id": { "type": "keyword" }
      }
    }
  }
}
</code></pre>
<p>Second, give the writer <code>create_doc</code> and nothing else, so it can add records but cannot reach for the by-query escape hatches:</p>
<pre><code>PUT _security/role/agent-action-audit-writer
{
  "indices": [
    { "names": ["agent-action-audit"], "privileges": ["create_doc", "auto_configure"] }
  ]
}
</code></pre>
<p>Tested against the running cluster, that pair behaves the way an audit store should:</p>
<p>| Attempt as the audit writer | Result |
| :---- | :---- |
| Append a decision record | <code>201 Created</code> |
| Overwrite a record by ID | <code>400</code>, only <code>op_type: create</code> is allowed in data streams |
| <code>_update_by_query</code> to change a decision | <code>403</code>, action unauthorized |
| <code>_delete_by_query</code> to erase history | <code>403</code>, action unauthorized |
| <code>_search</code> to read the trail back | <code>403</code>, action unauthorized |</p>
<p>The write-only behaviour in the last row is deliberate. The workflow that writes decisions has no reason to read them, so auditors get a separate read role and the writer stays write-only.</p>
<p>The two controls fail differently, which matters. The <code>400</code> comes from the data stream itself and applies to everyone, including a superuser. The <code>403</code> rows come from the role, and a superuser could still run them, which is why tamper-resistant retention means shipping records off the cluster the agent's operators administer.</p>
<p>For cluster-level activity, <a href="https://www.elastic.co/docs/deploy-manage/security/logging-configuration/enabling-audit-logs">enable Elasticsearch and Kibana security audit logging</a> and forward the logs to a monitoring deployment. On 9.5 <code>xpack.security.audit.enabled</code> became a dynamic cluster setting, so Elasticsearch no longer needs a restart to turn it on, though on orchestrated deployments the logs still have to be shipped somewhere readable.</p>
<h3 id="querythedecisiontrailwithesql">Query the decision trail with ES|QL</h3>
<p>Two runs of the workflow, one approved and one declined, produce two rows you can query alongside everything else in Elastic.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd2e55b4cd891ee8e/6a9554e18814aa7f0289c29f/10-decision-trail-esql.png" alt="ES|QL query over the agent-action-audit data stream returning two decision records" /></p>
<pre><code>FROM agent-action-audit
| KEEP @timestamp, agent.incident_class, agent.affected_pod, agent.recommended_action,
       agent.evidence_count, review.decision, review.reason, review.responded_by,
       workflow.execution_id
| SORT @timestamp DESC
</code></pre>
<p>Both runs saw the same 90 error events and proposed <code>restart-checkout-worker</code> on <code>checkout-worker-1</code>. The first review approved it as <code>supported-by-evidence</code>, and the second declined it as <code>wrong-target</code>, on the argument that restarting the pod hides a pricing-feed problem rather than fixing it.</p>
<p>Because both decisions are structured fields, disagreement between reviews is queryable. You can count rejections per incident class and group them by reason: <code>insufficient-evidence</code> sends you back to the investigation path, and <code>unsafe-action</code> sends you to the workflow and its permission boundary.</p>
<h2 id="aiagentobservabilitylimitstodesignaround">AI agent observability limits to design around</h2>
<p>Four behaviors are worth designing around, and each is cheaper to handle before the workflows are written.</p>
<ol>
<li><strong>Trace access is index-level, not per user.</strong> A space with sensitive conversations needs a role boundary on <code>traces-agent_builder.otel-*</code> rather than a UI setting.</li>
<li><strong>The managed dashboard is not installed automatically in a new space.</strong> Add it to your space provisioning checklist.</li>
<li><strong>The workflow execution carries its own APM <code>traceId</code>.</strong> It is not the same trace as the Agent Builder spans its <code>ai.agent</code> step produced, so correlate through the conversation ID or the <code>trace_id</code> returned by the agent rather than expecting one trace to span both.</li>
<li><strong>The <code>waitForInput</code> output shape differs from the reference page.</strong> The submitted values arrive under <code>response</code>, alongside <code>respondedBy</code>, as covered above.</li>
</ol>
<p>None of these blocks the pattern.</p>
<h2 id="wheretostartwithaiagentobservability">Where to start with AI agent observability</h2>
<p>Turn trace collection on, install the dashboard in the space your agents run in, and open the waterfall for one real conversation. It shows the tool sequence, the model split, and the latency distribution that the answer text does not.</p>
<p>Then pick the single incident class where you already trust the runbook, and add one <code>elasticsearch.index</code> step after its approval gate. An append-only decision record costs one workflow step and answers the three questions a review needs: who approved this, on what evidence, and what happened next.</p>
<p>For the details, see <a href="https://www.elastic.co/docs/explore-analyze/ai-features/agent-builder/collect-traces">Collect Agent Builder traces</a>, the <a href="https://www.elastic.co/docs/explore-analyze/ai-features/agent-builder/agent-traces-dashboard">traces overview dashboard</a>, <a href="https://www.elastic.co/docs/explore-analyze/ai-features/agent-builder/permissions">Agent Builder permissions</a>, <a href="https://www.elastic.co/docs/explore-analyze/workflows/authorization">workflow authorization</a>, and the <a href="https://www.elastic.co/docs/explore-analyze/workflows/steps/wait-for-input"><code>waitForInput</code> reference</a>.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/ai-agent-observability-audit-trail</link>
    <guid isPermaLink="false">ai-agent-observability-audit-trail</guid>
    <category><![CDATA[Agentic Observability]]></category>
    <category><![CDATA[LLM Observability]]></category>
    <category><![CDATA[OpenTelemetry]]></category>
    <dc:creator><![CDATA[Jeffrey Rengifo]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2c757a76a8a7504d/6a9553fe0897906e2aefb3e4/01-header.png" length="0" type="image/png"/>
    <pubDate>Mon, 31 Aug 2026 15:13:52 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[AI agent observability for Microsoft Foundry: two env vars, no collector]]></title>
    <description><![CDATA[Set up LLM tracing once and every model call, tool execution and handoff from your Foundry agent arrives in Kibana as one queryable trace, with token counts on each span and code for Agent Framework, LangGraph and Node.js.]]></description>
    <content:encoded><![CDATA[<p>Instrument your Microsoft Foundry agent with the OTel SDK, set two environment variables, and every LLM call, tool execution and agent handoff lands in Kibana as a queryable trace with token counts on every span. No OTel Collector sits in between, and nothing gets rewritten into a proprietary schema on the way in. Normal APM assumes a successful response is a correct one. AI agent observability can't, because an agent run can return HTTP 200 and still answer wrong, having burned 40,000 tokens to get there, so what you need is the whole decision tree. Code below for <a href="https://github.com/microsoft/agent-framework">Agent Framework</a>, <a href="https://github.com/langchain-ai/langgraph">LangGraph</a>, and custom Node.js containers.</p>
<h2 id="whydoesaiagentobservabilityneedadifferentmodel">Why does AI agent observability need a different model?</h2>
<p>Standard application monitoring answers: "Did this request succeed? How long did the database query take?" These questions work because traditional software is deterministic: same input, same output, errors have error codes.</p>
<p>AI agents break that model. A single user request to a Foundry hosted agent might trigger ten LLM calls, five tool executions, two file searches, and an MCP server call, each with its own latency and potential failure mode. The agent can return HTTP 200 and still produce a wrong answer. It can silently loop on the same tool call until it hits a token limit. It can hand off a task to a specialist agent and lose context in transit, and none of that shows up in your error rate or P99 latency.</p>
<p>The questions you actually need to answer in production are different:</p>
<ul>
<li>Why did this run consume 40,000 tokens when the average is 3,000?</li>
<li>Which tool call is responsible for the long tail?</li>
<li>When the orchestrator handed off to the search agent, did the trace context survive?
For those, you need hierarchical trace data that maps the agent's decision tree, not just its I/O boundary.</li>
</ul>
<p>Foundry Agent Service has strong built-in observability. For Prompt agents, <a href="https://learn.microsoft.com/en-us/azure/foundry/observability/how-to/trace-agent-setup">server-side tracing</a> is zero-config: connect an Application Insights resource to your project and Foundry automatically captures inputs, outputs, tool calls, token usage, and latency with no code changes required. For Hosted agents, you add client-side instrumentation to your container code, which is what this post focuses on.</p>
<p>What Foundry's built-in tracing doesn't give you is the ability to run arbitrary queries over the raw OTel data or correlate agent traces with infrastructure telemetry from the rest of your stack. That's where routing those same traces to Elastic adds value. The <a href="https://www.youtube.com/watch?v=WprbDyANqy0">Instrument → Debug → Evaluate → Optimize loop</a> that Foundry supports in its portal is even more powerful when you can drive it from ES|QL queries against the full trace corpus.</p>
<p>A note on Application Insights: it receives OTel traces but converts them into its own schema on ingestion. Elastic stores the data as-is (resource attributes, semantic convention fields, everything preserved) so you can query exactly what was emitted, without a translation layer between you and the data.</p>
<h2 id="llmtracingwiththeopentelemetrygenaiconventions">LLM tracing with the OpenTelemetry GenAI conventions</h2>
<p>Foundry uses <a href="https://github.com/open-telemetry/semantic-conventions-genai/blob/main/docs/gen-ai/README.md">OpenTelemetry's GenAI semantic conventions</a> to structure its traces. Three core span types cover most agent workloads.</p>
<p>Stability note: All <code>gen_ai.*</code> span names and attributes currently carry a <a href="https://github.com/open-telemetry/semantic-conventions-genai/blob/main/docs/gen-ai/gen-ai-agent-spans.md">Development stability badge</a> in the OpenTelemetry registry; they are pre-1.0 and have already changed once (for example, <code>gen_ai.system</code> was renamed to <code>gen_ai.provider.name</code>). Pin your SDK versions and expect attribute strings to shift before these conventions reach stable status.</p>
<p><strong>invoke_agent</strong> wraps the entire agent execution. Every run gets one of these as the root span.</p>
<p><strong>chat</strong> is a single LLM API call. It carries gen_ai.provider.name (the provider: openai, anthropic, aws.bedrock), gen_ai.request.model, and gen_ai.usage.input_tokens and gen_ai.usage.output_tokens on every single call, which is what makes per-call token cost attribution possible.</p>
<p><strong>execute_tool</strong> is a tool or function invocation triggered by the model, nested under the chat span that requested it.</p>
<p>For multi-agent systems, Microsoft (in collaboration with Cisco Outshift) extended these conventions with additional span types now integrated into Foundry, Agent Framework, LangChain, LangGraph, and the OpenAI Agents SDK:</p>
<ul>
<li><code>execute_task</code> captures task planning and how work is decomposed and distributed across agents.</li>
<li>agent_to_agent_interaction (child of invoke_agent) traces direct communication between agents.</li>
<li>agent_planning logs an agent's internal planning steps  </li>
<li>agent.state.management covers context and memory operations</li>
</ul>
<p>The resulting trace for a multi-step Foundry agent looks like this:</p>
<pre><code>[invoke_agent: research-agent]             ← root: the whole task
  [agent_planning]                         ← agent decides its approach
  [chat: azure]                            ← first LLM call
  [execute_tool: file_search]              ← Foundry built-in tool call
  [chat: azure]                            ← LLM call to reason over results
  [agent_to_agent_interaction: summarizer] ← handoff to specialist agent
    [invoke_agent: summarizer]             ← nested agent execution
      [chat: azure]
  [chat: azure]                            ← final synthesis call
</code></pre>
<p>In Elastic, the agent trace renders as a waterfall. You see how long each step took, which one errored, and where the tokens went, including across the agent handoff boundary. That's the data model we're getting into Elastic.</p>
<h2 id="howtoinstrumentafoundryhostedagenttoemitoteltraces">How to instrument a Foundry Hosted agent to emit OTel traces</h2>
<p>Foundry <a href="https://learn.microsoft.com/en-us/azure/foundry/agents/concepts/hosted-agents">Hosted agents</a> run your code in a container managed by Foundry. Because you own the container, you control the instrumentation. Which path you take depends on the framework you're using.</p>
<p>| Framework | Language | Key package | Instrumentation method |
|---|---|---|---|
| Agent Framework | Python | <code>azure-ai-projects</code> | <code>AIProjectInstrumentor().instrument()</code> |
| LangGraph | Python | <code>langchain-azure-ai</code> | <code>AzureAIOpenTelemetryTracer</code> callback |
| Custom container | TypeScript / Node.js | <code>@opentelemetry/sdk-node</code> | Manual <code>startActiveSpan</code> |</p>
<h3 id="traceagentframeworkagentswiththeazureaiprojectssdkpython">Trace Agent Framework agents with the Azure AI Projects SDK (Python)</h3>
<p><a href="https://github.com/microsoft/agent-framework">Agent Framework</a> is Microsoft's framework for building Hosted agents on Foundry. It calls the Foundry Responses API for model inference and tool orchestration. To route traces to Elastic, configure the OTel SDK at container startup and add the OTLP exporter pointing at your Elastic endpoint.</p>
<p>Install the packages:</p>
<pre><code>pip install azure-ai-projects azure-identity opentelemetry-sdk azure-core-tracing-opentelemetry opentelemetry-exporter-otlp-proto-http
</code></pre>
<p>Configure at startup in your agent container:</p>
<pre><code>import os
from azure.identity import DefaultAzureCredential
from azure.ai.projects import AIProjectClient
from azure.ai.projects.telemetry import AIProjectInstrumentor
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter

# OTLPSpanExporter reads OTEL_EXPORTER_OTLP_ENDPOINT and
# OTEL_EXPORTER_OTLP_HEADERS automatically, no need to pass them explicitly
provider = TracerProvider()
provider.add_span_processor(BatchSpanProcessor(OTLPSpanExporter()))
trace.set_tracer_provider(provider)

# Tracing is off by default — opt in explicitly (experimental preview as of 2026)
# Set AZURE_EXPERIMENTAL_ENABLE_GENAI_TRACING=true in your container environment
AIProjectInstrumentor().instrument()

client = AIProjectClient(
    endpoint=os.getenv("AZURE_AI_FOUNDRY_PROJECT_ENDPOINT"),
    credential=DefaultAzureCredential(),
)
</code></pre>
<p>Every model call, tool invocation, and agent handoff made through the Responses API now produces a structured OTel span with token usage, model identity, and tool metadata. Note that GenAI tracing in the Azure AI Projects SDK is an experimental preview — you must set <code>AZURE_EXPERIMENTAL_ENABLE_GENAI_TRACING=true</code> in your container environment and call <code>AIProjectInstrumentor().instrument()</code> before any agent runs, or no spans are produced.</p>
<h3 id="tracelanggraphagentswithazureaiopentelemetrytracerpython">Trace LangGraph agents with AzureAIOpenTelemetryTracer (Python)</h3>
<p><a href="https://github.com/langchain-ai/langgraph">LangGraph</a> is a supported framework for Foundry Hosted agents. Microsoft's <code>langchain-azure-ai</code> package provides an OTel-compliant tracer for LangGraph that emits spans for graph steps, tool invocations, and model calls. Configure the OTLP exporter the same way, then attach the tracer as a callback on each invocation:</p>
<pre><code>pip install langchain-azure-ai langgraph langchain langchain-openai opentelemetry-sdk opentelemetry-exporter-otlp-proto-http azure-identity
</code></pre>
<pre><code>from langchain_azure_ai.callbacks.tracers import AzureAIOpenTelemetryTracer
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter

# OTLPSpanExporter reads OTEL_EXPORTER_OTLP_ENDPOINT and
# OTEL_EXPORTER_OTLP_HEADERS automatically, no need to pass them explicitly
provider = TracerProvider()
provider.add_span_processor(BatchSpanProcessor(OTLPSpanExporter()))
trace.set_tracer_provider(provider)

# AzureAIOpenTelemetryTracer is Microsoft's supported tracer for LangChain and LangGraph.
# Pass it as a callback when invoking your graph — it uses the active TracerProvider above.
azure_tracer = AzureAIOpenTelemetryTracer(name="my-langgraph-agent")

# app is your compiled LangGraph workflow (e.g. workflow.compile())
config = {"callbacks": [azure_tracer]}
result = app.invoke({"messages": [...]}, config=config)
</code></pre>
<h3 id="tracecustomnodejsagentswiththeopentelemetrysdktypescript">Trace custom Node.js agents with the OpenTelemetry SDK (TypeScript)</h3>
<p>For custom agent architectures in Node.js, wrap your orchestration logic directly with the OTel SDK. The startActiveSpan API handles parent-child nesting automatically. Any span created inside the callback is a child of the current span, giving you the decision tree hierarchy without explicit parent references.</p>
<pre><code>import { NodeSDK } from "@opentelemetry/sdk-node";
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-proto";
import { trace, SpanKind, SpanStatusCode } from "@opentelemetry/api";

// OTLPTraceExporter reads OTEL_EXPORTER_OTLP_ENDPOINT and
// OTEL_EXPORTER_OTLP_HEADERS automatically, no need to pass them explicitly
const sdk = new NodeSDK({
  traceExporter: new OTLPTraceExporter(),
  serviceName: "my-foundry-agent",
});
sdk.start();

const tracer = trace.getTracer("foundry-agent", "1.0.0");

// Wrap your agent's entry point: everything inside becomes a child span
async function runAgent(userMessage: string) {
  return tracer.startActiveSpan(
    "invoke_agent my-agent",
    {
      kind: SpanKind.INTERNAL,
      attributes: {
        "gen_ai.operation.name": "invoke_agent",
        "gen_ai.agent.name": "my-agent",
        "gen_ai.provider.name": "azure",
      },
    },
    async (span) =&gt; {
      try {
        const result = await agentLoop(userMessage);
        span.setStatus({ code: SpanStatusCode.OK });
        return result;
      } catch (err) {
        span.recordException(err as Error);
        span.setStatus({ code: SpanStatusCode.ERROR });
        throw err;
      } finally {
        span.end();
      }
    }
  );
}

// Record token usage on every LLM call
async function callAzureOpenAI(messages: Message[]) {
  return tracer.startActiveSpan(
    "chat azure",
    {
      kind: SpanKind.CLIENT,
      attributes: {
        "gen_ai.operation.name": "chat",
        "gen_ai.provider.name": "azure",
        "gen_ai.request.model": "gpt-4o",
      },
    },
    async (span) =&gt; {
      const response = await client.chat.completions.create({ model: "gpt-4o", messages });
      span.setAttributes({
        "gen_ai.usage.input_tokens": response.usage.prompt_tokens,
        "gen_ai.usage.output_tokens": response.usage.completion_tokens,
        "gen_ai.response.model": response.model,
      });
      span.end();
      return response;
    }
  );
}
</code></pre>
<p>If your Node.js agent calls the OpenAI SDK directly, OpenTelemetry JS also ships an <a href="https://github.com/open-telemetry/opentelemetry-js-contrib/tree/main/packages/instrumentation-openai">instrumentation-openai</a> auto-instrumentation package as an alternative to the manual spans above (it works with Azure OpenAI clients too). Check its semantic-convention version against what's shown here before relying on it; third-party instrumentations for LangChain and others also exist but vary in how current they are.</p>
<h2 id="howtogetyourelasticmanagedotlpendpointandapikey">How to get your Elastic managed OTLP endpoint and API key</h2>
<p>The managed OTLP endpoint (mOTLP) is generally available on both <strong>Elastic Cloud Serverless</strong> and <strong>Elastic Cloud Hosted</strong>. It is not available for self-managed, ECE, or ECK deployments. For those, use the <a href="https://www.elastic.co/docs/reference/edot-collector/modes#edot-collector-as-gateway">EDOT Collector as a gateway</a> instead.</p>
<p><strong>Serverless:</strong> Log in to <a href="https://cloud.elastic.co">Elastic Cloud</a> → find your project → <strong>Manage</strong> → <strong>Application endpoints, cluster and component IDs</strong> → <strong>Ingest</strong>. Copy the endpoint value. Alternatively, go to <strong>Add data → Applications → OpenTelemetry</strong> inside your project, which also generates a pre-configured API key.</p>
<p><strong>Elastic Cloud Hosted:</strong> Log in → <strong>Hosted deployments</strong> → <strong>Manage</strong> → <strong>Application endpoints</strong> → <strong>Managed OTLP</strong>. Copy the public endpoint value.</p>
<p>The API key must have event:write privilege on the apm application. The Elastic Cloud quickstart wizard generates this for you. Set two environment variables in your Foundry Hosted agent container:</p>
<pre><code>export OTEL_EXPORTER_OTLP_ENDPOINT="https://&lt;your-motlp-endpoint&gt;"
export OTEL_EXPORTER_OTLP_HEADERS="Authorization=ApiKey &lt;your-api-key&gt;"
</code></pre>
<p>Note the header format: ApiKey \&lt;key&gt;, not Bearer. And the env var uses = as the separator between header name and value, not :.</p>
<p>Traces sent to the endpoint land in the traces-generic.otel-default data stream by default. In Kibana, find them under <strong>Observability → APM → Traces</strong> or query them directly with ES|QL against traces-generic.otel-*. No OTel Collector required. No schema translation.</p>
<h2 id="autoinstrumentfoundryagentsonakswiththeopentelemetryoperator">Auto-instrument Foundry agents on AKS with the OpenTelemetry Operator</h2>
<p>Foundry Hosted agents support bring-your-own VNet and can run container workloads on AKS. If you're in that configuration, you can skip the SDK-level OTLP configuration entirely. Add this annotation to your pod spec and the <a href="https://opentelemetry.io/docs/platforms/kubernetes/operator/">OpenTelemetry Operator</a> injects the SDK automatically:</p>
<pre><code>annotations:
  instrumentation.opentelemetry.io/inject-python: "true"
</code></pre>
<p>You still set OTEL_EXPORTER_OTLP_ENDPOINT and OTEL_EXPORTER_OTLP_HEADERS to point at Elastic. The SDK wiring is handled for you.</p>
<p>For agents running in Azure Container Apps, the platform's built-in managed OTel collector routes to Application Insights by default. To also get traces into Elastic, use the SDK-level configuration from the examples above alongside Foundry's built-in observability, or run a sidecar OTel Collector with an OTLP exporter configured for your Elastic endpoint.</p>
<h2 id="howtokeepamultiagenthandoffinonetrace">How to keep a multi-agent handoff in one trace</h2>
<p>If your architecture involves an orchestrator delegating to specialist agents, you want all of that to appear as one connected trace, not N disconnected fragments.</p>
<p>OTel handles this via the <a href="https://www.w3.org/TR/trace-context/">W3C TraceContext standard</a> (traceparent and tracestate headers). For HTTP-based inter-agent calls, the SDK propagates these automatically. For queue-based handoffs (Service Bus, Event Hubs), you carry the context in the message itself:</p>
<pre><code>from opentelemetry import propagate, context

# Sending agent: inject the active trace context into the message
carrier = {}
propagate.inject(carrier)

await queue.send_message({
    "payload": task_data,
    "trace_context": carrier,  # {"traceparent": "00-abc123...", "tracestate": "..."}
})

# Receiving agent: restore the trace context before doing any work
incoming_ctx = propagate.extract(message["trace_context"])
with context.use_context(incoming_ctx):
    await process_task(message["payload"])
</code></pre>
<p>With this in place, the entire work item appears as one trace in Elastic, from orchestrator through every worker agent, across process boundaries. The waterfall shows exactly where time was spent at each tier.</p>
<h2 id="whatfoundryagenttraceslooklikeinelasticapm">What Foundry agent traces look like in Elastic APM</h2>
<p>Traces arrive as a hierarchical waterfall in the APM UI. For each agent invocation, you get the full span tree: every LLM call, tool execution, and sub-agent call nested under the root invoke_agent span. Every chat span carries gen_ai.usage.input_tokens and gen_ai.usage.output_tokens, so you can see at a glance which model invocations are expensive and which are routine. Errors surface as specific failed spans with full stack traces, not a red line on a latency graph.</p>
<p>You can also query the trace data directly with ES|QL. To find every agent run in the past hour that consumed more than 20,000 input tokens:</p>
<pre><code>FROM traces-generic.otel-default
| WHERE attributes.gen_ai.operation.name == "invoke_agent"
  AND @timestamp &gt; NOW() - 1 hour
| STATS total_input_tokens = SUM(attributes.gen_ai.usage.input_tokens)
    BY trace.id, attributes.gen_ai.agent.name
| WHERE total_input_tokens &gt; 20000
| SORT total_input_tokens DESC
</code></pre>
<p>Combining trace structure with token accounting in a single query is what OTel native storage makes possible.</p>
<h2 id="howtosampleagenttraceswithoutlosingerrors">How to sample agent traces without losing errors</h2>
<p>For tail-based sampling, add a local <a href="https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/processor/tailsamplingprocessor">OTel Collector</a> as an intermediate hop before the Elastic endpoint. A policy that keeps all error traces, all slow traces, and samples down routine successes is a reasonable starting point:</p>
<pre><code># Run this Collector between your Foundry Hosted agent and the Elastic endpoint
processors:
  tail_sampling:
    decision_wait: 10s
    policies:
      - name: errors
        type: status_code
        status_code: { status_codes: [ERROR] }
      - name: slow-traces
        type: latency
        latency: { threshold_ms: 5000 }
      - name: sample-routine
        type: probabilistic
        probabilistic: { sampling_percentage: 10 }

exporters:
  otlp/elastic:
    endpoint: "${OTEL_EXPORTER_OTLP_ENDPOINT}"
    headers:
      Authorization: "ApiKey ${ELASTIC_API_KEY}"
    sending_queue:
      enabled: true
      sizer: bytes
      queue_size: 50_000_000
      block_on_overflow: true
</code></pre>
<p>If you're sending directly from the SDK without a Collector, start at 100% sampling. Agent trace volume is usually smaller than expected. Evaluate storage costs after a week and tune from there. For most Foundry Hosted agent workloads, the direct SDK path is the right starting point.</p>
<h2 id="howtouseagenttracesforevaluationandoptimization">How to use agent traces for evaluation and optimization</h2>
<p>The Build session demo framed tracing as a four-step production loop: instrument, debug, evaluate, optimize. Debugging is the obvious first payoff: when an agent run fails or produces a wrong answer, the trace shows exactly which span introduced the problem. But the evaluate and optimize steps are where the loop compounds.</p>
<p>With traces flowing into Elastic, you can identify which agent runs produced incorrect or low-quality outputs, then pull those traces directly into an evaluation workflow in Foundry. The trace gives you the full context (the prompt, the tool calls, the model's reasoning path) that an eval needs to score quality and catch regressions. From there, optimization targets are concrete: this tool call is slow, this model is expensive for its output quality, this planning step runs unnecessarily on every request.</p>
<p>Foundry's <a href="https://learn.microsoft.com/en-us/azure/foundry/agents/concepts/agent-optimizer-overview">agent optimizer</a> can use trace data to improve agent instructions automatically. Elastic gives you the query layer to find the traces worth optimizing in the first place.</p>
<h2 id="whatyouneedtostarttracingfoundryagents">What you need to start tracing Foundry agents</h2>
<p>You need four things: an <a href="https://cloud.elastic.co/registration">Elastic Cloud Serverless project</a> (the managed OTLP endpoint is included), your endpoint URL and API key from Project Management → Edit alias, instrumentation that matches your stack (<code>AIProjectInstrumentor</code> for Agent Framework, <code>AzureAIOpenTelemetryTracer</code> from <code>langchain-azure-ai</code> for LangGraph, or the OTel SDK directly for custom container code), and one agent run to verify the trace appears in Kibana's APM view.</p>
<p>The first trace that shows you exactly which tool call caused that 45-second timeout makes the setup worth it.</p>
<hr />
<p><em>More resources: <a href="https://learn.microsoft.com/en-us/azure/foundry/agents/overview">Microsoft Foundry Agent Service overview</a> | <a href="https://learn.microsoft.com/en-us/azure/foundry/observability/how-to/trace-agent-setup">Set up tracing in Foundry</a> | <a href="https://learn.microsoft.com/en-us/azure/foundry/observability/how-to/trace-agent-framework">Tracing integrations by framework</a> | <a href="https://www.elastic.co/docs/reference/opentelemetry/motlp">Elastic managed OTLP endpoint docs</a> | <a href="https://opentelemetry.io/docs/specs/semconv/gen-ai/">OpenTelemetry GenAI semantic conventions</a> | <a href="https://www.youtube.com/watch?v=WprbDyANqy0">Build 2026 DEM341: Any agent, any cloud</a></em></p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/ai-agent-observability-microsoft-foundry</link>
    <guid isPermaLink="false">ai-agent-observability-microsoft-foundry</guid>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[Kubernetes]]></category>
    <dc:creator><![CDATA[Greg Crist]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt85d71c4aee65b074/6a8e9dc313070e654f204872/elastic-de_149846_720x420_11-B.png" length="0" type="image/png"/>
    <pubDate>Mon, 24 Aug 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[OpenTelemetry Java extensions: customize traces without forking the agent]]></title>
    <description><![CDATA[One JAR, loaded at startup by the OpenTelemetry Java agent, lets you filter health checks, rename spans, add resource attributes, and control sampling with no application code changes.]]></description>
    <content:encoded><![CDATA[<p>You've just set up auto-instrumentation on a Java application. Without any code changes, traces start flowing to your observability platform.
After a few minutes, you realize health check endpoints are flooding your trace view, and transaction names reflect generic framework patterns rather than your domain operations.</p>
<p>Forking the agent would fix this, but then you own every upstream merge.
You could also use manual instrumentation for complete control, but that requires code changes and ongoing upkeep.
OpenTelemetry Java extensions give you a cleaner path: a separate JAR the agent loads at startup, giving you precise control over what gets captured and exported, without touching agent or application code.</p>
<p>For example, the following challenges are very common:</p>
<ul>
<li>Health check probes are flooding your trace view.</li>
<li>Span names reflect generic framework patterns rather than your domain operations.</li>
<li>Some span names or attributes have high cardinality creating noise in your traces.</li>
<li>Spans are missing attributes relevant to your business logic.</li>
<li>Baggage headers are propagating to downstream services when they shouldn't.</li>
<li>Resource attributes that describe your deployment are not automatically captured because they rely on custom environment variables.</li>
</ul>
<p>Some of those can be solved through configuration, or by using an intermediate OpenTelemetry Collector for processing.
However, this also might add complexity to the telemetry pipeline, and you might prefer to solve this at the source, where the data is captured.</p>
<h2 id="whatareopentelemetryjavaextensions">What are OpenTelemetry Java extensions</h2>
<p>An extension is a JAR file the agent loads at startup. It hooks into the agent's extension points through Java's Service Provider Interface (SPI) mechanism, the same mechanism the agent uses internally.</p>
<p>The extension mechanism works identically with the upstream OpenTelemetry Java agent and with <a href="https://github.com/elastic/elastic-otel-java">Elastic's OpenTelemetry distribution</a>. You write the extension once and it works with either.</p>
<p>For reference, the <a href="https://opentelemetry.io/docs/zero-code/java/agent/extensions/">upstream extension documentation</a> provides an exhaustive overview of extension points and a few examples.</p>
<p>This post does not aim to provide a complete reference, but focuses on simple use cases you're likely to reach for in production: renaming spans, filtering noisy traces, or propagating context that the agent doesn't cover in your environment.</p>
<p>Extensions also let you modify and extend the agent instrumentation itself. That goes beyond what this post covers. Here are two starting points:</p>
<ul>
<li><a href="https://opentelemetry.io/docs/zero-code/java/agent/extensions/#instrumentercustomizerprovider">Modify instrumentation using instrumenter customizers</a>.</li>
<li><a href="https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/main/examples/extension/src/main/java/com/example/javaagent/instrumentation/DemoServlet3InstrumentationModule.java">Modify or add instrumentation using the instrumentation module</a>.</li>
</ul>
<h2 id="settingupanopentelemetryjavaextensionproject">Setting up an OpenTelemetry Java extension project</h2>
<p>An extension is a standard Java Gradle project with two requirements: the output must be a shadow JAR (a fat JAR with all extension dependencies bundled), and OpenTelemetry dependencies must be declared <code>compileOnly</code> so you don't bundle the SDK itself.</p>
<p>The shadow JAR requirement exists because the agent loads the extension in its own classloader. If you declare a dependency as <code>implementation</code>, it gets bundled and may conflict with the version already in the agent. Using <code>compileOnly</code> keeps those JARs out of the extension JAR entirely.</p>
<p>Here is a minimal <code>build.gradle.kts</code> for a simple extension that does not customize instrumentation and thus relies only on the OpenTelemetry SDK/API.</p>
<pre><code>plugins {
  id("java")
  id("com.gradleup.shadow")
}

repositories {
  mavenCentral()
}

java {
  toolchain {
    languageVersion.set(JavaLanguageVersion.of(8))
  }
}

dependencies {
  // Use BOM to manage OpenTelemetry dependency versions
  compileOnly(platform("io.opentelemetry:opentelemetry-bom:1.64.0"))
  // OpenTelemetry SDK autoconfiguration SPI (provided by agent)
  compileOnly("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi")
  // OpenTelemetry SDK
  compileOnly("io.opentelemetry:opentelemetry-sdk")
  // Annotation processor for automatic SPI registration
  compileOnly("com.google.auto.service:auto-service:1.1.1")
  annotationProcessor("com.google.auto.service:auto-service:1.1.1")
}

tasks.assemble {
  dependsOn(tasks.shadowJar)
}
</code></pre>
<p>Check <a href="https://central.sonatype.com/artifact/io.opentelemetry/opentelemetry-bom/">Maven Central</a> for the latest version of the BOM before starting.</p>
<p>Extensions only depend at compile-time on the OpenTelemetry SDK and the autoconfiguration SPI. The agent provides the rest of the SDK and instrumentation implementation at runtime.</p>
<h2 id="loadingopentelemetryjavaextensionsatruntime">Loading OpenTelemetry Java extensions at runtime</h2>
<p>To load an OpenTelemetry Java extension at runtime, you can use the <code>otel.javaagent.extensions</code> system property or <code>OTEL_JAVAAGENT_EXTENSIONS</code> environment variable. The value is a comma-separated list of paths to extension JARs:</p>
<pre><code>java -Dotel.javaagent.extensions=/path/to/my-extension.jar -javaagent:/path/to/opentelemetry-javaagent.jar -jar myapp.jar
</code></pre>
<p>The upstream OpenTelemetry Java agent also lets you <a href="https://opentelemetry.io/docs/zero-code/java/agent/extensions/#embedding-extensions-in-the-agent">embed extensions directly into the agent JAR</a> to simplify deployment.</p>
<h2 id="filteringandrenamingspanswithopentelemetryjavaextensions">Filtering and renaming spans with OpenTelemetry Java extensions</h2>
<p>You can modify spans in two ways:</p>
<ul>
<li>Using a <code>SpanProcessor</code> that is called synchronously when the span starts or ends.</li>
<li>Using a <code>SpanExporter</code> that is called asynchronously when the span is exported.</li>
</ul>
<h3 id="renamespanswithaspanprocessor">Rename spans with a SpanProcessor</h3>
<p><code>SpanProcessor.onStart</code> receives a <code>ReadWriteSpan</code>, which means you can call <code>span.updateName()</code> before the span is exported. This is the right hook for renaming based on attributes that are available at span start.</p>
<pre><code>public class OperationRenamingSpanProcessor implements SpanProcessor {

  @Override
  public void onStart(Context parentContext, ReadWriteSpan span) {
    String operation = span.getAttribute(AttributeKey.stringKey("app.operation"));
    if (operation != null) {
      span.updateName(operation);
    }
  }

  @Override
  public boolean isStartRequired() { return true; }

  @Override
  public void onEnd(ReadableSpan span) {}

  @Override
  public boolean isEndRequired() { return false; }

  @Override
  public CompletableResultCode shutdown() { return CompletableResultCode.ofSuccess(); }

  @Override
  public CompletableResultCode forceFlush() { return CompletableResultCode.ofSuccess(); }
}
</code></pre>
<p>Register the SpanProcessor via <code>AutoConfigurationCustomizerProvider</code>, composing it with whatever processor you have already configured:</p>
<pre><code>@AutoService(AutoConfigurationCustomizerProvider.class)
public class RenamingCustomizerProvider implements AutoConfigurationCustomizerProvider {

  @Override
  public void customize(AutoConfigurationCustomizer customizer) {
    customizer.addTracerProviderCustomizer(this::configureSdkTracerProvider);
  }

  private SdkTracerProviderBuilder configureSdkTracerProvider(
      SdkTracerProviderBuilder tracerProvider, ConfigProperties config) {
    return tracerProvider.addSpanProcessor(new OperationRenamingSpanProcessor());
  }

}
</code></pre>
<p>The <a href="https://github.com/elastic/elastic-otel-java/tree/main/examples/extensions/modify-span">modify-span EDOT Java extension example</a> provides a complete implementation.</p>
<h3 id="filterspanswithaspanexporter">Filter spans with a SpanExporter</h3>
<p>A <code>SpanExporter</code> wrapper lets you modify or drop spans before they leave the process. This works well for known noisy endpoints like health checks.</p>
<pre><code>public class FilteringSpanExporter implements SpanExporter {

  private final SpanExporter delegate;

  public FilteringSpanExporter(SpanExporter delegate) {
    this.delegate = delegate;
  }

  @Override
  public CompletableResultCode export(Collection&lt;SpanData&gt; spans) {
    List&lt;SpanData&gt; filtered = new ArrayList&lt;&gt;();
    for (SpanData span : spans) {
      if (!"GET /health".equals(span.getName())) {
        filtered.add(span);
      }
    }
    return delegate.export(filtered);
  }

  @Override
  public CompletableResultCode flush() { return delegate.flush(); }

  @Override
  public CompletableResultCode shutdown() { return delegate.shutdown(); }
}
</code></pre>
<p>Register the FilteringSpanExporter via <code>addSpanExporterCustomizer</code>:</p>
<pre><code>customizer.addSpanExporterCustomizer((existing, config) -&gt; new FilteringSpanExporter(existing));
</code></pre>
<p>The <a href="https://github.com/elastic/elastic-otel-java/tree/main/examples/extensions/modify-span">modify-span EDOT Java extension example</a> provides a complete implementation.</p>
<p>The approach has two limitations:</p>
<ul>
<li>This won't discard any child span that may have been created, for example, if the healthcheck calls the database.</li>
<li>Spans filtered at the exporter have already passed through the full processor pipeline and occupied buffer space in the batch processor.</li>
</ul>
<p>If you're dropping a large fraction of your traffic at this stage, a custom <code>Sampler</code> (shown below) is more efficient because it drops spans before any processing happens and also filters out child spans.
Also, when using <a href="https://opentelemetry.io/docs/zero-code/java/agent/declarative-configuration/">declarative configuration</a>, the rule-based sampler lets you implement filtering on rules using only configuration.</p>
<h2 id="addingcustomresourceattributeswitharesourceprovider">Adding custom resource attributes with a ResourceProvider</h2>
<p>Resource attributes describe what's running: the service name, its version, the host. A <code>ResourceProvider</code> lets you attach additional attributes that the agent doesn't know about, such as deployment metadata your platform injects through environment variables.</p>
<p>The example below uses environment variables, but it could also be a configuration file, a cloud metadata service, or any other source available to the agent at startup.</p>
<p>Because the SDK initialization is synchronous, when querying an external service like a metadata endpoint, this can make the agent (and thus the application) startup slower.
If possible, prefer checking environment variables and local config first before calling an external service.</p>
<pre><code>@AutoService(ResourceProvider.class)
public class DeploymentResourceProvider implements ResourceProvider {

  @Override
  public Resource createResource(ConfigProperties config) {
    AttributesBuilder attributes = Attributes.builder();

    String region = System.getenv("DEPLOY_REGION");
    if (region != null) {
      attributes.put(AttributeKey.stringKey("deployment.region"), region);
    }

    String buildVersion = System.getenv("BUILD_VERSION");
    if (buildVersion != null) {
      attributes.put(AttributeKey.stringKey("build.version"), buildVersion);
    }

    return Resource.create(attributes.build());
  }
}
</code></pre>
<p>Attributes from a <code>ResourceProvider</code> merge with the agent's own resource. When two providers supply the same key, the one with the higher <code>order()</code> value wins. The agent's built-in providers use order 0, so overriding <code>order()</code> to return a positive integer gives your provider priority.</p>
<p>The <a href="https://github.com/elastic/elastic-otel-java/tree/main/examples/extensions/resource-attribute">resource-attribute EDOT Java extension example</a> provides a complete implementation.</p>
<h2 id="customsamplinginopentelemetryjava">Custom sampling in OpenTelemetry Java</h2>
<p>When filtering at the exporter is too late or too expensive, implement a <code>Sampler</code> directly. The sampler runs before any span processing, so dropped spans never touch the batch buffer.</p>
<p>However, the sampling decision can only rely on attributes that are provided when the span starts. For example, the status code of an HTTP response can't be used as it is only available when the span ends.</p>
<p>The key detail: wrap the existing sampler rather than replacing it. That way, your logic composes with whatever you configured, and parent-based decisions from an upstream service are still respected.</p>
<pre><code>public class HealthCheckSampler implements Sampler {

  private final Sampler delegate;

  public HealthCheckSampler(Sampler delegate) {
    this.delegate = delegate;
  }

  @Override
  public SamplingResult shouldSample(
      Context parentContext,
      String traceId,
      String name,
      SpanKind spanKind,
      Attributes attributes,
      List&lt;LinkData&gt; parentLinks) {
    if (spanKind == SpanKind.SERVER &amp;&amp; name.contains("health")) {
      return SamplingResult.create(SamplingDecision.DROP);
    }
    return delegate.shouldSample(parentContext, traceId, name, spanKind, attributes, parentLinks);
  }

  @Override
  public String getDescription() {
    return "HealthCheckSampler{" + delegate.getDescription() + "}";
  }
}
</code></pre>
<p>Register the HealthCheckSampler via <code>addSamplerCustomizer</code>, which gives you both the existing sampler and the resolved config:</p>
<pre><code>customizer.addSamplerCustomizer((existing, config) -&gt; new HealthCheckSampler(existing));
</code></pre>
<h2 id="communityextensionsinopentelemetryjavacontrib">Community extensions in opentelemetry-java-contrib</h2>
<p>The <a href="https://github.com/open-telemetry/opentelemetry-java-contrib">opentelemetry-java-contrib</a> repository contains several community-maintained extensions.</p>
<p>Some of them are already included in the OpenTelemetry Java agent (and inherited in the Elastic distribution), but are opt-in:</p>
<ul>
<li><a href="https://github.com/open-telemetry/opentelemetry-java-contrib/tree/main/azure-resources">azure-resources</a></li>
<li><a href="https://github.com/open-telemetry/opentelemetry-java-contrib/tree/main/aws-resources">aws-resources</a></li>
<li><a href="https://github.com/open-telemetry/opentelemetry-java-contrib/tree/main/gcp-resources">gcp-resources</a></li>
<li><a href="https://github.com/open-telemetry/opentelemetry-java-contrib/tree/main/cloudfoundry-resources">cloudfoundry-resources</a></li>
<li><a href="https://github.com/open-telemetry/opentelemetry-java-contrib/tree/main/baggage-processor">baggage-processor</a></li>
</ul>
<p>Most Elastic distribution <a href="https://www.elastic.co/docs/reference/opentelemetry/edot-sdks/java/features">features</a> exist as extensions in the contrib repository, so you can use them with the upstream agent in a vendor-neutral way.</p>
<ul>
<li><a href="https://github.com/open-telemetry/opentelemetry-java-contrib/tree/main/inferred-spans">inferred-spans</a></li>
<li><a href="https://github.com/open-telemetry/opentelemetry-java-contrib/tree/main/span-stacktrace">span-stacktrace</a></li>
</ul>
<h2 id="furtherreadingandextensionexamples">Further reading and extension examples</h2>
<p>The <a href="https://github.com/open-telemetry/opentelemetry-java-instrumentation/tree/main/examples/extension">upstream extension examples</a> cover additional extension points not shown here, including custom propagators, ID generators, and ignored-type configurers.</p>
<p>The <a href="https://github.com/elastic/elastic-otel-java/tree/main/examples/baggage">Elastic baggage example</a> shows the filtering propagator for baggage running end-to-end with a two-service application, it also demonstrates custom instrumentation to add baggage without modifying the application code.</p>
<p>This post covered the project setup and the patterns most likely to come up in production. Both links above go deeper: the upstream examples add extension points not covered here, and the baggage example shows a complete two-service implementation you can run locally.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/opentelemetry-java-extensions</link>
    <guid isPermaLink="false">opentelemetry-java-extensions</guid>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[Kubernetes]]></category>
    <category><![CDATA[What's New]]></category>
    <dc:creator><![CDATA[Sylvain Juge]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2b379be7ce7ba2c1/6a8ea21cbf814594cbd284ec/header.jpg" length="0" type="image/jpeg"/>
    <pubDate>Thu, 20 Aug 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Kubernetes observability: SLO templates that turn alerts into error budgets]]></title>
    <description><![CDATA[Two bad rollouts burned 88% of a 30-day error budget while the SLI still read 99.56%. This post adds four SLO templates that bring burn-rate tracking to the OTel-based alert rules from Part 1, no new instrumentation required.]]></description>
    <content:encoded><![CDATA[<p>Two bad rollouts on one Deployment burned <strong>88%</strong> of a <strong>30-day</strong> error budget in a day and fired a 26X burn-rate alert while the SLI still read 99.56%. That is the gap <a href="https://www.elastic.co/observability-labs/blog/kubernetes-dashboards-alerts-anomaly-detection">Part 1</a> alert rules cannot close on their own: they page when replicas drop; they do not tell you how much monthly reliability budget the incident cost.</p>
<p>The <strong>Kubernetes OpenTelemetry Assets</strong> package now ships four <strong>Kubernetes SLO templates</strong> for Deployments, StatefulSets, DaemonSets, and Jobs on those OTel metrics. If you already followed <a href="https://www.elastic.co/observability-labs/blog/kubernetes-dashboards-alerts-anomaly-detection">Part 1</a> and have the dashboards and alert rules, create an SLO from a template and you get SLI, remaining budget, and burn rate without new instrumentation.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4df618f6b39dd743/6a8ea15cf59d7c22f6c9386f/k8s_integration_extension.png" alt="Kubernetes observability with Elastic, flow diagram of OTel metrics and events into Dashboards, Alert rules with Page, ML jobs with Anomaly, and SLOs with Burn rate, converging on Overview to workload detail to pod logs" /></p>
<p>The diagram above extends the stack from <a href="https://www.elastic.co/observability-labs/blog/kubernetes-dashboards-alerts-anomaly-detection">Part 1</a>. The <strong>Kubernetes OpenTelemetry Assets</strong> package (<code>kubernetes_otel</code>) 2.3.0 includes:</p>
<ul>
<li>Dashboards designed for drill-down (Part 1)</li>
<li>Alert rule templates that fire on known bad states (Part 1)</li>
<li>ML anomaly detection jobs with workload baselines (Part 1)</li>
<li>SLO templates for rolling 30-day budgets (this post)</li>
</ul>
<p>All four use the same OTel metrics. Burn rate alerts on an SLO send you back into Overview, Workloads, and Deployment Details when the number alone is not enough.</p>
<h2 id="whykubernetesobservabilityneedsslomonitoringalongsidealerts">Why Kubernetes observability needs SLO monitoring alongside alerts</h2>
<p><a href="https://www.elastic.co/observability-labs/blog/kubernetes-dashboards-alerts-anomaly-detection">Part 1</a> built the reactive stack for the engineer who gets paged at 3 AM. SLOs serve the planning conversation on a <strong>30-day</strong> horizon: <strong>Are we meeting our reliability commitments?</strong> They give platform and engineering leaders a number for prioritisation: how much error budget remains and which workload is burning it fastest. The table later in this post maps each SLO template to its Part 1 alert counterpart.</p>
<p>The SLO templates in this post are part of the <strong>Kubernetes OpenTelemetry Assets</strong> package (<code>kubernetes_otel</code>). Install the <a href="https://www.elastic.co/docs/reference/integrations/kubernetes_otel">Kubernetes OpenTelemetry Assets package</a> and confirm your cluster is already sending Kubernetes metrics through OpenTelemetry (the same pipeline from Part 1). No additional instrumentation is required.</p>
<h2 id="fourslotemplatesforkubernetesdeploymentsstatefulsetsdaemonsetsandjobs">Four SLO templates for Kubernetes Deployments, StatefulSets, DaemonSets and Jobs</h2>
<p>In <strong>Integrations → Kubernetes OpenTelemetry → Assets</strong>, enable any of the four templates below. Names match Kibana; each includes the <code>[Kubernetes OTel]</code> prefix in the UI.</p>
<p>| <strong>Template</strong>                                                | <strong>Rolling objective</strong> | <strong>Package description</strong>                                                                                                                                                                                                                           |
| ----------------------------------------------------------- | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| <strong>Deployment Replica Availability 99.5% Rolling 30 Days</strong>   | 99.5% / 30d           | Tracks Deployment availability from OTel metrics: 99.5% of intervals should have each Deployment at its desired replica count. When <code>k8s.deployment.available &lt; k8s.deployment.desired</code>, the workload has fewer healthy replicas than configured. |
| <strong>StatefulSet Replica Availability 99.5% Rolling 30 Days</strong>  | 99.5% / 30d           | Same pattern for StatefulSets, where pod identity and ordering matter for databases, queues, and caches.                                                                                                                                          |
| <strong>DaemonSet Scheduling Availability 99.0% Rolling 30 Days</strong> | 99.0% / 30d           | Tracks whether each DaemonSet runs on all eligible nodes. Covers node-level agents such as log collectors, monitoring, security, and CNI plugins.                                                                                                 |
| <strong>Job Completion Success Rate 99.0% Rolling 30 Days</strong>       | 99.0% / 30d           | Tracks batch Jobs (ETL, backups, pipelines, scheduled tasks) completing without failed pods over the rolling window.                                                                                                                              |</p>
<p>Each is a <strong>timeslice-metric SLO</strong>: Elastic marks every five-minute window good or bad, then rolls those results into a <strong>30-day rolling</strong> objective per namespace and workload.</p>
<p>Reliability is scored at two levels. Each five-minute slice gets one verdict: Elastic aggregates OTel metrics in that window, evaluates the template equation, and compares the result to the metric threshold. For Deployments, that is <code>sum(available) / sum(desired) &gt;= 1</code>. At a ~30-second OTel scrape cadence, that is roughly ten measurements per slice, and the slice passes or fails on the aggregated result. The SLO target (99.5% or 99.0%) is the share of slices that must pass across the rolling window. Over 30 days at five-minute slices, that is 8,640 possible slices per workload (30 × 24 × 12). After you create an SLO from a template, the SLO detail view shows how many slices passed and how much error budget remains.</p>
<p>At 99.5%, a workload can miss roughly 43 of those slices (~3.6 hours of bad slices) before breach. At 99.0%, about 86 slices (~7.2 hours).</p>
<h3 id="howtosetslotargetsbykubernetesworkloadtype">How to set SLO targets by Kubernetes workload type</h3>
<p>We picked defaults per workload type, not one number for the whole cluster.</p>
<p><strong>Deployments and StatefulSets at 99.5%:</strong> We considered 99.9% (~43 minutes per month), which fits a single critical API or a formal SLA buffer. For a default integration template across many Deployments, 99.5% (~3.6 hours) leaves room for normal rollout churn: a 20-minute bad image tag is roughly half the monthly budget at 99.9%, but a small fraction at 99.5%. Tune per workload; payment paths often warrant 99.9% or higher.</p>
<p><strong>DaemonSets and Jobs at 99.0%:</strong> We considered 99.5% for DaemonSets, but node additions, replacements, and rolling updates often leave <code>ready_nodes</code> below <code>desired_scheduled_nodes</code> for several minutes per event. At 99.5%, that normal platform churn would burn error budget on infrastructure agents (log collectors, monitoring, CNI) as if they were user-facing outages. 99.0% (~7.2 hours) absorbs that lifecycle noise. Jobs get 99.0% for a different reason: a failed ETL run usually hurts data freshness, not live request availability, and failures can sit unnoticed until downstream teams see stale reports.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0b8c0979458feafa/6a8ea15fd98b5fb81fb15483/k8s-workload-resources.png" alt="Kubernetes observability with Elastic, Workload resources dashboard showing Deployments, DaemonSets, StatefulSets, Jobs, and ReplicaSets with availability and replica metrics" /></p>
<h3 id="deploymentreplicaavailability995">Deployment replica availability (99.5%)</h3>
<pre><code>Metric:     sum(k8s.deployment.available) / sum(k8s.deployment.desired) &gt;= 1
Target:     99.5% of 5-minute timeslices over 30 days
Group by:   resource.attributes.k8s.namespace.name + resource.attributes.k8s.deployment.name
</code></pre>
<p>When <code>available &lt; desired</code>, the application runs fewer healthy replicas than configured. Failed rollouts, crash loops, and node loss all show up here. <strong>99.5%</strong> leaves roughly <strong>3.6 hours</strong> of degradation per deployment per month before breach.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd80a6e2a042b37b6/6a8ea162cf026e84110c9d16/slo-detail-deployment-healthy.png" alt="Kubernetes observability with Elastic, grid of Deployment Replica Availability 99.5% Rolling 30 Days SLO cards at 100% for default and kube-system namespaces" /></p>
<p>Grouping by namespace and deployment name creates one SLO per workload. A cluster-wide average would let a healthy <code>frontend</code> mask a burning <code>checkout</code>. Linked dashboards (<strong>Overview</strong> and <strong>Workloads</strong>) connect the SLO view to investigation context in one click; from Workloads you drill into <strong>Deployment Detail</strong> for the failing deployment.</p>
<h3 id="statefulsetreplicaavailability995">StatefulSet replica availability (99.5%)</h3>
<pre><code>Metric:     sum(k8s.statefulset.ready_pods) / sum(k8s.statefulset.desired_pods) &gt;= 1
Target:     99.5% of 5-minute timeslices over 30 days
Group by:   resource.attributes.k8s.namespace.name + resource.attributes.k8s.statefulset.name
</code></pre>
<p>When <code>ready_pods &lt; desired_pods</code>, the StatefulSet reports fewer Ready replicas than configured. Ordered rollouts, stuck pods, and node loss show up here too. Rollouts proceed in order, and each pod keeps its name and volume, so a missing replica can stay below desired longer than a stateless pod would.</p>
<p>Grouping by namespace and StatefulSet name avoids a healthy workload masking another that is burning the SLO budget. </p>
<h3 id="daemonsetschedulingavailability990">DaemonSet scheduling availability (99.0%)</h3>
<pre><code>Metric:     sum(k8s.daemonset.ready_nodes) / sum(k8s.daemonset.desired_scheduled_nodes) &gt;= 1
Target:     99.0% of 5-minute timeslices over 30 days
Group by:   resource.attributes.k8s.namespace.name + resource.attributes.k8s.daemonset.name
</code></pre>
<p>DaemonSets run node-level infrastructure: log collectors, monitoring agents, security agents, and network plugins. When <code>ready_nodes &lt; desired_scheduled_nodes</code>, an eligible node lacks a Ready pod, which can leave that node without logs or metrics from that agent. Rolling updates and new nodes drive most gaps; pods that never become Ready show the same signal. Cordoned nodes often still run DaemonSet pods. 99.0% (~7.2 hours per month) reflects that churn. </p>
<p>Group by namespace and DaemonSet name so a healthy <code>fluentd</code> does not mask a broken <code>node-exporter</code> on the same SLO budget.</p>
<h3 id="jobcompletionsuccessrate990">Job completion success rate (99.0%)</h3>
<pre><code>Metric:     max(k8s.job.failed_pods) &lt;= 0
Target:     99.0% of 5-minute timeslices over 30 days
Group by:   resource.attributes.k8s.namespace.name + resource.attributes.k8s.job.name
</code></pre>
<p>Jobs cover batch workloads: ETL pipelines, backups, database migrations, and scheduled reports. When <code>failed_pods &gt; 0</code>, at least one pod created by the Job reached the <strong>Failed</strong> phase. Application errors, timeouts, and missing dependencies drive many failures; when retries reach the configured <code>backoffLimit</code>, Kubernetes marks the Job as <strong>Failed</strong>. Missed runs often surface as stale or delayed data, not as a serving outage. 99.0% (~7.2 hours per month) reflects that occasional batch failure is less time-sensitive than a Deployment or StatefulSet breach. </p>
<h2 id="howdoslosandalertsworktogetherinkubernetesobservability">How do SLOs and alerts work together in Kubernetes observability?</h2>
<p>The SLO templates and the alert rules from <a href="https://www.elastic.co/observability-labs/blog/kubernetes-dashboards-alerts-anomaly-detection">Part 1</a> serve different people asking different questions at different times.</p>
<p>| <strong>SLO Template</strong>                  | <strong>Alert rule (Part 1)</strong>                                                 | <strong>Failure consequence</strong>                                          | <strong>Monthly budget (30d)</strong> |
| --------------------------------- | ----------------------------------------------------------------------- | ---------------------------------------------------------------- | ------------------------ |
| Deployment Replica Availability   | Deployment below the desired replicas                                   | Reduced throughput, degraded UX                                  | ~3.6 hours at 99.5%     |
| StatefulSet Replica Availability  | No dedicated rule. Covered by CrashLoopBackOff / OOMKilled at pod level | Split-brain risk, degraded durability                            | ~3.6 hours at 99.5%     |
| DaemonSet Scheduling Availability | Pod stuck in Pending / node disk pressure                               | Blind spots: unmonitored nodes and gaps in node-level coverage   | ~7.2 hours at 99.0%     |
| Job Completion Success Rate       | CrashLoopBackOff / OOMKilled                                            | Stale or incomplete data                                         | ~7.2 hours at 99.0%     |</p>
<p>Alert rules answer: <em>Is something broken right now?</em> They fire within minutes, page the on-call engineer, and expect immediate action.</p>
<p>SLO templates answer: <em>Are we meeting our reliability commitments over time?</em> They accumulate signal across weeks and turn prioritisation debates into a number tied to remaining budget.</p>
<h3 id="fromincidenttoerrorbudgetburnakuberneteswalkthrough">From incident to error budget burn: a Kubernetes walkthrough</h3>
<p>A deployment drops from <code>3/3</code> to <code>2/3</code> available replicas during a rolling update. The new pod fails its readiness probe. Here is what happened in our test cluster, from dashboard signal through alert, SLO impact, and root cause.</p>
<p><strong>Rollout begins.</strong> The Deployment dashboard shows <code>available: 2, desired: 3</code>. The Part 1 <strong>Deployment unavailable replicas</strong> rule has a <strong>5-minute</strong> grace period, so on-call is not paged yet during a short rollout gap. The Deployment Detail view for <code>web-frontend</code> shows available replicas dropping while desired stays at 3. The Deployment replicas over time chart marks where the rollout started to fail.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2d6ad2f74e0a59bd/6a8ea166f65645658a54b8e7/k8s-workdload-replicaset-drop.png" alt="Kubernetes observability with Elastic, Workload resources view for web-frontend in blog-demo at 66.67% availability with available replicas at 2 of 3 desired and the replicas-over-time chart showing the drop" /></p>
<p><strong>Alert fires, then root cause.</strong> After the grace period, the alert rule triggers: <em>Deployment unavailable replicas</em>. The on-call engineer opens the Workloads dashboard, finds <code>web-frontend</code> at <code>available: 2, desired: 3</code>, and drills into Deployment Detail. The replicas-over-time chart confirms when availability dropped. </p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdb63a9993f704b20/6a8ea169b4c43ed5190f14ae/k8s-replica-alert-trigger.png" alt="Kubernetes observability with Elastic, Deployment unavailable replicas alert rule showing active alerts after the replica drop" /></p>
<p>In <strong>Discover</strong>, filter Kubernetes events for that pod with <code>k8s.object.name: "web-frontend-796fcd55b9-jmlkh"</code>. The event stream shows <code>ImagePullBackOff</code> and <code>Back-off pulling image "nginx:nonexistent-tag-999"</code>. The rollout references an image tag that does not exist.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt154dbe907e7b8e68/6a8ea16c76e02a2ba5fd5485/k8s-discoverview-image-error.png" alt="Kubernetes observability with Elastic, Discover view showing ImagePullBackOff events for the web-frontend pod after a bad image tag" /></p>
<p><strong>Rollback and recovery.</strong> The engineer runs <code>kubectl rollout undo deployment/web-frontend</code>. Replicas return to <code>3/3</code>.</p>
<h3 id="howtworolloutsconsumed88ofa30dayerrorbudget">How two rollouts consumed 88% of a 30-day error budget</h3>
<p>The rollback fixed availability. The SLO still counted the day's failures.</p>
<p>Two rollout failures left <code>web-frontend</code> with <strong>39 failed timeslices</strong> where <code>available &lt; desired</code>. That consumed <strong>88.0%</strong> of the <strong>30-day error budget</strong>. The SLI still read <strong>99.56%</strong>, above the <strong>99.5%</strong> target, but only <strong>12%</strong> of the monthly allowance remained.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdc7ae205fd2e6375/6a8ea16fbc5bb344a3f93c7e/k8s-slo-webserver-overview.png" alt="Kubernetes observability with Elastic, Deployment Replica Availability SLO for web-frontend showing SLI above target with most of the error budget already consumed" /></p>
<p>The burn rate alert fired next, even though replicas were healthy again. Over the past day the deployment consumed budget at <strong>26×</strong> the rate a <strong>99.5%</strong> SLO can sustain long term. Each failed 5-minute timeslice uses roughly <strong>2.3%</strong> of the monthly budget (about <strong>43</strong> failures allowed per 30 days). Thirty-nine failures across two rollouts is worth a reliability review, not a one-line postmortem. The burn rate alert often matters more than the raw SLI mid-month because it fires while you still have budget left to spend deliberately.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9e36052030c8b486/6a8ea1723a56ed4c1938fc79/slo-burnrate-alert.png" alt="Kubernetes observability with Elastic, Alerts page showing an active critical burn rate alert for the web-frontend Deployment Replica Availability SLO" /></p>
<h2 id="tryityourselftriggeranerrorbudgetburnonatestdeployment">Try it yourself: trigger an error budget burn on a test Deployment</h2>
<p>If you already have <strong>Kubernetes OpenTelemetry Assets</strong> installed, the SLO templates live under <strong>Integrations → Kubernetes OpenTelemetry → Assets</strong>.</p>
<p>Create a <strong>Deployment replica availability</strong> SLO for the deployment you use below. Open the SLO and note the baseline: current SLI, remaining error budget, and existing timeslice history.</p>
<p>Create an isolated namespace and a small deployment so the exercise does not affect production workloads. Wait a few minutes for the OTel collector to scrape metrics before you create the SLO.</p>
<pre><code>kubectl create namespace blog-demo
kubectl create deployment web-frontend --namespace blog-demo --image=nginx:latest --replicas=3
</code></pre>
<p>Trigger a bad rollout with a non-existent image tag:</p>
<pre><code>kubectl get deployment web-frontend -n blog-demo
kubectl set image deployment/web-frontend nginx=nginx:nonexistent-tag-999 --namespace blog-demo
</code></pre>
<p>Within a few minutes a new pod enters <code>ImagePullBackOff</code>, available replicas drop below desired, and the SLO records failed timeslices. Roll back to recover:</p>
<pre><code>kubectl rollout undo deployment/web-frontend -n blog-demo
</code></pre>
<p>Refresh the SLO view. You should see new failed timeslices in the 30-day history and a reduction in remaining error budget.</p>
<p>One failed timeslice consumes about <strong>2.3%</strong> of the monthly error budget at <strong>99.5%</strong>. Repeat that across deployments in a week and the burn rate alert becomes the prioritisation signal.</p>
<p>When you are done, delete the test namespace with:</p>
<pre><code>kubectl delete namespace blog-demo
</code></pre>
<h2 id="whatsnextfromslomonitoringtoagenticremediation">What's next: from SLO monitoring to agentic remediation</h2>
<p>Alerts tell you replicas dropped. SLOs tell you how much monthly budget that cost. In the walkthrough above, the same ImagePullBackOff showed up in Deployment Detail, the unavailable-replicas alert, and failed timeslices on the replica-availability SLO, all from the OTel pipeline you installed in <a href="https://www.elastic.co/observability-labs/blog/kubernetes-dashboards-alerts-anomaly-detection">Part 1</a>. The SLI still read <strong>99.56%</strong> while <strong>88%</strong> of the monthly error budget was gone.</p>
<p><a href="https://www.elastic.co/observability-labs/blog/kubernetes-dashboards-alerts-anomaly-detection">Part 1</a> closed by previewing <strong>Agentic Investigations</strong>: investigation workflows that run when an alert fires, with skills, tools, and MCP views. This post adds the SLO layer on those same metrics so you can quantify reliability debt before automating runbooks. A follow-up post will cover that agentic workflow and propose remediations you review before applying.</p>
<p>Which remediations would you trust a workflow to suggest on a Kubernetes incident, and which would you keep manual? <a href="https://discuss.elastic.co/c/observability">Join the Elastic Community discussion</a>.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/kubernetes-observability-slo-error-budget-templates</link>
    <guid isPermaLink="false">kubernetes-observability-slo-error-budget-templates</guid>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[Metrics]]></category>
    <dc:creator><![CDATA[Agi K Thomas]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltaf5fe3ce01efa1f7/6a8ea17576e02a4eccfd5489/kubernetes-observability-slo-error-budget-templates.png" length="0" type="image/png"/>
    <pubDate>Wed, 19 Aug 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Elastic Agent now runs as an OpenTelemetry Collector: Less memory overhead, zero config changes]]></title>
    <description><![CDATA[Elastic Agent 9.3 sends logs, metrics and traces through one OTel Collector pipeline, running Beats integrations alongside native OTel sources in a single Fleet-managed agent.]]></description>
    <content:encoded><![CDATA[<p>Elastic Agent 9.3 uses less memory and accepts data from any OpenTelemetry-compatible (OTel-compatible) source out of the box.
Under the hood, the old Beats subprocess architecture has been replaced by a single OTel-native pipeline for logs, metrics, and traces, built on the Elastic Distribution of OpenTelemetry (EDOT) Collector.
Your existing integrations, dashboards, Fleet policies, alerting rules, and ingest pipelines all work without changes.</p>
<h2 id="whatchangedinelasticagent93anativeotelcollectorunderthehood">What changed in Elastic Agent 9.3: A native OTel Collector under the hood</h2>
<p>Previously, Elastic Agent acted as a supervisor process, spinning up Beats-based subprocesses, such as Filebeat or Metricbeat.
From 9.3 onward, that architecture has been replaced.
Elastic Agent itself is now built on the EDOT Collector, turning it into a first-class OTel Collector under the hood while preserving its original functionality.</p>
<p>Key benefits of this architectural shift include:</p>
<ul>
<li><strong>Reduced footprint:</strong> Fewer subprocesses mean significantly less memory overhead and a simpler deployment model. In future releases, this footprint will be even further reduced.</li>
<li><strong>Unified telemetry pipeline:</strong> Logs and metrics flow through a single, standards-based OTel pipeline, as do traces.</li>
<li><strong>Ecosystem interoperability:</strong> Elastic Agent can now receive data from any OTel-compatible source out of the box. It can also be configured to emit to OTel-compatible destinations.</li>
<li><strong>Aligned with the OTel ecosystem:</strong> As the OTel ecosystem matures with new receivers, processors, and exporters, Elastic Agent deployments gain access to those capabilities automatically.</li>
</ul>
<p>When you deploy or update Elastic Agent from version 9.3 onward, you're deploying an OpenTelemetry Collector.
EDOT is the technology foundation; Elastic Agent is the product.</p>
<h2 id="howexistingbeatsconfigurationsruninsidetheotelcollectorpipeline">How existing Beats configurations run inside the OTel Collector pipeline</h2>
<p>Elastic has introduced Beats Receivers, which are Beat inputs and processors that execute natively inside the new OTel Collector pipeline.
For your teams and customers, this means:</p>
<ul>
<li>Existing <code>elastic-agent.yml</code> configurations require no modification.</li>
<li>Fleet-managed agents automatically translate policy configurations into OTel format internally.</li>
<li>All integrations, dashboards, ingest pipelines, and alerting rules continue to function exactly as before.</li>
<li>Data written via Beats Receivers lands in the same data streams as always.</li>
</ul>
<p>Upgrading to 9.3 is transparent because it uses the same inputs and produces the same outputs.</p>
<h2 id="runningbeatsandotelcollectorpipelinesinoneelasticagent">Running Beats and OTel Collector pipelines in one Elastic Agent</h2>
<p>The new Elastic Agent is a collector capable of simultaneously running traditional Beats-based collections alongside native OTel pipelines, all in a single deployment.
One agent policy can collect Elastic Common Schema–schematized (ECS-schematized) data via Beats Receivers and ingest native OpenTelemetry Protocol (OTLP) data from OTel-instrumented applications and infrastructure.
This same agent policy can apply OTel processing stages across all telemetry before export.</p>
<p>OTel integrations from Elastic's catalog can be added to any agent policy.
When native OTel data is ingested, Elastic automatically installs the relevant dashboards and alerts, in addition to necessary content packs, without any manual setup.</p>
<h2 id="whatstherelationshipbetweenelasticagentandedot">What's the relationship between Elastic Agent and EDOT?</h2>
<p>You may be familiar with EDOT, the Elastic Distribution of OpenTelemetry Collector, as a stand-alone product.
With this architectural change, EDOT is the technology foundation that now powers Elastic Agent, not a separate product that users need to track or deploy independently.</p>
<p>Going forward, Elastic Agent is the supported, Fleet-manageable, fully featured product.
A stand-alone deployment remains available for specific niche scenarios (environments where the full version of Elastic Agent cannot be installed), but it isn't the recommended path for the vast majority of users.</p>
<h2 id="elasticagentdeploymentoptionsfleetmanagedvsstandalone">Elastic Agent deployment options: Fleet-managed vs. stand-alone</h2>
<p>|                            | <strong>Fleet-managed Elastic Agent</strong> | <strong>Stand-alone Elastic Agent</strong>                                                                           |
| :------------------------- | :-----------------------------: | :-----------------------------------------------------------------------------------------------------: |
| Fleet lifecycle management | Yes                             | Can enroll into Fleet in-field without reinstallation                                                   |
| Beats Receivers            | Yes                             | Yes                                                                                                     |
| Elastic Defend             | Yes                             | No                                                                                                      |
| Cloud Security             | Yes                             | No                                                                                                      |
| Profiler support           | Yes                             | No                                                                                                      |
| OTel-native pipeline       | Yes                             | Yes                                                                                                     |
| Best for                   | Most deployments                | Environments where full Elastic Agent cannot be installed or management is handled by other tools       |</p>
<h2 id="doineedtochangeanythingwhenupgradingtoelasticagent93">Do I need to change anything when upgrading to Elastic Agent 9.3?</h2>
<p>For users running Elastic Agent today, upgrading to 9.3 requires no changes to configurations or integrations, and no changes to workflows.
For customers evaluating OTel adoption, Elastic Agent now provides a fully supported, production-ready OTel Collector with Fleet management and rich integrations, along with Elastic's full support matrix, and none of this requires a separate OTel deployment.</p>
<p>With Elastic Agent 9.3, Elastic's data collection is fully OpenTelemetry-native.
Elastic Agent is now an OpenTelemetry Collector.
Everything you have today still works, and you also get all the capabilities of OTel.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/opentelemetry-collector-elastic-agent</link>
    <guid isPermaLink="false">opentelemetry-collector-elastic-agent</guid>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[Agentic Observability]]></category>
    <category><![CDATA[What's New]]></category>
    <dc:creator><![CDATA[Nima Rezainia]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt36d2c1da5195912a/6a859a7218249c7a3818ec86/header.jpg" length="0" type="image/jpeg"/>
    <pubDate>Mon, 17 Aug 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Correlate logs, metrics, and traces in one ES|QL query]]></title>
    <description><![CDATA[Walk through four investigations, from CPU saturation to pod memory pressure, each answered by a single query across signal types.]]></description>
    <content:encoded><![CDATA[<p>ES|QL can now filter one observability signal by the live result of a query against another.
<a href="https://www.elastic.co/docs/reference/query-languages/esql/esql-in-subquery"><code>WHERE field IN (subquery)</code></a> is in technical preview in Elastic Stack 9.5 and on Elastic Cloud Serverless, and because these subqueries nest, one query can reach across logs, metrics, and traces at the same time.</p>
<p>The gain is in where the intermediate set lives.
When you ask what the saturated hosts logged, the list of saturated hosts is computed and consumed inside Elasticsearch.
Six host names or 500 trace IDs never land in a clipboard or in an AI agent's context window, and the set is recomputed from current data every time the query runs.</p>
<p>That changes the unit of investigation.
You go from "one slow request stalled on a lock" to "most of the slowest requests did," and only the second answer tells you which team to page.
It matters more the further apart your signals are: in many observability stacks, logs, metrics, and traces live in three separate systems, each with its own query language, its own time picker, and its own idea of what a host is, so the same question has to be asked two or three times and the answers joined by hand.</p>
<p>In this post we walk through four investigations, each of which is self-contained.
For every one of them, we set out the scenario that started it, the query that answers it, the table it returns, and a note on the difficulties you run into when you try to get the same answer any other way.</p>
<p>| Pattern | Starts from | Question it answers |
|---|---|---|
| Metrics to logs | CPU saturation | Which error patterns show up only on the saturated hosts? |
| Logs to metrics | Error logs | Do the erroring hosts look any different from the healthy ones? |
| Traces to logs | Slow spans | What did every service log during those specific requests? |
| All three signals | Pod memory pressure | Which log lines sit behind the requests that failed under that pressure? |</p>
<p>The data was collected with <a href="https://www.elastic.co/docs/reference/opentelemetry">OpenTelemetry</a> and lands in the <code>logs-*.otel-*</code>, <code>traces-*.otel-*</code>, and <code>metrics-*.otel-*</code> data streams, where fields keep their <a href="https://opentelemetry.io/docs/specs/semconv/">semantic convention</a> names rather than being rewritten into another schema.
The correlation pattern works just as well on Elastic Agent integrations, though the queries need translating rather than just renaming: ECS carries log severity as the text field <code>log.level</code> instead of a numeric <code>severity_number</code>, the System integration reports CPU as separate <code>system.cpu.*.pct</code> fields instead of one metric with a state dimension, and APM records durations in microseconds.
All of it lands in the same cluster either way, which is the part the subquery depends on.</p>
<p>In <a href="https://www.elastic.co/docs/explore-analyze/discover">Discover</a>, the time picker already applies the range, so the examples below omit an explicit <code>@timestamp</code> filter.
Outside Discover, add a filter by time yourself, either with literal timestamps in the query or with <code>?_tstart</code> and <code>?_tend</code> in the query and values in the <code>params</code> array of your <code>_query</code> request.
Every result below comes from a one hour window over a synthetic fleet of 300 hosts.</p>
<h2 id="metricstologswhatarethesaturatedhostscomplainingabout">Metrics to logs: what are the saturated hosts complaining about?</h2>
<p>An infrastructure alert tells you a handful of hosts in a fleet of a few hundred sat above 90% CPU over the last hour.
That tells you which hosts are hot and nothing about why.
The question worth answering is whether those hosts share a failure mode, or whether they are busy for unrelated reasons and the alert is a coincidence.</p>
<pre><code>FROM logs-*.otel-*
| WHERE severity_number &gt;= 17
  AND resource.attributes.host.name IN (
      TS metrics-hostmetrics.otel-*
      | WHERE attributes.state == "idle"
      | STATS idle = AVG(AVG_OVER_TIME(metrics.system.cpu.utilization))
          BY resource.attributes.host.name
      | WHERE idle &lt; 0.1
      | KEEP resource.attributes.host.name
    )
| STATS errors = COUNT(*), hosts = COUNT_DISTINCT(resource.attributes.host.name)
    BY pattern = CATEGORIZE(body.text), service = resource.attributes.service.name
| SORT errors DESC
</code></pre>
<p>The two halves of the query map onto the two halves of the question.
The subquery works out which hosts were saturated, averaging CPU utilization per host and keeping the ones that averaged under 10% idle, which is another way of saying above 90% busy for the window.
The outer query then works out what those hosts were complaining about, pulling their error logs and using <a href="https://www.elastic.co/docs/reference/query-languages/esql/functions-operators/grouping-functions/categorize"><code>CATEGORIZE</code></a> to collapse thousands of individual lines into a handful of error classes.</p>
<p>Two choices in there are worth pausing on.</p>
<p>The subquery uses <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/ts"><code>TS</code></a> rather than <code>FROM</code> because a host does not report one CPU number.
It reports a separate time series per CPU state, and per logical core as well if your collector is configured to break them out, so the reduction has to happen in two stages.
<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> collapses each series to a single value first, and the outer <code>AVG</code> then combines those into one figure per host.</p>
<p>Naming that inner function matters more than it looks.
Write <code>AVG(metrics.system.cpu.utilization)</code> on its own and <code>TS</code> supplies <code>LAST_OVER_TIME</code> for you, averaging each series' final sample rather than its average over the window.
In this dataset that one substitution moves a host from 7% idle to 10% idle, which is the difference between appearing in the results and not.
<a href="https://www.elastic.co/observability-labs/blog/esql-ts-command-querying-metrics">Querying metrics with the TS command</a> goes into the two aggregation phases in more depth.</p>
<p>The log filter tests <code>severity_number</code> (17 is the ERROR floor on the OpenTelemetry scale) rather than the severity text, because the numeric scale is fixed by the spec while the text is whatever the emitting library decided to write.
That is not a hypothetical distinction here: the error logs in this cluster carry four different labels, including <code>SEVERE</code> from a Java service.
Matching on the text alone returns 2,754 of checkout's errors and misses the billing service entirely, while the numeric filter returns all 5,910 of them and keeps billing too.</p>
<p>When you run the query in Discover, the result is a short table of error classes, scoped to the saturated hosts:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9a7ec0258240c9d3/6a85caf327c5cd90465f73f4/metrics-to-logs-result.jpg" alt="Discover ES|QL results showing error log patterns grouped by CATEGORIZE for hosts above 90% CPU" /></p>
<p>The subquery returned six saturated hosts, and on all six the same service is timing out against an upstream dependency and draining its connection pool.
The <code>hosts</code> column is what lets you set the other two rows aside without opening anything: the certificate errors reach only two of the six, and the gateway declines amount to five lines in an hour.
Neither tracks the cohort the way the checkout patterns do.</p>
<p>Having all three signals in one store already removed the exports from this investigation.
The subquery removes the step after that, and closing that last gap matters more than it sounds.
By the time you have read six host names off a chart and typed them into a log search, the set has moved: a host that crossed the threshold a minute ago is missing from your list, and one that has since recovered is still in it.
Here the host list is derived from current data on every run, so re-running the query during an incident gives you the current cohort.</p>
<p>The stale list is only half of it.
A correlation done by hand exists only in the head of the person who did it, so nobody else can check it, save it, or run it again tomorrow.</p>
<h2 id="logstometricsdotheerroringhostslookdifferentfromthehealthyones">Logs to metrics: do the erroring hosts look different from the healthy ones?</h2>
<p>Filtering metrics by a log-derived host set answers the opposite question.
The payments service is throwing errors on some hosts and not others, and you want to know whether resource pressure explains the split before you start reading deploy history.</p>
<p>That is a comparison, so the query needs both cohorts.
<a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/fork"><code>FORK</code></a> runs two branches over the same input, and <code>IN</code> and <code>NOT IN</code> against the same log-derived host set divide the fleet between them.</p>
<pre><code>TS metrics-hostmetrics.otel-*
| WHERE attributes.state == "idle"
| STATS idle = AVG(AVG_OVER_TIME(metrics.system.cpu.utilization))
    BY host = resource.attributes.host.name
| EVAL busy = 1 - idle
| FORK
    ( WHERE host IN (
        FROM logs-*.otel-*
        | WHERE severity_number &gt;= 17
          AND resource.attributes.service.name == "payments"
          AND resource.attributes.host.name IS NOT NULL
        | STATS errors = COUNT(*) BY resource.attributes.host.name
        | KEEP resource.attributes.host.name )
      | EVAL cohort = "logging errors" )
    ( WHERE host NOT IN (
        FROM logs-*.otel-*
        | WHERE severity_number &gt;= 17
          AND resource.attributes.service.name == "payments"
          AND resource.attributes.host.name IS NOT NULL
        | STATS errors = COUNT(*) BY resource.attributes.host.name
        | KEEP resource.attributes.host.name )
      | EVAL cohort = "no errors" )
| STATS hosts = COUNT(*), mean_busy = AVG(busy), busiest_host = MAX(busy)
    BY cohort
</code></pre>
<p>The query reads top to bottom as three stages.
The metrics query runs first and reduces the whole fleet to one busy figure per host.
<code>FORK</code> then splits that fleet in two using the same log query in both branches, separating the hosts that appear in it from the hosts that do not.
The final <code>STATS</code> summarizes each group, so both cohorts come back as two rows of one table, measured the same way over the same window.</p>
<p>This query is longer than the others, and three parts of it are less obvious than they look.</p>
<p>The natural way to label the two cohorts would be <code>EVAL cohort = CASE(host IN (...), "erroring", "healthy")</code>, and ES|QL rejects it.
In 9.5 an <code>IN</code> subquery has to be a top-level predicate in a <code>WHERE</code> condition rather than an argument to a scalar function, which is why the split happens at the command level with <code>FORK</code>.</p>
<p>The <code>STATS ... BY</code> inside each subquery looks redundant, since <code>KEEP</code> alone would return the same host names.
It is not: without it, the subquery returns one row per matching log document instead of one row per host, and those rows are all held in memory for the outer query to filter against.
Aggregating first turns millions of rows into a few hundred host names.</p>
<p>The <code>IS NOT NULL</code> filter guards the sharpest edge here, and this dataset is a live example rather than a hypothetical.
<code>NOT IN</code> follows SQL null semantics, so a single null in the subquery result makes the predicate match nothing at all.
Five of the payments error logs in this cluster came through a sidecar that dropped the host name.
Remove that one line from both branches and the query still succeeds, but it returns a single row: the nulls quietly delete the entire 286-host "no errors" cohort, and what is left looks like a perfectly plausible answer to a different question.</p>
<p>Run the query in Discover and you get two rows, one per cohort:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt867c2fae8e2b1116/6a85caf5abdc29f2f11224fc/cohort-comparison.jpg" alt="Discover ES|QL results comparing mean and peak CPU for hosts logging errors against hosts with no errors" /></p>
<p>CPU does not explain the split, and the numbers say so twice.
The 14 erroring hosts run slightly cooler on average than the 286 quiet ones, and the busiest machine among them averaged 50% over the hour while the quiet cohort contains a host that averaged 95%.
Whatever is failing on those 14, they had headroom the entire time, and deploy history is a better place to spend the next ten minutes.</p>
<p>A negative result like this is worth as much as a positive one, and it is usually the one people skip.
Getting it the long way means running the metrics query twice against two hand-built host lists and lining the numbers up afterwards, which is enough friction that the check often just does not happen.
Both cohorts here come from the same log query in the same execution, over identical time windows, so there is nothing to reconcile and no reason not to check.</p>
<h2 id="tracestologswhatdideveryservicelogduringtheslowrequests">Traces to logs: what did every service log during the slow requests?</h2>
<p>A <a href="https://www.elastic.co/observability-labs/blog/slo-burn-rate-analysis-trace-investigation">service level objective (SLO) burn alert</a> fires on checkout latency.
Tracing gives you the slow requests and their spans, and the next question is what the services involved were writing to their logs while those specific requests were in flight.</p>
<p>The trace ID is the join key, and there are far too many of them to move by hand.</p>
<pre><code>FROM logs-*.otel-*
| WHERE trace_id IN (
      FROM traces-*.otel-*
      | WHERE kind == "Server"
        AND resource.attributes.service.name == "checkout"
        AND name == "POST /api/orders"
        AND duration &gt; 2000000000
      | SORT duration DESC
      | LIMIT 500
      | KEEP trace_id
    )
| STATS lines = COUNT(*), traces = COUNT_DISTINCT(trace_id)
    BY pattern = CATEGORIZE(body.text),
       service = resource.attributes.service.name,
       severity_text
| SORT traces DESC
</code></pre>
<p>The subquery answers "which requests were slow."
It looks at the inbound request span for the checkout endpoint rather than the client and internal spans beneath it, then keeps the 500 slowest requests over two seconds.
Durations are recorded in nanoseconds, which is why the threshold has so many zeros.</p>
<p>The outer query answers "what got logged while they were running," gathering every log line that shares one of those trace IDs and grouping them into patterns.</p>
<p>Counting distinct traces per pattern is what makes the output readable.
A log pattern that appears 30,000 times across four traces is one chatty request, while a pattern that shows up in 470 of the 500 slowest traces is a property of being slow.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2d7d39a09bb47dec/6a85caf8f9373d09d496f594/slow-trace-log-patterns.jpg" alt="Discover ES|QL results ranking log patterns by how many of the 500 slowest checkout requests they appear in" /></p>
<p>From the results above, the top row is there by construction and can be set aside: checkout writes one <code>order submitted</code> line per order, so it appears in all 500 traces and says nothing about why these particular 500 were slow.</p>
<p>From the results above, the third row is the answer, and it points at a service nobody was looking at.
A lock wait timeout in inventory, two hops downstream from where the alert fired, shows up in 470 of the 500 slowest requests, and the 947 lines behind those 470 traces mean a good share of them retried more than once.
The payment gateway declines are real failures, and at 19 traces out of 500 they are not what is burning the SLO.</p>
<p>Done by hand, this means opening slow traces one at a time and reading the correlated logs for each, which is tedious at ten traces and nobody's idea of a plan at 500.
When the spans and the logs are held in different systems, every trace you check is a copied ID and a context switch, and the sample size you can afford drops to about three.
Three traces is enough to form a theory and not enough to test one.
Treating the slow requests as a population is what turns "this trace had a lock wait" into "470 of the 500 slowest requests had a lock wait," and that difference decides whether you page the inventory team.</p>
<h2 id="allthreesignalsfrompodmemorypressuretotheloglinesbehindthefailures">All three signals: from pod memory pressure to the log lines behind the failures</h2>
<p><code>IN</code> subqueries nest, so the pattern extends to as many signal types as the question needs.</p>
<p>A node pool starts reporting memory pressure after a rollout.
You want the log lines from the requests that actually failed on the pods under pressure, which means going from metrics to traces to logs without stopping in between.</p>
<pre><code>FROM logs-*.otel-*
| WHERE trace_id IN (
      FROM traces-*.otel-*
      | WHERE kind == "Server"
        AND status.code == "Error"
        AND resource.attributes.k8s.pod.uid IN (
            TS metrics-kubeletstats.otel-*
            | STATS peak = MAX(MAX_OVER_TIME(metrics.k8s.pod.memory_limit_utilization))
                BY resource.attributes.k8s.pod.uid
            | WHERE peak &gt; 0.95
            | KEEP resource.attributes.k8s.pod.uid
          )
      | STATS failures = COUNT(*) BY trace_id
      | SORT failures DESC
      | LIMIT 1000
      | KEEP trace_id
    )
| STATS lines = COUNT(*), traces = COUNT_DISTINCT(trace_id)
    BY pattern = CATEGORIZE(body.text), service = resource.attributes.service.name
| SORT traces DESC
</code></pre>
<p>Reading the query inside out, you can see each layer answering one part of the question.
The innermost subquery identifies the pods whose memory peaked above 95% of their limit, using <a href="https://www.elastic.co/docs/reference/query-languages/esql/functions-operators/time-series-aggregation-functions/max_over_time"><code>MAX_OVER_TIME</code></a> to take each pod's peak rather than its average.
The middle one narrows to the requests that actually failed on those pods and reduces them to at most 1,000 trace IDs.
The outer query then collects the logs for those traces from every service that took part, including services running on pods that were entirely healthy, and that last part turns out to be where the answer is.</p>
<p>The pods are matched on their UID rather than their name, because names repeat across namespaces and restarts.
The query assumes Kubernetes metadata reaches your spans, which is what the collector's <a href="https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/processor/k8sattributesprocessor"><code>k8sattributes</code> processor</a> is for; if it does not, the host or container ID works the same way.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3aa0104b29390bd2/6a85cafa07829040c5321766/three-signal-result.jpg" alt="Discover ES|QL results tracing Kubernetes pod memory pressure through failed spans to the log patterns behind them" /></p>
<p>From the results above, the first row sits at exactly 1,000 because that is the subquery's <code>LIMIT</code>, so it describes the size of the sample rather than the size of the incident.
Every trace in that sample carries the cart deadline line, which is the symptom you already knew about when you started.</p>
<p>Look at the second row instead.
<code>product-catalog</code> is rejecting oversized payloads across 946 of the same 1,000 traces, and it never appeared in the pod subquery at all: its pods peaked at 75% of their memory limit, well under the 95% threshold.
The rollout started sending larger payloads, which would account for both cart's memory climb and the failures.
Checkout's retry budget gives out in about half of them, which is how the failure became visible to users.</p>
<p>Filtering logs directly by the pressured pods would have shown you the cart line and hidden the product-catalog one, which is to say it would have confirmed the symptom and buried the cause.
Doing it without subqueries means three queries and two hand-built lists, and the second list is a thousand trace IDs.
That is usually the point at which people stop after the first hop and go with the cart theory.
The reason the second hop is cheap here is that all three signals sit in the same store behind the same query language, so widening from pods to traces to every service in the trace is a clause, not a project.</p>
<h2 id="whydoesqlsubqueriesmatterforaiagents">Why do ES|QL subqueries matter for AI agents?</h2>
<p>Keeping the intermediate set inside the cluster is convenient for a person and close to essential for an agent querying on your behalf.</p>
<p>Split across two tool calls, the intermediate result has to travel.
A list of 500 trace IDs comes back in a tool response and occupies the model's context, and the agent then has to rewrite every one of them into the next query.
That costs tokens on every hop, and it is where truncation and transcription errors come from.
With a subquery, the intermediate set stays inside Elasticsearch and the agent only ever sees the final table.</p>
<p>The problem compounds when the signals are spread across systems.
An agent then needs credentials, a client, and a working knowledge of the query language for each one, plus the judgment to join results that use different names for the same host.
One store and one query language reduce that to a single skill the agent has to be good at.</p>
<p>One ES|QL string is also a complete description of the correlation, which makes the investigation reproducible: an agent can put the query in its summary, and a human can paste it into Discover and get the same logic evaluated against current data.
A two-call sequence with a hardcoded host list in the middle gives you neither.
The one thing to watch is that an agent calling the <code>_query</code> API has to filter <code>@timestamp</code> itself, since nothing is binding a time picker.</p>
<h2 id="fourthingstoknowbeforewritingesqlinsubqueries">Four things to know before writing ES|QL IN subqueries</h2>
<p><code>IN</code> subqueries are in technical preview in Elastic Stack 9.5 and on Elastic Cloud Serverless, while <code>TS</code> and <code>FORK</code> have been generally available since 9.4.
<code>CATEGORIZE</code> has been generally available since 9.1 and requires a <a href="https://www.elastic.co/subscriptions">Platinum license</a>; every query above works without it if you group by an existing field instead.</p>
<p>Four things are worth knowing before you write your own:</p>
<ul>
<li>In 9.5 the subquery returns exactly one column, which is what the trailing <code>KEEP</code> does in each example.</li>
<li>Aggregate the subquery down to distinct values with <code>STATS ... BY</code> before returning them.
Its result is materialized for the outer query to filter against, so handing back a few hundred host names instead of a few million rows is both faster and safer.</li>
<li>Filter nulls out of any <code>NOT IN</code> subquery, because SQL null semantics mean one null makes the predicate match nothing.</li>
<li>Subqueries are non-correlated.
They run independently and cannot reference columns from the outer query, so this is a set filter rather than a row-by-row join.
Reach for <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/lookup-join"><code>LOOKUP JOIN</code></a> when you need per-row enrichment, which we covered in <a href="https://www.elastic.co/observability-labs/blog/elastic-esql-join-observability">ES|QL joins for richer observability</a>.</li>
</ul>
<h2 id="tryesqlsignalcorrelationonyourowndata">Try ES|QL signal correlation on your own data</h2>
<p>The pattern under all four examples is the same.
You start with a set you can describe in one signal and a question you can only answer in another, and the subquery carries that set across the boundary for you.</p>
<p>The syntax is the smaller part of what makes that work.
It works because logs, metrics, and traces sit in <a href="https://www.elastic.co/observability-labs/blog/elasticsearch-columnar-metrics-engine-30x-faster-prometheus">one store behind one query engine</a>, under field names they kept on the way in, so crossing from one signal to another is a clause in a query rather than an integration to build and maintain.
Where that is not true, the same four investigations turn into a sequence of exports, translations, and manual joins, and that costs more than slower answers.
It quietly shrinks the number of questions anyone is willing to ask, and the negative results are the first to go.</p>
<p>Each pattern here replaces two or three queries with one, and no host list or trace ID list has to move between them.
Fewer steps mean fewer places to be wrong, and a correlation you can save as a single string and hand to someone else.</p>
<p>To try it:</p>
<ol>
<li>Open an <a href="https://cloud.elastic.co/serverless-registration?onboarding_token=observability">Observability project on Elastic Cloud Serverless</a>, or upgrade to Elastic Stack 9.5.</li>
<li>Send data with the <a href="https://www.elastic.co/docs/reference/opentelemetry">Elastic Distributions of OpenTelemetry</a>, or point an existing collector at Elasticsearch.</li>
<li>In <strong>Discover</strong>, switch to ES|QL and start from the metrics to logs query above, swapping in your own data streams and thresholds.</li>
<li>Read the <a href="https://www.elastic.co/docs/reference/query-languages/esql/esql-in-subquery"><code>IN</code> subquery reference</a> for the full set of commands you can use inside a subquery.</li>
</ol>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/esql-subqueries-correlate-logs-metrics-traces</link>
    <guid isPermaLink="false">esql-subqueries-correlate-logs-metrics-traces</guid>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[Metrics]]></category>
    <category><![CDATA[What's New]]></category>
    <dc:creator><![CDATA[Vinay Chandrasekhar,Miguel Sánchez Gómez]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt746d25b6fd650a97/6a85cafe4710c625d2d3cb3d/header.jpg" length="0" type="image/jpeg"/>
    <pubDate>Fri, 14 Aug 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Android application monitoring with OpenTelemetry: distributed tracing from tap to backend]]></title>
    <description><![CDATA[EDOT Android adds mobile APM to your Android app with one Gradle dependency: crash reporting, session tracking and distributed tracing visible in Kibana.]]></description>
    <content:encoded><![CDATA[<p>People are handling more and more matters on their smartphones through mobile apps, both privately and professionally. With thousands or even millions of users, ensuring great performance and reliability is a key challenge for mobile app teams and the backend services they depend on. Understanding real user impact, crash patterns, and the root causes of slow response times is fundamental to managing mobile app quality.</p>
<p>The challenge deepens when something goes wrong. A crash on the device, a slow screen, or an error response might originate in the Android app itself, in a backend service, or somewhere in the network path between them. Debugging these problems without a connected, E2E view from the mobile client to the backend is time-consuming and frustrating. And without a standard instrumentation format, mobile teams often end up maintaining separate tooling that doesn't integrate with what the backend and infrastructure teams already use.</p>
<p><a href="https://opentelemetry.io/">OpenTelemetry</a> offers a way out: a unified, open-standard instrumentation model that works across platforms and languages, backed by a large community. The Elastic Distribution of OpenTelemetry Android, or EDOT Android, is an APM agent for native Android applications built on top of OpenTelemetry. It gives Android teams a practical path to observe mobile app behavior in Elastic, providing them with distributed tracing, crash reporting, session tracking, disk buffering, and automatic instrumentation, with as little code as possible while staying grounded in open standards.</p>
<p>To see what it all looks like, we will instrument a demo Android weather application end to end. You will run Elasticsearch, Kibana, and the Elastic Agent locally. The Elastic Agent provides the OTLP endpoint that receives telemetry from the Android app and backend. You will then generate distributed traces, custom spans, logs, and Android crashes from the app, and explore the results in Kibana using the Android OpenTelemetry dashboards.</p>
<p>This article focuses on a hands-on experiment to explore the E2E experience of observing Android apps with Elastic, using the EDOT Android agent. For more specific details on the EDOT Android agent, such as a list of supported features and a setup guide for your own Android project, take a look at <a href="https://www.elastic.co/docs/reference/opentelemetry/edot-sdks/android">EDOT Android docs</a>.</p>
<h2 id="settingupedotandroidwithelasticsearchandkibana">Setting up EDOT Android with Elasticsearch and Kibana</h2>
<p>We will use the <a href="https://github.com/elastic/android-agent-demo">EDOT Android demo application</a>. The demo is intentionally small but covers the main workflows you need when evaluating mobile observability with Elastic.</p>
<p>The demo has two main components: an <strong>Android app</strong>, and a <strong>Spring Boot backend</strong>. Additionally, you'll need an <strong>Elastic Stack</strong> environment up and running; we'll explain more about how to get one later in this guide.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd2e9cece7b3e7427/6a85cd5c18249cdfba18f809/blog-demo-project-components.png" alt="Demo app components" /></p>
<h3 id="prerequisites">Prerequisites</h3>
<ul>
<li>Java 17 or higher.</li>
<li><a href="https://www.docker.com/">Docker</a>.</li>
<li><a href="https://developer.android.com/studio">Android Studio</a>.</li>
<li>An <a href="https://developer.android.com/studio/run/emulator">Android emulator</a>.</li>
<li>On Windows, use <a href="https://learn.microsoft.com/en-us/windows/wsl/install">Windows Subsystem for Linux (WSL)</a> to run the demo scripts.</li>
</ul>
<h3 id="step1clonethedemoappsrepository">Step 1: Clone the demo app's repository</h3>
<p>We'll start by cloning the <a href="https://github.com/elastic/android-agent-demo">EDOT Android demo application</a>:</p>
<pre><code>git clone git@github.com:elastic/android-agent-demo.git
</code></pre>
<h3 id="step2starttheelasticstack">Step 2: Start the Elastic Stack</h3>
<p>The demo uses <a href="https://github.com/elastic/start-local/">start-local</a> to run Elasticsearch, Kibana, and the <a href="https://www.elastic.co/docs/reference/fleet/elastic-agent-as-otel-collector">Elastic Agent</a> with a single command. In this setup, the Elastic Agent provides the OTLP endpoint that receives telemetry from the application and backend. Run this from the directory where you want the local Elastic files to be created:</p>
<pre><code>curl -fsSL https://elastic.co/start-local | sh -s -- --edot
</code></pre>
<p>For more information on this step, take a look at the <a href="https://github.com/elastic/android-agent-demo#step-1-setting-up-elasticsearch-kibana-and-the-elastic-agent">demo app's instructions</a>.</p>
<h3 id="step3startthelocalbackend">Step 3: Start the local backend</h3>
<p>The demo backend is a Spring Boot service instrumented with the <a href="https://github.com/elastic/elastic-otel-java/">EDOT Java agent</a>. It handles the app's weather requests and calls the <a href="https://open-meteo.com/">Open-Meteo</a> public API for weather data.</p>
<pre><code>./backend-manager start
</code></pre>
<p>For more information on managing the backend service, take a look at the <a href="https://github.com/elastic/android-agent-demo#step-2-launching-the-backend-service">demo app's instructions</a>.</p>
<h3 id="step4launchtheandroidapplication">Step 4: Launch the Android application</h3>
<p>Use <a href="https://developer.android.com/studio/intro">Android Studio</a> to open up the <a href="https://github.com/elastic/android-agent-demo">EDOT Android demo application</a> repo and run the application in your emulator. More info on how to run Android apps from Android Studio <a href="https://developer.android.com/studio/run">here</a>.</p>
<h2 id="generatingdistributedtraceserrorsandcrashesfromanandroidapp">Generating distributed traces, errors and crashes from an Android app</h2>
<p>The Android app has two screens: a city selector and a weather display screen that shows the current weather for the selected city on the previous screen. It includes two intentional failure paths: the first one is reached by selecting <strong>New York</strong>, which causes the backend to reject the request (the demo backend only supports European cities), and tapping the floating crash button intentionally crashes the app so you can review crash reporting in Kibana after relaunch. We'll take a look at those use cases in more detail below.</p>
<h3 id="tracingasuccessfulrequestendtoend">Tracing a successful request end to end</h3>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb358eb3f6e58dbf0/6a85cd5f43c0b77b1e2f066c/blog-android-app-selecting-paris.png" alt="Selecting Paris" /></p>
<p>In the EDOT Android demo app, selecting "Paris" as the city triggers a successful backend request on the second screen, for which a span will be automatically generated using EDOT Android's <a href="https://www.elastic.co/docs/reference/opentelemetry/edot-sdks/android/automatic-instrumentation#okhttp">OkHttp auto-instrumentation</a>, which supports all OkHttp-generated HTTP requests and tools using it, such as Retrofit. Aside from the Android HTTP span, the successful city request continues e2e and creates a backend HTTP client span to Open-Meteo.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0ed067b86aa2fba2/6a85cd61eaf24566fea49f99/blog-trace-waterfall-view.png" alt="Trace waterfall" /></p>
<h3 id="howbackenderrorsappearintheandroiddistributedtrace">How backend errors appear in the Android distributed trace</h3>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5f4b20a6e5ab1e55/6a85cd65abdc29c09f122542/blog-android-app-selecting-new-york.png" alt="Selecting New York" /></p>
<p>The demo backend only supports European cities, so selecting "New York" causes it to fail, which in turn automatically creates an error associated with our Android app's HTTP span. This is done automatically. We'll see later how to find and inspect these issues from Kibana.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte19bebcdcb1c6b55/6a85cd67abdc295b7d12254a/blog-error-trace-waterfall-view.png" alt="Error trace waterfall" /></p>
<h3 id="howedotandroidcapturesandreportsappcrashes">How EDOT Android captures and reports app crashes</h3>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2075b1b23db04111/6a85cd6b33f244a7ec49f557/blog-android-app-selecting-crash.png" alt="Application crash" /></p>
<p>The crash button creates a crash event that appears in Kibana after the app is reopened. This event contains session information that will help us narrow down its root cause from Kibana, as we'll see later.</p>
<p>Note: EDOT Android automatically attaches Android session context to spans and logs. That means that any span or log created before the crash can be reviewed together with the crash event and nearby spans from the same session, giving you a complete picture of what the user was doing. This even applies to <a href="https://www.elastic.co/docs/reference/opentelemetry/edot-sdks/android/manual-instrumentation">manually created spans and logs</a>.</p>
<h2 id="visualizingandroidapplicationmonitoringdatainkibana">Visualizing Android application monitoring data in Kibana</h2>
<p>To see the whole story from our Android app in a single place, we'll install Kibana's <a href="https://www.elastic.co/docs/reference/integrations/otel_android_dashboards">Android OpenTelemetry Assets</a> package by following the steps below.</p>
<ol>
<li>In Kibana, search for "Android OpenTelemetry Assets" in the <a href="https://www.elastic.co/docs/explore-analyze/find-and-organize/find-apps-and-objects">global search field</a>.</li>
<li>Open it and click <strong>Install</strong> to add the Android dashboards to your Kibana instance.</li>
</ol>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte095f0fc9464311c/6a85cd6d5c2790e893f59b59/blog-content-pack-search.png" alt="Searching content pack" /></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1c2ec15025a351d8/6a85cd70682666f73c1eac55/blog-content-pack-install.png" alt="Installing content pack" /></p>
<h3 id="exploringandroidapplicationmonitoringdashboardsinkibana">Exploring Android application monitoring dashboards in Kibana</h3>
<p>Once the content package is installed, open the [Android OTel] Application Overview dashboard:</p>
<ol>
<li>In Kibana, search for Dashboards in the <a href="https://www.elastic.co/docs/explore-analyze/find-and-organize/find-apps-and-objects">global search field</a> or in Kibana's menu.</li>
<li>In Dashboards, search for Android OTel and open the "[Android OTel] Application Overview" dashboard.</li>
<li>Select your application from the Applications panel at the top of the dashboard.</li>
</ol>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt01e558c291c033e4/6a85cd735c2790eef2f59b5d/blog-dashboard-list.png" alt="Dashboard list" /></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7cd63db3287bbe1c/6a85cd7618249c7e3918f817/blog-dashboard-android-overview.png" alt="Android overview dashboard" /></p>
<p>The dashboard provides a set of metric panels for an overview of your app's health, performance, and RUM, as well as a set of panels that can be further explored either in Discover or the Exception dashboard, as explained below.</p>
<h2 id="howtoinspectthedistributedtracingwaterfallinkibana">How to inspect the distributed tracing waterfall in Kibana</h2>
<p>From the Application Overview dashboard, go to one of the span tables (either <strong>All spans</strong> or <strong>Failed spans</strong>) and click its <strong>Explore in Discover</strong> button.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt583214104943a381/6a85cd7811893c26cea7abc6/blog-dashboard-android-overview-explore-spans.png" alt="Explore spans" /></p>
<p>In Discover, click the expand icon on the left side of any span row to open its details panel. The trace waterfall UI appears inside, showing the full span hierarchy and timing for that trace. You can expand the waterfall to full screen and drill down from there.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdfb4a277fa1ca84a/6a85cd7b0782902c153217be/blog-discover-span-dialog-open.png" alt="Discover open span dialog" /></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt763fe1ec0c488c93/6a85cd7d80984c6d40669020/blog-discover-span-dialog-view.png" alt="Discover span dialog" /></p>
<h3 id="analyzingfailedspansandbackenderrorsinkibana">Analyzing failed spans and backend errors in Kibana</h3>
<p>While you can find all kinds of spans in the dashboard's <strong>All spans</strong> panel, you can narrow them down to failed ones only by exploring the <strong>Failed spans</strong> panel instead.</p>
<p>For the New York path, find a failed span and expand it. The trace waterfall highlights the backend error, and the exception details show the intentional backend rule that only supports European cities.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd3515f83ee7c1ce3/6a85cd8018249c1b6b18f81b/blog-dashboard-android-overview-explore-failed-spans.png" alt="Explore failed spans" /></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8075553525ac7f75/6a85cd834710c61975d3cbb6/blog-discover-failed-span-dialog-open.png" alt="Discover failed span dialog open" /></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte5dcfeb3df0e6c34/6a85cd86f5f1a06eb82ec95f/blog-discover-failed-span-dialog.png" alt="Discover failed span dialog" /></p>
<h2 id="reviewingcrashdetailsandstacktracesintheexceptiondashboard">Reviewing crash details and stacktraces in the exception dashboard</h2>
<p>Crash reporting is provided by the <a href="https://www.elastic.co/docs/reference/opentelemetry/edot-sdks/android/automatic-instrumentation#crash-reporting">crash automatic instrumentation plugin</a>. When an unhandled exception crashes the app, EDOT Android stores the crash event on disk. The event is exported the next time the app starts. Disk buffering ensures the crash event is not lost even if the network was unavailable at the time of the crash.</p>
<p>In the Application Overview dashboard, scroll to the <strong>Crashes</strong> section. You will see crash groups listed by a computed stacktrace group ID. Select a group and click <strong>View crash details</strong> to open the Exception Details dashboard.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5facd8c29dd053d6/6a85cd8927c5cdb9ee5f7444/blog-dashboard-android-overview-crash-list.png" alt="Crash list" /></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0be9ec5b582ea0be/6a85cd8c9d2b71fe05f939fc/blog-dashboard-android-overview-crash-view-details.png" alt="View crash details" /></p>
<p>The Exception Details dashboard shows a set of metrics to better understand the impact of the selected crash, as well as its full stacktrace. Crash events are grouped based on their stacktrace, which helps ensure that the same crash is counted and aggregated in this dashboard to better understand a single crash's impact on your application.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt36c7aefc29888240/6a85cd8f1aa1e11536ff8dab/blog-dashboard-android-exceptions.png" alt="Exception dashboard view" /></p>
<p>For this demo, the stacktrace points to the intentional crash in <code>MainActivity</code>. The nearby session events should also include the custom <code>Crash button click</code> log created just before the crash, which helps explain how the crash was triggered. We'll take a look at how to inspect a session to get an idea of the user's journey within your application that led them to a crash.</p>
<h2 id="usingsessionstounderstanduserflowinedotandroid">Using sessions to understand user flow in EDOT Android</h2>
<p>Mobile troubleshooting often starts with a single bad outcome (a crash, an error, a slow UX), but the useful question is what happened before that outcome. EDOT Android helps answer that by attaching <code>session.id</code> to every span and log emitted by the application, even for manually created ones.</p>
<p>A session is meant to cover a single user interaction with your application. A new one is created when there is no previous active session or when the previous session has expired. Sessions expire after 30 minutes of inactivity. If the app stays active, a session can last up to 4 hours.</p>
<p>This lets you query all the telemetry from a single session and review it in order. After finding a crash group, drill into one affected session from the <strong>Top affected sessions</strong> panel and review the event timeline. You can see the custom logs, app startup spans, HTTP request spans, and crash data together in one investigation path.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4afad0b2945a3c77/6a85cd92f61d6ea4129c2b6d/blog-dashboard-android-exceptions-view-session-details.png" alt="Exception dashboard view session details" /></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt239ba5bb7095356b/6a85cd9418249c055218f81f/blog-dashboard-android-overview-with-session-filter.png" alt="Overview dashboard with session filter" /></p>
<p>The <strong>Event timeline</strong> panel on the Application Overview dashboard is also useful here: select a session from the dashboard's top filters, and the timeline shows the full sequence of spans and logs in that session chronologically.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt13d994d2109d0253/6a85cd978c2944d923b8909f/blog-dashboard-android-overview-event-timeline.png" alt="Event timeline with session filter" /></p>
<h2 id="whatedotandroidaddsasamobileapmforproductionapps">What EDOT Android adds as a mobile APM for production apps</h2>
<p>The demo uses a local stack and simple code, but the same agent features apply to production apps.</p>
<p><strong>Disk buffering</strong> stores telemetry locally before export. This reduces data loss when the device has poor connectivity or the app is temporarily offline.</p>
<p><strong>Automatic instrumentation</strong> creates telemetry for supported targets without adding code around every call. Today that includes OkHttp, crash reporting, and an adapter for <a href="https://github.com/open-telemetry/opentelemetry-android">OpenTelemetry Android</a> instrumentation.</p>
<p><strong>Manual instrumentation</strong> lets you add spans, logs, and metrics for app-specific workflows. This is useful for screen loading times, checkout flows, login steps, feature usage, or any area where framework-level telemetry alone is not enough.</p>
<p><strong>Central configuration</strong> can remotely adjust selected EDOT Android behavior through Kibana when the OpAMP endpoint is configured. At the time of writing, central configuration for EDOT Android is in preview and supports settings such as recording and session sample rate.</p>
<p><strong>Distributed tracing</strong> connects Android app requests to backend service spans so you can trace the full path of any user action, from the tap on the screen to the database query on the server. EDOT Android ensures that your application's telemetry timestamps are in sync with the <a href="https://en.wikipedia.org/wiki/Coordinated_Universal_Time">coordinated universal time</a>. This ensures a proper trace waterfall hierarchy later on in Kibana, where different components are properly coordinated in time.</p>
<h2 id="cleanupthedemo">Clean up the demo</h2>
<p>When you are finished, stop the backend in case you're planning to restart it later, or uninstall it otherwise:</p>
<pre><code>./backend-manager stop
# ./backend-manager uninstall
</code></pre>
<p>Then stop or uninstall the local Elastic Stack:</p>
<pre><code>cd elastic-start-local
./stop.sh
# ./uninstall.sh
</code></pre>
<h2 id="gettingstartedwithedotandroidinyourownapp">Getting started with EDOT Android in your own app</h2>
<p>EDOT Android gives native Android teams an OpenTelemetry-based path for mobile APM in Elastic. With a small Gradle setup and one early initialization call, you get automatic HTTP spans, crash reporting, session tracking, and direct access to the OpenTelemetry SDK for custom telemetry, and you can see it all tied together in Kibana's Android dashboards.</p>
<p>Observability is a crucial part of modern mobile development. Crashes, slow screens, and backend errors all impact real users, and the sooner you can identify root causes across the full request path, from the device to the database, the better. The demo app is a good first step because it exercises the complete workflow without requiring a production deployment. After that, the same setup model applies to your own app with production endpoints, API key authentication, and custom spans and logs tailored to your use cases.</p>
<p>Developer resources:</p>
<ul>
<li><a href="https://github.com/elastic/android-agent-demo">EDOT Android demo application</a></li>
<li><a href="https://www.elastic.co/docs/reference/opentelemetry/edot-sdks/android">EDOT Android documentation</a></li>
<li><a href="https://www.elastic.co/docs/reference/opentelemetry/edot-sdks/android/getting-started">EDOT Android getting started guide</a></li>
<li><a href="https://www.elastic.co/docs/reference/opentelemetry/edot-sdks/android/automatic-instrumentation">EDOT Android automatic instrumentation</a></li>
<li><a href="https://www.elastic.co/docs/reference/opentelemetry/edot-sdks/android/manual-instrumentation">EDOT Android manual instrumentation</a></li>
<li><a href="https://www.elastic.co/docs/reference/opentelemetry/edot-sdks/android/configuration">EDOT Android configuration</a></li>
<li><a href="https://www.elastic.co/docs/troubleshoot/ingest/opentelemetry/edot-sdks/android">EDOT Android troubleshooting</a> </li>
<li><a href="https://www.elastic.co/docs/reference/integrations/otel_android_dashboards">Android OpenTelemetry Assets dashboard docs</a></li>
</ul>
<p>Don't have an Elastic Cloud account yet? <a href="https://cloud.elastic.co/registration">Sign up for Elastic Cloud</a> and try out mobile observability with EDOT Android as described in this guide. We'd love to hear about your experience gaining visibility into your Android application stack with Elastic.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/distributed-tracing-android-mobile-apm-opentelemetry</link>
    <guid isPermaLink="false">distributed-tracing-android-mobile-apm-opentelemetry</guid>
    <category><![CDATA[APM]]></category>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[What's New]]></category>
    <dc:creator><![CDATA[Cesar Munoz]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9a224bedfc4c93ec/6a85cd9a682666089e1eac5f/header-image.png" length="0" type="image/png"/>
    <pubDate>Wed, 12 Aug 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[From CrashLoopBackOff to OOMKilled with PromQL in Elasticsearch and Kibana]]></title>
    <description><![CDATA[Use PromQL in Elasticsearch and Kibana to move from a CrashLoopBackOff alert to OOMKilled, memory versus the limit, and a verified fix.]]></description>
    <content:encoded><![CDATA[<p>A <code>CrashLoopBackOff</code> alert on <code>checkout-api</code> is paging you.
With <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/promql">PromQL</a> in Elasticsearch and Kibana, you can move from that alert to <code>OOMKilled</code>, prove the container is hitting its memory limit (not the node), raise the limit, and watch the alert recover.
If you are new to PromQL in Elastic, start with <a href="https://www.elastic.co/observability-labs/blog/elasticsearch-supports-promql">Elasticsearch supports PromQL</a>, <a href="https://www.elastic.co/observability-labs/blog/promql-queries-run-in-kibana">PromQL queries in Kibana</a>, and <a href="https://www.elastic.co/observability-labs/blog/promql-investigate-kubernetes-infrastructure">Investigate Kubernetes infrastructure with PromQL</a>.</p>
<h2 id="whatyouneed">What you need</h2>
<ul>
<li>An <strong>Observability</strong> <a href="https://www.elastic.co/docs/solutions/observability/get-started">serverless project</a>, or an Elastic Cloud Hosted or self-managed stack at <strong>version 9.4 or later</strong>.
PromQL is <strong>generally available</strong> in Elastic Cloud Serverless and Elastic Stack 9.5, and available as a <strong>technical preview</strong> in Elastic Stack 9.4.</li>
<li>Kubernetes state and container memory metrics in Elasticsearch.</li>
</ul>
<h2 id="whatisthealerttellingus">What is the alert telling us?</h2>
<p>This is the alert that opened the investigation:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd2f013a3a12a2919/6a7f19f35967e55ed15dd6b9/active-alert.png" alt="Active checkout API CrashLoopBackOff alert in Kibana" /></p>
<p>It comes from this waiting-reason query:</p>
<pre><code>PROMQL
  max by (namespace, pod, container) (
    max_over_time(
      kube_pod_container_status_waiting_reason{
        namespace="checkout",
        pod=~"checkout-api-.*",
        container="api",
        reason="CrashLoopBackOff"
      }[2m]
    )
  ) == 1
</code></pre>
<p>A result of <code>1</code> means Kubernetes is delaying another start because the container has failed repeatedly.
That is the correct paging signal here because the checkout path has a single replica: when that replica restarts, requests fail.</p>
<p>The <code>max_over_time(...[2m])</code> range keeps the alert tied to recent samples.
Without it, the last observed value of <code>1</code> can outlive the pod, and the rule keeps matching after that pod is gone.</p>
<p>That PromQL query ran every minute over a two-minute window and created an alert after one matching run.</p>
<h2 id="whydidthelastcontainerstop">Why did the last container stop?</h2>
<p>The alert shows what Kubernetes is doing now.
It does not show how the previous container ended:</p>
<pre><code>PROMQL
  max by (namespace, pod, container) (
    kube_pod_container_status_last_terminated_reason{
      namespace="checkout",
      pod=~"checkout-api-.*",
      container="api",
      reason="OOMKilled"
    }
  ) == 1
</code></pre>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1b7cd5d1c73d9d32/6a7f19f6e02fac5a485d69a3/last-termination-oom.png" alt="PromQL result showing OOMKilled as the last termination reason for checkout-api-8655769b49-vwddl" /></p>
<p>A result of <code>1</code> for the same namespace, pod, and container means the last recorded exit was out of memory.
Kube-state-metrics keeps that last reason as a gauge, so the value can stay visible after recovery.
It points the investigation at memory; it does not prove that every restart in the window was an OOM kill.</p>
<h2 id="isthefailurerepeating">Is the failure repeating?</h2>
<p>A single restart can still be transient.
The restart counter shows whether the failure keeps happening:</p>
<pre><code>PROMQL
  max by (namespace, pod, container) (
    increase(
      kube_pod_container_status_restarts_total{
        namespace="checkout",
        pod=~"checkout-api-.*",
        container="api"
      }[10m]
    )
  )
</code></pre>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte798ed61aa455a3b/6a7f19f9bdcff037f7c4329f/restart-history.png" alt="PromQL chart showing repeated checkout API container restarts during the incident" /></p>
<p><code>increase()</code> shows how much the restart counter rose over the selected range.
Repeated increases during the incident window explain why Kubernetes entered backoff.</p>
<h2 id="howcloseismemorytothelimit">How close is memory to the limit?</h2>
<p>We need to know how close the container is to its memory limit, and whether that gap collapses right before each restart.
This deployment allows only 128MiB, so the next query divides working-set memory by that configured limit:</p>
<pre><code>PROMQL
  max by (namespace, pod, container) (
    container_memory_working_set_bytes{
      namespace="checkout",
      pod=~"checkout-api-.*",
      container="api",
      image!=""
    }
  )
  /
  max by (namespace, pod, container) (
    container_spec_memory_limit_bytes{
      namespace="checkout",
      pod=~"checkout-api-.*",
      container="api",
      image!=""
    }
  )
</code></pre>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltfba66d8eb586ec9a/6a7f19fc33fa8af83e202b78/memory-limit-percent.png" alt="Checkout API memory repeatedly climbing toward its 128MiB container limit before OOMKilled restarts" /></p>
<p>The chart shows a repeating sawtooth: memory approaches 90% of the limit, drops when the process stops, and climbs again after each restart.</p>
<p>Working set is the better signal here than total usage.
Total usage includes reclaimable file cache, so it can sit near the limit without a kill.
Working set is closer to the memory that triggers OOMKilled for this workload.</p>
<h2 id="isthenodeundermemorypressure">Is the node under memory pressure?</h2>
<p><code>OOMKilled</code> can mean the container hit its own limit, or the node ran low on memory and Kubernetes started reclaiming.
To separate those cases, first find which node runs the pod, then check whether that node (or any peer) reported <code>MemoryPressure</code>.</p>
<pre><code>PROMQL
  max by (namespace, pod, node) (
    kube_pod_info{
      namespace="checkout",
      pod=~"checkout-api-.*"
    }
  ) == 1
</code></pre>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt831b6ef6ee5cb750/6a7f19ffe02fac7f585d69a7/pod-node.png" alt="PromQL result mapping the checkout API pod to ip-10-0-2-18.ec2.internal" /></p>
<p>The pod sits on <code>ip-10-0-2-18.ec2.internal</code>.
That is the node whose <code>MemoryPressure</code> result matters most for this incident:</p>
<pre><code>PROMQL
  max by (node) (
    max_over_time(
      kube_node_status_condition{
        condition="MemoryPressure",
        status="true"
      }[30m]
    )
  )
</code></pre>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltec63d50e8f11cd0f/6a7f1a023ce8e24429cf57a1/node-memory-pressure.png" alt="PromQL result showing no Kubernetes MemoryPressure on the cluster nodes" /></p>
<p>Every node returns <code>0</code>, including <code>ip-10-0-2-18.ec2.internal</code>.
So the host was not under node-wide memory pressure.
The kill came from the container limit itself.</p>
<h2 id="doesraisingthelimitclearthealert">Does raising the limit clear the alert?</h2>
<p><code>checkout-api</code> was healthy, then began building an in-memory cache that grows to 200MiB in 10MiB steps.
The container only allows 128MiB, so the process is killed with <code>OOMKilled</code> before that cache is fully allocated.</p>
<p>We will raise the memory limit to 512MiB so the 200MiB cache fits with room for the runtime, then check whether <code>CrashLoopBackOff</code> clears:</p>
<pre><code>kubectl set resources deployment/checkout-api -n checkout --limits=memory=512Mi
</code></pre>
<p>The same waiting-reason query then stops matching.
<code>CrashLoopBackOff</code> drops off:</p>
<pre><code>PROMQL
  max by (namespace, pod, container) (
    max_over_time(
      kube_pod_container_status_waiting_reason{
        namespace="checkout",
        pod=~"checkout-api-.*",
        container="api",
        reason="CrashLoopBackOff"
      }[2m]
    )
  ) == 1
</code></pre>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1d0d9f8d705b70eb/6a7f1a0473d9bde46629df4f/waiting-reason-cleared.png" alt="PromQL result showing CrashLoopBackOff clearing after the memory limit increase" /></p>
<p>And the alert that started this investigation? Gone.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte501026dbe06098d/6a7f1a0805b7b57f9d18bd3d/recovery.png" alt="Recovered checkout API CrashLoopBackOff alert in Kibana" /></p>
<p>That is how you detect and investigate a Kubernetes CrashLoopBackOff with PromQL: from the firing alert, through <code>OOMKilled</code> and the limit mismatch, to a recovered alert.
Elasticsearch holds the metrics; Kibana runs the same PromQL queries you already know from Prometheus.</p>
<h2 id="tryit">Try it</h2>
<ol>
<li>Open an <a href="https://cloud.elastic.co/serverless-registration">Observability project on Elastic Cloud Serverless</a>, or use Elastic Stack 9.4 or later.</li>
<li>In the ES|QL editor in Kibana, run the waiting-reason query against a workload you care about.</li>
<li>Follow the same path from that alert to termination reason, restarts, memory versus the limit, and recovery.</li>
</ol>
<p>For more PromQL in Elastic, see <a href="https://www.elastic.co/observability-labs/blog/elasticsearch-supports-promql">Elasticsearch supports PromQL</a>, <a href="https://www.elastic.co/observability-labs/blog/promql-queries-run-in-kibana">PromQL queries in Kibana</a>, and <a href="https://www.elastic.co/observability-labs/blog/promql-investigate-kubernetes-infrastructure">Investigate Kubernetes infrastructure with PromQL</a>.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/promql-kubernetes-oomkilled-crashloopbackoff</link>
    <guid isPermaLink="false">promql-kubernetes-oomkilled-crashloopbackoff</guid>
    <category><![CDATA[Kubernetes]]></category>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[Metrics]]></category>
    <category><![CDATA[What's New]]></category>
    <dc:creator><![CDATA[Miguel Sánchez Gómez]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt256fcae0e6b1797d/6a7f1a0bfc63ab76c464d06c/header.jpg" length="0" type="image/jpeg"/>
    <pubDate>Thu, 06 Aug 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Skip writing alert rules: 6 ready-made ES|QL templates ship inside the NGINX OTel integration]]></title>
    <description><![CDATA[Elastic integrations come with alerting rule templates, each one an ES|QL query with a threshold already set. Create Elasticsearch alert rules in minutes, tune them to your traffic, and catch silent data streams early.]]></description>
    <content:encoded><![CDATA[<p>The NGINX OpenTelemetry Assets integration ships six <a href="https://www.elastic.co/docs/reference/fleet/alerting-rule-templates">alerting rule templates</a>. Each one is an <a href="https://www.elastic.co/docs/reference/query-languages/esql">ES|QL</a> query with a threshold already tuned. Install the integration, create a rule from one of the templates, and adjust the threshold to match your traffic. You get working alerts in minutes instead of writing them from scratch. This walkthrough covers the full setup, threshold tuning, and how to use an idle data streams rule to catch a service that stops sending data.</p>
<h2 id="prerequisitesforelasticintegrationalertingruletemplates">Prerequisites for Elastic integration alerting rule templates</h2>
<p>Elastic Stack 9.4.0 or later.</p>
<p><em>Alerting rule templates have been available since 9.2.1, under the integration <strong>Assets</strong> tab. This article covers three things that need 9.4.0: the dedicated <strong>Alerting</strong> tab, idle data streams rules, and the NGINX OpenTelemetry Assets package, which is in technical preview.</em></p>
<h2 id="step1sendnginxlogsandmetricstoelasticsearchwithopentelemetry">Step 1: Send NGINX logs and metrics to Elasticsearch with OpenTelemetry</h2>
<p>First, get NGINX metrics and logs into Elasticsearch.</p>
<p>Enable the NGINX <code>stub_status</code> module and make the access and error logs readable by the collector. Then, configure an <a href="https://www.elastic.co/docs/reference/opentelemetry">EDOT</a> or upstream OpenTelemetry Collector with the <code>nginx</code> and <code>filelog</code> receivers to export metrics and logs to Elasticsearch.</p>
<p>The <a href="https://www.elastic.co/docs/reference/integrations/nginx_otel">integration setup</a> has the full receiver and pipeline configuration.</p>
<p>If you want to reproduce this example, you can use the <a href="https://github.com/Delacrobix/Creating-alerts-from-OOTB-alerting-template">companion repository</a>.</p>
<p>Both signals matter for alerting, and each group of templates reads a different data stream:</p>
<ul>
<li>The <strong>log-based</strong> templates (4xx and 5xx error rates, error log spike) query <code>logs-nginx.access.otel-*</code> and <code>logs-nginx.error.otel-*</code>, which come from the <code>filelog</code> receiver.</li>
<li>The <strong>metric-based</strong> templates (active connections, dropped connections) query <code>metrics-nginxreceiver.otel-*</code>, which comes from the <code>nginx</code> receiver.</li>
</ul>
<p>This is easy to get wrong: the Fleet <a href="https://www.elastic.co/docs/solutions/observability/infra-and-hosts/collect-nginx-data-otel-integration-fleet-managed"><strong>Nginx (OpenTelemetry)</strong> input package</a> collects <code>stub_status</code> metrics only. Its companion for logs is the classic Nginx integration, which writes ECS-based <code>nginx.access</code> and <code>nginx.error</code> datasets, not the <code>*.otel-*</code> data streams the log-based templates query. If you rely on that pairing alone, the log-based rules have nothing to evaluate and silently never fire. Run the <code>filelog</code> receiver too, not just the <code>nginx</code> receiver.</p>
<h2 id="step2installthenginxopentelemetryassetsintegration">Step 2: Install the NGINX OpenTelemetry Assets integration</h2>
<p>NGINX OpenTelemetry Assets is a content-only package. It ships the dashboards, alerting rule templates, and SLO templates, but it does not collect data itself. The data comes from the collector you set up in Step 1.</p>
<p>You don't need to install it by hand. Once the NGINX OTel data from Step 1 starts arriving, Elastic detects it and installs the Assets package for you, which takes a minute or two. Confirm it under <strong>Management</strong> &gt; <strong>Integrations</strong> &gt; <strong>Installed integrations</strong>, where <code>NGINX OpenTelemetry Assets</code> should appear.</p>
<h2 id="step3createelasticsearchalertrulesfromaruletemplate">Step 3: Create Elasticsearch alert rules from a rule template</h2>
<p>Open the integration and select the <strong>Alerting</strong> tab.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt06c9c544b653b81c/6a85c98d43c0b7f8172f05f0/02-alerting-tab.png" alt="The Alerting tab of the NGINX OpenTelemetry Assets integration listing its rule templates" /></p>
<p>This package ships six templates: high 4xx and 5xx error rates, high active connections, an error log spike, dropped connections, and a generic <code>High error rate by service</code> template that points at a placeholder <code>logs-myservicereceiver.otel-*</code> index for you to repoint and rename. The five NGINX rules run ES|QL every minute and group results by <code>host.name</code>, so an alert points at the host with the problem. The generic template groups by <code>service.name</code> instead, since it is meant to be repointed at whichever service you choose.</p>
<p>Select a template, for example <code>[Nginx OTel] High 5xx error rate</code>. Kibana opens a prefilled <strong>Create rule</strong> form built on an <a href="https://www.elastic.co/docs/explore-analyze/alerts-cases/alerts/rule-type-es-query">Elasticsearch query rule</a>. It runs the template's ES|QL on a schedule and alerts when the query returns rows.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2d9547363761de4b/6a85c991bc5bb36529f81acd/03-create-rule-form.png" alt="The prefilled Create rule form for the High 5xx error rate template" /></p>
<p>The query looks like this:</p>
<pre><code>FROM logs-nginx.access.otel-*
// Flag each access log entry as a server error (5xx) or not
| EVAL is_5xx = CASE(http.response.status_code &gt;= 500, 1, 0)
// Aggregate total requests and 5xx count per NGINX host
| STATS total = COUNT(*), errors_5xx = SUM(is_5xx) BY host.name
// Minimum sample size to avoid noisy low-traffic hosts
| WHERE total &gt; 50
// Calculate 5xx error rate as a percentage
| EVAL error_rate_pct = ROUND(TO_DOUBLE(errors_5xx) / TO_DOUBLE(total) * 100.0, 2)
// Alert threshold: adjust to tune sensitivity
| WHERE error_rate_pct &gt; 5.0
| SORT error_rate_pct DESC
| LIMIT 10
</code></pre>
<p>It counts requests and 5xx responses per host, keeps hosts with enough traffic to matter, and returns those above five percent.</p>
<p>Three things to get right while the form is open:</p>
<ul>
<li><strong>Send data first.</strong> ES|QL validates column names against the indices that exist when the query runs. Open a template before any NGINX data has been ingested and the editor reports <code>Unknown column "http.response.status_code"</code> and the form shows errors. Once data is flowing (Step 1), the same query validates and the error clears, so collect data before you create the rule.</li>
<li><strong>Set the time field to <code>@timestamp</code>.</strong></li>
<li><strong>Leave "Create an alert for each row" selected.</strong> Because the query groups by <code>host.name</code>, this makes every affected host raise its own alert.</li>
</ul>
<p>Add a <a href="https://www.elastic.co/docs/deploy-manage/manage-connectors">connector</a> and an action so the alert reaches Slack, email, or PagerDuty, then save and enable the rule.</p>
<h2 id="step4tunealertingruletemplatethresholdsinesql">Step 4: Tune alerting rule template thresholds in ES|QL</h2>
<p>The thresholds are starting points, so confirm them against your own traffic. The threshold lives in the ES|QL <code>WHERE</code> clause.</p>
<p>To make the rule stricter, change <code>error_rate_pct &gt; 5.0</code> to <code>error_rate_pct &gt; 2.0</code>. To require more traffic before it fires, raise <code>total &gt; 50</code>. Use <strong>Test query</strong> in the rule form to confirm the edited query parses and returns rows before you save.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8b66296ca6cd70c8/6a85c994bc5bb3f376f81ad1/04-test-query.png" alt="Test query results after editing the threshold in the ES|QL query" /></p>
<p>Three more settings are worth a look:</p>
<ul>
<li><strong>Time window</strong>: the look-back period the query runs over. A shorter window reacts faster but is noisier on bursty traffic.</li>
<li><strong>Rule schedule</strong>: how often the query runs, every minute by default.</li>
<li><strong>Alert delay</strong>: the number of consecutive runs the condition must hold before an alert is created, which filters out single-run blips.</li>
</ul>
<h2 id="howdoyoudetectidledatastreamsinelasticsearch">How do you detect idle data streams in Elasticsearch?</h2>
<p>Threshold rules only fire while data keeps arriving. When an agent goes offline or an output breaks, the data stops, and a threshold rule has nothing to evaluate.</p>
<p>Many Elastic integrations include a dynamically generated <a href="https://www.elastic.co/docs/reference/fleet/alerting-rule-templates">idle data streams template</a> for exactly this case. It is named <code>[{Integration name}] Idle data streams</code> and appears in the same Alerting tab, though it is generated automatically rather than bundled with the integration. It alerts when no data is written to any of the integration's data stream patterns within a set period.</p>
<p>The NGINX OpenTelemetry packages do not include an idle data streams template, which is why no such template appears in the Alerting tab from Step 3. The end of this section covers what to do instead.</p>
<p>The default period is 24 hours, which is usually too long. A production service can go quiet for most of a day before you hear about it.</p>
<p>When you create the rule, drop the period to match how fast you need to know. Fifteen minutes to one hour works for a critical service. For a batch job, set a period comfortably longer than its run interval, so the quiet gaps between runs do not trigger it.</p>
<p>So why is this example left out? The template is generated from the data stream patterns an integration defines, and it is not generated for input-only packages. A content-only package like NGINX OpenTelemetry Assets defines no data streams of its own either. To catch silence in a collector-based setup like this, recreate the rule by hand with an <a href="https://www.elastic.co/docs/explore-analyze/alerts-cases/alerts/rule-type-es-query">Elasticsearch query rule</a>. Use the query DSL or KQL variant rather than ES|QL, because an ES|QL rule fires on returned rows and so cannot alert on the <em>absence</em> of data. Point it at the OTel data streams (<code>logs-nginx.access.otel-*</code>, or <code>metrics-nginxreceiver.otel-*</code>) and set the condition to fire when the number of matching documents <strong>is below 1</strong> over a window of, say, the last 15 minutes. That reproduces what an idle data streams template does, scoped to the data streams your collector writes.</p>
<h2 id="getstartedwithelasticintegrationalertingruletemplates">Get started with Elastic integration alerting rule templates</h2>
<p>Alerting rule templates turn alert setup into a few steps: send data, install the integration, create a rule from a template, and adjust the threshold. Treat the bundled thresholds as defaults to confirm, not numbers to trust blindly. And where an idle data streams template is available, reduce its 24-hour default so you find out quickly when a service goes silent.</p>
<h2 id="resources">Resources</h2>
<ul>
<li><a href="https://github.com/Delacrobix/Creating-alerts-from-OOTB-alerting-template">Companion repository</a>, to generate the NGINX demo data used here</li>
<li><a href="https://www.elastic.co/docs/reference/fleet/alerting-rule-templates">Alerting rule templates</a></li>
<li><a href="https://www.elastic.co/docs/reference/integrations/nginx_otel">NGINX OpenTelemetry Assets integration</a></li>
<li><a href="https://www.elastic.co/observability-labs/blog/nginx-opentelemetry-end-to-end-tracing">End-to-end tracing for NGINX with OpenTelemetry</a></li>
<li><a href="https://www.elastic.co/docs/explore-analyze/alerts-cases/alerts/rule-type-es-query">Elasticsearch query rule</a></li>
<li><a href="https://www.elastic.co/docs/reference/fleet/alert-templates">Elastic Agent built-in alerts</a>, for monitoring the agents themselves</li>
</ul>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/alerting-rule-templates-elastic-integrations</link>
    <guid isPermaLink="false">alerting-rule-templates-elastic-integrations</guid>
    <category><![CDATA[Incident Management]]></category>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[Infrastructure Monitoring]]></category>
    <dc:creator><![CDATA[Jeffrey Rengifo]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt20e53c65a0321a28/6a85c997078290b06c321742/01-header.jpg" length="0" type="image/jpeg"/>
    <pubDate>Fri, 31 Jul 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[You have the IP, you want the hostname: building a lookup processor for OpenTelemetry]]></title>
    <description><![CDATA[Look up any value from YAML, CSV or DNS inside the OpenTelemetry Collector or wire in your own source through a processor Elastic built and shipped to Collector Contrib.]]></description>
    <content:encoded><![CDATA[<p>Enrichment is one of those tasks that sounds trivial until you try to do it inside a telemetry pipeline. You have a <code>user.id</code> on a log record and you want the <code>user.name</code>. You have a <code>client.ip</code> and you want the hostname behind it. Until now, the OpenTelemetry Collector had no general way to do this kind of lookup.</p>
<p>The closest option today is to hand-code the mapping in the transform processor:</p>
<pre><code># otel.yml
processors:
  transform:
    log_statements:
      - context: log
        statements:
          - set(attributes["user.name"], "Alice") where attributes["user.id"] == "user001"
          - set(attributes["user.name"], "Bob") where attributes["user.id"] == "user002"
          - set(attributes["user.name"], "Carol") where attributes["user.id"] == "user003"
          # ...and one more line for every user
</code></pre>
<p>That works for a handful of entries, but it does not scale beyond 10 to 20 items. Every new mapping means another statement, the lookup data lives in the same file as your pipeline config, and there is no way to point at reference data that already exists. It proves the need is real, but it only covers the simplest, smallest cases.</p>
<p>If you run the Collector and you have been reaching for the transform processor, a sidecar script, or a downstream ingest pipeline just to add reference data, this component is for you.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt19206d54078356b9/6a7f18df227b1c6a875989e5/cover.png" alt="Raw telemetry flowing through the lookup processor into enriched telemetry" /></p>
<h2 id="whytheopentelemetrycollectorneededalookupprocessor">Why the OpenTelemetry Collector needed a lookup processor</h2>
<p>The Collector was already good at a couple of enrichment patterns. The <a href="https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/processor/transformprocessor">transform processor</a> reshapes and derives data from what is already on a record. The <a href="https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/processor/k8sattributesprocessor">k8sattributes</a> and <a href="https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/processor/resourcedetectionprocessor">resourcedetection</a> processors attach system and environment metadata, like Kubernetes pod details or cloud host information.</p>
<p>What it could not do was look up related data by a key you already have. Three patterns in particular had no home:</p>
<ul>
<li><strong>File-based lookups</strong> from static reference data in JSON, YAML, or CSV</li>
<li><strong>HTTP and API-based enrichment</strong> from an external service</li>
<li><strong>DNS lookups</strong>, such as resolving an IP to a hostname</li>
</ul>
<p>These are everyday tasks in other data collectors and transformation tools. Mapping an internal service ID to a friendly name, attaching business metadata by customer ID, or resolving an IP all fall into this category. Without a native component, teams built brittle workarounds or pushed the work downstream where it is harder to reuse.</p>
<p>That gap is what the new <strong>lookup processor</strong> closes. Elastic's Data Processing team proposed it, the community accepted it, and it was built in partnership with Grafana, thanks to Sam DeHaan (<a href="https://github.com/dehaansa">GH: dehaansa</a>). The lookup processor takes a value from your telemetry, uses <a href="https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/pkg/ottl">OTTL</a> to build a lookup key, queries a source such as a YAML file or DNS, and writes the result back as new attributes.</p>
<h2 id="howtheopentelemetrylookupprocessorisdesigned">How the OpenTelemetry lookup processor is designed</h2>
<p>The design grew out of repeated requests from the community for richer enrichment. A few examples that fed into it:</p>
<ul>
<li><a href="https://github.com/open-telemetry/opentelemetry-collector-contrib/issues/40526">Enrich attributes based on key matching from YAML or CSV definition (#40526)</a></li>
<li><a href="https://github.com/open-telemetry/opentelemetry-collector-contrib/issues/40936">Enrich telemetry with resource metadata from an inventory datasource (#40936)</a></li>
<li><a href="https://github.com/open-telemetry/opentelemetry-collector-contrib/issues/29627">Generic resource detector (#29627)</a></li>
<li><a href="https://github.com/open-telemetry/opentelemetry-collector-contrib/issues/18526">Alert Manager receiver and exporter (#18526)</a></li>
<li><a href="https://github.com/open-telemetry/opentelemetry-collector-contrib/issues/20888">gRPC processor / connector (#20888)</a></li>
</ul>
<p>Rather than build a one-off component for each request, the goal was a single processor flexible enough to cover them. Four ideas shaped it:</p>
<ul>
<li><strong>Multiple lookups per processor</strong>, so one instance can enrich several attributes in a single pass.</li>
<li><strong>Caching</strong>, so external sources like DNS do not get queried for the same key over and over.</li>
<li><strong>OTTL for key extraction</strong>, so you get a full expression language for pulling the lookup key off a record, including converters.</li>
<li><strong>Extensible sources</strong>, so you are not limited to the built-in set. You can register a custom source for your own data.</li>
</ul>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc4ad99e402d4f0b6/6a7f18e2e02fac31ae5d6981/lookup-sources.png" alt="One lookup processor with pluggable sources: YAML, CSV, and DNS available today, HTTP and custom sources on the roadmap" /></p>
<p>Extensible sources matter most for the processor's long-term value. A source is a small, well-defined interface, so the processor is a foundation for enrichment rather than a fixed list of features. YAML and CSV cover static reference data today, DNS covers dynamic resolution, and the same interface leaves the door open for HTTP APIs, key-value stores, or anything specific to your environment.</p>
<h2 id="howthelookupprocessorevaluateskeyswithottl">How the lookup processor evaluates keys with OTTL</h2>
<p>Whatever source you configure, the processor runs the same three steps for every record.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4ca88a68dddd0e5f/6a7f18e5b6b734e1ace49198/lookup-processor-flow.png" alt="The lookup processor evaluates an OTTL key, queries a source, and writes the result back as attributes" /></p>
<p>First, it evaluates an OTTL expression to produce a lookup key from the record. Second, it hands that key to the configured source and gets a value back. Third, it writes the value to the attributes you name, on the record or on its parent resource. When a key has no match, the processor writes a configurable default so downstream queries stay predictable.</p>
<p>The next two sections walk through the two sources available today.</p>
<h2 id="filebasedlookupswithyamlintheopentelemetrycollector">File-based lookups with YAML in the OpenTelemetry Collector</h2>
<p>The most common case is static reference data. You keep a mapping file next to the Collector and enrich records against it. Here the processor reads a YAML file and adds <code>user.name</code> to each log based on its <code>user.id</code>.</p>
<pre><code># otel.yml
processors:
  lookup:
    source:
      type: yaml
      path: /etc/otel/mappings.yaml
    lookups:
      - key: log.attributes["user.id"]
        attributes:
          - destination: user.name
            default: "Unknown User"
</code></pre>
<p>The mapping file is a plain set of key-value pairs:</p>
<pre><code># /etc/otel/mappings.yaml
user001: "Alice"
user002: "Bob"
</code></pre>
<p>The <code>key</code> field is an OTTL value expression, so <code>log.attributes["user.id"]</code> reads the <code>user.id</code> attribute off the log record. The <code>destination</code> is where the looked-up value lands, and <code>default</code> is what gets written when the key is missing from the file.</p>
<p>Given this incoming log record:</p>
<pre><code>{
  "body": "User logged in",
  "attributes": {
    "user.id": "user001",
    "http.method": "POST"
  }
}
</code></pre>
<p>The processor looks up <code>user001</code>, finds <code>Alice</code>, and produces:</p>
<pre><code>{
  "body": "User logged in",
  "attributes": {
    "user.id": "user001",
    "user.name": "Alice",
    "http.method": "POST"
  }
}
</code></pre>
<p>The original attributes stay intact and the enriched value is added alongside them. The CSV source works the same way for teams that keep reference data in spreadsheets or exports rather than YAML.</p>
<h2 id="dnslookupenrichmentinsidetheopentelemetrycollector">DNS lookup enrichment inside the OpenTelemetry Collector</h2>
<p>Static files are one thing, but some enrichment requires live, changing data. Resolving an IP address to a hostname is the classic example, and it is the first dynamic source the processor supports. Instead of a file, you point it at a DNS server.</p>
<pre><code># otel.yml
processors:
  lookup:
    source:
      type: dns
    lookups:
      - key: log.attributes["client.ip"]
        attributes:
          - destination: client.hostname
            default: "Not found"
</code></pre>
<p>The shape of the config is identical to the YAML example. Only the source changed. The processor pulls <code>client.ip</code> off the record, asks a DNS resolver to reverse-resolve it, and writes the hostname back.</p>
<p>Given this incoming record:</p>
<pre><code>{
  "body": "User logged in",
  "attributes": {
    "client.ip": "8.8.8.8",
    "http.method": "POST"
  }
}
</code></pre>
<p>The DNS source resolves <code>8.8.8.8</code> and produces:</p>
<pre><code>{
  "body": "User logged in",
  "attributes": {
    "client.ip": "8.8.8.8",
    "client.hostname": "dns.google",
    "http.method": "POST"
  }
}
</code></pre>
<p>Because DNS queries hit an external system, this is where caching earns its place. The processor keeps results in an in-memory cache so a stream of records sharing the same IP does not turn into a stream of identical DNS queries. That keeps latency down and avoids hammering your resolver.</p>
<h2 id="writingacustomlookupsource">Writing a custom lookup source</h2>
<p>The built-in sources cover common cases, but the real design goal was extensibility. A source is a small contract: you implement a lookup function that takes a string key and returns a value. The processor takes care of OTTL key evaluation, caching, defaults, and writing attributes, so a custom source only has to answer the question "what value goes with this key?"</p>
<p>That means if you already run an internal metadata API, a Redis cache, or a custom database, you can wire it in as a source and reuse everything else the processor provides. HTTP-based sources and key-value stores are natural fits, and they are on the roadmap precisely because the interface makes them straightforward to add.</p>
<h2 id="howelasticplanstousethislookupprocessor">How Elastic plans to use this lookup processor</h2>
<p>Elastic builds its Collector distributions on OpenTelemetry Collector Contrib. The plan is to include lookup processor so the enrichment work teams do today with brittle workarounds can run inside the pipeline instead of downstream.</p>
<p>Two patterns stand out. The first is reference-data enrichment: turning an internal service ID into a friendly name, or attaching metadata such as a team or customer by ID, so telemetry arrives already labeled for search and correlation. The second is DNS resolution: turning a <code>client.ip</code> into a hostname before the data lands, which matters for network and security telemetry where you want the name rather than the raw address.</p>
<p>Doing this in the Collector keeps reference data close to where telemetry is processed and avoids duplicating the logic in separate ingest steps. As the source interface grows to cover HTTP APIs and key-value stores, the same processor can back richer enrichment without changing how pipelines are configured.</p>
<h2 id="lookupprocessorroadmapandhowtocontribute">Lookup processor roadmap and how to contribute</h2>
<p>The main implementation of the lookup processor has merged upstream into OpenTelemetry Collector Contrib. The <a href="https://github.com/open-telemetry/opentelemetry-collector-contrib/pull/45340">core processor and YAML source landed first</a>, and the DNS source followed as the first dynamic lookup.</p>
<p>There is a healthy backlog of work ahead, and contributions are welcome:</p>
<ul>
<li><strong>An HTTP lookup source</strong> for enrichment from external APIs.</li>
<li><strong>More DNS capabilities</strong>, including A and AAAA queries and support for multiple DNS servers.</li>
<li><strong>Component telemetry</strong>, so you can observe cache hit and miss rates, lookup latency, and error rates.</li>
<li><strong>Performance improvements</strong> as real-world usage grows.</li>
</ul>
<p>If you want to try it, add the processor to a Collector build that includes Contrib, point a YAML source at a mapping file, and enrich a real record. Then open an issue or pull request on the <a href="https://github.com/open-telemetry/opentelemetry-collector-contrib">OpenTelemetry Collector Contrib</a> repository. The component is community-owned, and the more sources and feedback it gets, the more useful it becomes.</p>
<p>To go deeper, read the <a href="https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/processor/lookupprocessor">lookup processor README</a> and the <a href="https://github.com/open-telemetry/opentelemetry-collector-contrib/issues/41816">enrichment tracking issue</a>. For more on how Elastic builds on OpenTelemetry, browse the <a href="https://www.elastic.co/observability-labs/blog/tag/opentelemetry">OpenTelemetry articles on Elastic Observability Labs</a>.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/opentelemetry-collector-lookup-processor</link>
    <guid isPermaLink="false">opentelemetry-collector-lookup-processor</guid>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[What's New]]></category>
    <dc:creator><![CDATA[Vihas Makwana]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte6f2a87fd8059793/6a7f18e8ead8ecb74abaac32/header.png" length="0" type="image/png"/>
    <pubDate>Thu, 30 Jul 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Three clicks from alert to error log: breaking down RED metrics by any span attribute in Elastic Observability]]></title>
    <description><![CDATA[See which pod, deployment or version is driving a RED metrics change by breaking down span attributes in Discover, then trace a failing span to the error log behind it.]]></description>
    <content:encoded><![CDATA[<p>Elastic Observability now lets you break down <a href="https://www.elastic.co/docs/solutions/observability/apm/metrics">RED metrics</a> in Discover by any span attribute on your traces. Split by pod, deployment, service version or any custom dimension to see which values moved the metric. From there, you can open a failing span's trace waterfall and follow it through to the linked error log in a few clicks, no query needed.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1cf0a6474d8514d8/6a7f1a2d96b5a66aff87b881/metric-drivers-2.gif" alt="Breaking down RED metrics by span attribute in Discover" /></p>
<p><strong>Availability</strong></p>
<p>This is available in serverless today and is coming to Elastic Cloud Hosted and self-managed deployments in 9.5.</p>
<h2 id="whatyouneedforredmetricsbreakdowninelasticobservability">What you need for RED metrics breakdown in Elastic Observability</h2>
<p>You need trace data from a service instrumented with any method <a href="https://www.elastic.co/docs/solutions/observability/apm/ingest">Elastic APM supports</a>.</p>
<ul>
<li><strong>Application instrumentation:</strong> one of the following:</li>
<li><strong><a href="https://www.elastic.co/docs/solutions/observability/apm/apm-agents">Elastic APM agents</a></strong> for Java, .NET, Node.js, Python, PHP, Ruby, Go, and other supported languages.</li>
<li><strong><a href="https://www.elastic.co/docs/solutions/observability/apm/opentelemetry">OpenTelemetry SDKs</a></strong> sending OTLP via Elastic Agent or an <a href="https://www.elastic.co/docs/solutions/observability/get-started/opentelemetry/use-cases/upstream-collector">upstream OpenTelemetry Collector</a> with the <a href="https://www.elastic.co/docs/reference/edot-collector/components/elasticapmconnector"><code>elasticapm</code> connector</a> under <strong>connectors</strong> (not processors).</li>
<li><strong>Useful attributes:</strong> breakdown works best when spans include the dimensions you want to compare (<code>k8s.pod.name</code>, <code>k8s.deployment.name</code>, <code>service.version</code>, and others). You can also declare custom attributes on spans: add <a href="https://www.elastic.co/docs/solutions/observability/apm/metadata">labels</a> to transactions and spans with Elastic APM agents, or set <a href="https://www.elastic.co/docs/solutions/observability/apm/opentelemetry/attributes">OpenTelemetry attributes</a> on spans and resources with OpenTelemetry SDKs. Those custom fields work as breakdown dimensions too.</li>
<li><strong>Backend:</strong> Observability serverless today, or Elastic Stack 9.5 on Elastic Cloud Hosted and self-managed when 9.5 releases.</li>
</ul>
<h2 id="howtogofromaredmetricsalerttotherootcauseerrorlog">How to go from a RED metrics alert to the root-cause error log</h2>
<h3 id="step1reviewredmetricsonthealertdetailpage">Step 1: Review RED metrics on the alert detail page</h3>
<p>When you receive a notification for a RED metric threshold breach, if you open the <a href="https://www.elastic.co/docs/solutions/observability/apm/create-apm-rules-alerts">alert detail page</a>, you can review the symptoms for the impacted service on one page.</p>
<p>In our example, failed transactions have clearly increased for the cart service, so we want to understand what is driving that RED metric change:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd60e8db89d43d27f/6a7f1a31eab5be222b20aaf4/metric-drivers-1.gif" alt="Alert detail showing RED symptoms for the cart service" /></p>
<h3 id="step2breakdownredmetricsbyspanattributesindiscover">Step 2: Break down RED metrics by span attributes in Discover</h3>
<p>To investigate why a RED metric changed, open <strong>Traces in Discover</strong> and use the new <strong>breakdown</strong> feature to split RED metrics by any attribute on your spans.
In our example, we're going to check Kubernetes attributes and service version, but you could break down by any span attribute you send (e.g. <code>cloud.region</code>, <code>cloud.availability_zone</code>, or <code>container.id</code>).</p>
<p>Each breakdown shows which attribute values moved the metric, so you can see whether the problem is isolated to one pod, deployment, version, or whatever dimension you split on.</p>
<p>In our example, error rate clusters on a single Kubernetes deployment, which points the investigation at a release. We will break down by <code>service.version</code> to validate our hypothesis:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1cf0a6474d8514d8/6a7f1a2d96b5a66aff87b881/metric-drivers-2.gif" alt="Breaking down RED metrics by span attribute in Discover" /></p>
<h3 id="step3openthetracewaterfallandreadthelinkederrorlog">Step 3: Open the trace waterfall and read the linked error log</h3>
<p>Once trace breakdown has identified a specific service version as the likely cause, we can filter by that <code>service.version</code> and look at sample failing spans to see if they explain why the version is causing failures.</p>
<p>Open the trace waterfall for one failing span and follow through to the linked error log.</p>
<p>In our example, the error log points to bad configuration that could be causing the issue. Either way, we have narrowed the investigation to a solid hypothesis we can act on in just a few clicks:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9ef23df1d803c870/6a7f1a356c6eac6b6ef14598/metric-drivers-3.gif" alt="Trace waterfall and error log for a sample failing span" /></p>
<h2 id="fromredmetricsalerttoerrorloginelasticobservability">From RED metrics alert to error log in Elastic Observability</h2>
<p>From a RED metric alert, you can review the symptomatic service, break down <strong>Traces</strong> in Discover by any attribute on your spans, and open a failing span's trace waterfall to reach the error log in just a few clicks.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/red-metrics-trace-breakdown-discover</link>
    <guid isPermaLink="false">red-metrics-trace-breakdown-discover</guid>
    <category><![CDATA[APM]]></category>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[What's New]]></category>
    <dc:creator><![CDATA[Roshan Gonsalkorale,Irene Blanco Fabregat]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte55244369ae6789c/6a7f1a3896b5a6329d87b885/header.jpg" length="0" type="image/jpeg"/>
    <pubDate>Mon, 27 Jul 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[From alert to failing dependency in four clicks: Elastic APM's embedded service map]]></title>
    <description><![CDATA[The APM service map is now embedded on alert pages with filters, connection metrics and a service fly-out so you can analyse dependencies and find the root cause without leaving the alert.]]></description>
    <content:encoded><![CDATA[<p>Elastic APM 9.5 embeds the <a href="https://www.elastic.co/docs/solutions/observability/apm/service-map">service map</a> on every alert detail page, in the APM UI and on Kibana dashboards.
Open a <a href="https://www.elastic.co/docs/solutions/observability/apm/metrics">RED metric</a> alert and start dependency analysis without leaving the page.
The map now includes text search, health filters, connection RED metrics with drill-through to Traces in Discover, and a service fly-out that previews any node's health right from the map.
This walkthrough uses the <a href="https://github.com/elastic/opentelemetry-demo">OpenTelemetry Demo</a> (Astronomy Shop) to go from a RED metric alert on a checkout service to a failing shipping dependency in four steps.</p>
<div>
    
</div>
<p><em>From a checkout RED metric alert to a failing shipping dependency on the APM service map (OpenTelemetry Demo).</em></p>
<h2 id="wheretheapmservicemapisavailable">Where the APM service map is available</h2>
<p>This is available in Elastic Observability serverless today and is coming to Elastic Cloud Hosted and self-managed deployments in 9.5.</p>
<h2 id="prerequisitesforapmservicemapdependencyanalysis">Prerequisites for APM service map dependency analysis</h2>
<p>You need trace data from services instrumented with any method <a href="https://www.elastic.co/docs/solutions/observability/apm/ingest">Elastic APM supports</a>.</p>
<ul>
<li><strong>Application instrumentation:</strong> one of the following:</li>
<li><strong><a href="https://www.elastic.co/docs/solutions/observability/apm/apm-agents">Elastic APM agents</a></strong> for Java, .NET, Node.js, Python, PHP, Ruby, Go, and other supported languages</li>
<li><strong><a href="https://www.elastic.co/docs/reference/opentelemetry">Elastic Distributions of OpenTelemetry (EDOT)</a></strong> language SDKs</li>
<li><strong><a href="https://www.elastic.co/docs/solutions/observability/apm/opentelemetry">OpenTelemetry SDKs</a></strong> sending OTLP via the EDOT Collector, Elastic Agent, APM Server, or the Managed OTLP endpoint.
If you run a custom upstream Collector pipeline, include both the <a href="https://www.elastic.co/docs/reference/edot-collector/components/elasticapmconnector"><code>elasticapm</code> connector</a> and the <a href="https://www.elastic.co/docs/reference/edot-collector/components/elasticapmprocessor"><code>elasticapm</code> processor</a>.
Those components ship with the EDOT Collector (or a custom EDOT-like build); they are not part of the standard OpenTelemetry Collector Contrib distribution.
For wiring details, see the <a href="https://www.elastic.co/docs/solutions/observability/get-started/opentelemetry/use-cases/upstream-collector">upstream collector setup</a>.</li>
<li><strong>Service map data:</strong> distributed traces that link the services in your architecture. The map draws connections from span parent-child relationships across instrumented services.</li>
<li><strong>Backend:</strong> Elastic Observability serverless today, or Elastic Stack 9.5 on Elastic Cloud Hosted and self-managed when 9.5 releases.</li>
</ul>
<h2 id="dependencyanalysiswalkthroughfromapmalerttoshippingdependency">Dependency analysis walkthrough: from APM alert to shipping dependency</h2>
<h3 id="step1apmservicemaponthealertdetailpage">Step 1: APM service map on the alert detail page</h3>
<p>When you receive a notification for a <a href="https://www.elastic.co/docs/solutions/observability/apm/create-apm-rules-alerts">RED metric threshold breach</a> on the checkout service, open the alert detail page.</p>
<p>The upgraded <a href="https://www.elastic.co/docs/solutions/observability/apm/service-map">service map</a> is embedded on the page, so you can start analysing dependencies the moment you land on the alert. You see checkout in context with its upstream and downstream connections without navigating away.</p>
<p>In our example, failed transactions have increased on checkout. The map is already scoped to the alert time range:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt38b23ac64f528167/6a7f1a956693f8002f664383/depedencies-walkthrough-1.gif" alt="RED metric alert for checkout with embedded service map" /></p>
<h3 id="step2spottingafailingdependencyontheservicemap">Step 2: Spotting a failing dependency on the service map</h3>
<p>When we scan through the downstream dependencies, we can see some indicators that there is a problem with the <strong>checkout</strong> and <strong>shipping</strong> services. When we open the shipping service fly-out to view more details, we can see the transactions for <code>/get-quote</code> have an elevated failure rate:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte13f5353240dc962/6a7f1a99e88c65206c00bb04/depedencies-walkthrough-2.gif" alt="Identifying shipping as the failing downstream dependency on the service map" /></p>
<h3 id="step3drillingintofailingtransactionstoidentifyredmetricdrivers">Step 3: Drilling into failing transactions to identify RED metric drivers</h3>
<p>Click on the <code>/get-quote</code> transaction to drill into these transactions. We can see a release marker for version <code>2.3.0</code> that seems to correlate to increase in failures. To validate this is the main contributing factor, we want to analyse these transactions and check a few dimensions to isolate the behaviour to the release:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltfa07c9992b1e7ba0/6a7f1a9cea068d80f9f0a2d1/depedencies-walkthrough-3.gif" alt="Service fly-out preview for shipping with RED metrics and transaction breakdown" /></p>
<h3 id="step4comparingredmetricsbydimensionintracesindiscover">Step 4: Comparing RED metrics by dimension in Traces in Discover</h3>
<p>Using Traces in Discover, we can use the <strong>breakdown</strong> feature to compare various attributes to confirm the RED metric change is due to the release. As we break down by <code>service.version</code>, <code>k8s.deployment.name</code> and <code>k8s.pod.name</code>, we can see the failure rate is highly elevated for just this Kubernetes deployment.</p>
<p>These field names match the OpenTelemetry semantic conventions used by the demo (EDOT / OTel instrumentation). If you instrument with classic Elastic APM agents, use the ECS-style <code>kubernetes.*</code> equivalents instead (see <a href="https://www.elastic.co/docs/reference/ecs/ecs-otel-alignment-details">ECS ↔ OTel field alignment</a>).</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0de3780b0fc2841b/6a7f1aa03cab1cd5d60e4ca1/depedencies-walkthrough-4.gif" alt="Service fly-out preview for shipping with RED metrics and transaction breakdown" /></p>
<p>Given we have clear indicators that this problem is almost certainly due to a problem caused by this Kubernetes deployment, we would next investigate the changes this deployment made so we can identify a mitigation strategy to return the system to a healthy status, most likely a rollback of the Kubernetes deployment.</p>
<h2 id="dependencyanalysiswiththeservicemaponcustomdashboards">Dependency analysis with the service map on custom dashboards</h2>
<p>The APM service map can also be added as a panel to custom Kibana dashboards.
If you have a custom Dashboard you would like to surface for a debugging problem with your service, you can <a href="https://www.elastic.co/docs/solutions/observability/incident-management/create-manage-rules#observability-create-manage-rules-add-investigation-resources">attach it to your alert rule</a> so users can use this to complement the default alert detail view.</p>
<p>When you receive the notification and land on the alert detail page, you can click <strong>Related Dashboards</strong> and open your custom dashboard. From here, you can use the same Service Map panel to quickly analyse dependencies just like the alert detail page:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltab1edeb5a3eadb02/6a7f1aa3de231529c6fd80a1/depedencies-variant-dashboards.gif" alt="Related dashboard with service map panel" /></p>
<h2 id="whatsnewintheapmservicemap">What's new in the APM service map</h2>
<p>Below is a list of the upgrades to the service map:</p>
<h3 id="servicemapembeddedonapmalertpagesanddashboards">Service map embedded on APM alert pages and dashboards</h3>
<ul>
<li>All APM alert detail pages will have the service map embedded to facilitate faster dependency analysis.</li>
<li>There is a new <strong>Service Map</strong> Dashboard panel that you can add to any Dashboard.</li>
</ul>
<h3 id="fullscreenservicemapforlargearchitectures">Full-screen service map for large architectures</h3>
<p>Open the map in full screen from the APM UI or from a dashboard panel. Full screen gives you more room on large architectures and works the same whether you opened the map from an alert, a service, or a dashboard.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltcad0dd14b294d020/6a7f1aa72f00b21ad9efef29/depedencies-fullscreen.gif" alt="Full screen service map mode" /></p>
<h3 id="servicemapcontrolssearchfiltersandorientation">Service map controls: search, filters, and orientation</h3>
<p>On the service map in the APM UI and on dashboard embeds, the map toolbar adds controls to move faster on busy environments:</p>
<ul>
<li><strong>Search</strong> — find services by text string</li>
</ul>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt22ba0bee13abadc2/6a7f1aaab4377067e84d710a/depedencies-quickfilter.gif" alt="Service map search, orientation, and filter controls" /></p>
<ul>
<li><strong>Orientation</strong> — switch layout direction when dense graphs are hard to read</li>
</ul>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2a5aae3c8b2aa685/6a7f1aad42a1179b7495c30b/depedencies-orientation.gif" alt="Service map search, orientation, and filter controls" /></p>
<ul>
<li><strong>Filters</strong> — narrow the map by alert status, whether a service has dependencies, anomaly status, and SLO status</li>
</ul>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd7a696138886669a/6a7f1ab1c2cc0937d52499be/depedencies-quickfilters.gif" alt="Service map search, orientation, and filter controls" /></p>
<h3 id="servicemaplegendnodeshapesconnectionsandanomalyscores">Service map legend: node shapes, connections, and anomaly scores</h3>
<p>A built-in legend explains node shapes, connection styles, and health indicators on the map. You spend less time decoding colours and icons when you are triaging under pressure.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1a46daf232cc305b/6a7f1ab5ea068d282bf0a2d5/depedencies-legend.png" alt="Service map with legend open" /></p>
<p>The legend covers node shapes (instrumented services, databases and messaging, grouped resources), connection styles (one-way and two-way requests), and anomaly score colour rings from low through critical:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6430ac89390b5e54/6a7f1ab805b7b5b71e18bd49/depedencies-legend-detail.png" alt="Service map legend detail: node shapes, connections, and anomaly scores" /></p>
<h3 id="minimapfornavigatinglargeservicemaps">Minimap for navigating large service maps</h3>
<p>A minimap helps you orient yourself on large maps. It also highlights anomalous services at a glance, so you can spot outliers without panning across the full graph.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8711d8dc8fa6ac00/6a7f1abc6693f8048e664389/depedencies-minimap.gif" alt="Service map minimap showing anomalous services" /></p>
<h3 id="redmetricsonconnectionswithdrillthroughtotracesindiscover">RED metrics on connections with drill-through to Traces in Discover</h3>
<p>Select a connection between two services to view <a href="https://www.elastic.co/docs/solutions/observability/apm/metrics">RED metrics</a> for the requests between them: rate, errors, and duration for that specific dependency edge.</p>
<p>From there, open <strong>Traces in Discover</strong> in one click to analyse those requests with full query flexibility. That is how we confirmed shipping was failing the requests checkout sent to it in the walkthrough above.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1997328c2e5e1f37/6a7f1abf1967ea8534330b80/depedencies-edgeanalysis.gif" alt="Connection RED metrics with one-click to Traces in Discover" /></p>
<h3 id="addingtheservicemaptoadashboardfromtheapmui">Adding the service map to a dashboard from the APM UI</h3>
<p>From the service map in the APM UI, add the current map view to a dashboard in one click. You do not need to rebuild filters or time range settings manually when you want the same map on a team dashboard.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1c6854adfac03760/6a7f1ac33cab1c1f090e4ca7/depedencies-add-to-dashboard.gif" alt="One-click add service map to dashboard from APM UI" /></p>
<h2 id="serviceflyoutpreviewredmetricsanomaliesandslosfromthemap">Service fly-out: preview RED metrics, anomalies and SLOs from the map</h2>
<p>Wherever you view the service map in Kibana, you can open a service fly-out to preview a service without leaving the map.</p>
<p>The fly-out shows:</p>
<ul>
<li><strong>RED metrics</strong> for the service</li>
<li><strong>Anomaly status</strong> — whether machine learning has flagged unusual behaviour</li>
<li><strong>SLO status</strong> — whether the service is meeting its objectives</li>
<li><strong>Transaction breakdown</strong> — how rate, errors, and duration split across transaction types</li>
</ul>
<p>Use it to sanity-check a node before you open the full service page or follow a connection into <strong>Traces in Discover</strong>.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd1844d9ad0008151/6a7f1ac6ead8ec2693baac5a/depedencies-fly-out.gif" alt="Service fly-out with RED metrics, anomaly and SLO status, and transaction breakdown" /></p>
<h2 id="summaryfromapmalerttorootcausewiththeservicemap">Summary: From APM alert to root cause with the service map</h2>
<p>From a RED metric alert on checkout, the embedded service map showed shipping as the failing downstream dependency. Map controls, connection RED metrics, dashboard embeds, and the service fly-out are available on every map view in Kibana, so you can start dependency analysis wherever you already work.</p>
<h2 id="furtherreading">Further Reading</h2>
<p>You can see how we implemented these changes in the following post from Jenny Pavlova, who led the technical implementation:</p>
<p><a href="https://ela.st/9-5-apm-service-map-update">6x faster at 500 services: how we rebuilt the Kibana APM service map from canvas to React DOM</a></p>
<h3 id="relatedposts">Related Posts</h3>
<p>See other recent improvements we've made for observing instrumented services in the blogs below:</p>
<ul>
<li><a href="https://ela.st/9-5-infrastructure-metric-analysis-instrumented-services">Four clicks from alert to root cause: how Elastic Observability links APM services to Kubernetes infrastructure</a></li>
<li><a href="https://ela.st/9-5-faster-slo-burn-rate-analysis-instrumented-services">Your SLO is on fire; here's how to find the arsonist in Elastic Observability</a></li>
<li><a href="https://ela.st/9-5-analyse-red-metric-drivers">Three clicks from alert to error log: breaking down RED metrics by any span attribute in Elastic Observability</a></li>
</ul>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/service-map-apm-dependency-analysis</link>
    <guid isPermaLink="false">service-map-apm-dependency-analysis</guid>
    <category><![CDATA[APM]]></category>
    <category><![CDATA[OpenTelemetry]]></category>
    <dc:creator><![CDATA[Roshan Gonsalkorale,Jenny Pavlova,Karolina Kurstak]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltcb9c625d49e1272d/6a7f1ac9e88c656f9800bb0c/depedencies-header.png" length="0" type="image/png"/>
    <pubDate>Mon, 27 Jul 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Four clicks from alert to root cause: how Elastic Observability links APM services to Kubernetes infrastructure]]></title>
    <description><![CDATA[Check service dependencies and compare per-pod CPU, memory and network trends on the Infrastructure tab to find which instance is causing trouble, all without leaving the alert investigation.]]></description>
    <content:encoded><![CDATA[<p>Elastic Observability links your <a href="https://www.elastic.co/docs/solutions/observability/apm/opentelemetry">OTel-instrumented services</a> to the Kubernetes hosts, containers, and pods they run on.
The <a href="https://www.elastic.co/docs/solutions/observability/apm/infrastructure">Infrastructure tab</a> in APM puts per-instance CPU, memory and network trends a few clicks away, so when a service degrades you can spot which pod lines up with when the problem started, all from inside the investigation.
This walkthrough follows a latency alert on a recommendation service from notification to the problematic pod in four steps.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte83736c1f42a7f15/6a7f02af2f00b296c3efe6ef/step-04-check-infra-metric-trends.gif" alt="Correlating service latency with per-pod infrastructure metrics" /></p>
<h2 id="availability">Availability</h2>
<p>This is available in Elastic Observability serverless today and is coming to Elastic Cloud Hosted and self-managed deployments in 9.5.</p>
<h2 id="prerequisitesforlinkingapmservicestokubernetesinfrastructure">Prerequisites for linking APM services to Kubernetes infrastructure</h2>
<p>You need application traces and Kubernetes infrastructure metrics in the same Elastic Observability project.</p>
<ul>
<li><strong>Application instrumentation:</strong> EDOT-instrumented services sending traces via the EDOT Collector or an <a href="https://www.elastic.co/docs/solutions/observability/get-started/opentelemetry/use-cases/upstream-collector">upstream OpenTelemetry Collector</a> with both the <a href="https://www.elastic.co/docs/reference/edot-collector/components/elasticapmconnector"><code>elasticapm</code> connector</a> and the <a href="https://www.elastic.co/docs/reference/edot-collector/components/elasticapmprocessor"><code>elasticapm</code> processor</a>. The EDOT Collector includes both by default; for a custom upstream pipeline, see the <a href="https://www.elastic.co/docs/solutions/observability/get-started/opentelemetry/use-cases/upstream-collector">upstream collector setup</a>.</li>
<li><strong>Kubernetes observation:</strong> the cluster observed via OpenTelemetry with host and Kubernetes metrics from the EDOT Collector. See the <a href="https://www.elastic.co/docs/solutions/observability/get-started/opentelemetry/quickstart/serverless/k8s">Kubernetes quickstarts</a> and <a href="https://www.elastic.co/docs/solutions/observability/get-started/opentelemetry/use-cases/kubernetes">Kubernetes observability with EDOT</a> for setup.</li>
<li><strong>Backend:</strong> Observability serverless today, or Elastic Stack 9.5 on Elastic Cloud Hosted and self-managed when 9.5 releases.</li>
</ul>
<h2 id="apmalerttriagefromnotificationtoproblematicpod">APM alert triage: from notification to problematic pod</h2>
<h3 id="step1confirmtheservicedegradationontheapmalertdetailpage">Step 1: Confirm the service degradation on the APM alert detail page</h3>
<p>The redesigned <a href="https://www.elastic.co/docs/solutions/observability/apm/create-apm-rules-alerts">alert detail page</a> in Elastic Observability shows the impacted service, environment, endpoint and RED metrics in one view.
Open it from the alert notification.</p>
<p>You can clearly see which service is impacted, which environment it runs in, what endpoint is being affected and easily look for correlations in their RED metrics.
In this case, we can immediately rule out a spike in traffic as the throughput is clearly stable.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9ee2e802b0b09fc9/6a7f02b34c4bfbf920ccd0fd/step-01-alert-detail.gif" alt="Alert showing high transaction latency on the recommendation service" /></p>
<h3 id="step2ruleoutservicedependencieswiththeembeddedservicemap">Step 2: Rule out service dependencies with the embedded service map</h3>
<p>The newly embedded <a href="https://www.elastic.co/docs/solutions/observability/apm/service-map">service map</a> preview on the alert detail page shows the health and RED metrics of every dependent service, so you can rule out upstream causes without navigating away.
In this case, we have been able to quickly rule out problems with other services causing the symptom with the symptomatic service:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1711cc95500177fb/6a7f02b7448e4e15195c0268/step-02-check-dependencies.gif" alt="Service map showing healthy dependent services" /></p>
<h3 id="step3reviewkubernetesinfrastructuremetricsperpodcontainerandhost">Step 3: Review Kubernetes infrastructure metrics per pod, container and host</h3>
<p>After ruling out service dependencies, open the service's updated <a href="https://www.elastic.co/docs/solutions/observability/apm/infrastructure"><strong>Infrastructure</strong> tab</a> in Elastic Observability to check for infrastructure-level patterns.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta5526498737af6af/6a7f02ba227b1cf310598174/step-03-view-service-check-infra.gif" alt="Infrastructure tab showing average metrics per instance for the symptomatic service" /></p>
<h3 id="step4compareperinstancemetrictrendstofindtherootcause">Step 4: Compare per-instance metric trends to find the root cause</h3>
<p>The <a href="https://www.elastic.co/docs/solutions/observability/apm/infrastructure">Infrastructure tab</a> shows the average metric values over the specified time period.
To really understand whether there is a problem with the infrastructure, we need to <strong>compare the pod, container and host metrics over time</strong>.
This allows us to easily spot differences between different entities that may correlate with when the service started showing symptoms.
In our example, we can clearly see a difference between some of the metrics between the pods that correlates with when the service symptoms began.
So we know there is something going on with the infrastructure that needs investigating:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte83736c1f42a7f15/6a7f02af2f00b296c3efe6ef/step-04-check-infra-metric-trends.gif" alt="Infrastructure metric trends correlating latency with a change in CPU or network" /></p>
<h2 id="summaryfromapmalerttorootcauseinfourclicks">Summary: from APM alert to root cause in four clicks</h2>
<p>In just a few clicks from an alert in Elastic Observability, you can rule out healthy dependent services without leaving the alert detail page, then compare per-pod infrastructure metrics to see which instance correlates with when the symptoms started.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/apm-kubernetes-infrastructure-metrics-analysis</link>
    <guid isPermaLink="false">apm-kubernetes-infrastructure-metrics-analysis</guid>
    <category><![CDATA[APM]]></category>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[Kubernetes]]></category>
    <dc:creator><![CDATA[Roshan Gonsalkorale,Miguel Sánchez Gómez]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta9d1c405e47bfbf5/6a7f02beeab5be600a20a278/header.jpg" length="0" type="image/jpeg"/>
    <pubDate>Mon, 27 Jul 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[How Elastic cut OpenTelemetry tail sampling memory by 65% with disk-backed trace storage]]></title>
    <description><![CDATA[Elastic contributed two features upstream to the OTel Collector's tail sampling processor. The span-ingest strategy lets sampling decisions happen earlier, and Pebble tail storage moves trace buffering to disk. It costs more CPU, but operators can raise decision_wait and num_traces without OOM kills.]]></description>
    <content:encoded><![CDATA[<p>Elastic contributed two upstream improvements to the OpenTelemetry Collector's tail sampling processor (<a href="https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/processor/tailsamplingprocessor"><code>tailsamplingprocessor</code></a>) that cut memory usage by up to 65%.
<code>sampling_strategy: span-ingest</code> lets sampling decisions happen at ingest time, releasing traces before <code>decision_wait</code> elapses.
<a href="https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/extension/tailstorage/pebbletailstorageextension"><code>pebbletailstorageextension</code></a> moves trace buffering to a Pebble LSM database on disk, so storage scales with disk capacity instead of RAM. That means operators can increase <code>decision_wait</code> and <code>num_traces</code> without OOM kills. The cost is roughly 2x CPU.</p>
<h2 id="whatistailsampling">What is Tail Sampling?</h2>
<p>Distributed tracing is useful for debugging, but at production scale it comes with processing overhead and storage costs, at which point sampling becomes a natural way to maintain the value of tracing while keeping costs under control. Tail-based sampling, or tail sampling, is a technique that makes a sampling decision conditionally at a later stage, so that high-value traces like errors or slow transactions are more likely to be sampled. The opposite is head sampling, which makes the decision at the start of a trace, before any such information is available.</p>
<h2 id="howdoesthetailsamplingprocessorwork">How does the tail sampling processor work?</h2>
<p>The tail sampling processor buffers 100% of incoming traces (or spans, used interchangeably), then forwards the sampled subset after applying the sampling policies.
Buffering is a major source of memory usage, and it scales proportionally to the volume of spans, a well known pain point in the community.</p>
<p>Memory usage is bounded by configuration parameters like <code>decision_wait</code> and <code>num_traces</code>.
Setting <code>decision_wait</code> to 1 minute means a sampling decision is made for a trace after 1 minute, during which all spans for that trace are expected to have arrived.
If a trace is slower than 1 minute, the decision is made with some spans missing.</p>
<p>As a side note, scaling out the tail sampling setup involves using the <a href="https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/exporter/loadbalancingexporter"><code>loadbalancingexporter</code></a> to satisfy the requirement that all spans for a trace must be routed to the same collector.
This introduces some operational complexity and potentially data loss during collector restarts.
But this post focuses on the memory usage of a single tail sampling processor instance, regardless of horizontal scaling.</p>
<h2 id="whydoestailsamplingcausememorypressure">Why does tail sampling cause memory pressure?</h2>
<p>These parameters introduce a tradeoff between data loss and memory usage, and they require assumptions about the shape of traces: how slow they can be, how many spans they contain, how large each span is. These assumptions can become stale as instrumentation evolves.</p>
<p>How much data loss is acceptable to limit memory usage, and can the tradeoff be improved? The following two contributions aim to give operators more flexibility.</p>
<h2 id="howspaningestreducestailsamplingmemorybyreleasingspansearly">How span-ingest reduces tail sampling memory by releasing spans early</h2>
<p><code>sampling_strategy</code> is a new configuration option added to the tail sampling processor in <a href="https://github.com/open-telemetry/opentelemetry-collector-contrib/releases/tag/v0.149.0"><code>v0.149.0</code></a>.</p>
<p><code>sampling_strategy</code> defaults to <code>trace-complete</code>, which matches the original behavior: sampling policies are only evaluated when <code>decision_wait</code> has elapsed, at which point the trace is considered complete.
(There is a similar config, <code>decision_wait_after_root_received</code>, for optimization, but it is excluded from this discussion for simplicity.)
This means all spans are buffered in memory for roughly <code>decision_wait</code> before being released, regardless of whether a decision could have been made earlier.
For example, health check spans that should always be dropped are still held in memory until policy evaluation time.</p>
<p>Alternatively, <code>sampling_strategy</code> can be set to <code>span-ingest</code>, where spans are evaluated individually at ingest time.
This allows terminal decisions, specifically <code>drop</code> or <code>sampled</code>, to be made earlier, freeing memory by dropping or exporting all spans buffered so far for that trace before <code>decision_wait</code> elapses.
In the health check example, a policy can be configured to drop the entire trace as soon as the root span belongs to a health check.
It is worth noting that an <code>unsampled</code> decision, unlike an explicit <code>drop</code>, is not terminal, as it can be overruled by a <code>sampled</code> or <code>drop</code> decision from another span in the same trace, so <code>unsampled</code> traces cannot be released early.</p>
<p>Switching from <code>trace-complete</code> to <code>span-ingest</code> will require policy adjustments, as policies can no longer assume all spans are available at evaluation time.
Moreover, not all policy types are supported with the <code>span-ingest</code> strategy.</p>
<h2 id="diskbackedtailsamplingstoragewithpebble">Disk-backed tail sampling storage with Pebble</h2>
<p>Even with <code>span-ingest</code>, all spans are still buffered in memory.
As <code>decision_wait</code> is increased to accommodate slow traces and <code>num_traces</code> is increased to limit data loss, the collector will eventually hit its memory limit and get OOM killed, resulting in further data loss.</p>
<p>What if traces were buffered on disk instead, where there is an order of magnitude more capacity?
The main drawback is performance: disk throughput and latency, even with SSDs, are at least an order of magnitude slower than memory, so disk writes need to be efficient.
For this reason, <a href="https://github.com/cockroachdb/pebble"><code>Pebble</code></a>, an LSM database, was chosen as the storage backend for its fast write performance.
Read performance is less of a concern, as reads only happen for the sampled subset of traces when <code>sampling_strategy</code> is set to <code>span-ingest</code>.</p>
<p>The implementation introduces a <code>TailStorage</code> interface for trace storage operations, and a new <code>tail_storage</code> option in <a href="https://github.com/open-telemetry/opentelemetry-collector-contrib/releases/tag/v0.150.0"><code>v0.150.0</code></a> (behind feature gate <code>processor.tailsamplingprocessor.tailstorageextension</code>) to configure the storage backend.
The default in-memory behavior is unchanged, but it is now possible to swap in a different storage backend, like the new <a href="https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/extension/tailstorage/pebbletailstorageextension"><code>pebbletailstorageextension</code></a> contributed to Collector Contrib.</p>
<h2 id="tailsamplingmemorybenchmarkstracecompletevsspaningestwithpebble">Tail sampling memory benchmarks: trace-complete vs span-ingest with Pebble</h2>
<h3 id="benchmarksetupopentelemetrydemowithfanoutcollectors">Benchmark setup: OpenTelemetry Demo with fan-out collectors</h3>
<p>The following benchmarks were produced by running <a href="https://github.com/open-telemetry/opentelemetry-demo"><code>OpenTelemetry Demo</code></a> with increased load against a pipe collector, which receives all spans and fans them out to two identical collectors under observation (<code>CUO-A</code> and <code>CUO-B</code>), differing only in their tail sampling configuration.
Measurements include pipe collector throughput, spans received, spans sent (sampled), CPU usage, and memory usage.</p>
<h3 id="benchmarksetupdiagram">Benchmark setup diagram</h3>
<pre><code>                      demo ns
     +----------------------------------------+
     |  opentelemetry-demo                     |
     |    loadgenerator (locust)               |
     |    services: frontend, cart, ...        |
     |    demo-collector                       |
     +----------------------------------------+
                          |  OTLP/gRPC
                          v
                     chamber ns
     +----------------------------------------+
     |             pipe-collector             |
     |         receive once, fan out          |
     |      exporters: [otlp/a, otlp/b]       |
     +----------------------------------------+
              | OTLP                  | OTLP
              v                       v
     +----------------+      +----------------+
     |     CUO-A      |      |     CUO-B      |
     | tail_sampling  |      | tail_sampling  |
     |   (config A)   |      |   (config B)   |
     +----------------+      +----------------+
</code></pre>
<h3 id="tailsamplingprocessorconfigurations">Tail sampling processor configurations</h3>
<h4 id="cuoa">CUO-A</h4>
<pre><code>config:
  processors:
    tail_sampling:
      sampling_strategy: trace-complete
      decision_wait: 5m
      num_traces: 5000000
      block_on_overflow: true
      decision_cache:
        sampled_cache_size: 10000
        non_sampled_cache_size: 200000
      policies:
        - name: root_1pct
          type: and
          and:
            and_sub_policy:
              - name: root_span_only
                type: ottl_condition
                ottl_condition:
                  error_mode: ignore
                  span:
                    - "IsRootSpan()"
              - name: root_probabilistic
                type: probabilistic
                probabilistic:
                  sampling_percentage: 1.0
</code></pre>
<h4 id="cuob">CUO-B</h4>
<p><code>CUO-B</code> uses the same tail sampling processor configuration as <code>CUO-A</code>, except it sets <code>sampling_strategy: span-ingest</code> and <code>tail_storage: pebble_tail_storage/main</code>, along with its corresponding <a href="https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/extension/tailstorage/pebbletailstorageextension"><code>pebbletailstorageextension</code></a> configuration.</p>
<pre><code>extensions:
  pebble_tail_storage/main:
    directory: /var/lib/otelcol/pebble
</code></pre>
<h3 id="memorycpuandthroughputresults">Memory, CPU and throughput results</h3>
<p>The following tables compare trace-complete (CUO-A) against span-ingest with Pebble disk storage (CUO-B) across memory, CPU and throughput.
The process RSS, Go heap allocation, and per-process CPU measurements come from OpenTelemetry Collector internal process and runtime metrics, while container working set and container CPU come from Kubernetes cgroup metrics scraped by kubelet/cAdvisor.</p>
<h4 id="memorypeakoverthewindow">Memory (peak over the window)</h4>
<p>| Metric | cuo-a | cuo-b | Δ (B vs A) |
| --- | ---: | ---: | ---: |
| process RSS | 916.4 MiB | 442.7 MiB | -51.7% |
| Go heap alloc | 699.3 MiB | 241.7 MiB | -65.4% |
| container working set | 763.0 MiB | 282.9 MiB | -62.9% |</p>
<h4 id="cputotaloverthewindow">CPU (total over the window)</h4>
<p>| Metric | cuo-a | cuo-b | Δ (B vs A) |
| --- | ---: | ---: | ---: |
| per-process CPU | 11.9 core-s | 22.7 core-s | +90.7% |
| container CPU | 11.9 core-s | 22.7 core-s | +90.1% |</p>
<h4 id="throughputtotaloverthewindow">Throughput (total over the window)</h4>
<p>| Metric | cuo-a | cuo-b | Δ (B vs A) |
| --- | ---: | ---: | ---: |
| spans received | 257,804 | 257,804 | 0.0% |
| spans sent | 2,477 | 2,477 | 0.0% |</p>
<h4 id="tailsampling">Tail sampling</h4>
<p>| Metric | cuo-a | cuo-b | Δ (B vs A) |
| --- | ---: | ---: | ---: |
| traces in memory peak | 29,284 | 29,245 | -0.1% |
| traces sampled by root_1pct policy | 496 | 496 | 0.0% |</p>
<ul>
<li><code>cuo-a</code> = <code>trace-complete</code>, <code>cuo-b</code> = <code>span-ingest-pebble</code></li>
<li>Window: 15m 39s (<code>t+0:00</code> start, <code>t+10:06</code> drain start, <code>t+15:39</code> drain end)</li>
</ul>
<p>The results show a significant memory reduction when using <a href="https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/extension/tailstorage/pebbletailstorageextension"><code>pebbletailstorageextension</code></a> with <code>span-ingest</code>, at the cost of increased CPU usage from event serialization and database overhead.</p>
<h2 id="whatsnextforopentelemetrytailsampling">What's next for OpenTelemetry tail sampling</h2>
<p>Both <code>sampling_strategy</code> and <a href="https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/extension/tailstorage/pebbletailstorageextension"><code>pebbletailstorageextension</code></a> are still in their early stages at the time of writing.
Feedback and contributions are welcome in the OpenTelemetry Collector Contrib repo.
Stay tuned for more improvements.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/tail-sampling-memory-opentelemetry</link>
    <guid isPermaLink="false">tail-sampling-memory-opentelemetry</guid>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[APM]]></category>
    <category><![CDATA[What's New]]></category>
    <dc:creator><![CDATA[Carson Ip]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blteb738eca25e4e5c5/6a7f1b746693f828d066439f/header.png" length="0" type="image/png"/>
    <pubDate>Tue, 21 Jul 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Common ES|QL queries for Kubernetes monitoring]]></title>
    <description><![CDATA[Copy-paste ES|QL queries for Elasticsearch that turn memory pressure and error spikes into a five-minute diagnosis.]]></description>
    <content:encoded><![CDATA[<p>This post has nine ES|QL queries for diagnosing Kubernetes problems in Elasticsearch. They cover crash-looping pods, memory pressure before an OOM kill, saturated nodes, and error spikes by namespace. Every query runs against Kubernetes data collected with the <a href="https://www.elastic.co/docs/reference/opentelemetry">Elastic Distributions of OpenTelemetry (EDOT)</a> and pastes into Discover with little to no editing. Turn any of them into a dashboard panel or an alert once you've found the one you need. Jump straight to the query that matches what you're seeing or read through the whole set to get a feel for your cluster.</p>
<h2 id="whatyouneedbeforerunningthesekubernetesesqlqueries">What you need before running these Kubernetes ES|QL queries</h2>
<p>To follow the queries in this article, you need:</p>
<ul>
<li>Elasticsearch 9.2 or later.</li>
<li>EDOT Collectors running in your cluster and shipping data to Elasticsearch,
with the <code>kubeletstats</code>, <code>k8s_cluster</code>, and <code>filelog</code> receivers enabled.</li>
</ul>
<h2 id="whyesqlforkubernetesmonitoring">Why ES|QL for Kubernetes monitoring</h2>
<p>Elasticsearch <a href="https://www.elastic.co/docs/reference/query-languages/esql">ES|QL</a> is a piped query language that lets you start from a data source and then add one operation per line: filter, compute, aggregate, sort. That structure fits investigation work well because you refine a query step by step as you narrow down a problem.</p>
<h2 id="whentousetheesqltscommandforkubernetesmetrics">When to use the ES|QL TS command for Kubernetes metrics</h2>
<p>The <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/ts"><code>TS</code></a> command understands time series. This matters because most Kubernetes metrics are counters or gauges sampled over time. Using <code>TS</code> for metrics avoids the common mistake of summing cumulative counters across pods and getting a meaningless number.</p>
<h2 id="edotreceiversthesekubernetesqueriesdependon">EDOT receivers these Kubernetes queries depend on</h2>
<p>The queries below assume EDOT Collectors are running in your cluster and shipping data to Elasticsearch. A typical setup uses:</p>
<ul>
<li>The <code>kubeletstats</code> receiver for pod, container, and node resource metrics.</li>
<li>The <code>k8s_cluster</code> receiver for object state such as pod phase and container restarts.</li>
<li>The <code>filelog</code> receiver with the <code>k8sattributes</code> processor for container logs.</li>
</ul>
<p>Resource attributes follow the <a href="https://opentelemetry.io/docs/specs/semconv/">OpenTelemetry semantic conventions</a>. Pod, namespace, and node identifiers appear as <code>k8s.pod.name</code>, <code>k8s.namespace.name</code>, and <code>k8s.node.name</code>. Metric names are defined by the receiver that emits them, not by the semconv spec itself: <code>k8s.pod.phase</code> comes from the <code>k8s_cluster</code> receiver, while utilization metrics like <code>k8s.container.memory_limit_utilization</code> come from <code>kubeletstats</code>. EDOT preserves all of these names natively in Elasticsearch. The exact fields you have depend on which receivers you enabled, so treat the queries as templates and adjust field names if a metric is missing.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3633a3879fe6bdc6/6a85c83b99083f767840f971/image2.jpg" alt="Kubernetes cluster overview" /></p>
<h2 id="exploringthecluster">Exploring the cluster</h2>
<p>Start broad. Before investigating a specific symptom, it helps to see what the cluster looks like in your data.</p>
<p>This query counts the pods reporting metrics in each namespace over the past hour.</p>
<pre><code>FROM metrics-*
| WHERE @timestamp &gt;= NOW() - 1 hour
| STATS pod_count = COUNT_DISTINCT(k8s.pod.name) BY k8s.namespace.name
| SORT pod_count DESC
</code></pre>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt808ee0ccf116be22/6a85c83df5f1a039cd2ec873/image3.jpg" alt="Pod count by namespace" /></p>
<p><code>COUNT_DISTINCT</code> collapses the many metric samples per pod into a single count per namespace. The result is a quick inventory: which namespaces are busy and whether anything you expected to be running is missing.</p>
<h2 id="findingpodrestartsandcrashloops">Finding pod restarts and crash loops</h2>
<p>Restarts are usually the first signal that something is wrong. The <code>k8s.container.restarts</code> metric is a gauge that reports the current restart count for each container.</p>
<p>This query surfaces the containers that have restarted the most in the last 24 hours.</p>
<pre><code>FROM metrics-*
| WHERE @timestamp &gt;= NOW() - 24 hours AND k8s.container.restarts IS NOT NULL
| STATS restarts = MAX(k8s.container.restarts)
    BY k8s.namespace.name, k8s.pod.name, k8s.container.name
| WHERE restarts &gt; 0
| SORT restarts DESC
| LIMIT 20
</code></pre>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1a4a354576a92c07/6a85c840982926cb3e583888/image4.jpg" alt="Container restarts leaderboard" /></p>
<p><code>MAX</code> takes the highest restart count seen in the window, which reflects the latest value of the gauge. A container with a high and climbing restart count is the textbook sign of a crash loop. Once you have the pod name, you can pivot straight to its logs with the queries further down.</p>
<h2 id="spottingpodsthatarenotrunning">Spotting pods that are not running</h2>
<p>A restart count tells you a pod recovered. The pod phase tells you whether it is healthy right now. The <code>k8s.pod.phase</code> metric encodes the phase as a number:</p>
<p>| Value | Phase |
| :---- | :---- |
| 1 | Pending |
| 2 | Running |
| 3 | Succeeded |
| 4 | Failed |
| 5 | Unknown |</p>
<p>This query uses <code>TS</code> to read the latest phase per pod and keeps anything that is not Running.</p>
<pre><code>TS metrics-*
| WHERE TRANGE(15m)
| STATS phase = MAX(LAST_OVER_TIME(k8s.pod.phase))
    BY k8s.namespace.name, k8s.pod.name
| WHERE phase != 2
| EVAL phase_name = CASE(
    phase == 1, "Pending",
    phase == 3, "Succeeded",
    phase == 4, "Failed",
    phase == 5, "Unknown",
    "Other")
| SORT phase_name
</code></pre>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc48536c7b187dae4/6a85c84333f244bcc149f492/image5.jpg" alt="Non-running pods" /></p>
<p><code>LAST_OVER_TIME</code> picks the most recent sample for each pod's time series, so you compare the current state rather than an average. Pods stuck in <code>Pending</code> often point to scheduling problems, such as insufficient CPU or memory on the nodes. Pods in <code>Failed</code> or <code>Unknown</code> are worth an immediate look.</p>
<h2 id="catchingmemorypressurebeforetheoomkill">Catching memory pressure before the OOM kill</h2>
<p>Out-of-memory kills are one of the most common Kubernetes failures, and they are easier to prevent than to debug after the fact. When you enable limit metadata on the <code>kubeletstats</code> receiver, EDOT reports <code>k8s.container.memory_limit_utilization</code> as a fraction between 0 and 1 of the container's memory limit.</p>
<p>This query finds containers that ran close to their limit in the last hour.</p>
<pre><code>TS metrics-*
| WHERE TRANGE(1h)
| STATS peak_mem_pct = MAX(MAX_OVER_TIME(k8s.container.memory_limit_utilization))
    BY k8s.namespace.name, k8s.pod.name, k8s.container.name
| EVAL peak_mem_pct = ROUND(peak_mem_pct * 100, 1)
| WHERE peak_mem_pct &gt; 85
| SORT peak_mem_pct DESC
</code></pre>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt525f72c7f235f1d5/6a85c84680984ca3b5668f7a/image1.jpg" alt="Memory pressure near limit" /></p>
<p><code>MAX_OVER_TIME</code> finds the peak within each container's series, and the outer <code>MAX</code> keeps that peak per container. A container that repeatedly touches 95% or higher is a strong candidate for the next OOM kill. Pair this with the restart query above: a container with both a rising restart count and high memory utilization was very likely OOM killed.</p>
<h2 id="trackingcpuusageandnodepressure">Tracking CPU usage and node pressure</h2>
<p>CPU problems show up as throttling and slow response times rather than crashes. The <code>k8s.pod.cpu.node.utilization</code> metric reports each pod's CPU use as a fraction of total node capacity.</p>
<p>This query charts the busiest pods over the last hour in five-minute buckets.</p>
<pre><code>TS metrics-*
  | WHERE TRANGE(1h)
  | STATS avg_cpu = AVG(AVG_OVER_TIME(k8s.pod.cpu.node.utilization))
      BY k8s.pod.name, TBUCKET(5m)
  | SORT avg_cpu DESC
</code></pre>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3c8cf93267186f11/6a85c84999083fa62340f975/image6.jpg" alt="CPU usage by pod over time" /></p>
<p><code>AVG_OVER_TIME</code> averages the samples inside each pod's series for the bucket, and the outer <code>AVG</code> combines series that share a pod name. <code>TBUCKET(5m)</code> produces one point every five minutes, which renders cleanly as a time series chart.</p>
<p>To check whether the nodes themselves are saturated, query node utilization directly.</p>
<pre><code>TS metrics-*
  | WHERE TRANGE(1h)
  | STATS cpu = AVG(AVG_OVER_TIME(k8s.node.cpu.usage)),
          mem = AVG(LAST_OVER_TIME(k8s.node.memory.usage))
      BY k8s.node.name, TBUCKET(5m)
  | SORT cpu DESC
</code></pre>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltcaac108bf0586043/6a85c84c501a852e4efbb2b4/image7.jpg" alt="Node CPU and memory utilization" /></p>
<p>A node sitting near full CPU explains throttled pods across many namespaces at once, which is easy to misread as an application bug when you only look at a single pod.</p>
<h2 id="investigatingcontainerlogs">Investigating container logs</h2>
<p>Once metrics point you at a pod, logs explain what it was doing. EDOT stores the log message in <code>body.text</code> and the level in <code>severity_text</code>, alongside the same <code>k8s.*</code> fields as the metrics.</p>
<p>This query ranks namespaces and pods by error volume in the last hour.</p>
<pre><code>FROM logs-*
| WHERE @timestamp &gt;= NOW() - 1 hour
  AND severity_text IN ("ERROR", "FATAL")
| STATS errors = COUNT(*)
    BY k8s.namespace.name, k8s.pod.name
| SORT errors DESC
| LIMIT 20
</code></pre>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt042eff9ad8d18180/6a85c84ee2447a79218b13a4/image8.jpg" alt="Error count by pod" /></p>
<p>Counting by pod tells you whether errors are concentrated in one workload or spread across the cluster. A single noisy pod and a cluster-wide spike call for very different responses.</p>
<p>To read what a specific pod is logging, filter by pod name and search the message text.</p>
<pre><code>FROM logs-*
| WHERE @timestamp &gt;= NOW() - 1 hour
  AND k8s.pod.name == "checkout-&lt;your-hash&gt;"
  AND body.text LIKE "*timeout*"
| KEEP @timestamp, severity_text, body.text
| SORT @timestamp DESC
| LIMIT 50
</code></pre>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt158bdcfec830c991/6a85c85133f2443e7649f49a/image9.jpg" alt="Pod log messages filtered by keyword" /></p>
<p><code>LIKE "*timeout*"</code> does a simple wildcard match on the message. For full-text relevance instead of wildcards, swap it for <a href="https://www.elastic.co/docs/reference/query-languages/esql/functions-operators/search-functions"><code>MATCH(body.text, "timeout")</code></a>.</p>
<h2 id="chainingesqlqueriestoinvestigateakubernetesincident">Chaining ES|QL queries to investigate a Kubernetes incident</h2>
<p>Real Kubernetes investigations chain multiple ES|QL queries together instead of running one in isolation.</p>
<p>A useful loop looks like this:</p>
<ol>
<li>Count errors by pod to find the noisy workload.</li>
<li>Check that pod's restart count and memory utilization to see if it is crashing or starved.</li>
<li>Read its recent logs to find the specific failure.</li>
</ol>
<p>Because every query uses the same <code>k8s.namespace.name</code> and <code>k8s.pod.name</code> fields, you can carry a pod name straight from one query to the next. The same fields let you build a single dashboard where a metrics panel and a logs panel filter together as you click through namespaces.</p>
<h2 id="turningqueriesintoalertsanddashboards">Turning queries into alerts and dashboards</h2>
<p>Any ES|QL query in this post that produces an aggregated value can back an alert, not just support ad hoc investigation.</p>
<p>For example, the restart query becomes an alert when you keep only containers above a threshold and trigger on a non-empty result.</p>
<pre><code>FROM metrics-*
| WHERE @timestamp &gt;= NOW() - 15 minutes AND k8s.container.restarts IS NOT NULL
| STATS restarts = MAX(k8s.container.restarts)
    BY k8s.namespace.name, k8s.pod.name, k8s.container.name
| WHERE restarts &gt;= 5
</code></pre>
<p>Wire this into an <a href="https://www.elastic.co/docs/explore-analyze/alerts-cases/alerts/rule-type-es-query">Elasticsearch query rule</a> and you get notified the moment a container crosses five restarts in fifteen minutes, instead of finding out when a user does. The same pattern applies to memory utilization, node saturation, and error counts.</p>
<h2 id="buildingakubernetesmonitoringtoolkitwithesql">Building a Kubernetes monitoring toolkit with ES|QL</h2>
<p>ES|QL gives you one language for every Kubernetes signal, from object state to resource metrics to container logs. Start with the exploration query to understand your cluster's shape, then keep the restart, phase, memory, CPU, and log queries close for the next incident. Use <code>TS</code> when you need time series functions like <code>MAX_OVER_TIME</code> or <code>TBUCKET</code> to aggregate correctly within each pod or container series. For counting distinct values or taking a simple MAX on a gauge, <code>FROM</code> is enough.</p>
<p>From here, you can adapt the field names to your own receivers, save the most useful queries as dashboard panels, and promote the critical ones to alerts.</p>
<p>To go deeper, see the <a href="https://www.elastic.co/docs/reference/query-languages/esql">ES|QL reference</a>, the <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/ts"><code>TS</code> command documentation</a>, and the <a href="https://www.elastic.co/docs/reference/opentelemetry/use-cases/kubernetes">EDOT Kubernetes guide</a>.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/esql-kubernetes-monitoring</link>
    <guid isPermaLink="false">esql-kubernetes-monitoring</guid>
    <category><![CDATA[Kubernetes]]></category>
    <category><![CDATA[OpenTelemetry]]></category>
    <dc:creator><![CDATA[Jeffrey Rengifo]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd8fedad7d2a4e61e/6a85c854d6cf29b0cabb089c/cover.png" length="0" type="image/png"/>
    <pubDate>Wed, 08 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[Elasticsearch: best-in-class for logs, now best-in-class for metrics]]></title>
    <description><![CDATA[Elasticsearch is now best-in-class for metrics: 30× faster than Prometheus, up to 2.5× more storage-efficient, 50% less than Datadog. Learn about all the capabilities we’ve added.]]></description>
    <content:encoded><![CDATA[<p>Over the past few months, Elastic has shipped a columnar storage engine in Elasticsearch purpose-built for time series data, native Prometheus ingest and storage, PromQL support and we’ve delivered a new metrics exploration experience, pre-built infrastructure dashboards, agentic investigation, and a migration path from Datadog and Grafana. Capabilities now include:</p>
<ul>
<li><p>Elasticsearch is a Prometheus-compatible metrics backend — <a href="https://www.elastic.co/observability-labs/blog/prometheus-remote-write-elasticsearch">Prometheus Remote Write</a> and <a href="https://www.elastic.co/observability-labs/blog/elasticsearch-supports-promql">PromQL now works natively in Kibana</a>, no translation layer required.</p></li>
<li><p>Metrics land in <a href="https://www.elastic.co/search-labs/blog/elasticsearch-metrics-columnar-engine">Elasticsearch's columnar TSDS architecture</a> storing data up to <a href="https://www.elastic.co/observability-labs/blog/elasticsearch-columnar-metrics-engine-30x-faster-prometheus">2.5× more efficient than Prometheus</a> and 2× more efficient than ClickHouse.</p></li>
<li><p>ES|QL time series queries run <a href="https://www.elastic.co/observability-labs/blog/elasticsearch-columnar-metrics-engine-30x-faster-prometheus">up to 30× faster than Prometheus</a> on gauge averages and counter rates, including high-cardinality workloads.</p></li>
<li><p><a href="https://www.elastic.co/blog/metrics-pricing">Elastic costs approximately 50% less than Datadog</a>, with no custom metric classification and no cardinality-based billing.</p></li>
<li><p>Grafana can query Elasticsearch directly through the <a href="https://www.elastic.co/observability-labs/blog/elasticsearch-native-prometheus-api">native Prometheus API</a>, keeping your visualization layer while replacing the backend.</p></li>
<li><p><a href="https://www.elastic.co/observability-labs/blog/eks-agent-builder-mcp-kubernetes-troubleshooting">Kubernetes</a> and AWS monitoring ship with pre-built dashboards, alert templates, ML anomaly jobs, and agentic investigation content ready at ingest. Additionally <a href="https://github.com/elastic/agent-skills/tree/main/plugins/observability">skills</a> and <a href="https://www.elastic.co/observability-labs/blog/ai-powered-kubernetes-observability-elastic-mcp">MCP apps</a> are available.</p></li>
<li><p>Unified backend for Metrics, logs, and traces enabling <a href="https://www.elastic.co/observability-labs/blog/ai-powered-kubernetes-observability-elastic-mcp">agentic investigations</a> without stitching context across tools.</p></li>
<li><p>Metrics exploration in Discover lets anyone start querying and analyzing metrics immediately, no query language expertise required.</p></li>
<li><p>Custom dashboarding is fast and flexible — dashboards-as-code, AI-assisted dashboard creation, variable controls, and collapsible panels mean less time building and more time investigating.</p></li>
<li><p>Migration tooling to help easily migrate dashboards and alerting rules / monitors from Datadog and Grafana.</p></li>
</ul>
<p>Elasticsearch metrics now competes on every dimension that matters to SREs: you can afford to keep every metric at full resolution, query it up to 30x faster than Prometheus, pay 50% less than Datadog, migrate dashboards and alerting rules from Grafana or Datadog easily, and go from alert to root cause without stitching context across disconnected tools. The rest of this post walks through each of these in detail.</p>
<h2 id="elasticsearchmetricsperformance30fasterthanprometheusandmimir">Elasticsearch metrics performance: 30× faster than Prometheus and Mimir</h2>
<p>Datadog and Prometheus force the same tradeoff: drop high-cardinality data or watch costs spiral. SREs managing Kubernetes, AWS, or any high-cardinality infrastructure know the specific shape of this problem. The Kubernetes labels, ephemeral pod data, and fine-grained OTel dimensions that matter most during an incident are the first to go when budgets tighten.</p>
<p>Elastic rebuilt the time series data store and ES|QL compute engine into a fully columnar metrics engine. Adding a new Kubernetes label, a new AWS instance tag, or a new application dimension doesn't strain the system; it adds far less cost than systems that index every label. OTel, Prometheus, and application-defined metrics all land in the same columnar backend at full resolution, with logs, traces, and metrics in a single store. No data dropped, no retention shortened.</p>
<p>Elasticsearch stores metrics up to 2.5× more efficiently than Prometheus (results may differ due to factors like compaction), and 2× more efficiently than ClickHouse. Query performance via ES|QL runs <a href="https://www.elastic.co/observability-labs/blog/elasticsearch-columnar-metrics-engine-30x-faster-prometheus">up to 30× faster than Prometheus</a> on gauge averages and counter rates, including high-cardinality workloads where competitors stall. The <a href="https://www.elastic.co/observability-labs/blog/prometheus-remote-write-elasticsearch-architecture">architecture post</a> covers how TSDS is organized and why the columnar layout produces these results.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1fc77441a43b2a65/6a7f19c1e88c65894500baf4/promql.png" alt="PromQL" /></p>
<p>|                            |                    |                  |                    |
| :------------------------: | :----------------: | :--------------: | :----------------: |
|        <strong>Dimension</strong>       | <strong>vs. Prometheus</strong> |   <strong>vs. Mimir</strong>  | <strong>vs. ClickHouse</strong> |
| Query performance (ES|QL) |  Up to 30× faster  | Up to 30× faster |   Up to 8× faster  |
|     Storage efficiency     |  Up to 2.5× better |      On par      |      2× better     |</p>
<p>The key architectural difference is that Elasticsearch metrics does not maintain a per-series in-memory state that scales with cardinality, so adding thousands of new Kubernetes pod labels or OTel dimensions doesn't drive up memory pressure.</p>
<p>OTel, Prometheus-native, and application-defined metrics are all stored the same way at full resolution, queried fast, at half the cost of Datadog.</p>
<h2 id="elasticobservabilitymetricspricingwithoutthedatadogcustommetricpenalties">Elastic Observability metrics pricing without the Datadog custom metric penalties</h2>
<p>Observability cost is the #1 reason teams switch platforms. For Datadog customers, the pain comes down to one pricing mechanic: custom metrics. Any user-defined value outside of Datadog's built-in integrations is classified as a custom metric and billed at a premium rate. That includes the high-cardinality data that Kubernetes, OpenTelemetry, and cloud-native workloads generate by default. The more granular your instrumentation, the faster the bill compounds. Teams running modern infrastructure hit this ceiling quickly, and the response is predictable: drop data, shorten retention, lose the context that matters most when an incident happens.</p>
<p>Elasticsearch metrics removes that classification. Every metric is priced the same, with no per-metric penalties, no cardinality-based billing, and no forced rollups. You keep every metric at full resolution without a surprise invoice at the end of the month. And because Elastic is 50% the cost of Datadog, the conversation with finance changes: not what data you had to drop to stay on budget, but what you found because you kept everything. It's also why the AI investigation works. Unlike Grafana's fragmented LGTM stack, the context is already unified when the alert fires, not assembled by hand across disconnected tools.</p>
<h2 id="nativeprometheusandpromqlsupportinelasticsearch">Native Prometheus and PromQL support in Elasticsearch</h2>
<p>Most SRE teams aren't running a clean, single-format telemetry pipeline. Prometheus is deeply embedded in applications, services, platforms, and automations. Migrating metrics backends historically meant rewriting queries, rebuilding dashboards, and retraining engineers — enough friction that teams stay on platforms they've outgrown rather than go through it.</p>
<p>Elasticsearch metrics has removed most of that friction. Prometheus metrics arrive via <a href="https://www.elastic.co/observability-labs/blog/prometheus-remote-write-elasticsearch">Prometheus Remote Write</a> and land in the same columnar store without semantic changes, preserving full metric fidelity end to end. Point them at Elasticsearch instead of Mimir and the data flows. No translation layer, no changes to existing scrape configs.</p>
<p><a href="https://www.elastic.co/observability-labs/blog/elasticsearch-supports-promql">PromQL now works natively in Kibana</a>, so engineers who live in PromQL don't have to change how they work. Existing PromQL queries, dashboards, and alert rules migrate into Kibana directly. </p>
<p><strong>PromQL queries work unchanged on Elasticsearch</strong></p>
<p>If your team already writes PromQL, nothing needs to change. These queries run as-is against Elasticsearch as your backend — copy, paste, and go.</p>
<p><strong>CPU usage rate (container-level)</strong> The per-second CPU rate across containers, grouped by pod. Useful for spotting which pods are burning CPU during an incident.</p>
<pre><code>PROMQL sum by (pod) (rate(container_cpu_usage_seconds_total[5m]))
</code></pre>
<p><strong>Memory working set (container-level)</strong> Current memory in active use per container — the number that matters for OOM risk, not total allocated memory.</p>
<pre><code>PROMQL sum by (container) (avg_over_time(container_memory_working_set_bytes[5m]))
</code></pre>
<p><strong>HTTP request rate (application-level)</strong> Per-second request throughput grouped by instance. A standard first signal when investigating latency or error spikes.</p>
<pre><code>PROMQL sum by (instance) (rate(http_requests_total[5m]))
</code></pre>
<p>All three follow standard PromQL syntax. If you use Elasticsearch as your backend, they run without modification. For the full syntax reference and what's covered, see the<a href="https://www.elastic.co/docs/reference/query-languages/promql"> PromQL support documentation</a>.</p>
<p>The <a href="https://www.elastic.co/observability-labs/blog/elasticsearch-native-prometheus-api">native Prometheus API</a> makes Elasticsearch a fully Prometheus-compatible backend. Any Prometheus-compatible frontend (Grafana included) can query Elasticsearch directly, so teams that want to keep Grafana as their visualization layer while consolidating onto Elasticsearch can do exactly that without modifying existing dashboards or alert rules.</p>
<p>When SREs need to go deeper than PromQL allows, <a href="https://www.elastic.co/observability-labs/blog/esql-ts-command-querying-metrics">ES|QL</a> works across metrics, logs, and traces in a single interface. The <code>TS</code> command handles the time series specifics: counter rates, gauge averages, window functions, and multilevel aggregations across high-cardinality dimensions. The same query that pulls a CPU counter rate can join against logs from the same host and surface the deployment event that preceded the spike. No tool switching, no new query language. The query language, the dashboards, the alert rules, the visualization layer — all of it carries over. The only thing that changes is that Elasticsearch is the single backend powering everything.</p>
<h2 id="elasticobservabilityoutoftheboxdashboardsalertsandinfrastructurecontent">Elastic Observability: out-of-the-box dashboards, alerts, and infrastructure content</h2>
<p>Most Observability vendors require you to build everything from scratch. Elastic Observability has reduced this need across three areas:</p>
<p><strong>Metrics exploration in Discover.</strong> The <a href="https://www.elastic.co/observability-labs/blog/exploring-metrics-new-data-source-discover">new Elasticsearch metrics exploration experience</a> lets SREs explore metrics in the same interface used for logs — no tab switching, no duplicate queries. Connect an OTel pipeline or Prometheus scrape config, open Streams, and every metric in the data stream renders as a time series chart immediately. No dashboard to build, no query to write. This is where teams can validate data, spot patterns, and start building alerts and SLOs from a live view of what's flowing and cross correlate with logs, traces and other indexed data in Elasticsearch.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt82233276f588b597/6a7f19c5bd21984d9475849b/ts-metrics.png" alt="Metrics Exploration" /></p>
<p><strong>Dashboards.</strong> Kibana dashboards have gained collapsible panels with lazy loading, so panels that aren't immediately visible don't generate queries until they're needed and ES|QL control variables that let SREs manipulate visualizations through dropdowns without writing new queries. Dashboards-as-code is also shipping, enabling version-controlled dashboard definitions that can be templated, shared, and deployed programmatically across environments.</p>
<p><strong>Out-of-the-box infrastructure content.</strong>  Elastic is shipping with two new infrastructure OOTB experiences:</p>
<ul>
<li>The <a href="https://www.elastic.co/observability-labs/blog/kubernetes-dashboards-alerts-anomaly-detection">new Kubernetes integration</a> ships with hierarchical dashboards, alert rule templates, ML anomaly detection jobs, and the context and prompts needed for AI-assisted root cause analysis — all pre-configured and ready the moment data starts flowing. </li>
</ul>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt27a0aea38d8a3800/6a7f19c8c2e91457c0016fe0/k8s-dashboard.png" alt="Kubernetes Integration" /></p>
<ul>
<li>AWS infrastructure monitoring follows the same pattern: OOTB content for core AWS services activates at ingest, so teams aren't starting from scratch every time a new service or account comes online. The same approach extends to databases and other core infrastructure — the platform arrives opinionated, not blank.</li>
</ul>
<h2 id="agenticinvestigationsacrossyourinfrastructurewithelasticobservability">Agentic investigations across your infrastructure with Elastic Observability</h2>
<p>Elasticsearch correlates metrics, logs, and traces in a single backend, so the investigation context is assembled before an engineer is paged.</p>
<p>The hard part is 2am. An RDS instance hitting connection limits, starving services upstream. An Auto Scaling group failing health checks for a reason buried in application logs. A pod restart cascading across a namespace.</p>
<p>In a Grafana LGTM stack, you're opening three tabs before you have enough context to form a hypothesis.</p>
<p>In Datadog, the context is unified but the AI is a black box: no BYO-LLM, no data residency options.</p>
<p>In Elastic, metrics, logs, and traces share a single backend and a common schema, so the investigation context is already assembled when the alert fires — no manual correlation across tools, no context lost in translation between query languages. ML anomaly detection runs automatically against infrastructure metrics (Kubernetes, AWS, databases), so the investigation starts from a scored anomaly with context about what's typical, what changed, and how severe the deviation is, not just a raw threshold breach.</p>
<p>When an alert fires, Elastic's investigation workflow correlates signals, assembles root cause context, and surfaces recommended next steps before anyone is paged. The <a href="https://www.elastic.co/observability-labs/blog/ai-powered-kubernetes-observability-elastic-mcp">agentic Kubernetes observability post</a> walks through a complete example end to end. The <a href="https://www.elastic.co/observability-labs/blog/eks-agent-builder-mcp-kubernetes-troubleshooting">EKS troubleshooting walkthrough</a> shows how Agent Builder and MCP work together for a full root cause loop across EC2, EKS, and related AWS services.</p>
<p>In addition to investigating issues in Elastic Observability, you can use Claude, Cursor, VS Code, or your favorite tool to analyze issues using MCP Apps and agent skills from Elastic. The Observability MCP App extends the analysis to wherever your team already works. If your team investigates in Claude, Cursor, or VS Code, the same investigation capabilities (infrastructure health rollup, service dependency graph, anomaly detail, blast radius analysis) render as interactive views directly in the conversation. Neither Grafana nor Datadog offer this.</p>
<ul>
<li><strong>Observability MCP App</strong> — Connects Claude, Cursor, VS Code, or any MCP-compatible tool directly to your Elasticsearch data, so infrastructure health, service dependencies, and anomaly context surface as interactive views inside the conversation without leaving your tool of choice.<a href="https://www.elastic.co/observability-labs/blog/ai-powered-kubernetes-observability-elastic-mcp"> See how it works with Kubernetes.</a></li>
</ul>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd67fe754bc52576b/6a7f19cb3ce8e2b5e5cf5799/mcp-app.png" alt="Observability MCP App" /></p>
<ul>
<li><strong>Agent Skills</strong> — Pre-built skills for Kubernetes, AWS, and other core infrastructure let any agent — in Elastic or your own — run structured investigations against your observability data without custom prompt engineering. Drop them into Claude, Cursor, or your own agent pipeline and they work out of the box.<a href="https://www.elastic.co/observability-labs/blog/elastic-agent-skills-observability-workflows"> Explore the observability skills</a> or<a href="https://github.com/elastic/agent-skills/tree/main/plugins/observability"> browse the skills library on GitHub.</a></li>
</ul>
<h2 id="migratingfromdatadogorgrafanatoelasticobservability">Migrating from Datadog or Grafana to Elastic Observability</h2>
<p>The most common reason SRE teams don't switch observability platforms is migration. Moving years of alert rules, hundreds of dashboards, and runbook-embedded PromQL queries is a daunting operational task, and the cost of maintaining parallel stacks while doing it compounds every day.</p>
<p>The <a href="https://www.elastic.co/observability-labs/blog/migrate-datadog-grafana-dashboards-alerts-to-kibana">Observability Migration Platform</a> handles the translation automatically. Point the CLI or Claude/Cursor (with Elastic’s agent skills) at your Datadog org or Grafana instance and it converts supported dashboards, alert rules, and PromQL queries into Kibana-native outputs. The tool allows you to see what was fully migrated, what needed tweaks and what is needed from you to migrate everything. You move what you've already built.</p>
<p>On the ingest side, <a href="https://www.elastic.co/observability-labs/blog/prometheus-remote-write-elasticsearch">Prometheus Remote Write</a> means the pipeline requires no changes. Scrape configs point to Elasticsearch instead of another Prometheus-compatible backend and the data lands in the same columnar store. Workflows, queries, and alert configurations carry over without change. For teams that want to keep Grafana as a visualization layer during or after migration, the native Prometheus API and PromQL support in Kibana mean the transition can be phased rather than cut over all at once.</p>
<p><strong>Elasticsearch as a backend for Grafana</strong></p>
<p>For teams not ready to leave Grafana, replacing the backend is a migration path in its own right, and there are two ways to do it depending on your workflow.</p>
<p>If your team runs Prometheus today, the lowest-friction path is Grafana's <strong>Prometheus data source</strong>. Elasticsearch now exposes a native Prometheus-compatible API, so you can <a href="https://www.elastic.co/observability-labs/blog/query-prometheus-metrics-grafana-elasticsearch">point Grafana's existing Prometheus plugin directly at Elasticsearch</a>. No sidecars, no adapters, no pipeline changes required. Existing PromQL dashboards, alert rules, and variable dropdowns work without modification, including Grafana's Metrics Drilldown explorer. Add Elasticsearch as a <code>remote_write</code> target in your Prometheus config and swap the data source URL. That's the full migration for most teams.<a href="https://www.elastic.co/observability-labs/blog/elasticsearch-native-prometheus-api"> See the end-to-end setup guide.</a></p>
<p>For teams that want to go further and query logs, metrics, and traces together from a single Grafana query editor, the <strong>official Grafana Elasticsearch plugin</strong> now ships with ES|QL support. This unlocks cross-signal correlation directly in Grafana, with Elasticsearch handling all three data types in a unified columnar backend.<a href="https://www.elastic.co/observability-labs/blog/esql-grafana-elasticsearch-plugin"> See how to set it up.</a></p>
<p>Either way, keep Grafana, replace Mimir and Loki, and gain the full benefit of Elasticsearch's columnar storage and query performance underneath. Years of operational work, preserved. The migration that teams have been putting off becomes a backend swap.</p>
<h2 id="whatsgaandwhatsintechpreview">What's GA and what's in tech preview</h2>
<p>| Capability                                | Status       |
| ----------------------------------------- | ------------ |
| Columnar metrics engine (TSDS)            | GA           |
| ES|QL time series support                | GA           |
| PromQL support in Kibana                  | GA           |
| Prometheus Remote Write ingest            | GA           |
| Kubernetes infrastructure OOTB experience | GA           |
| AWS infrastructure OOTB experience        | Tech Preview |
| Observability MCP App                     | Tech Preview |
| Agent skills                              | Tech Preview |
| Observability Migration Platform          | Tech Preview |</p>
<p>The individual posts linked throughout cover GA versus preview specifics and known limitations.</p>
<p>All of this  (the columnar metrics engine, native PromQL, agentic investigations, and migration tooling) runs across Elastic's three deployment modes: serverless, Elastic Cloud, and self-managed. Datadog has no on-prem option; Grafana Cloud limits its highest-value features to hosted deployments. With Elastic, you choose where your data lives.</p>
<h2 id="elasticobservabilitylowercostwithoutdroppingdata">Elastic Observability: lower cost without dropping data</h2>
<p>Modern cloud infrastructure broke the observability model built around separate tools for separate signals. The cost is real: duplicate tooling bills, manual correlation during incidents, and data dropped just to stay on budget.</p>
<p>A single backend that stores every signal efficiently means you keep what you need without the bill that usually comes with it. That's a different kind of conversation to have with finance: not "we had to drop data to stay on budget," but "here's what we found." The AI gets the full picture because there's only one picture, and the platform arrives with enough pre-built content to be useful on day one, not after weeks of dashboard toil.</p>
<p>That's possible because of how Elasticsearch is built differently from the platforms you're likely replacing:</p>
<ul>
<li><p><strong>Columnar metrics storage</strong> stores stores metrics data highly efficiently in TSDS index mode. </p></li>
<li><p><strong>Native Prometheus compatibility</strong> means existing scrape configs, PromQL queries, and dashboards work without rewriting.</p></li>
<li><p><strong>Unified metrics, logs, and traces</strong> in a single backend means investigation context is assembled at query time, not manually across tabs.</p></li>
<li><p><strong>Search and analytics in the same engine</strong> — an inverted index for logs, a columnar index for metrics, queried together with ES|QL.</p></li>
<li><p><strong>Agentic investigations</strong> that correlate signals, surface anomalies, and suggest remediation before anyone is paged.</p></li>
<li><p><strong>Serverless, Elastic Cloud, or self-managed</strong> — you choose where your data lives, which Datadog cannot offer.</p></li>
</ul>
<p>The cost conversation with finance becomes about what you found, not what you spent.</p>
<p><strong>Get started</strong></p>
<ul>
<li><p><a href="https://cloud.elastic.co/registration">Start a free trial</a></p></li>
<li><p><a href="https://www.elastic.co/docs/solutions/observability">Elastic Observability documentation</a></p></li>
<li><p><a href="https://www.elastic.co/observability-labs">Elastic Observability Labs</a></p></li>
</ul>
<h2 id="frequentlyaskedquestions">Frequently asked questions</h2>
<p><strong>Is Elasticsearch now a production-ready metrics platform?</strong></p>
<p>Yes. As of June 2026, Elasticsearch ships a rebuilt columnar storage engine purpose-built for time series data, native Prometheus Remote Write ingest, PromQL support in Kibana, ES|QL time series querying, and out-of-the-box infrastructure dashboards for Kubernetes and AWS. The columnar metrics engine, ES|QL time series support, PromQL, and Prometheus ingest are all generally available in Elastic Serverless and soon GA in Elastic Cloud Hosted.</p>
<p><strong>How does Elasticsearch compare to Datadog for metrics cost?</strong></p>
<p>In comparable metrics workloads, Elastic Observability Serverless costs significantly less than Datadog — in illustrative examples based on published list pricing, more than 50% less, and often closer to two-thirds less. The gap is structural: Datadog bills primarily per host, then adds charges for custom metrics and containers as instrumentation grows. The cost difference is largest for exactly the workloads where Datadog bills most: high-cardinality, densely instrumented environments like Kubernetes and OTel.</p>
<p><strong>How does Elasticsearch metrics performance compare to Prometheus and Grafana Mimir?</strong></p>
<p>ES|QL queries on Elasticsearch run up to 30× faster than Prometheus and Mimir on gauge averages and counter rates, including high-cardinality workloads. Elasticsearch stores OTel metrics at 3.75 bytes per data point; up to 2.5× more efficiently than Prometheus and 2× more efficiently than ClickHouse.</p>
<p><strong>Can teams migrate from Datadog or Grafana to Elasticsearch without rebuilding everything?</strong></p>
<p>Yes. Elastic's Observability Migration Platform converts Datadog and Grafana dashboards, alert rules, and migrates PromQL queries into Kibana as-is. Teams can also keep Grafana as a visualization layer while replacing the backend with Elasticsearch, using the native Prometheus API and PromQL support in Kibana.</p>
<p><strong>What makes Elasticsearch different from Grafana for metrics observability?</strong></p>
<p>Elasticsearch stores metrics, logs, and traces in a single unified backend with one query language (ES|QL), while Grafana's LGTM stack splits metrics (Mimir/Prometheus) and logs (Loki) across separate backends requiring separate query languages. Elasticsearch also ships agentic investigation capabilities, which includes AI Agent, Workflows, MCP App, and Agent skills, a more comprehensive set of capabilities than Grafana. </p>
<p><strong>Does Elasticsearch support Prometheus and PromQL natively?</strong></p>
<p>Yes, in two distinct ways. First, Elasticsearch accepts Prometheus metrics via Prometheus Remote Write and exposes a native Prometheus-compatible API, so it can serve as a backend for any Prometheus-compatible frontend, including Grafana. Second, Kibana supports PromQL natively, meaning existing queries, dashboards, and alert rules run directly in Kibana without a translation layer or modification.</p>
<p><strong>What infrastructure monitoring content ships out of the box with Elastic Observability?</strong></p>
<p>Elastic ships pre-built dashboards, alert templates, and ML anomaly detection jobs across hundreds of infrastructure integrations covering hosts, containers, cloud services, databases, network devices, and more. For Kubernetes and AWS specifically, the platform also includes agentic investigation content such as agent skills and an Observability MCP App that lets teams run investigations directly from Claude, Cursor, or VS Code. All of this is available at ingest with no configuration required.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/prometheus-metrics-elasticsearch-faster-cheaper-datadog</link>
    <guid isPermaLink="false">prometheus-metrics-elasticsearch-faster-cheaper-datadog</guid>
    <category><![CDATA[Metrics]]></category>
    <category><![CDATA[OpenTelemetry]]></category>
    <dc:creator><![CDATA[Bahubali Shetti,Vinay Chandrasekhar]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltab11d1e390d9cfcc/6a7f19cede23150cc4fd808b/header.png" length="0" type="image/png"/>
    <pubDate>Mon, 29 Jun 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Self-Driving Observability: From Stacktraces to Profiling-Derived Metrics]]></title>
    <description><![CDATA[Profiling-derived metrics turn raw stacktraces into time-series KPIs, unlock continuous profiling for every user and lay the foundation for an observability system that detects, investigates, and acts on its own.]]></description>
    <content:encoded><![CDATA[<p>Continuous profiling has come a long way. With the <a href="https://opentelemetry.io/blog/2026/profiles-alpha/">OpenTelemetry Profiles signal entering Alpha</a> and the <a href="https://github.com/open-telemetry/opentelemetry-ebpf-profiler">OpenTelemetry eBPF profiler</a> — donated by Elastic — now operating as a first-class OpenTelemetry Collector receiver, low-overhead, whole-system profiling on Linux is finally available to every OpenTelemetry user. No instrumentation, no recompilation, no service restarts. Just deploy the profiler and get visibility from the kernel, through native code, all the way up into HotSpot, Python, V8, .NET, Go, PHP, Perl, BEAM Erlang and Ruby runtimes.</p>
<p>The processing pipeline is straightforward: The profiler samples every CPU core on the system at a fixed rate
(19Hz by default), unwinds execution stacks, symbolizes the resulting stacktraces and ships the profiles to
a backend like Elasticsearch.</p>
<p>And then… the user has to figure out what to do with them.</p>
<p>That last step is where continuous profiling has historically faced adoption challenges, as
the path from "profiling is on" to "profiling is useful" is steeper than it should be.</p>
<h2 id="fourbarrierstoadoption">Four barriers to adoption</h2>
<ul>
<li><p><strong>Storage cost:</strong> Full stacktraces, even after deduplication and clever storage schemas, are expensive to store at fleet scale. That cost makes continuous profiling an opt-in feature in practice: a lot of potential users never enable it, and the ones who do, tend to enable it only on a subset of hosts.</p></li>
<li><p><strong>Query friction:</strong> A normalized stacktrace schema is optimized for ingestion and storage but complicates ad-hoc questions. "How much CPU time does my service spend in TLS?" is a simple question that may require intricate ES|QL or custom code in order to be answered.</p></li>
<li><p><strong>AI-hostile data:</strong> Normalized stacktrace data (typically involving multiple levels of indirection) resists straightforward algorithmic analysis. LLMs in particular struggle with it and necessitate further data transformations into representations more amenable to LLM processing.</p></li>
<li><p><strong>UX barrier:</strong> Flamegraphs are extremely useful when you know how to read them but intimidating when you don't.</p></li>
</ul>
<p>These four barriers compound: storage cost limits coverage, the UX barrier limits who benefits from coverage, query friction limits what questions users can ask and the AI-hostile data representation limits what the system can do when users don't know what questions to ask.</p>
<h2 id="howprofilingderivedmetricsworkclassifyattheedge">How profiling-derived metrics work: classify at the edge</h2>
<p>The core idea is simple: instead of sending full stacktraces all the way to a backend and asking the user to make sense of them there, we classify and count at the edge, inside an OpenTelemetry Collector pipeline, and emit ordinary OpenTelemetry time-series counters. The profiling logic itself doesn't change; it's still the OpenTelemetry eBPF profiler running inside the OpenTelemetry Collector. All the new work happens in a stateless connector inside the Collector: the connector inspects each stacktrace produced by the profiler, classifies its frames into one or more categories and increments counters.</p>
<p>We've released <a href="https://github.com/elastic/opentelemetry-collector-components/tree/main/connector/profilingmetricsconnector"><code>profilingmetricsconnector</code></a> as part of Elastic's <code>opentelemetry-collector-components</code> repository. It sits between the OpenTelemetry eBPF profiler receiver and any metrics exporter, and turns symbolized stacktraces into named, aggregated counters with attributes.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5bb4283e563187e2/6a7f197c33fa8a9fe8202b6c/profilingmetricsconnector-pipeline.svg" alt="profilingmetricsconnector pipeline" /></p>
<p>Because the profilingmetricsconnector lives inside the standard OpenTelemetry Collector pipeline, every metric it produces flows through the same processors as the rest of your telemetry. In the following example, the <a href="https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/processor/resourcedetectionprocessor/README.md"><code>resourcedetectionprocessor</code></a> enriches each counter with host-derived attributes.</p>
<pre><code>connectors:
  profilingmetrics:
    flush_interval: 30s

receivers:
  profiling: {}

exporters:
  elasticsearch:
    endpoints:
      - # ENDPOINT
    api_key: # API_KEY
    mapping:
      mode: otel

processors:
  resourcedetection:
    detectors: ["system"]
    system:
      hostname_sources: ["os"]
      resource_attributes:
        host.name:
          enabled: true
        host.id:
          enabled: false
        host.arch:
          enabled: true
        os.description:
          enabled: true
        os.type:
          enabled: true

service:
  pipelines:
    profiles:
      receivers: [ profiling ]
      exporters: [ profilingmetrics ]
    metrics:
      receivers: [ profilingmetrics ]
      processors: [resourcedetection]
      exporters: [ elasticsearch ]
</code></pre>
<h2 id="profilingderivedcpumetricswhatgetsemitted">Profiling-derived CPU metrics: what gets emitted</h2>
<p>The connector ships with a set of pre-baked counters built from useful classification rules. Each metric is a count of stacktrace samples whose leaf frame matched a particular category, with the frequency value standing in for CPU consumption.</p>
<p>| Metric | Classifies | Attached metadata |
|---|---|---|
| <code>kernel.count</code> | Kernel leaf frames | <code>syscall</code>, <code>category</code> (<code>disk/rw</code>, <code>ipc/rw</code>, <code>network/{tcp,udp,other}/rw</code>, <code>memory</code>, <code>synchronization</code>, …) |
| <code>native.count</code> | Native C/C++/Rust leaf frames | shared library name (<code>libcrypto</code>, <code>libclrjit</code>, <code>libsystemd</code>, …) |
| <code>hotspot.count</code>, <code>go.count</code>, <code>python.count</code>, … | Runtime-specific leaf frames | runtime-specific attributes |</p>
<p>The kernel categorization is worth a closer look as a modern Linux kernel has more than 400 system calls. However, most of what shows up in CPU stacktraces falls into a handful of subsystems: filesystem read/write, network read/write, memory management, scheduling, synchronization. Some syscalls (e.g. <code>read</code>, <code>write</code>) are ambiguous on their own and only become specific when one examines more frames down the stack: <code>ext4_file_read_iter</code> points to filesystem, <code>tcp_v4_rcv</code> to network. The connector handles this disambiguation as part of frame iteration.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt282d1eb8637b61fc/6a7f197fb4377095a24d70e6/kibana-kernel-cpu-by-category.png" alt="Kernel CPU breakdown by category in Kibana" /></p>
<p>Native frames typically lack symbolic information beyond shared library names, but those names are still informative: <code>libssl</code> and <code>libcrypto</code> mean cryptographic work as part of OpenSSL or one of its variants; <code>libz</code> means compression; <code>libclrjit</code> means the .NET JIT is busy. We don't need to enumerate libraries statically as the connector dynamically generates <code>shlib_name</code> attribute values using the trimmed library name (e.g. <code>libssl</code> not <code>libssl.so.3</code>) for clean cardinality.</p>
<p>Currently, for each stacktrace, the connector computes a <strong>Self CPU</strong> count (the leaf frame matched the category) corresponding to exclusive CPU usage. A complication exists for fine-grained kernel categories like <code>network/tcp/write</code> where the actual leaf frame is usually a device-driver call that we can't meaningfully match. We deal with that by trying to match frames further up the stack (e.g. <code>tcp_sendmsg</code> is enough to correctly classify the sample).</p>
<p>Users can also add their own categories by specifying a frame pattern (e.g. a function or package) and the connector will generate counters for them.</p>
<h2 id="benefitsofprofilingderivedmetricsforobservability">Benefits of profiling-derived metrics for observability</h2>
<p>This shift looks small from the outside — "we're emitting counters" — but it changes almost everything about how profiling fits into an observability stack.</p>
<ul>
<li><p><strong>Orders of magnitude less storage:</strong> A counter aggregated over a 5-second (or 30-second or one-minute) window is dramatically cheaper than the full stacktraces it distills. The pre-aggregation interval is configurable with the trade-off being time resolution rather than categorization fidelity. For most "where is my CPU being spent?" questions, 30 seconds is plenty.</p></li>
<li><p><strong>On by default:</strong> Because the storage cost is now in line with regular metrics, profiling-derived metrics can be on for everyone, on every host, from the moment the profiler is deployed. Users get a CPU breakdown by runtime, syscall, kernel category and shared library on day one.</p></li>
<li><p><strong>Standard dashboards:</strong> These are ordinary OpenTelemetry time-series counters and can be visualized ad-hoc using stacked bar graphs, pie charts, top-N panels or any other visualization Kibana supports out of the box. The same Lens and TSDB-backed views for application metrics work here.</p></li>
</ul>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte715bd8eb945ab75/6a7f198296b5a6391f87b86b/kibana-user-cpu-over-time.png" alt="User CPU by frame type over time in Kibana" /></p>
<ul>
<li><p><strong>AI and query-friendly:</strong> Standard time-series data is trivially consumable by ES|QL, ML jobs, anomaly detectors and by LLMs. "Show me the top services by <code>network/udp/write</code> time, filtered to the payments namespace, over the last six hours" is one query that is not only simple for the system to answer but also simple for an LLM to generate.</p></li>
<li><p><strong>Cross-signal correlation:</strong> Because the metrics flow through the standard OpenTelemetry Collector pipeline, they pick up the same resource attributes (e.g. <code>service.name</code>, <code>k8s.pod.name</code>, <code>host.name</code>, <code>deployment.environment</code>) that logs, other metrics and traces already carry.</p></li>
<li><p><strong>Instant value, with a path to more detail:</strong> A user who just wants to know "what's burning my CPU?" gets a meaningful answer without ever opening a flamegraph. A user who wants to dig deeper still has the full eBPF profiler underneath, ready to hand back complete stacktraces when they're warranted.</p></li>
</ul>
<h2 id="userprogrammableprofilingandadaptivesampling">User-programmable profiling and adaptive sampling</h2>
<p>The longer-term direction is for the profiler to stop being something users <em>consume</em> and start being something they <em>program</em>. User-defined metrics are the first step in this direction, complemented by on-demand (full) profiling and adaptive sampling.</p>
<p>Profiling-derived metrics or other signals can act as a trigger for on-demand profiling where the system enables full profiling on a specific host or service to capture complete stacktraces. In that way, the full profiling processing and storage cost is paid only when it matters.</p>
<p>We can apply the same idea to the sampling rate. 19Hz is a sensible baseline for steady state but when the metrics signal an interesting event or an anomaly, the system can automatically ramp to 100Hz or higher to capture high-fidelity data for the time window during which it's relevant. It can then ramp down to baseline.</p>
<h2 id="howprofilingderivedmetricsenableselfdrivingobservability">How profiling-derived metrics enable self-driving observability</h2>
<p>Most observability stacks today use an open-loop model: the profiler emits data with a fixed configuration. Then a human looks at flamegraphs and dashboards, potentially correlates with logs, other metrics and traces, forms a hypothesis and triggers a deeper investigation. Every link in this chain requires a human decision. Nothing feeds back into the profiler at speed and the system cannot act on its own observations.</p>
<p>Profiling-derived metrics close that loop.</p>
<ol>
<li><p>A "significant host events" metric, an anomaly on <code>network/udp/write</code> or a spike in <code>native.count/libz</code>: something crosses a threshold.</p></li>
<li><p>The profiler adjusts in response: sampling rate increases, full profiling turns on for the affected hosts.</p></li>
<li><p>The richer data is correlated against logs, traces, and other metrics by an LLM, by a human or both. The same resource attributes that make cross-signal correlation easy for the user make it easy for the system.</p></li>
<li><p>A root cause is identified. A remediation is suggested or applied. The metric returns to baseline and the loop continues.</p></li>
</ol>
<p>This is what we mean when we talk about <em>self-driving observability</em>. The profiler is no longer just an instrument that someone wields. It is the sensory organ of an autonomous feedback loop: a system that observes itself, decides what to look at more closely and adjusts its own configuration in response to what it sees.</p>
<h2 id="whatsnextinclusivecpuoffcpumetricsandruntimespecificprofiling">What's next: inclusive CPU, off-CPU metrics, and runtime-specific profiling</h2>
<p>Any piece of data visible in a stacktrace can be a metric source and several extensions are already on the roadmap.</p>
<ul>
<li><p><strong>Inclusive-CPU metrics:</strong> Today's pre-baked counters attribute CPU at the leaf frame (exclusive-CPU). Inclusive-CPU metrics will attribute the entire call chain which is useful when you care about the total cost of a function call — the function plus everything it transitively calls — not just the work done directly in its own body.</p></li>
<li><p><strong>Runtime-specific metrics:</strong> GC time per runtime, JSON/Protobuf serialization, RPC frameworks, FFI boundaries. The kinds of questions every team eventually asks about their language runtime, answered by default.</p></li>
<li><p><strong>Off-CPU metrics:</strong> On-CPU profiling tells you where you're spending CPU but Off-CPU profiling tells you where you're <em>not</em> (e.g. blocked on I/O, locks). The same classification logic applies, with the only change being the source signal.</p></li>
</ul>
<p>Profiling-derived metrics are an active area of work within Elastic and the <a href="https://github.com/elastic/opentelemetry-collector-components/tree/main/connector/profilingmetricsconnector">profilingmetricsconnector</a> is the place to start if you want to play with this today. A ready-made <a href="https://www.elastic.co/docs/reference/integrations/profilingmetrics_otel">Kibana integration</a> ships dashboards for all the metrics described above.</p>
<p>If you're already using Elastic's continuous profiling, expect these metrics to show up as first-class citizens in the Elastic stack. If you're not, this is a very low-friction way in as no flamegraph expertise is required and storage
cost is minimal.</p>
<p>The flamegraph isn't going anywhere, but for the first time, it isn't the <em>only</em> way profiling yields results.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/otel-profiling-metrics</link>
    <guid isPermaLink="false">otel-profiling-metrics</guid>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[Metrics]]></category>
    <category><![CDATA[APM]]></category>
    <dc:creator><![CDATA[Christos Kalkanis,Roger Coll]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt418826f669e25898/6a7f19859090b02bc984ee13/header.jpg" length="0" type="image/jpeg"/>
    <pubDate>Mon, 01 Jun 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Bringing Fire to Elasticsearch: Adding Native Prometheus API Support]]></title>
    <description><![CDATA[Query Elasticsearch directly from Prometheus-compatible clients via native PromQL, discovery, and metadata endpoints. Send data to Elasticsearch with Prometheus Remote Write.]]></description>
    <content:encoded><![CDATA[<p>Point any Prometheus-compatible client at Elasticsearch and run PromQL directly against your existing metrics.
Elasticsearch is adding native Prometheus query, discovery, and metadata endpoints as a tech preview that work over metrics ingested through Prometheus Remote Write, <a href="https://www.elastic.co/docs/manage-data/data-store/data-streams/tsds-ingest-otlp">OpenTelemetry</a>, or the Bulk API.
The API runs on top of Elasticsearch's <a href="https://www.elastic.co/docs/manage-data/data-store/data-streams/time-series-data-stream-tsds">time series data streams (TSDS)</a>, so there's no separate Prometheus-specific storage layer to operate.</p>
<p>This post explains how the query, discovery, and metadata endpoints build on the earlier ingest and query work to form that API surface.
Companion posts go deeper on individual pieces:</p>
<ul>
<li><a href="https://www.elastic.co/observability-labs/blog/elasticsearch-supports-promql">Native PromQL support in ES|QL</a> covers how PromQL queries are translated into ES|QL execution plans.</li>
<li><a href="https://www.elastic.co/observability-labs/blog/prometheus-remote-write-elasticsearch">Ship Prometheus Metrics to Elasticsearch with Remote Write</a> covers ingestion setup.</li>
<li><a href="https://www.elastic.co/observability-labs/blog/prometheus-remote-write-elasticsearch-architecture">How Prometheus Remote Write Ingestion Works in Elasticsearch</a> covers the remote write internals.</li>
</ul>
<p>This is still a work in progress.
The sections below call out what is supported today and which parts are still evolving.</p>
<h2 id="theapisurface">The API surface</h2>
<p>Today, the Prometheus-compatible API surface falls into three groups.</p>
<h3 id="queryendpoints">Query endpoints</h3>
<p>The query endpoints let Prometheus-compatible clients evaluate PromQL expressions:</p>
<ul>
<li><code>GET /_prometheus/api/v1/query_range</code> evaluates a PromQL expression over a time window (matrix results).</li>
<li><code>GET /_prometheus/api/v1/query</code> evaluates at a single point in time (vector results).
Currently implemented as a short range query that returns the last sample.</li>
</ul>
<p>Only GET is supported for query endpoints today.
Some clients default to POST, so you may need to configure them to use GET.
The Prometheus POST convention uses <code>application/x-www-form-urlencoded</code> bodies, which Elasticsearch's HTTP layer rejects as a CSRF safeguard before the request ever reaches the handler.</p>
<p>For the full PromQL coverage status, see the <a href="https://www.elastic.co/observability-labs/blog/elasticsearch-supports-promql">companion post on PromQL in ES|QL</a>.</p>
<h3 id="metadataendpoints">Metadata endpoints</h3>
<p>The metadata endpoints serve the discovery information that clients need for autocomplete, variable dropdowns, and metric browsing.</p>
<p>The series, labels, and label values endpoints all accept <code>match[]</code> selectors and a time range (<code>start</code>/<code>end</code>).
The <code>match[]</code> parameter takes a Prometheus series selector like <code>http_requests_total{job="api"}</code> and restricts the response to time series that match.
This keeps responses fast and relevant on clusters with large numbers of metrics.
For example:</p>
<pre><code>GET /_prometheus/api/v1/series?match[]=http_requests_total{job="api"}
GET /_prometheus/api/v1/labels?match[]=http_requests_total
GET /_prometheus/api/v1/label/instance/values?match[]=http_requests_total{job="api"}
</code></pre>
<p>The first returns all series for <code>http_requests_total</code> where <code>job="api"</code>, with their full label sets.
The second returns only the label names that exist on <code>http_requests_total</code> series.
The third returns only the <code>instance</code> values that appear on matching series.</p>
<p><code>GET /_prometheus/api/v1/metadata</code> is different: it returns type and unit for each metric, optionally filtered by name via a <code>metric</code> parameter.</p>
<pre><code>GET /_prometheus/api/v1/metadata?metric=http_requests_total
</code></pre>
<p>It does not accept <code>match[]</code> selectors or a time range.
In Prometheus, metadata is collected from active scrape targets (the <code>HELP</code>, <code>TYPE</code>, and <code>UNIT</code> lines they expose), so the response does not involve a data scan.
Elasticsearch does not have a dedicated metadata store like that, so the current implementation discovers metric metadata by visiting time series data from the last 24 hours.
This keeps the query fast without requiring a full index scan.
That 24-hour lookback is fixed today: the Prometheus metadata API does not expose <code>start</code> or <code>end</code> parameters that Elasticsearch could use to make it user-adjustable.</p>
<p>How the metadata endpoints work under the hood, including the <code>TS_INFO</code> and <code>METRICS_INFO</code> commands that power them, is covered <a href="https://www.elastic.co/observability-labs/blog/elasticsearch-native-prometheus-api#ts_info-and-metrics_info">below</a>.</p>
<h3 id="indexprefiltering">Index pre-filtering</h3>
<p>All query and metadata endpoints accept an optional <code>{index}</code> path segment after <code>/_prometheus/</code>:</p>
<pre><code>GET /_prometheus/metrics-prod-*/api/v1/query_range?query=up&amp;start=...&amp;end=...
</code></pre>
<p>This restricts which Elasticsearch indices the query runs against before any expression evaluation begins.
On clusters with many data streams across teams or environments, this avoids scanning unrelated indices and can significantly reduce query latency.
You can configure separate data sources per index pattern to give teams scoped access to their own metrics.</p>
<h3 id="anoteaboutremotewrite">A note about Remote Write</h3>
<p>For ingestion, Elasticsearch also exposes the standard Prometheus Remote Write endpoint:</p>
<ul>
<li><code>POST /_prometheus/api/v1/write</code> ingests time series via the Prometheus Remote Write v1 protocol.
v2 is not yet supported.</li>
</ul>
<p>Remote Write writes into Elasticsearch's existing time series data streams (TSDS), not a separate Prometheus-specific storage layer.
Prometheus labels become TSDS dimensions, and metric names become fields in the index mapping.
The <a href="https://www.elastic.co/observability-labs/blog/prometheus-remote-write-elasticsearch-architecture">remote write architecture post</a> covers the full mapping in detail, including how metric types are inferred and how labels are stored with a <code>labels.</code> prefix.</p>
<h3 id="howitworks">How it works</h3>
<p>Under the hood, all endpoints work the same way: parse the incoming HTTP parameters, build an ES|QL query plan, execute it against time series data streams, and convert the columnar result back into the JSON format Prometheus clients expect.</p>
<h2 id="ts_infoandmetrics_info">TS_INFO and METRICS_INFO</h2>
<p>The metadata endpoints need to answer questions like "what labels exist?" or "what metric types are defined?" across potentially millions of time series, without scanning every data point.</p>
<p>Internally, the Prometheus metadata endpoints answer those questions by building ES|QL plans around two new processing commands: <code>METRICS_INFO</code> and <code>TS_INFO</code>.
You do not need to use these commands directly to use the Prometheus API, but they are the core execution primitives behind the metadata responses.
Both work by visiting only one document per time series to extract its metadata, rather than scanning all samples.
This means their cost scales with the number of distinct time series, not the number of data points.</p>
<p><code>METRICS_INFO</code> returns one row per distinct metric with its name, type, unit, and associated dimension fields.
<code>TS_INFO</code> is more granular: one row per (metric, time series) combination, including the actual dimension values as a JSON object.</p>
<pre><code>TS metrics-*
| METRICS_INFO
| SORT metric_name
</code></pre>
<p>A dedicated blog post on <code>TS_INFO</code> and <code>METRICS_INFO</code> is coming soon, covering the two-phase execution model, how they scale, and how to use them directly in ES|QL queries beyond the Prometheus API.</p>
<h3 id="howthemetadataendpointsusethem">How the metadata endpoints use them</h3>
<p>Each metadata endpoint constructs an ES|QL plan with one of these commands at its core.</p>
<p><code>/api/v1/labels</code> and <code>/api/v1/series</code> use <code>TS_INFO</code>, since they need per-time-series detail (which labels exist, which dimension values identify each series).
<code>/api/v1/metadata</code> and <code>/api/v1/label/__name__/values</code> use <code>METRICS_INFO</code>, since they only need per-metric information (metric names, types, units).</p>
<p><code>/api/v1/label/{name}/values</code> for regular labels (anything other than <code>__name__</code>) does not use either command.
Regular labels like <code>job</code> or <code>instance</code> are actual dimension fields in the index, so the endpoint can query them directly with a group-by aggregation.
When <code>match[]</code> selectors are provided, they are translated into a <code>WHERE</code> clause that filters the time series before the aggregation runs.</p>
<p>The <code>__name__</code> label needs a different strategy because it is not always present as a dimension field.
Prometheus Remote Write does store <code>labels.__name__</code>, but metrics ingested through other paths (OpenTelemetry, the bulk API) do not have it.
The metric name is encoded in the field name itself (e.g., <code>metrics.http_requests_total</code>).
You could look at the index mappings to enumerate field names, but mappings alone do not tell you which metric has which dimensions, and they cannot be filtered by label values from a <code>match[]</code> selector.
<code>METRICS_INFO</code> can do both: it enumerates metric names across indices while respecting upstream <code>WHERE</code> filters.</p>
<p>In all cases, the API layer handles the translation back to Prometheus conventions: stripping the <code>labels.</code> and <code>metrics.</code> storage prefixes and synthesizing <code>__name__</code> for non-Prometheus metrics that lack it.</p>
<h2 id="inconclusion">In conclusion</h2>
<p>The result: any Prometheus-compatible client can query and explore Elasticsearch metrics through endpoints it already understands.
Remote Write metrics, OpenTelemetry metrics, and metrics indexed through other paths all show up through the same API, backed by the same TSDS indices.</p>
<p>All the Prometheus APIs mentioned here are available as tech preview in Elasticsearch Serverless today.
For self-managed clusters and Elastic Cloud Hosted deployments, they will arrive as tech preview in Elasticsearch 9.4,
with the exception of the <code>GET /_prometheus/api/v1/metadata</code> API which will arrive in 9.5.
To try it locally, use <a href="https://www.elastic.co/docs/deploy-manage/deploy/self-managed/local-development-installation-quickstart">start-local</a>.</p>
<h2 id="frequentlyaskedquestions">Frequently asked questions</h2>
<p><strong>Do the Prometheus-compatible query and metadata APIs only work with data that is ingested via Remote Write?</strong>
No.
The Prometheus-compatible API runs on top of Elasticsearch any time series data stream (TSDS).
The same indices that hold metrics from Prometheus Remote Write, OpenTelemetry, or the Bulk API are queried directly through PromQL, with no extra storage layer to operate.</p>
<p><strong>Why do my Prometheus clients fail when calling the query endpoints with POST?</strong>
Today only GET is supported on the Prometheus query endpoints.
Some clients default to POST with <code>application/x-www-form-urlencoded</code>, which Elasticsearch's HTTP layer rejects as a CSRF safeguard before the request reaches the handler.
Configure the client to use GET.</p>
<p><strong>Can I scope a Prometheus query to a subset of indices in Elasticsearch?</strong>
Yes.
Every query and metadata endpoint accepts an optional <code>{index}</code> segment, like <code>/_prometheus/metrics-prod-*/api/v1/query_range</code>.
This restricts the query to the matching indices before evaluation, which avoids scanning unrelated data.</p>
<p><strong>How does the native Prometheus API compare to running a dedicated Prometheus server?</strong>
Elasticsearch consolidates Prometheus Remote Write, OpenTelemetry, and other ingestion paths into one TSDS-backed store, queryable through PromQL or ES|QL.
You keep Prometheus client compatibility while reusing existing Elasticsearch capabilities like long-term storage, ILM, and correlation with logs and traces stored in the same cluster.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/elasticsearch-native-prometheus-api</link>
    <guid isPermaLink="false">elasticsearch-native-prometheus-api</guid>
    <category><![CDATA[Metrics]]></category>
    <category><![CDATA[OpenTelemetry]]></category>
    <dc:creator><![CDATA[Felix Barnsteiner]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf6ee85474d7681ec/6a859a56501a856126fba888/header.jpg" length="0" type="image/jpeg"/>
    <pubDate>Tue, 26 May 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[From averages to any percentile: Elasticsearch ships native exponential histogram support in ES|QL]]></title>
    <description><![CDATA[Query any percentile at any time. Elasticsearch natively stores OTel exponential histograms and lets you analyze distributions in ES|QL without fixed buckets or lossy conversions.]]></description>
    <content:encoded><![CDATA[<p>Elasticsearch adds native support for OpenTelemetry exponential histograms in ES|QL.
Unlike fixed-bucket histograms, exponential histograms dynamically adapt to your data — giving you accurate percentile estimates (median, p99, any percentile you want) at query time with guaranteed error bounds.
No more pre-defining buckets, no more lossy conversions. Just send your OTel metrics to the <a href="https://www.elastic.co/docs/manage-data/data-store/data-streams/tsds-ingest-otlp">Elasticsearch OTLP/HTTP endpoint</a>
and they're stored using the new <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/exponential-histogram">exponential_histogram</a> type and queryable immediately.
Already have historical data stored in the classic histogram type? A simple ::exponential_histogram cast in your ES|QL queries handles the migration transparently.
Already using <a href="https://www.elastic.co/docs/manage-data/data-store/data-streams/downsampling-time-series-data-stream">downsampling</a>? Both histogram field types are now fully supported.</p>
<h2 id="histogrammetrics">Histogram metrics</h2>
<p>When dealing with metrics (in OpenTelemetry or Prometheus, for instance), counters and gauges are the most common metric types.
Gauges allow you to monitor values that rise or fall (e.g., CPU utilization). Counters allow you to, well, count things, such as the total number of HTTP requests your service is handling. Counters normally just increase in value, with a few exceptions when they reset, like when a server reboots.</p>
<p>In the case of counters, you can additionally collect a counter measuring the total sum of your HTTP response times,
which allows you to derive the average response time by dividing that sum by the total number of requests.
However, average response times provide limited insights into the collected data and the system behavior.
The best insights are gained by analyzing the collected metric distribution, e.g., through median and percentile calculations. This is where counters fall short.</p>
<p>In the past, workarounds have been applied: For example, classic Prometheus-style histograms attempt to capture the distribution using a set of counters.
By defining fixed buckets (e.g., one for response times in the range <code>[0s, 1s)</code>, one for <code>[1s, 4s)</code>, and so on) and associating a counter with each, we can at least estimate percentiles broadly.
However, the key problem here is that we have to know the distribution of our data up front to properly define these buckets.</p>
<p>To that end, the OpenTelemetry community has come up with a better solution: exponential histograms.
Exponential histograms assign collected values to buckets, just like classic Prometheus-style histograms. The key differentiator is that these buckets vary dynamically based on the collected values.
The name "exponential" comes from the fact that the bucket sizes increase exponentially: we use small buckets for small values and wider buckets for larger values.
You can find an excellent introduction in the <a href="https://opentelemetry.io/blog/2022/exponential-histograms/">OpenTelemetry exponential histograms introduction</a>.</p>
<p>Note that in addition to classic histograms, Prometheus also added <a href="https://prometheus.io/docs/specs/native_histograms/">native histograms</a>, which directly
map to OTel <a href="https://prometheus.io/docs/specs/native_histograms/#opentelemetry-interoperability">exponential histograms</a>. Native histograms have their own <a href="https://prometheus.io/docs/specs/native_histograms/#promql">PromQL syntax</a>.
We are actively working on adding support for that syntax to the <a href="https://www.elastic.co/observability-labs/blog/elasticsearch-supports-promql">Elasticsearch PromQL implementation</a>, so that you can directly query exponential histograms using PromQL.</p>
<h2 id="demosetup">Demo setup</h2>
<p>Let's start by collecting some histogram metrics to show how they can be stored and analyzed in Elasticsearch using ES|QL.</p>
<p>We'll focus on a Java JVM metric: garbage collection durations.
OpenTelemetry defines the <a href="https://opentelemetry.io/docs/specs/semconv/runtime/jvm-metrics/#metric-jvmgcduration">jvm.gc.duration</a>, which is a histogram-typed metric.
The <a href="https://github.com/open-telemetry/opentelemetry-java-instrumentation">OpenTelemetry Java agent</a> natively supports collecting this metric.</p>
<p>We'll spin up a JVM running a <a href="https://renaissance.dev/">Renaissance benchmark</a> to put it under stress.
We'll start that JVM with the vanilla OpenTelemetry Java agent attached and have it send the metrics directly to Elasticsearch.</p>
<p>You can find the ready-to-run Docker-compose file <a href="https://github.com/JonasKunz/es-histogram-demo">here</a>.
You'll just need to insert your <a href="https://www.elastic.co/docs/manage-data/data-store/data-streams/tsds-ingest-otlp">Elasticsearch OTLP/HTTP endpoint</a> and API key in the <code>docker-compose.yml</code>:</p>
<pre><code>OTEL_EXPORTER_OTLP_ENDPOINT: https://&lt;elasticsearch url&gt;/_otlp
OTEL_EXPORTER_OTLP_HEADERS: "Authorization=ApiKey &lt;base64 API key&gt;"
</code></pre>
<p>Note that you don't have to use this demo setup. We even encourage you to try it with your own application.
Here are the other important OpenTelemetry agent settings the demo already includes, which you should include too if you're bringing your own app:</p>
<pre><code>OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE: delta
OTEL_EXPORTER_OTLP_METRICS_DEFAULT_HISTOGRAM_AGGREGATION: BASE2_EXPONENTIAL_BUCKET_HISTOGRAM
OTEL_INSTRUMENTATION_RUNTIME_TELEMETRY_ENABLED: "true"
</code></pre>
<p>Let's step through them:</p>
<ul>
<li><em>Temporality preference</em>: OpenTelemetry supports both cumulative and delta-based histograms.
Cumulative means that the histogram is only cleared after an application restart, while delta clears it after each export.
At the time of writing, Elasticsearch only supports delta temporality for histograms. We are actively working on supporting cumulative histograms as well.</li>
<li><em>Default Histogram Aggregation</em>: By default, OpenTelemetry exports histograms in the Prometheus-style fixed bucket format. Since we want to reap the benefits of exponential histograms, we tell the agent to use them instead.</li>
<li><em>Runtime Telemetry enabled</em>: This tells the agent to actually collect the detailed JVM metrics, which include <code>jvm.gc.duration</code>.</li>
</ul>
<p>Now we are ready to go! We'll let the application run in the background and switch over to Kibana to analyze the GC metric.</p>
<h2 id="queryingwithesql">Querying with ES|QL</h2>
<p>Now let's open up Kibana and navigate to "Discover". There we'll switch to <a href="https://www.elastic.co/docs/explore-analyze/discover/try-esql">ES|QL mode</a>, and start querying the collected data:</p>
<pre><code>TS metrics-* | STATS COUNT(jvm.gc.duration)
</code></pre>
<p>As a response, we now see the metric panel shown below. If you don't see any data, make sure to double-check the Kibana <a href="https://www.elastic.co/docs/explore-analyze/query-filter/filtering#set-time-filter">time range filter</a>.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta1ba250bc614d88d/6a859a76abdc292210121aaa/count_single_stat.png" alt="ES|QL metric panel showing the total count of jvm.gc.duration samples" /></p>
<p>This number represents the total number of garbage collection operations that happened in our test application during the selected time frame.</p>
<p>Similarly, we can query the total time spent on those garbage collection operations:</p>
<pre><code>TS metrics-* | STATS SUM(jvm.gc.duration)
</code></pre>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt634864a6f43107b9/6a859a788c29445a73b8856d/sum_single_stat.png" alt="ES|QL metric panel showing the sum of jvm.gc.duration values in the selected time range" /></p>
<p>So we have roughly 270k garbage collections, which in total took 713 seconds.
Given these two numbers, we can now compute the average if we are still fluent in primary school-level math.
Even if not, you can just let ES|QL do that for you:</p>
<pre><code>TS metrics-* | STATS AVG(jvm.gc.duration)
</code></pre>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdffbe368ff62bcd1/6a859a7bd7b2e7a071fe7a76/avg_single_stat.png" alt="ES|QL metric panel showing the average jvm.gc.duration value" /></p>
<p>Now we know that the average garbage collection operation took about 3 milliseconds.
However, Java experts might know that there are different kinds of garbage collections happening, which can have significantly different pause times.
Fortunately the OpenTelemetry metric comes with attributes, which allow us to slice the data accordingly:</p>
<pre><code>TS metrics-* | STATS AVG(jvm.gc.duration) BY jvm.gc.action
</code></pre>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt48d3ee0d405990f5/6a859a7ed6cf29906dbafe76/avg_by_action.png" alt="ES|QL bar chart showing the average jvm.gc.duration grouped by jvm.gc.action" /></p>
<p>As expected, major garbage collections take a lot more time per collection than minor ones, at least on average.
So far, we have done nothing you couldn't also achieve by just using counters. Let's now use histograms to understand the actual distribution of the GC latency.
We'll look at the data over time (by grouping using <code>TBUCKET</code>) and focus on the major garbage collections:</p>
<pre><code>TS metrics-*
| WHERE jvm.gc.action == "end of major GC"
| STATS MAX(jvm.gc.duration),
        PERCENTILE(jvm.gc.duration, 99),
        MEDIAN(jvm.gc.duration),
        MIN(jvm.gc.duration)
 BY TBUCKET(100)
</code></pre>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt14ed0b38e7e5e588/6a859a80501a85ecc8fba8b6/distribution_over_time.png" alt="ES|QL line chart of min, median, p99 and max jvm.gc.duration for major garbage collections" /></p>
<p>The graph now shows us the minimum, maximum, median and 99th percentile for major garbage collections.
Note that we aren't bound to only querying the median and the 99th percentile.
We can query any percentile we'd like to see, as these are estimated at query time from the raw exponential histograms.</p>
<h2 id="anoteonbackwardscompatibility">A note on backwards compatibility</h2>
<p>So far, we have seen how you can use the new shiny toy in Elasticsearch and ES|QL: exponential histograms.
However, since this has just reached general availability (GA) in the 9.4 release, what about your historical data?</p>
<p>Before exponential histograms were added, Elasticsearch was already capable of storing OpenTelemetry histograms in the <code>histogram</code> field type.
To do so, we converted them to a different data structure supported by the <code>histogram</code> field type: <a href="https://github.com/tdunning/t-digest/blob/main/docs/t-digest-paper/histo.pdf">T-Digest</a>.
T-Digest provides good accuracy for extreme percentiles (e.g., 99th percentile) at the cost of accuracy for percentiles in the middle of the distribution, such as the median.
In contrast, exponential histograms provide a guaranteed upper bound on the relative error for every percentile.
As conversions always introduce errors, we are happy to now have native support for exponential histograms, allowing you to collect and analyze your metrics end-to-end without unnecessary conversions.</p>
<p>But still, what should you do if you have historical data and still want to query it?
Thanks to <a href="https://www.elastic.co/docs/reference/query-languages/esql/esql-multi-index#esql-multi-index-union-types">ES|QL union types</a>, the answer is actually easy: You just have to add a <code>::exponential_histogram</code> suffix to the histogram metrics in your queries:</p>
<pre><code>TS metrics-* | STATS AVG(jvm.gc.duration::exponential_histogram)
</code></pre>
<p>When this query encounters <code>histogram</code> fields, it will attempt to convert them to exponential histograms. When operating on <code>exponential_histogram</code> fields, the <code>::exponential_histogram</code> cast has no effect.
Note that this also works with mixed data sets: if your backing indices use both types, the query will just do the right thing.</p>
<p>So if you are building queries or dashboards that you expect to run on pre-9.4 ingested data, we recommend that you simply add: <code>::exponential_histogram</code> casts.</p>
<h2 id="wrappingup">Wrapping up</h2>
<p>Native support for OpenTelemetry exponential histograms in Elasticsearch gives you better metric fidelity and more flexible analysis in ES|QL.
In this blog post, we have shown you how to easily ingest and analyze your histogram metrics with ES|QL using various aggregations and the impact exponential histograms have.</p>
<p><a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/exponential-histogram">Exponential histograms</a> are <strong>generally available</strong> in Elasticsearch basic starting with the 9.4.0 release. They will be available in Elastic Cloud <a href="https://www.elastic.co/cloud/serverless">Serverless</a> a few weeks after the 9.4.0 release, once <a href="https://www.elastic.co/docs/reference/opentelemetry/motlp">mOTLP</a> (the managed observability OTLP intake) switches to use the Elasticsearch OTLP endpoint. We'll update this blog post and add a note on the Elastic Cloud Serverless release notes when that happens.</p>
<h2 id="frequentlyaskedquestions">Frequently asked questions</h2>
<p><strong>How do I query OpenTelemetry histogram metrics in Elasticsearch?</strong>
Elasticsearch 9.4 natively stores OpenTelemetry exponential histograms and supports querying them in ES|QL. You can use standard aggregation functions like AVG, SUM, COUNT, PERCENTILE, MEDIAN, MIN, and MAX directly on histogram fields. Just send your OTel metrics to the Elasticsearch OTLP/HTTP endpoint and query them in Kibana Discover using ES|QL.</p>
<p><strong>What's the difference between exponential histograms and classic Prometheus-style histograms?</strong>
Classic Prometheus-style histograms require you to predefine fixed buckets, which means you need to know your data distribution upfront. Exponential histograms dynamically adapt their bucket boundaries based on collected values, giving you accurate percentile estimates without any upfront configuration. This makes them far more flexible for real-world workloads where distributions vary.</p>
<p><strong>Why are exponential histograms better than T-Digest for metric storage?</strong>
Before 9.4, Elasticsearch converted OTel histograms to T-Digest for storage. T-Digest provides good accuracy for extreme percentiles (like p99) but loses accuracy for mid-range percentiles like the median. Exponential histograms provide a guaranteed upper bound on relative error for every percentile, and native storage eliminates the lossy conversion step entirely. Also T-Digests don't support cumulative temporality.</p>
<p><strong>Can I query historical histogram data after upgrading to Elasticsearch 9.4?</strong>
Yes. If you have older data stored in the classic <code>histogram</code> field type, you can query it alongside new <code>exponential_histogram</code> data by adding a <code>::exponential_histogram</code> cast to your ES|QL queries. ES|QL union types handle the conversion transparently, even across mixed indices.</p>
<p><strong>How do I send OpenTelemetry exponential histograms to Elasticsearch?</strong>
Configure your OpenTelemetry SDK or agent to use delta temporality (<code>OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE: delta</code>) and exponential bucket histograms (<code>OTEL_EXPORTER_OTLP_METRICS_DEFAULT_HISTOGRAM_AGGREGATION: BASE2_EXPONENTIAL_BUCKET_HISTOGRAM</code>). Point the OTLP exporter at your Elasticsearch OTLP/HTTP endpoint and the histograms will be stored natively — no intermediate collector or conversion needed.</p>
<p><strong>Does Elasticsearch support downsampling for histogram metrics?</strong>
Yes. Starting with Elasticsearch 9.4, both the <code>exponential_histogram</code> and classic <code>histogram</code> field types are supported in time series data stream downsampling. This lets you retain long-term histogram data at reduced storage cost while still being able to query percentiles.</p>
<p><strong>How does Elasticsearch's histogram support compare to other observability platforms?</strong>
Most observability platforms either require fixed-bucket histograms (losing accuracy) or convert distributions to sketches on ingest (losing raw fidelity). Elasticsearch 9.4 stores OTel exponential histograms natively and lets you compute any percentile at query time using ES|QL, without predefining buckets or losing data to intermediate conversions.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/otel-histogram-metrics-esql</link>
    <guid isPermaLink="false">otel-histogram-metrics-esql</guid>
    <category><![CDATA[Metrics]]></category>
    <category><![CDATA[OpenTelemetry]]></category>
    <dc:creator><![CDATA[Jonas Kunz]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3e071e3ac3b866eb/6a859a839829267e69582e0a/header.png" length="0" type="image/png"/>
    <pubDate>Mon, 25 May 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[30x faster than Prometheus: how we rebuilt Elasticsearch as a leading columnar metrics datastore]]></title>
    <description><![CDATA[Elasticsearch now stores OTel metrics at 3.75 bytes per data point and queries them up to 30x faster than Prometheus. Here's how we rebuilt TSDS and ES|QL.]]></description>
    <content:encoded><![CDATA[<p>Elasticsearch now stores OTel metrics at <strong>3.75 bytes per data point</strong> — down from 25 bytes a year ago — and queries them up to <strong>30x</strong> faster and with up to <strong>2.5x</strong> better storage efficiency, compared to <strong>Prometheus</strong>, <strong>Mimir</strong> and <strong>ClickHouse</strong>. These gains came from rebuilding <a href="https://www.elastic.co/docs/manage-data/data-store/data-streams/time-series-data-stream-tsds">TSDS</a> storage and the ES|QL compute engine into a <strong>fully columnar metrics engine</strong>, with native OTel ingestion added as part of the effort — all while keeping Elasticsearch's ability to store and query logs, traces, and any other data alongside metrics.</p>
<p>Elasticsearch has supported storing metrics in time-series data streams (<a href="https://www.elastic.co/docs/manage-data/data-store/data-streams/time-series-data-stream-tsds">TSDS</a>) since <strong>version 8.7</strong>. This offering mainly focused on storage gains as explained in an earlier <a href="https://www.elastic.co/search-labs/blog/time-series-data-elasticsearch-storage-wins">blog post</a>. Still, performance was not on par with specialized systems for storing and querying metrics, in terms of storage efficiency, indexing throughput and query latency.</p>
<p>In the past year, we revisited the storage layer, optimized ingestion for OTel metrics and extended the ES|QL compute engine with vectorized processing for time series data. These efforts led to substantial performance wins across the board, compared to earlier versions of TSDS:</p>
<ol>
<li>Up to <strong>6.6x</strong> improvement in storage efficiency, reaching 3.75 bytes per data point in OTel metrics</li>
<li>Up to <strong>50%</strong> improvement in indexing throughput for OTel data</li>
<li>Up to <strong>160x</strong> improvement in query latency, including blazing fast counter rate evaluation and window support in time series aggregations</li>
</ol>
<p>Elasticsearch has thus become a <strong>leading columnar metrics engine</strong>, matching or exceeding the competition (like <strong>Prometheus</strong>, <strong>Mimir</strong>, and <strong>ClickHouse</strong>) in indexing throughput and exceeding it by up to <strong>2.5x</strong> in storage efficiency and <strong>30x</strong> in query performance. All while maintaining the ability to store logs and other data and fully use the rich querying capabilities of ES|QL (e.g. <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/inlinestats-by">inline stats</a>, <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/lookup-join">lookup join</a>) — which other PromQL-based systems lack. Elasticsearch can thus serve as a unified storage and query engine for all user data, with no compromises for metrics and observability applications.</p>
<h2 id="howtsdsisorganized">How TSDS is organized</h2>
<p>TSDS has the following properties that help improve the performance of time-series codecs and produce correct results when aggregating data points per time series:</p>
<ul>
<li>The <a href="https://www.elastic.co/docs/manage-data/data-store/data-streams/time-series-data-stream-tsds#time-series-metric">metric</a> name and the <a href="https://www.elastic.co/docs/manage-data/data-store/data-streams/time-series-data-stream-tsds#time-series-dimension">dimension</a> names and values are used to calculate the <code>_tsid</code>, a unique identifier per time series.</li>
<li>TSDS get sorted by <code>[_tsid ascending, timestamp descending]</code> order. Each time series is thus stored in sequence on disk, with newer data points appearing first. Since the <code>_tsid</code> is calculated over dimension values, the latter are also clustered on disk.</li>
<li>Shard routing is based on <code>_tsid</code>, with each <code>_tsid</code> value appearing in one shard only.</li>
<li>Backing indices are <a href="https://www.elastic.co/docs/manage-data/data-store/data-streams/time-bound-tsds">time-bound</a>, with no overlap over time between them.</li>
</ul>
<p>The rest of this post explains how we use these properties to improve storage, indexing, and query performance.</p>
<h2 id="storageoptimizations">Storage optimizations</h2>
<p>TSDS <a href="https://www.elastic.co/search-labs/blog/time-series-data-elasticsearch-storage-wins">already</a> achieved a very competitive storage footprint, reaching <strong>0.9 bytes per data point</strong>, when it is possible to combine many metrics in a single doc, sharing the same dimension values. However, when most data points have a unique set of dimensions (which is typical for OTel or Prometheus metrics), docs end up containing a single data point. In this setup, storage required 25 bytes per data point, with dedicated metrics stores requiring less than 10 bytes per data point.</p>
<p>To further reduce the storage footprint, we applied a series of optimizations over the past year:</p>
<h3 id="replaceinvertedindicesandbkdtreeswithdocvalueskippers">Replace inverted indices and BKD trees with doc value skippers</h3>
<p>Elasticsearch creates inverted indices (for text values) or BKD trees (for numeric values) by default for all non-metric fields, i.e. for <a href="https://www.elastic.co/docs/manage-data/data-store/data-streams#backing-indices">@timestamp</a> and dimensions. These indices improve performance for queries including filters on these fields, but have significant impact to storage — effectively doubling the footprint for each field. More so, they are also processed during <a href="https://www.elastic.co/docs/reference/elasticsearch/index-settings/merge">segment merging</a>, increasing the cpu, memory and storage overhead and slowing down the system — especially in high ingest throughput scenarios, as is often the case with metrics.</p>
<p>Lucene has been extended with <a href="https://lucene.apache.org/core/10_1_0/core/org/apache/lucene/index/DocValuesSkipper.html">doc value skippers</a>, a form of hierarchical sparse indices that store the minimum and maximum value of blocks of documents. Range queries can check these min and max values and ignore blocks that don't fall into the requested range. Skippers work particularly well on sorted fields. Since TSDS are sorted by <code>[_tsid, timestamp desc]</code>, dimension values get also clustered on disk. It's therefore possible to replace indices on <code>@timestamp</code> and dimension fields with doc value skippers that <strong>amplify the columnar layout</strong> — each field stored in its own files, with no duplicate tracking of each doc for indexing purposes.</p>
<p>Doc value skippers have negligible storage overhead — replacing indices with them led to a reduction of <strong>10 bytes</strong> out of the initial 25 bytes per data point in OTel. Moreover, they work very well in practice when queries include filters on time ranges or dimension values (including prefixes and regex) — there was no noticeable regression in query performance in our benchmarks when they replaced separate indices. Doc value skippers are enabled for TSDS by default since <strong>version 9.3</strong>.</p>
<h3 id="enablesyntheticids">Enable synthetic ids</h3>
<p>The <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/mapping-id-field"><code>_id</code></a> metadata field was another big contributor to the storage footprint. TSDS has already been extended to trim the doc values once they were no longer needed for replication, but the inverted index was kept around to efficiently support the id-based APIs (<a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-get">Get</a>, <a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-delete">Delete</a>, <a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-update">Update</a>).</p>
<p>The id value for TSDS is synthesized by combining the <code>_tsid</code> and <code>@timestamp</code> values that uniquely identify each data point. Since these fields are configured with doc value skippers, it's possible to replace the inverted index on <code>_id</code> with (a) retrieval of the <code>_tsid</code> and <code>@timestamp</code> value from the <code>_id</code> value, and (b) checks for matches using doc value skippers respectively. Care has to be taken to avoid expensive checks for duplicate ids during metric ingestion, with segment-level bloom-filters keeping the overhead at bay.</p>
<p>Supporting synthetic ids in metrics is a first for Elasticsearch. It led to a reduction of <strong>5 bytes</strong> out of the initial 25 bytes per data point for OTel metrics, with no loss of functionality. Synthetic ids are enabled for TSDS by default in <strong>version 9.4</strong>. We plan to extend their uses in logs and other applications after further evaluation.</p>
<h3 id="trimsequencenumbers">Trim sequence numbers</h3>
<p>Sequence numbers are used as part of replication, but also to provide strong consistency semantics on doc modification operations through <a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/optimistic-concurrency-control">Optimistic Concurrency Control</a> (OCC). While such semantics are applicable to certain scenarios, they don't fit in metrics where concurrent updates are very rare, with no practical need for guarding against concurrent operations on data points with matching ids. We therefore decided to <a href="https://www.elastic.co/docs/reference/elasticsearch/index-settings/index-modules#index-disable-sequence-numbers">disable the use of sequence numbers</a> in all APIs, along with OCC support, for TSDS, in <strong>version 9.4</strong>. This leads to a substantial storage reduction of <strong>4 bytes</strong> out of the initial 25 bytes per data point for OTel data, as there's no inverted index and sequence numbers get trimmed once no longer needed for replication. <a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/update-by-query-api">Update</a> and <a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-delete-by-query">delete</a> by query operations are still supported, albeit with weaker consistency semantics.</p>
<p>If OCC is still deemed important for a particular metrics application, the old behavior can be restored by setting <code>index.disable_sequence_numbers: false</code> in the index template of the involved TSDS.</p>
<h3 id="uselargenumericcodecblocks">Use large numeric codec blocks</h3>
<p>TSDS already uses an advanced codec, as explained in an earlier <a href="https://www.elastic.co/search-labs/blog/time-series-data-elasticsearch-storage-wins#specialized-codecs">article</a>. The codec works very well in most cases, but has poor performance in case of repeated sequences of keywords and numbers, leading to an inflated storage footprint for dimensions containing IP and MAC addresses. We identified that the existing logic for identifying repeated sequences requires larger codec blocks to work well, especially as the sequence length increases. After experimentation, the numeric block size was increased from 128 to 512 elements in <strong>version 9.3</strong>, leading to a reduction of <strong>2 bytes</strong> out of the initial 25 bytes per data point for an OTel dataset containing IP and MAC addresses as dimensions. We're also working on a more configurable codec layout that will allow more flexibility with block sizes and other parameters, based on field type and cardinality.</p>
<h2 id="indexingthroughput">Indexing throughput</h2>
<p>Elasticsearch has support for bulk ingestion of documents. This entrypoint has long been optimized for leniency, ensuring that all docs get accepted. This flexibility, however, incurs additional processing cost during indexing. Metric applications proved good candidates for using different approaches to reduce this overhead, as explained below.</p>
<h3 id="introduceotlpprotobufentrypoint">Introduce OTLP protobuf entrypoint</h3>
<p>OTel metrics and Prometheus have established protocols for metrics ingestion, using protocol buffers. In the past, a translation step was required to convert collected protobuf messages to bulk requests that Elasticsearch can consume.</p>
<p>Elasticsearch was recently extended with endpoints accepting messages from OTel metrics collectors and over Prometheus remote write. Parsing and processing these (binary) messages is cheaper, compared to json parsing, while hash operation over dimensions for <code>_tsid</code> calculations get reused and amortized across more data points within a single protobuf message. Furthermore, <code>_tsid</code>s get evaluated once per doc in the coordinator nodes and propagated to data nodes for indexing, thus deduplicating an expensive step per indexed doc. These improvements led to up to a 20% speedup in indexing throughput for OTel metrics. The OTLP entrypoint was added in version 9.2 (tech preview) and reached GA in <strong>version 9.3</strong>. We've added similar entrypoints for <a href="https://www.elastic.co/observability-labs/blog/prometheus-remote-write-elasticsearch">Prometheus remote write</a> in <strong>version 9.4</strong> (tech preview) and are actively working to cover OTel Logs and Traces.</p>
<h3 id="reduceindexingcpuwithdocvalueskippers">Reduce indexing CPU with doc value skippers</h3>
<p>In addition to a substantial storage footprint, inverted indices require a lot of cpu to build and reconstruct during segment merging. The use of doc value skippers in their place helps also reduce cpu load at ingestion and thus improves indexing throughput by 10%, a welcome bonus on top of the aforementioned storage wins.</p>
<h3 id="syntheticrecoverysource">Synthetic recovery source</h3>
<p>The original <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/mapping-source-field">source</a> of a document, as provided at index time, is never stored for metrics. Still, Elasticsearch needed to temporarily store it for replication purposes. That changed in <strong>version 9.1</strong>, where the source gets synthesized on demand for replication purposes. This is known as synthetic recovery source and reduces disk I/O by 50%, with a significant impact to metrics indexing performance. Check out this <a href="https://www.elastic.co/search-labs/blog/elastic-logsdb-tsds-enhancements">article</a> for more details.</p>
<h2 id="queryexecution">Query execution</h2>
<p>Replacing inverted indices with doc value skippers leads to a pure columnar storage layout for TSDS, with metric and dimension fields stored as Lucene doc values, each field encoded and compressed in their own file. Combined with the introduction of the <a href="https://www.elastic.co/blog/elasticsearch-query-language-esql#dedicated-query-engine">ES|QL compute engine</a> that uses vectorized execution internally, it became possible to introduce a fully columnar storage and query processing engine for metrics in Elasticsearch. We pushed this idea to the extreme and implemented a <strong>columnar metrics processing engine</strong> that comfortably outperforms dedicated metrics engines and other columnar stores in query performance.</p>
<h3 id="timeseriesintegrationincomputeengine">Time series integration in compute engine</h3>
<p>Time series processing is largely based on applying aggregation functions per time series (or <code>_tsid</code>), such as a <a href="https://opentelemetry.io/docs/specs/otel/metrics/data-model/#gauge">gauge</a> average or a <a href="https://opentelemetry.io/docs/specs/otel/metrics/data-model/#sums">counter</a> rate. These partial results are then reduced by a secondary function to produce results for the grouping dimensions, e.g. per host and process. Observability dashboards are built on top of this execution model, providing summary views of how metrics evolve over time and allowing for quick deep-dives by filtering on dimension values and time ranges.</p>
<p>To support this execution model, we introduced the <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/ts#description">TS source command</a>, providing a simple yet powerful syntax for executing such queries that combine an inner aggregation function per time series with an outer aggregation over the partial results of the former. For instance, the following query calculates the hourly sum of rate of search requests per host over the last day:</p>
<pre><code>TS metrics
  | WHERE TRANGE(1d)
  | STATS SUM(RATE(search_requests)) BY TBUCKET(1h), host
</code></pre>
<p>To execute this query, the compute engine is aware of how data is stored and applies the inner aggregation function per <code>_tsid</code> value. Since data are sorted by <code>_tsid</code>, time series aggregation functions process metric values as they get fetched, until the <code>_tsid</code> changes or the timestamp belongs to the next time bucket. This leads to vectorized execution of these functions over the fetched columns of metric values, while dimension values are only fetched (once) when the <code>_tsid</code> changes. The evaluation of the secondary aggregation function is also efficient, with partial aggregates stored in arrays of primitive values that get populated when <code>_tsid</code> values change.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf3aa04dd3516c3fb/6a859a248c294428fdb88511/image12.png" alt="Vectorized time series aggregation execution" /></p>
<p>The compute engine has inherent support for parallel query evaluation, taking full advantage of the available processing cores. Time series aggregations fully use this feature and process data points in parallel as applicable, reducing response times through improved cpu utilization.</p>
<p>Time series processing in ES|QL was introduced in version 9.2 as tech preview and reaches GA in <strong>version 9.4</strong>. We expect all metrics applications to adopt it and benefit from the much improved query performance wins.</p>
<h3 id="zerocopydatadecodingandloading">Zero-copy data decoding and loading</h3>
<p>Vectorized processing of time series data delivered immediate performance wins (<strong>8x</strong> for some queries), compared to aggregations through the <code>/_search</code> API, but the performance was still inferior when compared to competitive metrics stores. Benchmarking and profiling showed that there were too many array copies within the compute engine, between data decoding and evaluation of aggregation functions. To that end, the following optimizations were introduced:</p>
<ul>
<li>The codec for TSDS was extended to decode on-disk data directly into primitive arrays inside blocks that the compute engine uses to evaluate time series aggregations. No additional copies required, as the compute engine can bulk-read these blocks and process their arrays, one column at a time.</li>
<li>Blocks containing a single value N times are represented as constant blocks with these 2 values, as opposed to an array with length N, a form of in-memory run-length encoding. Filtering and aggregation operations were extended to efficiently consume these blocks. This reduced memory pressure and cpu overhead for the <code>_tsid</code> and dimension fields, as their values get clustered due to index sorting.</li>
<li>Documents with null values for the aggregated metric fields are filtered out at the Lucene level, before they get decoded and copied into blocks.</li>
<li>All filters and regular expressions on the timestamp and dimension fields get pushed down to Lucene that makes use of doc value skippers to efficiently filter out non-matching docs.</li>
</ul>
<p>Combined, these optimizations led to query execution speedups exceeding <strong>10x</strong> (totaling 80x when combined with the 8x speedup from vectorized execution). They were included since the introduction of the TS source command in <strong>version 9.2</strong>, and fine-tuned ever since.</p>
<h3 id="optimizedcounterrateevaluation">Optimized counter rate evaluation</h3>
<p>While most time series aggregations can be trivially parallelized and evaluated, rate evaluation of cumulative counters is tricky as it requires processing all data points in order to detect counter resets (e.g. when a host restarts). To address this, the compute engine uses the <code>_tsid</code> prefix to shard time series across threads. Care has been taken to assign in-order ranges of <code>_tsid</code> values to each thread, as opposed to hash-partitioning <code>_tsid</code>s, so that each thread can scan on-disk data in order, still making use of efficient decoding and zero-copying into blocks. The performance wins are impressive, with rate evaluation performance far exceeding dedicated metrics stores as we shall see in the next section.</p>
<p>Another interesting problem for cumulative counters is how to properly calculate counter increases for the entire time bucket when there are no data points at the bucket boundary timestamps. Metrics systems often use extrapolation, extending the first and last data points of each time bucket to the boundaries, or calculate the delta between the last data point of adjacent buckets. We posit we can do better, by interpolating between the last data point of each bucket and the first of the next, to get an estimate of the value on each boundary. The delta is then calculated over the interpolated values of the lower and upper boundary of each time bucket.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9f30a4e56924facc/6a859a28e2447a3c268b085b/image10.png" alt="Counter rate interpolation across time bucket boundaries" /></p>
<h3 id="slidingwindowsupport">Sliding window support</h3>
<p>Elasticsearch has long supported aggregations bucketed by time, but it was not possible to extend the window of processed data beyond each time bucket. Using windows larger than the time bucket, e.g. a window of 5 minutes for per-minute bucketing, helps smoothen out spikes and observe the underlying trend per time series with reduced noise:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta471cf615145ac8d/6a859a2bf61d6e6e8e9c2037/image3.png" alt="Sliding window smoothing example" /></p>
<p>All <a href="https://www.elastic.co/docs/reference/query-languages/esql/functions-operators/time-series-aggregation-functions">time-series aggregation functions</a> have been extended with window support, as an optional argument. In case the window is a multiple of the time bucket (e.g. 1h window with <a href="https://www.elastic.co/docs/reference/query-languages/esql/functions-operators/grouping-functions/tbucket"><code>TBUCKET</code></a><code>(5m)</code>), the compute engine first aggregates data points over intervals matching the time bucket span, and then combines these partial results per window span. This 2-phase approach eliminates repeated scans of data points and makes optimal reuse of intermediate results, improving response times. Window support was introduced as tech preview in version 9.3 and reaches GA in <strong>version 9.4</strong>.</p>
<h3 id="efficientdatetimerounding">Efficient datetime rounding</h3>
<p>Queries on time-series data commonly include time bucketing. While data points can be trivially assigned to sub-hour time buckets, larger buckets start interfering with issues like time zones, daylight savings, variable days per month etc. Elasticsearch has elaborate logic for datetime rounding that takes these peculiarities into account, but that has relatively high cpu cost when processing time series data.</p>
<p>To mitigate this, the compute engine has been extended to identify cases where simpler logic can be employed to assign data points to time buckets. For instance, it can identify when the buckets are sub-hour or when timezones and daylight savings don't affect a particular query, and switches to simple modulo operations for datetime rounding. This led to a further <strong>30%</strong> improvement in response times for certain queries. This change is introduced in <strong>version 9.4</strong>.</p>
<h2 id="performanceevaluation">Performance evaluation</h2>
<p>To evaluate the performance of our offering and track how it evolves and improves over time, we focused on OTel metrics since (a) Open Telemetry is the industry standard for collecting metrics, with universal adoption by all cloud providers and (b) they lead to a storage layout with 1 metric per doc, a setup that traditionally hurt performance for Elasticsearch.</p>
<p>We rely on <a href="https://github.com/elastic/metricsgenreceiver">Metricsgenreceiver</a> to generate datasets. This tool is inspired by <a href="https://github.com/timescale/TSBS">TSBS</a>, producing data simulating the data points collected by the OTel <a href="https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/receiver/hostmetricsreceiver#readme">hostmetricreceiver</a>. We used two datasets:</p>
<ol>
<li>A low-cardinality setup, with 100 hosts sending metrics every 1s, containing 14k time series in total</li>
<li>A high-cardinality setup, with 10k hosts sending metrics every 10s, containing 1.4M time series in total</li>
</ol>
<p>We benchmarked on single-node deployments on EC2, using <a href="https://aws.amazon.com/ec2/instance-types/c6i/">c6i.4xlarge</a> and <a href="https://aws.amazon.com/ec2/instance-types/c8g/">c8g.8xlarge</a> machines for the low- and high-cardinality datasets respectively.</p>
<p>For competitive comparison, we used Prometheus (v.3.11.1), Mimir (v.3.0.6.) and ClickHouse (v26.3.9.8-lts). Prometheus and Mimir have proper time series processing, e.g. for counter rate, whereas ClickHouse <a href="https://clickhouse.com/docs/use-cases/time-series/analysis-functions">lacks such support</a> and only provides approximate values at best (for instance, it can't track counter resets consistently). We still report response times for ClickHouse to showcase that, once we optimize Elasticsearch for columnar query processing, it can exceed competing columnar engines even when they don't process the data per time series as expected.</p>
<p>We strived to use the default configuration for every system (including Elasticsearch), without tweaking them to optimize performance for the particular workload. This helps understand the user experience when systems are deployed by novice users, without much experience (or time) to tweak before receiving metrics traffic and setting up dashboards. We focused on single-node runs to keep noise low and accommodate all systems (Prometheus doesn't offer a multi-node setup out of the box). Elasticsearch performance provably scales well with the number of nodes; we plan to share scalability results in a future post.</p>
<h3 id="storageefficiencyandindexingthroughput">Storage efficiency and indexing throughput</h3>
<p>Our efforts to improve storage efficiency paid big dividends. Performance on OTel metrics dropped <strong>from 25 to 3.75</strong> bytes per data point, in a year. Such an improvement, on top of an offering already optimized for time series, is really impressive and very rare in the industry:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2f9e6b91c5dcdaa1/6a859a2e98292617ab582db0/image1.png" alt="Storage efficiency improvements over time" /></p>
<p>The competitive picture looks favorable at this point, with Elasticsearch:</p>
<ul>
<li>Slightly outperforming Mimir in storage efficiency and indexing throughput</li>
<li>Outperforming Prometheus by 2.5x in storage efficiency and by a small margin in indexing throughput</li>
<li>Outperforming ClickHouse by 2x in storage efficiency and by 40% in indexing throughput</li>
</ul>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8ccb2947bcbce791/6a859a31f9373d7ace96eb5b/image7.png" alt="Storage efficiency comparison across systems" /></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt96ed82b099550f20/6a859a344710c6021ad3c055/image11.png" alt="Indexing throughput comparison across systems" /></p>
<h3 id="queryperformance">Query performance</h3>
<p>The novel columnar engine for metrics processing proves very efficient in practice. We used a mix of queries based on gauge averages and counter rates, the most common operations that require different optimization approaches. The queried interval was 4 hours of data, covering all time series per metric.</p>
<p>ClickHouse doesn't support time series aggregations, so the results have limited value and are not directly comparable to Prometheus or Mimir that natively support time series processing. We used the published <a href="https://clickhouse.com/docs/use-cases/time-series/analysis-functions">guidelines</a> to adjust each query to get similar results to the extent possible. The point is to show how our columnar engine compares to generic columnar stores.</p>
<p>Here is a summary of the results:</p>
<p>| Query type | vs Mimir | vs Prometheus | vs ClickHouse †   |
|---|---|-------------------|-------------------|
| Gauge average | up to 30x faster | up to 30x faster  | up to 8x faster   |
| Counter rate | up to 30x faster | up to 30x faster  | up to 3.5x faster |
| Prefix filter on host name | up to 5x faster | up to 5x faster | up to 3x faster   |
| Gauge average with window | up to 25x faster | up to 25x faster | up to 4x faster   |</p>
<p>†ClickHouse lacks native support for time series aggregations and counter reset detection.</p>
<h4 id="gaugeaverage">Gauge average</h4>
<p>We compared performance of evaluating the per-host hourly average of average memory utilization per time series, using the following queries:</p>
<pre><code># PromQL
avg by (host.name) (avg_over_time(system.memory.utilization[1h]))
</code></pre>
<pre><code># ES|QL
TS metrics-hostmetricsreceiver.otel-default
| STATS AVG(AVG_OVER_TIME(system.memory.utilization)) BY host.name, TBUCKET(1h)
</code></pre>
<p>Elasticsearch comfortably outperforms the other systems by up to 30x, in both low and high cardinality datasets:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf153d92f6c1f2452/6a859a378c2944279ab8851b/image14.png" alt="Gauge average query performance — low cardinality" /></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta53ce295d2c310ee/6a859a39ba7acca4239916b7/image2.png" alt="Gauge average query performance — high cardinality" /></p>
<h4 id="counterrate">Counter rate</h4>
<p>We next compared performance of evaluating the per-host hourly average of cpu rate, using the following queries:</p>
<pre><code># PromQL
avg by (host.name) (rate(system.cpu.time[1h]))
</code></pre>
<pre><code># ES|QL
TS metrics-hostmetricsreceiver.otel-default
| STATS AVG(RATE(system.cpu.time)) BY host.name, TBUCKET(1h)
</code></pre>
<p>Despite processing data points per time series in order, counter rate performance matches calculating gauge average (the involved time series have 6.6x more docs than the query above). Elasticsearch maintains its wide advantage compared to the other systems and outperforms Mimir and Prometheus by 30x in the low cardinality dataset and by 16x in the high cardinality one:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt03149d9c0cc613b5/6a859a3cf5f1a0151a2ebf26/image4.png" alt="Counter rate query performance — low cardinality" /></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2a36f24c1eb3f55d/6a859a3ef61d6e4afb9c203f/image9.png" alt="Counter rate query performance — high cardinality" /></p>
<p>It's really impressive that, for the high cardinality dataset, Elasticsearch is able to process 4 hours of data for half a million time series in less than 2 seconds, while the other systems take more than 30 seconds, leading to unresponsive dashboards for such queries. ClickHouse is also slower, despite having no logic to detect counter resets and extrapolate/interpolate deltas across buckets.</p>
<h4 id="prefixfilteronhostname">Prefix filter on host name</h4>
<p>We next compared performance of filtering on host names based on their prefix, using the following queries:</p>
<pre><code># PromQL
avg by (host_name)
  (avg_over_time(system_cpu_load_average_1m{host_name=~"host-.*"}[5m]))
</code></pre>
<pre><code># ES|QL
TS metrics-hostmetricsreceiver.otel-default
| WHERE host.name LIKE "host-*"
| STATS AVG(AVG_OVER_TIME(system.cpu.load_average.1m)) BY host.name, TBUCKET(5m)
</code></pre>
<p>Elasticsearch manages to maintain an advantage of up to 5x compared to the other systems, despite replacing the inverted index on <code>host.name</code> with a doc value skipper:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt32cffb96a5847bc2/6a859a418c29443b00b8851f/image5.png" alt="Prefix filter query performance — low cardinality" /></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt90f3cd1cabc4b8e0/6a859a44f5f1a019892ebf2a/image6.png" alt="Prefix filter query performance — high cardinality" /></p>
<h4 id="gaugeaveragewithwindow">Gauge average with window</h4>
<p>We compared the performance of time series aggregations with a window of 90 minutes and time buckets of 30 minutes, using the following queries:</p>
<pre><code># PromQL
avg by (host.name) (avg_over_time(system.memory.utilization[90m]))&amp;step=30m
</code></pre>
<pre><code># ES|QL
TS metrics-hostmetricsreceiver.otel-default
| STATS AVG(AVG_OVER_TIME(system.memory.utilization, 90m))
    BY host.name, TBUCKET(30m)
</code></pre>
<p>Elasticsearch comfortably outperforms the other systems in both low and high cardinality datasets:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5b30bd2052cb4c64/6a859a4ad6cf295c04bafe42/image13.png" alt="Gauge average with window — low cardinality" /></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6c811ebf944ec4fa/6a859a4de2447a44c98b08bd/image8.png" alt="Gauge average with window — high cardinality" /></p>
<p>Elasticsearch maintains an advantage that reaches 25x for the low cardinality dataset and 8x for the high cardinality one. ClickHouse is outperformed by close to 4x, denoting the efficiency of our approach for windowed query operations.</p>
<h2 id="whatsnextforelasticsearchmetrics">What's next for Elasticsearch metrics</h2>
<p>Elasticsearch has been extended with metrics storage and processing capabilities that outperform Prometheus, Mimir, and ClickHouse. We're making fast progress with supporting <a href="https://www.elastic.co/observability-labs/blog/elasticsearch-supports-promql">PromQL</a> and <a href="https://www.elastic.co/observability-labs/blog/prometheus-remote-write-elasticsearch-architecture">Prometheus remote write</a>, also available as tech preview in <strong>version 9.4</strong>. These extensions enable users familiar with Prometheus and relevant systems to switch their applications to Elasticsearch — no need to migrate existing dashboards. Since Prometheus integration reuses the same storage and query engine that has been presented in this article, the same performance wins are also expected for Prometheus. Furthermore, collected metrics can be queried with PromQL and ES|QL, side-by-side or in ES|QL query pipelines, further boosting the analytics capabilities far beyond what was conceivable so far with Prometheus-based systems.</p>
<p>The improvements in storage efficiency, indexing throughput and query performance are already impressive, but we're not done. We'll be introducing more refinements to the codec for time series data, further reducing bytes per data point. Batch processing of ingested metrics will be further improved, reducing synchronization overhead and redundant processing layers that are not needed for well-formatted collected metrics. We're also planning to make wider use of doc value skippers, storing pre-computed aggregates like sum and count per block of values, to shortcut data point loading and processing where applicable, as well as use more cpu-friendly partitioning and grouping operations.</p>
<h2 id="frequentlyaskedquestions">Frequently asked questions</h2>
<p><strong>What is a columnar metrics engine and why does it matter?</strong>
A columnar metrics engine stores each field in its own file rather than row-by-row, then processes queries by reading only the columns needed. For time series data, this means Elasticsearch can decode metric values, dimension fields, and timestamps independently, applying vectorized operations across each column. The result is faster aggregations and lower storage overhead compared to row-oriented stores.</p>
<p><strong>How does Elasticsearch compare to Prometheus for time series metrics storage and querying?</strong>
Elasticsearch stores OTel metrics at 3.75 bytes per data point in version 9.4, roughly 2.5x less than Prometheus. For queries, Elasticsearch outperforms Prometheus and Mimir by up to 30x in gauge average and counter rate benchmarks. For the high-cardinality dataset (1.4M time series), Elasticsearch processes 4 hours of data in under 2 seconds while Prometheus takes over 30 seconds.</p>
<p><strong>What is Elasticsearch TSDS and when should I use it?</strong>
TSDS (time-series data streams) is Elasticsearch's storage format for metrics and time series data. It sorts documents by time series identifier (<code>_tsid</code>) and timestamp, stores fields in columnar doc values, and uses specialized codecs for compression. Use TSDS for any metrics workload, particularly OpenTelemetry or Prometheus data, where storage efficiency and query speed matter.</p>
<p><strong>What is the TS source command in ES|QL?</strong>
<code>TS</code> is an ES|QL source command, GA in version 9.4, that executes time series queries using a two-level model: an inner aggregation per time series (such as <code>RATE()</code> or <code>AVG_OVER_TIME()</code>), then an outer aggregation over the results. The compute engine processes data in time series sort order, enabling vectorized and parallel execution. Example: <code>TS metrics | STATS AVG(RATE(cpu.time)) BY host.name, TBUCKET(1h)</code>.</p>
<p><strong>How did Elasticsearch go from 25 bytes to 3.75 bytes per OTel data point?</strong>
Four storage changes contributed across versions 9.1 through 9.4: replacing inverted indices with doc value skippers (-10 bytes), enabling synthetic IDs (-5 bytes), trimming sequence numbers (-4 bytes), and increasing codec block size from 128 to 512 elements (-2 bytes). The result is a 6.7x reduction in storage footprint in roughly one year.</p>
<p><strong>Can Elasticsearch replace Prometheus without migrating dashboards?</strong>
Elasticsearch supports Prometheus remote write (tech preview, version 9.4) and PromQL queries (tech preview, version 9.4). Existing Grafana dashboards using PromQL can point to Elasticsearch with minor modifications, and we expect to offer a seamless migration experience when our Prometheus offering reaches GA. The same TSDS storage and ES|QL compute engine power both PromQL and ES|QL queries, so the performance improvements apply to both.</p>
<p><strong>What are doc value skippers and why do they matter for metrics?</strong>
Doc value skippers are Lucene index structures that store min/max values for blocks of documents. For TSDS, which sorts by <code>_tsid</code> and timestamp, they replace inverted indices on dimension fields and <code>@timestamp</code>. This reduces storage by up to 10 bytes per data point and cuts indexing CPU by about 10%, with no measured regression in query performance for time range and dimension filters.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/elasticsearch-columnar-metrics-engine-30x-faster-prometheus</link>
    <guid isPermaLink="false">elasticsearch-columnar-metrics-engine-30x-faster-prometheus</guid>
    <category><![CDATA[Metrics]]></category>
    <category><![CDATA[OpenTelemetry]]></category>
    <dc:creator><![CDATA[Kostas Krikellas,Martijn Van Groningen,Nhat Nguyen,Felix Barnsteiner]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc2478264f91421cc/6a859a514710c6cf3fd3c083/header.jpg" length="0" type="image/jpeg"/>
    <pubDate>Tue, 19 May 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[How to trace MCP server tool calls with OpenTelemetry and Elastic APM]]></title>
    <description><![CDATA[Add OpenTelemetry tracing to an MCP server, visualize tool call performance in Elastic APM, and query the trace data from Claude Desktop using the Agent Builder MCP.]]></description>
    <content:encoded><![CDATA[<p>An MCP server is just a Node process, which means OpenTelemetry instrumentation is one <code>--import</code> flag away. What is new is what happens after the traces land in <a href="https://www.elastic.co/docs/solutions/observability/apm">Elastic APM</a>. The same Claude Desktop session that produced them can query them back through the <a href="https://www.elastic.co/docs/explore-analyze/ai-features/agent-builder/mcp-server">Elastic Agent Builder MCP</a>. The agent analyzes its own tool-call latency, identifies slow tools, and explains failures without leaving the chat. Observability stops being a dashboard that the human checks after the fact and becomes the context the agent uses while working. This post walks through the OTel semantic conventions for MCP, the wrapper pattern for tool spans, and how the loop closes on the Elastic side.</p>
<h2 id="prerequisites">Prerequisites</h2>
<ul>
<li><p>Elastic Cloud hosted (9.3+) or <a href="https://www.elastic.co/docs/deploy-manage/deploy/elastic-cloud/serverless">serverless</a></p></li>
<li><p><a href="https://claude.com/download">Claude Desktop</a></p></li>
<li><p>An MCP server instrumented with the Elastic Distribution of OpenTelemetry (EDOT). We cover how to instrument one below.</p></li>
</ul>
<h2 id="theobservabilitygapinmcpservers">The observability gap in MCP servers</h2>
<p>MCP servers ship with no built-in observability, which means tool-call latency, errors, and performance baselines are invisible to developers.
<a href="https://modelcontextprotocol.io/docs/getting-started/intro">MCP (Model Context Protocol) servers</a> are increasingly used as infrastructure for AI-powered applications, giving AI models access to databases, APIs, internal <a href="https://modelcontextprotocol.io/specification/2025-06-18/server/tools">tools</a>, and business data.
The MCP SDK does not instrument any of it.</p>
<p>The gap shows up in three concrete ways:</p>
<ul>
<li>When a tool call takes 3 seconds, you don't know if the bottleneck is in your business logic, a downstream API, or the data layer.</li>
<li>When a tool call fails, you get the error message but no context about what the server was doing before it failed.</li>
<li>When you add a new tool, you have no baseline to compare its performance against.</li>
</ul>
<p>These are the same problems any backend service faces.
The answer is the same one backend developers have used for years: distributed tracing with <a href="https://www.elastic.co/docs/solutions/observability/apm/opentelemetry">OpenTelemetry</a>.</p>
<p>MCP servers are standard programmatic processes.
There is nothing special about them from an instrumentation perspective.
You add the <a href="https://opentelemetry.io/docs/languages/js/getting-started/nodejs/">OTel SDK</a>, define spans around your tool handlers, and ship traces to your backend.
The only new part is knowing which span names and attributes to use so your traces are meaningful and consistent.</p>
<h2 id="whatwebuilt">What we built</h2>
<p>For this article, we use the <a href="https://www.npmjs.com/package/@modelcontextprotocol/server-everything"><code>@modelcontextprotocol/server-everything</code></a> package, the official reference MCP server published by Anthropic.
It ships with a set of tools that cover the common patterns you will find in real-world MCP servers: simple request/response, parameterized calls, long-running operations, and calls that return structured data.</p>
<p>The server exposes several tools.
In this article we use three of them:</p>
<ul>
<li><p><code>echo</code>: receives a string and returns it unchanged. A minimal request/response tool, useful for verifying that the instrumentation pipeline works end to end.</p></li>
<li><p><code>get-sum</code>: receives two numbers and returns their sum. Represents a parameterized tool with simple business logic.</p></li>
<li><p><code>trigger-long-running-operation</code>: starts a multi-step operation that takes several seconds to complete. Simulates tools that call downstream APIs or run expensive computations.</p></li>
</ul>
<p>We instrumented it with <a href="https://www.elastic.co/docs/reference/opentelemetry/edot-sdks/node/setup">EDOT Node.js</a> (<code>@elastic/opentelemetry-node</code>), which is Elastic's distribution of the OpenTelemetry SDK.
EDOT replaces the five or six individual OTel packages you would otherwise install and adds the <a href="https://www.elastic.co/docs/reference/opentelemetry/compatibility/edot-vs-upstream"><code>elasticapm</code> connector</a> that the Kibana APM UI needs to build its service maps, transaction groupings, and latency charts.
Without that connector, raw OTLP data arrives in Elasticsearch but the APM views have nothing to build from.</p>
<p>The architecture looks like this:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte33fadb139d4917e/6a7f0d48c2cc0911f82495b0/architecture.png" alt="Architecture diagram showing Claude Desktop connected to two MCP servers: an instrumented MCP server that emits OpenTelemetry traces to Elastic APM, and the Elastic Agent Builder MCP that queries those traces back from Elasticsearch" /></p>
<p>This is the loop: Claude executes tools, generates telemetry, and then uses a second MCP to analyze that telemetry.
The observability data becomes something the AI can reason about, not just something that sits in a dashboard waiting for a human to check it.</p>
<p>Both MCP servers are active simultaneously in Claude Desktop.
The instrumented MCP generates telemetry.
The Agent Builder MCP lets us query it.</p>
<p>To connect Claude Desktop to both servers, the <code>claude_desktop_config.json</code> looks like this:</p>
<pre><code>{
  "mcpServers": {
    "everything": {
      "command": "node",
      "args": [
        "--import",
        "/path/to/node_modules/@elastic/opentelemetry-node/import.mjs",
        "/path/to/everything/dist/index.js",
        "stdio"
      ],
      "env": {
        "OTEL_SERVICE_NAME": "everything-mcp-server",
        "OTEL_EXPORTER_OTLP_ENDPOINT": "https://&lt;your-otlp-endpoint&gt;",
        "OTEL_EXPORTER_OTLP_HEADERS": "Authorization=ApiKey &lt;your-api-key&gt;",
        "OTEL_LOG_LEVEL": "none"
      }
    },
    "elastic-agent-builder": {
      "command": "npx",
      "args": [
        "mcp-remote",
        "https://&lt;your-kibana-url&gt;/api/agent_builder/mcp",
        "--header",
        "Authorization:ApiKey &lt;your-api-key&gt;"
      ]
    }
  }
}
</code></pre>
<p>The <code>--import /path/to/@elastic/opentelemetry-node/import.mjs</code> flag is all it takes for zero-code auto-instrumentation.
But auto-instrumentation only captures HTTP calls, database queries, and other Node.js instrumented libraries.
MCP tool calls are application logic, and application logic needs manual spans.</p>
<h2 id="whattracesmcptoolcallsgenerate">What traces MCP tool calls generate</h2>
<p>The OpenTelemetry specification includes <a href="https://opentelemetry.io/docs/specs/semconv/gen-ai/mcp/">official semantic conventions for MCP</a>.
Following them means your traces are consistent, searchable by name across tools and teams, and compatible with any OTel-aware backend, including Elastic APM.</p>
<p><strong>Span naming</strong> follows the pattern <code>{mcp.method.name} {target}</code>.
For a tool call, this becomes <code>tools/call echo</code> or <code>tools/call get-sum</code>.
This is what you will see as the transaction name in Kibana APM.</p>
<p><strong>Key attributes</strong> on each span:</p>
<p>| Attribute               | Value            | Purpose                   |
| ----------------------- | ---------------- | ------------------------- |
| <code>mcp.method.name</code>       | <code>tools/call</code>     | The MCP protocol method   |
| <code>gen_ai.tool.name</code>      | <code>echo</code>           | The specific tool invoked |
| <code>gen_ai.operation.name</code> | <code>execute_tool</code>   | GenAI semantic convention |
| <code>error.type</code>            | error class name | Set only on failure       |</p>
<p>The wrapper pattern that creates these spans looks like this:</p>
<pre><code>const tracer = trace.getTracer('everything-mcp-server', '1.0.0');

function withToolSpan(toolName, fn) {
  return tracer.startActiveSpan(`tools/call ${toolName}`, (span) =&gt; {
    span.setAttribute('mcp.method.name', 'tools/call');
    span.setAttribute('gen_ai.tool.name', toolName);
    span.setAttribute('gen_ai.operation.name', 'execute_tool');

    try {
      const result = fn();
      span.end();
      return result;
    } catch (err) {
      span.recordException(err);
      span.setStatus({ code: SpanStatusCode.ERROR, message: err.message });
      span.setAttribute('error.type', err.constructor.name);
      span.end();
      throw err;
    }
  });
}
</code></pre>
<p>Each tool handler wraps its logic in <code>withToolSpan</code>.
The result is a named span in Elastic APM for every tool invocation, with duration, status, and error details attached.</p>
<p><strong><em>Security note:</em></strong> <em>The OTel spec defines two optional attributes for tool calls:</em> <code>gen_ai.tool.call.arguments</code> <em>and</em> <code>gen_ai.tool.call.result</code>_. Both are flagged as potentially containing sensitive data. The_ <code>get-env</code> <em>tool in the everything server is a good example of why this matters: it returns all environment variables, which may include API keys and credentials. Capture these attributes only if you have confirmed the data is safe to store in your observability backend, and consider masking or filtering at the SDK level before export.</em></p>
<h2 id="kibanaapmexploringmcpserverperformance">Kibana APM: exploring MCP server performance</h2>
<p>After starting Claude Desktop with both MCPs configured and triggering a few tool calls, the <code>everything-mcp-server</code> service appears in Kibana under <strong>Observability &gt; Applications &gt; Services Inventory</strong>:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdbd9c14fcc24ffb0/6a7f0d4bbd2198f4547580d3/services-inventory.png" alt="Kibana APM Services Inventory view showing the everything-mcp-server entry with latency, throughput, and error rate columns populated" /></p>
<h3 id="transactionsview">Transactions view</h3>
<p>Kibana groups traces by transaction name.
Because we follow the semantic conventions, each tool gets its own row: <code>tools/call echo</code>, <code>tools/call get-sum</code>, <code>tools/call trigger-long-running-operation</code>.
You can immediately see latency, throughput, and error rate per tool without any configuration.</p>
<p>This is where the value of consistent span naming becomes concrete.
If you have five different developers adding tools to an MCP server and everyone follows <code>tools/call {toolName}</code>, the APM UI stays organized automatically.</p>
<h3 id="tracewaterfall">Trace waterfall</h3>
<p>Clicking on a specific trace shows the waterfall view.
For a single tool call, the waterfall is straightforward: one span covering the full execution.
If your tool handler makes downstream HTTP requests or database queries that are auto-instrumented, those appear as child spans.
You can see exactly how much time was spent in business logic versus waiting for external calls.</p>
<h3 id="latencydistribution">Latency distribution</h3>
<p>The latency chart shows p50, p95, and p99 distribution across all executions of a given tool.
This makes it easy to distinguish between tools that are consistently fast and those that have occasional outliers.
The <code>trigger-long-running-operation</code> tool, for example, shows a wide distribution depending on how many steps were requested: a useful baseline for understanding expected execution time ranges before setting alerts.</p>
<h3 id="errortracking">Error tracking</h3>
<p>Failed tool calls appear in the Errors panel with their full stack trace, the span attributes attached at the time of failure, and a count of how many times the error has occurred.
If you record the exception with <code>span.recordException(err)</code>, Kibana links the error directly to the trace that produced it.</p>
<h2 id="closingtheloopwiththeagentbuildermcp">Closing the loop with the Agent Builder MCP</h2>
<p>The <a href="https://www.elastic.co/docs/explore-analyze/ai-features/agent-builder/mcp-server">Elastic Agent Builder MCP</a> lets Claude query its own trace data from Elasticsearch in the same chat session that produced the traces.
The Agent Builder MCP can query any Elasticsearch index the API key has access to, and APM traces are stored under <code>.ds-traces-apm.otel-default-*</code>.
Granting the Agent Builder API key read access to those indices is what closes the loop: the agent that executed the tool calls can now reason about how they performed.</p>
<p>Here is what this looks like in practice.
To generate traces, let's ask in Claude Desktop: <em>"Use the echo tool to say hello, then use get-sum to add 1337 and 42, then run a long-running operation with 3 steps."</em></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf3f00a45a235deb1/6a7f0d4eeab5be043420a709/claude-tool-execution.png" alt="Claude Desktop conversation showing three tool calls executed in sequence: echo, get-sum, and trigger-long-running-operation, each with its result rendered inline" /></p>
<p>Claude executes three tool calls on the instrumented MCP.
Three spans land in Elastic APM.</p>
<p>Now, without leaving the chat, let's try querying the traces by asking: <em>"Search the APM trace data from the last 10 minutes. What tool calls were made, and how long did each one take?"</em></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt74ef63c895e22bbc/6a7f0d51bd219823847580d9/claude-trace-query.png" alt="Claude Desktop chat where the model uses the Elastic Agent Builder MCP to run an ES|QL query against the APM traces index and reports tool names, durations, and status in natural language" /></p>
<p>We can confirm the information against the services data:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb362dbef6539a49f/6a7f0d5305b7b511c418b998/services-data-confirmation.png" alt="Kibana APM transactions view for everything-mcp-server showing the same three tool call transactions and their durations as returned by Claude" /></p>
<p>Claude uses the Agent Builder MCP to run a query against the traces index.
It returns the tool names, durations, and status from the actual trace data, then synthesizes an answer in natural language.</p>
<p>You can go further by asking:</p>
<ul>
<li><p>"Which of those tool calls had the highest p95 latency?"</p></li>
<li><p>"Did any tool calls fail? If so, what was the error message?"</p></li>
<li><p>"Compare the latency of the echo tool vs get-sum across all calls in the last hour."</p></li>
</ul>
<p>Each of these questions translates into an ES|QL query via the Agent Builder's <code>platform.core.execute_esql</code> tool, run against the APM trace indices.</p>
<h2 id="whyelasticformcpobservability">Why Elastic for MCP observability</h2>
<p><strong>The Agent Builder closes the loop:</strong> this is the part that is specific to the Elastic ecosystem.
Because APM data lives in Elasticsearch, and Elasticsearch is queryable via the Agent Builder MCP, you can bring your AI agent's own observability data back into the conversation.
Your AI can reflect on its own performance and spot anomalies.</p>
<p><strong>APM UI built for <a href="https://www.elastic.co/observability-labs/blog/openai-tracing-elastic-opentelemetry">distributed tracing</a>:</strong> Kibana's APM interface is designed for exactly this kind of data: named transactions, trace waterfalls, latency percentiles, error tracking with stack traces, and service maps.</p>
<p><strong>Managed <a href="https://www.elastic.co/docs/reference/opentelemetry/motlp">OTLP</a> endpoint:</strong> Elastic APM accepts OTLP directly since Elastic 8.x.
You point <code>OTEL_EXPORTER_OTLP_ENDPOINT</code> at your APM server and it works.</p>
<p><strong><a href="https://www.elastic.co/observability-labs/blog/elastic-distributions-opentelemetry-ga">EDOT</a> simplifies the setup:</strong> the <code>elasticapm</code> connector is included, which means the APM UI views work without any additional configuration.</p>
<h2 id="conclusion">Conclusion</h2>
<p>MCP servers do not need special observability tooling.
They are programmatic processes, and OpenTelemetry is the right instrument for processes.
The OTel MCP semantic conventions are stable and give you a consistent naming scheme that scales across tools and teams.</p>
<p>What makes the Elastic setup interesting is not the instrumentation itself.
It is the second MCP.
When your observability data lives in Elasticsearch, you can query it from the same AI session that generated it.
That feedback loop is new, and it opens up use cases that dashboards alone cannot cover: real-time anomaly questions, automated triage, and <a href="https://www.elastic.co/observability-labs/blog/elastic-agent-skills-observability-workflows">AI-assisted incident investigation</a> from the chat interface your team is already using.</p>
<h2 id="nextsteps">Next steps</h2>
<ul>
<li><p><a href="https://www.elastic.co/docs/reference/opentelemetry/edot-sdks/node/setup">Set up EDOT Node.js</a></p></li>
<li><p><a href="https://www.elastic.co/docs/explore-analyze/ai-features/agent-builder/mcp-server">Configure the Elastic Agent Builder MCP</a></p></li>
<li><p><a href="https://opentelemetry.io/docs/specs/semconv/gen-ai/mcp/">OTel semantic conventions for MCP</a></p></li>
<li><p><a href="https://www.elastic.co/observability-labs/blog/elastic-managed-otlp-endpoint-ga-elastic-cloud-hosted">Managed OTLP endpoint on Elastic Cloud (now GA)</a></p></li>
<li><p><a href="https://www.elastic.co/observability-labs/blog/elastic-opentelemetry-openai">Instrumenting Node.js applications with EDOT</a></p></li>
</ul>
<h2 id="frequentlyaskedquestions">Frequently asked questions</h2>
<p><strong>How do I add tracing to an MCP server?</strong>
Use the OpenTelemetry SDK and follow the official OTel MCP semantic conventions. With the Elastic Distribution of OpenTelemetry (EDOT) for Node.js, a single <code>--import</code> flag enables auto-instrumentation for HTTP and database calls. Tool-call spans need to be added manually using a wrapper that sets <code>mcp.method.name</code>, <code>gen_ai.tool.name</code>, and <code>gen_ai.operation.name</code>.</p>
<p><strong>Why are my MCP tool calls slow and how do I find the bottleneck?</strong>
Without tracing, an MCP tool call is a black box: you see the result but not where the time went. Instrument the server with OpenTelemetry, ship traces to Elastic APM, and use the trace waterfall view in Kibana to see exactly how much time was spent in business logic versus downstream HTTP or database calls.</p>
<p><strong>Can I send MCP server traces to Elastic APM without a custom collector?</strong>
Yes. Elastic APM accepts OTLP directly. Set <code>OTEL_EXPORTER_OTLP_ENDPOINT</code> to your APM endpoint and <code>OTEL_EXPORTER_OTLP_HEADERS</code> with an API key, and traces flow in. EDOT bundles the <code>elasticapm</code> connector that the Kibana APM UI needs for service maps and transaction grouping.</p>
<p><strong>What span names and attributes should I use for MCP tool calls?</strong>
Follow the OpenTelemetry MCP semantic conventions: name spans <code>{mcp.method.name} {target}</code> (for example, <code>tools/call echo</code>), and set <code>mcp.method.name</code>, <code>gen_ai.tool.name</code>, and <code>gen_ai.operation.name=execute_tool</code>. On failure, set <code>error.type</code> to the error class name. Consistent naming means the Elastic APM transactions view groups your tool calls automatically.</p>
<p><strong>How is this different from sending MCP traces to Datadog or Grafana?</strong>
Any OTel-compatible backend can receive the traces. The Elastic-specific part is the Agent Builder MCP, which lets the same AI agent that generated the traces query them back from Elasticsearch in natural language. That feedback loop, where the AI reasons about its own tool-call performance, is not available with backends that do not expose their data through an MCP server.</p>
<p><strong>Should I capture MCP tool call arguments and results in my traces?</strong>
The OTel spec defines <code>gen_ai.tool.call.arguments</code> and <code>gen_ai.tool.call.result</code> as optional and warns they may contain sensitive data. Tools like <code>get-env</code>, which returns environment variables, illustrate the risk: API keys and credentials can land in your observability backend. Capture these only when the data is safe to store, and consider masking at the SDK level before export.</p>
<p><strong>Does this work for MCP servers written in languages other than Node.js?</strong>
The OpenTelemetry MCP semantic conventions are language-agnostic. EDOT is available for Node.js, Java, Python, .NET, and other languages, and any of them can send OTLP to Elastic APM. The wrapper pattern shown in this post translates directly: open a span around the tool handler, set the standard attributes, record exceptions on failure.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/mcp-tracing-opentelemetry-elastic-apm</link>
    <guid isPermaLink="false">mcp-tracing-opentelemetry-elastic-apm</guid>
    <category><![CDATA[APM]]></category>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[AI]]></category>
    <dc:creator><![CDATA[Jeffrey Rengifo]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt01a77347914d1229/6a7f0d57c2e9145f21016c0c/header_image.png" length="0" type="image/png"/>
    <pubDate>Thu, 14 May 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Kubernetes observability: MCP specialist agents for safer EKS triage]]></title>
    <description><![CDATA[Scope a specialist EKS MCP agent for cluster checks while the Elastic AI Agent triages; fix a service misconfiguration using the specialist agent in a few prompts.]]></description>
    <content:encoded><![CDATA[<p>Elastic Observability shows you which services and edges in your service map are failing. You may still need to access details like live kubernetes service specs and containerPort to targetPort mapping, which still reside at the cluster. They can be made available in Elasticsearch via EKS MCP. The fix is to equip your Elastic AI Agent with a focused set of EKS tools, through a specialist agent. The Elastic AI agent keeps its stock tools and remains the only surface your SREs interact with. A specialist K8s Troubleshooter agent carries ~20 EKS MCP tools, scoped to a single IAM identity and Kubernetes RBAC. They hand off through an <a href="https://www.elastic.co/docs/explore-analyze/workflows">Elastic Workflow</a> that calls the <a href="https://www.elastic.co/docs/explore-analyze/ai-features/elastic-agent-builder">Agent Builder</a> <a href="https://www.elastic.co/docs/explore-analyze/ai-features/agent-builder/kibana-api#chat-and-conversations">converse</a> API, so the boundary between observability reasoning and cluster actions is callable, reviewable, and auditable. To prove it works, we break targetPort on product-catalog in <a href="https://github.com/elastic/opentelemetry-demo">elastic-opentelemetry-demo</a> and recover it in 4 prompts on a single thread.</p>
<h2 id="problemcontext">Problem context</h2>
<p>Outages often show up as correlated errors on multiple services like checkout, frontend, and recommendation in Elasticsearch.
That pattern can mean a shared dependency, or it can mean Kubernetes is misleading callers: wrong targetPort, empty Endpoints, or pods that never become ready.
Observability tools like Elasticsearch tell you <em>that</em> callers fail and <em>which</em> edges look wrong.
They generally do not fetch the live Service spec or compare containerPort to targetPort for you.</p>
<p>The Elastic AI Agent in Agent Builder is built for APM, logs, metrics, dependencies, and service maps.
It is not a full EKS operations console.
You could attach all EKS MCP tools to the same agent, but long tool lists increase wrong-tool calls, slow planning, and widen blast radius if a prompt accidentally asks for mutating actions.</p>
<h2 id="solutionoverview">Solution overview</h2>
<p>Use <strong>Elastic AI Agent</strong> as the only agent your SRE chats with.
It reasons from Elasticsearch first.
When evidence points to cluster config, it calls a workflow tool that invokes the <strong>K8s Troubleshooter agent</strong> over <code>/api/agent_builder/converse</code> with a structured <code>user_prompt</code>.
The <strong>K8s Troubleshooter agent</strong> carries only the EKS MCP tools, and cluster access stays scoped to one specialist identity, IAM, and RBAC. You can audit like any other integration.</p>
<p>Elasticsearch reaches EKS through an in-cluster bridge, exposed to Kibana as an MCP connector with a shared secret.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt00eb659814dcf87b/6a7f05ce05b7b5561e18b681/solution_overview.png" alt="Solution Overview" /></p>
<h2 id="beforeyoustart">Before you start</h2>
<p>You need:</p>
<ul>
<li>An EKS cluster with kubectl configured.</li>
<li>An Elasticsearch 9.3+ deployment, an OTLP endpoint, an Elasticsearch API key, Agent Builder, and rights to create agents, MCP tools, and Workflows.</li>
<li>An AI Connector in Elasticsearch for your chosen LLM.</li>
<li>Budget two to four hours the first time you run these steps.</li>
</ul>
<h2 id="implementationwalkthrough">Implementation walkthrough</h2>
<h3 id="step1deploytheelasticopentelemetrydemoandshiptelemetrytoelasticobservability">Step 1: deploy the Elastic OpenTelemetry Demo and ship telemetry to Elastic Observability</h3>
<p>Follow <a href="https://github.com/elastic/opentelemetry-demo"><strong>elastic/opentelemetry-demo</strong></a> for Kubernetes and deploy elastic-opentelemetry-demo application to your EKS cluster.
Configure your Elasticsearch OTLP endpoint and API key, confirm workloads are running, and note the namespace.
In Kibana (APM, Logs, or Service Map), confirm data for checkout, frontend, recommendation, and product-catalog.</p>
<p>If you see healthy traffic to <code>product-catalog</code>, you are ready for the failure drill.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt92e83f13b69e3bc4/6a7f05d1e3a21975f399f1a4/04-service-map-or-errors.png" alt="Healthy Elastic Observability service map for demo services." /></p>
<h3 id="step2runtheeksmcpbridgeregistertheconnectorandbulkimporteksmcptools">Step 2: run the EKS MCP bridge, register the connector, and bulk import EKS MCP tools</h3>
<p>Complete the steps in <a href="https://github.com/ramp-km/aws-eks-mcp-setup/blob/main/README.md"><strong>aws-eks-mcp-setup</strong></a> end to end.
The flow you would be following is: </p>
<ol>
<li>Build and push the EKS MCP Bridge image</li>
<li>Create IAM policies</li>
<li>Create IRSA Service Account</li>
<li>Map IRSA role in aws-auth and apply Kubernetes RBAC</li>
<li>Deploy the bridge with a strong API_ACCESS_TOKEN to the EKS cluster</li>
<li>Connect Elastic Agent Builder with EKS MCP</li>
</ol>
<p>A green MCP connector proves Kibana can reach the bridge.</p>
<p>For production, restrict LoadBalancer security groups to known Elasticsearch egress, prefer TLS on real paths, store tokens in Kubernetes Secrets, and use read-only MCP modes when you only diagnose.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1bf20bb14fb36532/6a7f05d477b034a7333ff22b/05-eks-mcp.png" alt="MCP connector pointed at the EKS bridge." /></p>
<h3 id="step3createak8stroubleshooteragentwithekstoolsonly">Step 3: create a <strong>K8s Troubleshooter agent</strong> with EKS tools only</h3>
<p>In Agent Builder, create an agent with agent ID <code>k8s_troubleshooter</code>, display name <code>K8s Troubleshooter</code>, and custom instructions from <a href="https://github.com/ramp-km/blogs/blob/main/Custom%20K8s%20Troubleshooter/k8s_troubleshooter_agent.md">k8s_troubleshooter_agent</a>.
Attach only EKS MCP tools to this agent.</p>
<p>Chat directly with <strong>K8s Troubleshooter agent</strong> once and confirm a harmless read (for example list pods in the demo namespace).</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5c03fc63aa35b190/6a7f05d7c2e914cf690168ff/02-k8s-troubleshooter-agent.png" alt="K8s Troubleshooter agent" /></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb8c1ac20d8b1e65c/6a7f05db6693f8f101663c85/02-k8s-troubleshooter-agent-2.png" alt="K8s Troubleshooter agent with EKS MCP tools attached." /></p>
<h3 id="step4elasticsearch93onlyclonetheobservabilityagentwithoutekstools">Step 4 (<code>Elasticsearch 9.3 only</code>): clone the Observability Agent without EKS tools</h3>
<p>Clone the bundled <code>Observability Agent</code> (Agent Builder → Manage Agents → Observability Agent → Clone) and name it <strong>Elastic AI Agent</strong> so it keeps the stock Observability system instructions and tools.
Do not attach EKS MCP tools to this copy.</p>
<p>The parent <strong>Elastic AI Agent</strong> stays an observability-first interface for whoever chats with it.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7a3f591e0527edc7/6a7f05dfe88c65e1bb00b3b8/01-observability-agent-v2.png" alt="Observability Agent v2 tools and instructions." /></p>
<h3 id="step5createtheworkflowandmakeitacallabletool">Step 5: create the workflow and make it a callable tool</h3>
<p><a href="https://www.elastic.co/docs/explore-analyze/workflows/get-started/build-your-first-workflow">Create</a> a new Elastic Workflow by importing <a href="https://github.com/ramp-km/blogs/blob/main/Custom%20K8s%20Troubleshooter/k8s_troubleshooter_workflow.yaml">k8s_troubleshooter_workflow.yaml</a> and enable it.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt24957065ecb6477b/6a7f05e233fa8a71032023ca/05-workflow.png" alt="Kibana Workflows editor: k8s_troubleshooter workflow YAML enabled." /></p>
<p>Create a new tool in Agent Builder of type <code>Workflow</code>. Select the <code>k8s_troubleshooter</code> workflow, set tool ID <code>custom.k8s_troubleshooter</code>, and set the description to <code>Tool to triage and troubleshoot kubernetes related issues</code> (or equivalent wording your team standardizes on).</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf334d5d0b642e7fd/6a7f05e52f00b2c209efe8ce/05-workflow_tool_k8s_troubleshooter.png" alt="Agent Builder: Workflow tool wired to k8s_troubleshooter with custom tool id and description." /></p>
<p>On <strong>Elastic AI Agent</strong>, attach the <code>custom.k8s_troubleshooter</code> workflow tool that you just created.</p>
<p>The parent’s tool list should show the <code>custom.k8s_troubleshooter</code> workflow tool attached, and <strong>K8s Troubleshooter agent</strong> should still answer when invoked on its own.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9b029c737e738946/6a7f05e873d9bd41d829d874/03-workflow-tool-parent-agent.png" alt="Workflow registered as a tool on the parent agent." /></p>
<h3 id="step6injecttheproductcatalogservicemisconfiguration">Step 6: inject the product-catalog service misconfiguration</h3>
<p>Save the original <code>targetPort</code>, then patch to a wrong value (for example 9999).</p>
<pre><code>kubectl get svc -A | grep product-catalog
kubectl get svc product-catalog -n YOUR_NAMESPACE -o yaml
</code></pre>
<pre><code>kubectl patch svc product-catalog -n YOUR_NAMESPACE --type='json' \
  -p='[{"op": "replace", "path": "/spec/ports/0/targetPort", "value": 9999}]'
</code></pre>
<pre><code>kubectl rollout restart deployment/checkout deployment/recommendation deployment/frontend -n YOUR_NAMESPACE
</code></pre>
<p>Callers still resolve Endpoints, but traffic lands on a port the container does not listen on, so Elasticsearch shows downstream errors on checkout, frontend, and recommendation.</p>
<p>You now have symptoms in Elasticsearch and a clear kubernetes cluster-side fault.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb7371f0ca6cee50d/6a7f05ebb4377063a24d69dd/06-demo-services-service-map-or-errors.png" alt="Elastic Observability service map or error view after the misconfiguration." /></p>
<h3 id="step7runtwopromptsontheparentagent">Step 7: run two prompts on the parent agent</h3>
<p>Use AI Agent chat on <strong>Elastic AI Agent</strong>, not on the specialist.</p>
<p><code>Note:</code> If you are using Elasticsearch 9.3, make sure you use the <strong>Elastic AI Agent</strong> that you created, not the stock agent.</p>
<p>Prompt 1: <em>Why are failure transactions increasing for services like checkout and frontend?</em></p>
<p>Expect <strong>Elastic AI Agent</strong> to narrow the issue to the product-catalog service and note possible configuration issues as one of the probable causes, without yet invoking the <code>custom.k8s_troubleshooter</code> tool.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2bf7243ff1f82610/6a7f05ee42a117b6d895bbe2/07-ai-agent-product-catalog-issues.png" alt="Elastic AI Agent identifying product catalog issues" /></p>
<p>Prompt 2: <em>Why is product-catalog service not servicing any requests in (insert your k8s cluster name) cluster? Is there any misconfiguration in the service?</em></p>
<p>Expect <strong>Elastic AI Agent</strong> to call the <code>custom.k8s_troubleshooter</code> tool, which invokes the <strong>K8s Troubleshooter agent</strong> and reads Service, Endpoints, and pods, compares <code>targetPort</code> to <code>containerPort</code>, and explains the mismatch with evidence. Expect to also see the recommended remediation steps.</p>
<p><code>Note:</code> depending on the LLM you are using, the response from the agents may vary.</p>
<p>You get agent-led triage in Elastic Observability and cluster-grounded confirmation in the same thread, along with recommended remediation steps.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltaaa6b9e3ffc40882/6a7f05f1e02fac3bf25d62e0/07-ai-agent-chat-custom-k8s-troubleshooter.png" alt="Agent Builder chat on Observability Agent v2 invoking the K8s Troubleshooter agent workflow." /></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4e0151dfb52c819e/6a7f05f405b7b5ead618b6b6/07-ai-agent-chat-port-misconfiguration.png" alt="Agent Builder chat on Observability Agent v2 identifying port misconfiguration." /></p>
<h3 id="step8patchtheproductcatalogservice">Step 8: patch the product-catalog service</h3>
<p>Prompt 3: <em>Patch the product-catalog service in (your EKS cluster name) cluster to have 8080 as the targetPort</em></p>
<p>Expect <strong>Elastic AI Agent</strong> to call the <code>custom.k8s_troubleshooter</code> tool, which invokes the <strong>K8s Troubleshooter agent</strong> and patches the product-catalog service.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltaea2e5aa61e128e2/6a7f05f833fa8ab2262023de/08-ai-agent-chat-patch-product-catalog.png" alt="Agent Builder chat on Observability Agent v2 patching product-catalog service." /></p>
<p>Prompt 4: <em>Rollout restart upstream services of product-catalog service</em></p>
<p>Expect <strong>Elastic AI Agent</strong> to identify all upstream services of product-catalog and call the <code>custom.k8s_troubleshooter</code> tool, which invokes the <strong>K8s Troubleshooter agent</strong> to roll out restarts for upstream services such as checkout, frontend, and recommendation.</p>
<p>Confirm product-catalog and upstream services recover in Elasticsearch.</p>
<h2 id="validationandtradeoffs">Validation and trade-offs</h2>
<p>You validated that <strong>Elastic AI Agent</strong> stays the main surface, that ~20 EKS tools live on one specialist <strong>K8s Troubleshooter agent</strong>, and that the Workflow plus Agent Builder <code>converse</code> API keeps a clear boundary for audits and reviews.</p>
<p>Trade-offs: MCP bridges need ongoing token and network hygiene, and you should keep mutating tools off or tightly RBAC-scoped until you accept the risk.</p>
<h2 id="frequentlyaskedquestions">Frequently asked questions</h2>
<h3 id="whyiselasticaiagentnottriagingtheissuesasexplainedinthisarticle">Why is Elastic AI Agent not triaging the issues as explained in this article?</h3>
<p>There could be two primary reasons. (A) If you are on Elasticsearch 9.3, make sure you chat on the Elastic AI Agent that you created, and not on the stock agent. (B) Make sure to use one of the LLM models rated <code>Excellent</code> or <code>Great</code> in <a href="https://www.elastic.co/docs/solutions/observability/ai/llm-performance-matrix">Large language model performance matrix for Observability</a></p>
<h3 id="whydomyserviceslookunhealthyinelasticsearchwhentheappcodedidnotchange">Why do my services look unhealthy in Elasticsearch when the app code did not change?</h3>
<p>Kubernetes can mislead HTTP clients: a bad Service <code>targetPort</code>, empty Endpoints, or pods that never become ready can fan out as errors on multiple edges in traces and service maps. Elastic Observability shows which dependencies fail; confirming the live Service spec usually needs cluster access.</p>
<h3 id="howdoigivekubernetesaccesstoelasticaiagentwithoutputtingeveryekstoolonit">How do I give Kubernetes access to Elastic AI Agent without putting every EKS tool on it?</h3>
<p>Run two Agent Builder agents: keep the stock tools on the parent (Elastic AI Agent), and attach only EKS MCP tools to a specialist agent(K8s Troubleshooter agent). Invoke the specialist through a workflow that calls the Agent Builder converse API so the boundary is explicit and auditable.</p>
<h3 id="whychainagentswithelasticworkflowsinsteadofonelongsystemprompt">Why chain agents with Elastic Workflows instead of one long system prompt?</h3>
<p>Workflows give a callable, reviewable step between observability reasoning and cluster actions, which helps with governance and keeps the parent agent’s tool list short. Long unified tool lists often increase mistaken tool use and broaden blast radius if a prompt requests a mutating operation.</p>
<h3 id="howdoesthiscomparetokubectloracloudconsoleforincidentresponse">How does this compare to kubectl or a cloud console for incident response?</h3>
<p>Consoles and kubectl stay the source of truth for live object state. This pattern automates the handoff from Elastic Observability signals to those checks through MCP, while still relying on IAM and Kubernetes RBAC on the specialist identity.</p>
<h3 id="whatarethemainlimitationsorrisksofaneksmcpbridgewithagentbuilder">What are the main limitations or risks of an EKS MCP bridge with Agent Builder?</h3>
<p>MCP bridges need token rotation, network restrictions, and TLS discipline on real paths. Mutating EKS tools should stay off or tightly RBAC-scoped until you accept operational risk.</p>
<h3 id="whydoweneedaneksmcpbridge">Why do we need an EKS MCP bridge?</h3>
<p>The managed EKS MCP server authenticates via AWS SigV4 through a stdio-based proxy (mcp-proxy-for-aws). Elastic's MCP connector requires an HTTP/SSE endpoint. The bridge pod runs mcp-proxy to expose the stdio proxy as an SSE/HTTP endpoint.</p>
<h3 id="canireusethesamelayoutongkeaksorselfmanagedkubernetes">Can I reuse the same layout on GKE, AKS, or self-managed Kubernetes?</h3>
<p>Yes. The separation principle is the same: observability data in Elasticsearch plus a specialist agent with cluster-scoped tools and a workflow-mediated handoff. Swap the MCP server or bridge, adjust RBAC, and parameterize cluster name or region in workflow inputs where needed.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/eks-agent-builder-mcp-kubernetes-troubleshooting</link>
    <guid isPermaLink="false">eks-agent-builder-mcp-kubernetes-troubleshooting</guid>
    <category><![CDATA[Agentic Observability]]></category>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[Kubernetes]]></category>
    <category><![CDATA[AI]]></category>
    <dc:creator><![CDATA[Ramprasad KM]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt32b449a412d139b6/6a7f05fbc2cc09008c24922b/header.png" length="0" type="image/png"/>
    <pubDate>Mon, 11 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[Investigate Kubernetes infrastructure issues with PromQL in Elasticsearch & Kibana]]></title>
    <description><![CDATA[Walkthrough of a Kubernetes fleet-wide CPU investigation in Elastic Observability, from cluster to namespace to the noisy pod, using PromQL in Elasticsearch and Kibana.]]></description>
    <content:encoded><![CDATA[<p><a href="https://www.elastic.co/observability-labs/blog/elasticsearch-supports-promql">Elasticsearch now supports PromQL natively</a>, and <a href="https://www.elastic.co/observability-labs/blog/promql-queries-run-in-kibana">you can run PromQL queries in Kibana</a> through the <code>PROMQL</code> source command in ES|QL.
That means you can use PromQL to query your Kubernetes metrics stored in Elasticsearch. You can run those queries directly in Discover, Dashboards or alerting rules.</p>
<p>When <strong>cluster CPU spikes</strong> and you need to find <strong>which workload</strong> is responsible, narrow from <strong>fleet</strong> to <strong>namespace</strong> to <strong>pod</strong>, one step at a time.</p>
<h2 id="whatyouneed">What you need</h2>
<ul>
<li>An <strong>Observability</strong> <a href="https://www.elastic.co/docs/solutions/observability/get-started">serverless project</a> or a self-managed or Elastic Cloud Hosted stack at <strong>version 9.4 or later</strong>, where <strong>PromQL</strong> is available as a <strong>preview</strong> query language for metrics.</li>
<li><strong>Kubernetes</strong> metrics flowing into Elasticsearch. For this exercise we have considered <strong>OpenTelemetry</strong> data.</li>
<li>One or more clusters with workloads running so <code>group by</code> queries have something to compare.</li>
</ul>
<h2 id="thescenario">The scenario</h2>
<p>You manage a fleet of Kubernetes clusters:</p>
<p>| Cluster | Region | Role |
|---------|--------|------|
| <code>prod-us-east-1</code> | US East | Production: services, ML training |
| <code>prod-eu-west-1</code> | EU West | Production: regional web tier, cache |
| <code>staging-us-east-1</code> | US East | Staging: QA, integration tests |
| <code>dev-sandbox</code> | US East | Developer sandbox |</p>
<p>The production cluster in US East runs a mix of services and ML training jobs across several namespaces.</p>
<p>An <strong>alert</strong> fires: <strong>cluster-wide CPU is elevated</strong>, but only one team is complaining about slower response times.</p>
<p>You are triaging <strong>which cluster</strong>, then <strong>which namespace</strong>, then <strong>which pod</strong>.</p>
<p>You are not after a full root-cause proof in one query, but enough to <strong>name the suspect</strong> and hand off.</p>
<h2 id="yourdata">Your data</h2>
<p>The OpenTelemetry Collector's <strong>Kubelet Stats Receiver</strong> populates data streams like <code>metrics-kubeletstatsreceiver.otel-default</code>.
Metrics follow the <code>k8s.*</code> naming convention (for example <code>k8s.pod.cpu.usage</code>) and labels like <code>k8s.cluster.name</code> or <code>k8s.namespace.name</code> let you slice by cluster, namespace, or pod.</p>
<p>To verify the data is there, open <strong>Discover</strong>, switch to ES|QL mode, run <strong><code>TS metrics-*</code></strong>, and scope the query with <strong><code>WHERE data_stream.dataset == "kubeletstatsreceiver.otel"</code></strong>.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt78109b18cefd6655/6a7f19dbe3a219121f99f8b2/discover-ts-metrics-k8s.png" alt="Discover: kubernetes metrics from OpenTelemetry" /></p>
<h2 id="investigationfindthenoisyneighbor">Investigation: find the noisy neighbor</h2>
<h3 id="step1whichclusterishot">Step 1: Which cluster is hot?</h3>
<p>When you manage multiple clusters, start at the fleet level.</p>
<pre><code>PROMQL sum by (k8s.cluster.name) (k8s.pod.cpu.usage)
</code></pre>
<p>This groups total pod CPU by cluster.</p>
<p><code>prod-us-east-1</code> immediately stands out: total pod CPU is <strong>an order of magnitude higher</strong> than the other clusters.</p>
<p>The EU production cluster, staging, and dev-sandbox are all quiet.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1ccaed4498c9d723/6a7f19de96b5a62c2c87b873/promql-fleet-cpu-by-cluster.png" alt="Fleet-level PromQL chart showing prod-us-east-1 as the outlier" /></p>
<p>Now you know <strong>where</strong> the problem is, time to zoom in.</p>
<h3 id="step2overallcpuinthehotcluster">Step 2: Overall CPU in the hot cluster</h3>
<p>Filter to <code>prod-us-east-1</code> and look at total CPU:</p>
<pre><code>PROMQL sum(k8s.pod.cpu.usage{k8s.cluster.name="prod-us-east-1"})
</code></pre>
<p>This gives you the <strong>cluster-wide pod CPU footprint</strong> over time.</p>
<p>If the total is climbing or spiking, something changed, but you don't yet know <strong>what</strong>.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt14eca383e549700a/6a7f19e24c4bfb7d30ccd8ec/promql-hot-cluster.png" alt="Overall CPU in prod-us-east-1 showing a clear spike" /></p>
<h3 id="step3breakdownbynamespace">Step 3: Break down by namespace</h3>
<p>The fastest way to isolate <strong>which team</strong> is responsible: group by namespace.</p>
<pre><code>PROMQL sum by (k8s.namespace.name) (k8s.pod.cpu.usage{k8s.cluster.name="prod-us-east-1"})
</code></pre>
<p>Set the <strong>time picker</strong> in Kibana to cover your incident window.</p>
<p><code>ml-training</code> dominates at <strong>~2.0 cores</strong> while every other namespace stays well below <strong>0.2 cores</strong>.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte0eef1cef1efae0c/6a7f19e5448e4e068c5c0b56/promql-group-by-noisy-neighbor.png" alt="Grouped PromQL chart showing ml-training as the dominant series" /></p>
<h3 id="step4drilldowntothepod">Step 4: Drill down to the pod</h3>
<p>Now that you know the namespace, identify the specific pod:</p>
<pre><code>PROMQL sum by (k8s.pod.name) (k8s.pod.cpu.usage{k8s.cluster.name="prod-us-east-1", k8s.namespace.name="ml-training"})
</code></pre>
<p>That ranks pods in the namespace by total CPU.</p>
<p>The chart should make the outlier obvious.</p>
<p>Pod <code>model-train-v2-run-47-d9j67</code> is consuming the full <strong>2.0 cores</strong>.
It is a training job saturating its allocation.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc9b5a73325ae96ab/6a7f19e85967e564495dd6b5/promql-drilldown-pod.png" alt="Pod drill-down showing model-train-v2-run-47-d9j67 as the CPU consumer" /></p>
<h3 id="step5checkresourceutilizationratios">Step 5: Check resource utilization ratios</h3>
<p>Raw CPU cores tell you <strong>how much</strong>.
Utilization ratios tell you <strong>how close to limits</strong>.</p>
<p>A pod hitting 100% of its CPU limit is being throttled, and it is both the noisy neighbor <strong>and</strong> a victim of its own limits.</p>
<pre><code>PROMQL sum by (k8s.namespace.name) (k8s.container.cpu_limit_utilization{k8s.cluster.name="prod-us-east-1"})
</code></pre>
<p><code>ml-training</code> shows <strong>~100% CPU limit utilization</strong> (pegged at the 2-core limit), while the other namespaces stay under 20%.</p>
<p>This confirms the training job is <strong>saturating its allocation</strong> and likely causing scheduling pressure on the shared node.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt64374adda3576ad3/6a7f19ebea068d317bf0a2bb/promql-cpu-utilization.png" alt="CPU limit utilization by namespace — ml-training pegged near 100%" /></p>
<h2 id="whathappensnext">What happens next</h2>
<p>The PromQL query <strong>named the suspect</strong>: the training job <code>model-train-v2-run-47</code> in <code>ml-training</code>.</p>
<p>From here:</p>
<ul>
<li><strong>Logs</strong>: Filter by the pod name in Discover to see what the training job was doing and whether it logged errors or warnings.</li>
<li><strong>Kube events</strong>: Check for OOMKilled, throttling, or eviction events in the same time window.</li>
<li><strong>Resource policies</strong>: Review whether the training job's requests and limits match its actual usage. A large gap between request and limit lets a pod burst past what the scheduler planned for. Consider <code>ResourceQuota</code> or <code>LimitRange</code> on the namespace.</li>
</ul>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/promql-investigate-kubernetes-infrastructure</link>
    <guid isPermaLink="false">promql-investigate-kubernetes-infrastructure</guid>
    <category><![CDATA[Kubernetes]]></category>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[Metrics]]></category>
    <dc:creator><![CDATA[Miguel Sánchez Gómez]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3651d463b7cb4316/6a7f19eebdcff04042c4329b/cover.png" length="0" type="image/png"/>
    <pubDate>Thu, 07 May 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Visualizing OpenTelemetry Data in Elastic with OpenTelemetry Content Packages]]></title>
    <description><![CDATA[Learn and explore how OpenTelemetry Content Packages in Elastic provide instant dashboards, alerts, and SLOs for your telemetry data.]]></description>
    <content:encoded><![CDATA[<p>If you've been in the observability space for the last couple of years, you've seen OpenTelemetry go from "promising standard" to the default choice for collecting metrics, logs, and traces. Elastic has been in that journey from early on — which is why we built the <a href="https://www.elastic.co/observability-labs/blog/elastic-distributions-opentelemetry">Elastic Distributions of OpenTelemetry (EDOT)</a>: a hardened, production-ready suite of OTel components including the EDOT Collector and language SDKs, tuned for infrastructure and application monitoring without the typical setup overhead.</p>
<p>EDOT is now generally available. The collector, the SDKs, the whole stack — production-ready, enterprise-supported, no asterisks.</p>
<p>But here's the thing: getting your data into Elastic is only half the job. The harder half, in practice, is what happens after. Someone still has to build the dashboards, write the alert rules, and figure out which SLOs are worth tracking — before any of it is useful.</p>
<p>That gap is what OpenTelemetry Content Packages are designed to close.</p>
<hr />
<h2 id="whatareopentelemetrycontentpackages">What Are OpenTelemetry Content Packages?</h2>
<p>Elastic's traditional Beats-based integrations always bundled data collection and visualizations together — you got curated dashboards and alerts the moment you turned something on. As Elastic moves to an OpenTelemetry-first world, that same philosophy carries over, but the model is cleaner.</p>
<p>OpenTelemetry Content Packs are purely about the observability assets for a given service. No data collection config is bundled in, because in an OTel world, the collector handles that. Each package contains:</p>
<ul>
<li><strong>Dashboards</strong> — curated, pre-built Kibana visualizations tailored to the service being monitored</li>
<li><strong>Alert rules</strong> — pre-configured alerting rules that fire on meaningful thresholds, helping teams minimize Mean Time to Detect (MTTD) and Mean Time to Resolve (MTTR)</li>
<li><strong>SLO templates</strong> — ready-made Service Level Objective definitions you can apply immediately to track reliability targets, error budgets, and burn rates</li>
</ul>
<p>More asset types are planned for future packages as the content pack model continues to evolve.</p>
<hr />
<h2 id="howdoesitwork">How Does It Work?</h2>
<p>The core idea is simple: as soon as data arrives in Elastic, the right dashboards, alert rules, and SLO templates are ready to use. The content package activates based on the incoming data, regardless of how that data was collected.</p>
<p>One of the most powerful aspects of this system is <strong>automatic installation</strong>. When Elastic detects that data for a particular service has started arriving in Elasticsearch, the corresponding content pack is installed automatically — no manual steps, no hunting through the integrations catalog. By the time you open Kibana, your dashboards are already there waiting for you, your alert rules are ready to be enabled, and your SLO templates are pre-loaded.</p>
<p>To get the data flowing in the first place, we need to configure the collector — a YAML file that defines the building blocks of your telemetry pipeline:</p>
<ul>
<li><strong>Receivers</strong> — define what data to collect and from where. Each service has its own receiver (for example, the MySQL receiver scrapes metrics directly from the database).</li>
<li><strong>Exporters</strong> — define where the collected data is sent. In our case, we use the Elasticsearch exporter, which ships the telemetry data directly into Elasticsearch in OpenTelemetry native format.</li>
<li><strong>Pipelines</strong> — wire the receivers and exporters together, defining the flow of data through the collector.</li>
</ul>
<p>Once this configuration is in place and the collector is running, data starts flowing into Elasticsearch — and the content pack takes it from there.</p>
<h4 id="datasources">Data Sources</h4>
<p>OpenTelemetry data can reach Elastic through any of the following:</p>
<ul>
<li><strong><a href="https://www.elastic.co/observability-labs/blog/elastic-distributions-opentelemetry">EDOT Collector</a></strong> — the Elastic Distribution of the OpenTelemetry Collector, embedded in or used alongside the Elastic Agent</li>
<li><strong><a href="https://github.com/open-telemetry/opentelemetry-collector-contrib">Upstream OTel Collector</a></strong> — the standard community OpenTelemetry Collector (Contrib or custom builds)</li>
<li><strong><a href="https://www.elastic.co/docs/reference/opentelemetry/edot-cloud-forwarder">EDOT Cloud Forwarder (ECF)</a></strong> — a serverless OTel Collector that collects telemetry from AWS, GCP, and Azure (VPC Flow Logs, CloudTrail, CloudWatch, and more) and forwards it directly to Elastic Observability, with no infrastructure to manage</li>
</ul>
<p>The content pack doesn't care how the data arrived — only that it's there.</p>
<hr />
<h2 id="seeingitinpracticemysqlmonitoring">Seeing It in Practice: MySQL Monitoring</h2>
<p>Take a team running MySQL who wants to track query throughput, connection counts, buffer pool utilization, and slow query rates — and get alerted before small problems turn into 2am incidents. Historically, that means hours of dashboard building, custom alert queries, and a lot of guesswork about which metrics actually matter.</p>
<p>With the <strong><a href="https://www.elastic.co/docs/reference/integrations/mysql_otel">MySQL OpenTelemetry Assets Package</a></strong>, that work is already done. Here's how the whole thing comes together.</p>
<h3 id="step1getthedatain">Step 1: Get the Data In</h3>
<p>The data pipeline is driven by a collector configuration that defines receivers (where to scrape data from), processors (how to enrich or transform it), and exporters (where to send it — in this case, Elasticsearch).</p>
<p>Regardless of whether you use the <a href="https://www.elastic.co/observability-labs/blog/elastic-distributions-opentelemetry">EDOT Collector</a> or the <a href="https://github.com/open-telemetry/opentelemetry-collector-contrib">Upstream OTel Collector</a>, the fundamental configuration structure is the same. The configuration below uses separate receivers for the primary and replica instances, because replication metrics are only available on replicas. Replace the placeholders with your actual endpoints, credentials, and Elasticsearch details.</p>
<pre><code>receivers:
  mysql/primary:
    endpoint: &lt;MYSQL_PRIMARY_ENDPOINT&gt;
    username: &lt;MYSQL_USER&gt;
    password: &lt;MYSQL_PASSWORD&gt;
    collection_interval: 10s
    statement_events:
      digest_text_limit: 120
      limit: 250
    query_sample_collection:
      max_rows_per_query: 100
    events:
      db.server.query_sample:
        enabled: true
      db.server.top_query:
        enabled: true
    metrics:
      mysql.client.network.io:
        enabled: true
      mysql.connection.errors:
        enabled: true
      mysql.max_used_connections:
        enabled: true
      mysql.query.client.count:
        enabled: true
      mysql.query.count:
        enabled: true
      mysql.query.slow.count:
        enabled: true
      mysql.table.rows:
        enabled: true
      mysql.table.size:
        enabled: true

processors:
  resourcedetection:
    detectors: [system, env]

exporters:
  elasticsearch/otel:
    endpoint: &lt;ES_ENDPOINT&gt;
    api_key: &lt;ES_API_KEY&gt;
    mapping:
      mode: otel

service:
  pipelines:
    metrics:
      receivers: [mysql/primary, mysql/replica]
      processors: [resourcedetection]
      exporters: [elasticsearch/otel]
</code></pre>
<p>The <a href="https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/receiver/mysqlreceiver/README.md#mysql-receiver">MySQL receiver</a> scrapes metrics and events from the database at the configured interval and emits them as OpenTelemetry metrics. These flow through the pipeline and land in Elasticsearch, ready to be visualized.</p>
<h3 id="step2openkibanaeverythingsalreadythere">Step 2: Open Kibana — Everything's Already There</h3>
<h4 id="dashboards">Dashboards</h4>
<p>As soon as the MySQL metrics and events arrive in Elasticsearch, the <a href="https://www.elastic.co/docs/reference/integrations/mysql_otel">MySQL OpenTelemetry Assets Package</a> is automatically installed in the background. By the time you navigate to Kibana, the <a href="https://www.elastic.co/docs/reference/integrations/mysql_otel#screenshots">dashboards</a> are already populated and waiting.</p>
<p>Users immediately get visibility into:</p>
<ul>
<li>Active and max connections</li>
<li>Query throughput — statements executed per second</li>
<li>InnoDB buffer pool hit rate and memory usage</li>
<li>Slow query count and trends</li>
<li>Table lock waits and contention</li>
<li>Bytes sent and received over time</li>
<li>Replication lag (for replicated setups)</li>
</ul>
<p>No manual field mapping. No dashboard building from scratch. Just data in, insights out.</p>
<p>Below are some screenshots of the MySQL OpenTelemetry dashboard in Kibana, showing the out-of-the-box visualizations that are automatically available as soon as your data starts flowing in.</p>
<p>Overview Dashboard
<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5152c95269216cdf/6a7f1c149090b0601984ee53/overview.png" alt="" /></p>
<p>Queries Dashboard
<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7b009de3d2d1df00/6a7f1c182f00b23996efef4f/queries.png" alt="" /></p>
<p>Availability Dashboard
<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7e4c4ba1782931fe/6a7f1c1b448e4e42635c0b89/availability.png" alt="" /></p>
<h4 id="alertrulesreadytoenable">Alert Rules, Ready to Enable</h4>
<p>The package includes six pre-built <a href="https://www.elastic.co/docs/reference/integrations/mysql_otel#alert-rules">alert rules</a> — covering high connection error rates, slow query spikes, thread saturation, replication lag, buffer pool dirty page ratio, and row lock contention — each with recommended thresholds and severity levels. These are available immediately on install and can be enabled, tuned, and extended directly in Kibana without any custom query authoring. Below is an example of one of the alerts.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3779d7ac5a0a0128/6a7f1c1e05b7b514c318bd65/alert1.png" alt="" /></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdaf98fbb471eb1f1/6a7f1c215967e561495dd6ef/alert2.png" alt="" /></p>
<h4 id="slotemplatespreloaded">SLO Templates, Pre-Loaded</h4>
<p>Four <a href="https://www.elastic.co/docs/reference/integrations/mysql_otel#slo-templates">SLO templates</a> are included out of the box, tracking replication lag, connection exhaustion errors, slow query rate, and connected thread count — each with a pre-configured target and 30-day rolling window. Teams can adopt them as-is or tune the thresholds to match their own reliability requirements.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd307a19b82ea64b8/6a7f1c2505b7b5756918bd6f/slo.png" alt="" /></p>
<hr />
<h2 id="whatsavailabletoday">What's Available Today</h2>
<p>The MySQL OpenTelemetry Assets Package is just one example from a growing library of OpenTelemetry Content Packages that Elastic has already built out. Content packs are available for a range of services — and we have also started extending this to the cloud, with initial support for Cloud Service Provider integrations that use the <a href="https://www.elastic.co/docs/reference/opentelemetry/edot-cloud-forwarder">EDOT Cloud Forwarder (ECF)</a> to bring AWS, GCP, and Azure telemetry into Elastic with ready-made dashboards.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt783f630fca5a601e/6a7f1c28c2e914bbab01701c/contentpacks.png" alt="" /></p>
<p>The same pattern holds across all of them — data in, and a complete observability package (dashboards, alert rules, SLO templates) instantly ready — whether you're monitoring a self-managed database or cloud-native services from your preferred cloud service provider.</p>
<h2 id="wherethisisgoing">Where This Is Going</h2>
<p>The next step worth watching is <strong>OTel Integration Packages</strong>, which will let you push collector configurations directly from the Kibana UI — making the entire setup experience point-and-click, from data collection through to visualization, with no YAML editing required.</p>
<hr />
<h2 id="getstarted">Get Started</h2>
<p>Ready to try it? Start with the <a href="https://www.elastic.co/observability-labs/blog/elastic-distributions-opentelemetry">EDOT Collector documentation</a> and explore the growing library of OpenTelemetry content packages in Kibana's Integrations page.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/visualizing-opentelemetry-data-elastic-content-packages</link>
    <guid isPermaLink="false">visualizing-opentelemetry-data-elastic-content-packages</guid>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[Metrics]]></category>
    <dc:creator><![CDATA[Ishleen Kaur]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte829026a132ec529/6a7f1c2bb43770a7fb4d7142/otelcp.png" length="0" type="image/png"/>
    <pubDate>Fri, 10 Apr 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Composing OpenTelemetry Reference Architectures]]></title>
    <description><![CDATA[A conceptual framework for reasoning about OpenTelemetry Collection architectures — edge, processing, and resilience layers that compose into the right pipeline for your environment.]]></description>
    <content:encoded><![CDATA[<p>Most OpenTelemetry tutorials end at the same place: an application instrumented with the SDK, exporting traces to a single collector, forwarding to a backend. It works. Then production happens.</p>
<p>Traffic grows. Teams want metrics derived from traces. The backend goes down for maintenance and you lose an hour of telemetry. A compliance requirement means PII must be stripped before data leaves the cluster. Suddenly, that single collector isn't enough — and the question becomes: what should the architecture actually look like?</p>
<p>The OpenTelemetry Collector is designed to be composed. It can run in multiple deployment modes, be chained into pipelines, and scaled independently at each stage. But the documentation describes individual components, not how to think about assembling them. That thinking is what this article is about.</p>
<p>What follows is a conceptual framework for reasoning about collector architectures — not a set of rigid templates. The building blocks described here are reference points. In practice, they combine, overlap, and adapt to your constraints. A tail sampling tier might also need Kafka-backed resilience. A gateway might absorb the role of a sampling tier at low volumes. The goal is to understand the concepts well enough to compose the right architecture for your situation, not to pick a pre-built one off a shelf.</p>
<h2 id="threeconceptuallayers">Three conceptual layers</h2>
<p>It helps to think about collector architectures in three layers: <strong>edge</strong>, <strong>processing</strong>, and <strong>resilience</strong>. These aren't physical tiers that must exist as separate deployments — they're categories of concern. A single collector can address multiple layers. A complex deployment might have several components within one layer. The layers are a thinking tool, not a deployment diagram.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb0c3d663572bdc9a/6a7f18eceab5be375920aad7/three-layers.png" alt="The three conceptual layers: Edge, Processing, and Resilience" /></p>
<h3 id="edgehowtelemetryentersthepipeline">Edge: how telemetry enters the pipeline</h3>
<p>The edge layer is about the first hop — how telemetry gets from your applications and infrastructure into the pipeline. At this stage, the collector gathers data in two fundamentally different ways. <strong>Pull-based receivers</strong> like <code>filelog</code> and <code>hostmetrics</code> actively reach out to collect data — tailing log files on disk or scraping system-level metrics from the host. <strong>Push-based receivers</strong> like <code>otlp</code> listen for data sent to them — applications instrumented with OpenTelemetry SDKs export traces, metrics, and logs directly to the collector's OTLP endpoint. A single edge collector typically runs both: pull receivers for infrastructure telemetry the application doesn't know about, and push receivers for application telemetry the SDK produces. There are several common deployment patterns, and the right one depends on your environment and what you need to collect.</p>
<p><strong>DaemonSet Agent</strong> — One OpenTelemetry Collector per Kubernetes node, deployed as a DaemonSet. Applications export to the agent running on the same node (typically via status.hostIP:4317 using the Kubernetes Downward API). The agent also tails container log files from disk via the filelog receiver and scrapes host-level metrics via the hostmetrics receiver. This is the most common Kubernetes pattern because it handles both application and infrastructure telemetry with a single deployment, and applications only need to know about localhost.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt898169e98cea105d/6a7f18effc63abe98364d054/daemonset-agent.png" alt="DaemonSet Agent pattern: Application with OTel SDK exporting over OTLP to a per-node DaemonSet collector" /></p>
<p><strong>Sidecar Agent</strong> — One OpenTelemetry Collector per pod, deployed as a sidecar container. Each service gets its own collector with a custom configuration. This is required on managed container platforms like AWS Fargate or Azure Container Apps where DaemonSets aren't available, and it's useful when services have different processing requirements. When running alongside a DaemonSet, the sidecar handles application telemetry while the DaemonSet independently collects node-level telemetry — applications don't send to both.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltbf752fb09630a0a5/6a7f18f2c2cc091599249994/sidecar-agent.png" alt="Sidecar Agent pattern: Application with OTel SDK exporting over OTLP to a per-pod sidecar collector" /></p>
<p><strong>Host Agent</strong> — A standalone OpenTelemetry Collector running as a systemd service on bare-metal or VM hosts. It serves the same role as the DaemonSet agent but outside Kubernetes: collecting host metrics, tailing log files, and receiving OTLP from local applications.</p>
<p><strong>Direct SDK Export</strong> — Applications export directly to the next stage (gateway or backend) with no local collector. This is the simplest option but only works when you don't need infrastructure collection. For log collection, the recommended pattern is still to write to stdout and use a collector with the <code>filelog</code> receiver — even if the SDK is exporting traces and metrics directly.</p>
<p>These patterns aren't mutually exclusive. A Kubernetes cluster might run DaemonSet agents for infrastructure collection alongside sidecars for services that need custom processing. A VM environment might use host agents for some services and direct SDK export for others. The edge layer is about matching the collection pattern to the workload, not picking one pattern for everything.</p>
<h3 id="processingcentralpolicysamplingandtransformation">Processing: central policy, sampling, and transformation</h3>
<p>Not every architecture needs a processing layer. If your edge collectors can export directly to your backend and you don't need centralized policy, you can skip it to favour simplicity. But several scenarios push you toward central processing — and the way you address them can range from a single gateway to a multi-stage pipeline.</p>
<p><strong>Centralized policy (Gateway)</strong> — A pool of OpenTelemetry Collectors that sits between edge collectors and the backend. This is where you enforce consistent filtering, transformation, and PII redaction across all services. It's also where you manage backend credentials — edge collectors export to the gateway over OTLP, and only the gateway holds the API keys. Credential isolation is often the primary reason teams add a gateway.</p>
<p>Replica count scales with data volume. At low volumes (under 1K events/sec), 2 replicas co-located with workloads is sufficient. At medium volumes, 3–5 replicas on a dedicated node pool. At high volumes, 5–20+ replicas, potentially in a separate cluster. This is a general rule of thumb, and you should adapt it to your specific needs as loads might vary significantly between payload types.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2c5fe1b38941f73d/6a7f18f533fa8a2253202b5c/gateway-pool.png" alt="Gateway pattern: Load Balancer distributing traffic to a Gateway Pool of OTel Collectors" /></p>
<p><strong>Tail-based sampling</strong> — Sampling decisions that consider the complete trace (e.g., "keep all traces with errors, sample 10% of successful traces") require that all spans of a trace reach the same collector instance. This is achieved with the <code>loadbalancingexporter</code> using <code>routing_key: traceID</code>, which consistently routes spans from the same trace to the same downstream collector.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2e5c9ce1457269a3/6a7f18f8e88c654d6f00bada/tail-sampling.png" alt="Tail sampling pattern: LB Exporter routing to Sampling Collectors with tail_sampling" /></p>
<p>There's a critical subtlety here: if you're deriving span metrics (RED metrics) from traces using the <code>spanmetrics</code> connector, the derivation must happen <strong>before</strong> sampling. Otherwise, your metrics only reflect the sampled subset, not the true traffic. The correct pattern is a two-step pipeline within the sampling stage:</p>
<ol>
<li>Receive traces, derive spanmetrics from 100% of traffic, forward via a <code>forward</code> connector.</li>
<li>Apply <code>tail_sampling</code> to the forwarded traces, export only kept traces.</li>
<li>A separate metrics pipeline exports the derived RED metrics.</li>
</ol>
<p>This ensures accurate metrics regardless of your sampling rate.</p>
<p><strong>The key point about processing</strong> is that these capabilities — gateway policy, tail sampling, span metrics derivation — are not separate products or fixed modules. They're configurations of the same OpenTelemetry Collector. At low volumes, a single gateway deployment might handle policy enforcement, sampling, and metrics derivation all at once. At high volumes, you might split them into dedicated stages for independent scaling. The architecture adapts to your scale, not the other way around.</p>
<h3 id="resiliencewhathappenswhenthebackendisdown">Resilience: what happens when the backend is down</h3>
<p>The resilience layer determines how much data you're willing to lose during backend outages or collector restarts. This isn't a separate tier you bolt on — it's a property you apply to any stage of the pipeline.</p>
<p><strong>In-Memory Queues</strong> — The default. The collector's <code>sending_queue</code> retries failed exports with exponential backoff. If the collector process crashes or restarts, queued data is lost. This is acceptable for development and for workloads where some data loss during incidents is tolerable.</p>
<p><strong>Persistent Queues (WAL)</strong> — The <code>file_storage</code> extension writes queued data to disk before export. If the collector crashes, it resumes from where it left off after restart. In Kubernetes, this requires a PersistentVolumeClaim. This is the right choice for most production workloads — it survives collector restarts and brief backend outages without the operational complexity of an external message bus.</p>
<p><strong>Kafka Buffer</strong> — An external Kafka cluster sits between collectors and the backend. Producer collectors write to Kafka topics; consumer collectors read from Kafka and export to the backend. This provides the strongest durability guarantee — Kafka can buffer hours of telemetry during extended outages and enables replay. But it adds significant operational complexity.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltbff2af063797b1e9/6a7f18fa448e4eacd65c0b38/kafka-buffer.png" alt="Kafka buffer pattern: Collector Pool producing to Kafka, consumed by another Collector Pool" /></p>
<p>The important thing to understand is that resilience is orthogonal to the other layers. You can add persistent queues to an edge agent, a gateway, or a sampling tier. You can put Kafka in front of a gateway, in front of a sampling tier, or in front of the backend. A tail sampling deployment that needs to survive extended outages might use Kafka-backed ingestion — combining what might look like two separate "modules" into a single stage. The building blocks compose freely based on what you need to protect against.</p>
<h2 id="wheretostartwithyourarchitecture">Where to start with your architecture</h2>
<p>The Agent + Gateway two-tier pattern is the de facto production standard, used by the vast majority of organizations running OpenTelemetry at scale. DaemonSet agents on every node handle local collection — pulling infrastructure telemetry via <code>filelog</code> and <code>hostmetrics</code>, receiving application telemetry via OTLP — while a centralized gateway pool enforces policy, manages credentials, and exports to the backend. Persistent queues (WAL) on the gateway protect against backend outages without external dependencies.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4b123115a47e99f1/6a7f18fd73d9bd051c29df2f/where-to-start.png" alt="A Kubernetes architecture with DaemonSet agents, a processing tier with tail sampling and gateway pool, exporting over OTLP to an observability backend" /></p>
<p>Every other configuration either simplifies this pattern or extends it. Smaller environments might drop the gateway and export directly from agents. Larger ones might add a tail sampling tier with traceID-based load balancing, a Kafka buffer for extended resilience, or span metrics derivation before sampling. The building blocks described in the previous sections — edge, processing, resilience — are the modules you add or remove from this foundation.</p>
<p>The key is to start with the two-tier pattern and evolve incrementally:</p>
<ul>
<li>Need credential isolation or centralized PII redaction? You already have the gateway.</li>
<li>Need tail-based sampling? Add a load-balancing exporter and a sampling tier between agents and gateway.</li>
<li>Need hours of buffer during extended outages? Insert Kafka between agents and the processing tier.</li>
<li>Running on Fargate or Azure Container Apps? Swap DaemonSet agents for sidecars — the rest of the pipeline stays the same.</li>
</ul>
<p>Start here. Add modules as your needs grow. The architecture adapts to your scale, not the other way around.</p>
<h2 id="decisionpointsthatshapewhereyouneedtotakeyourarchitecture">Decision points that shape where you need to take your architecture</h2>
<p>When designing a collector architecture, these are the questions that determine which patterns you need:</p>
<p>| Question | Impact |
|----------|--------|
| Do I need infrastructure telemetry (host metrics, disk logs)? | Determines whether you need a local collector or can use direct SDK export |
| Am I on a managed container platform (Fargate, ACA)? | Forces sidecar pattern instead of DaemonSet |
| Do I need centralized filtering, PII redaction, or credential isolation? | Adds a gateway stage |
| Do I need tail-based sampling? | Adds a sampling stage with load-balancing exporter and traceID routing |
| Do I want span-derived metrics (RED metrics)? | Requires spanmetrics before sampling in a two-step pipeline |
| How much data loss is acceptable during outages? | Determines in-memory queues vs. persistent queues vs. Kafka — applied to whichever stage needs protection |
| What is my expected data volume? | Determines whether capabilities can be co-located in a single deployment or need dedicated stages |</p>
<p>The answers to these questions don't map to a single "correct" architecture. They constrain the design space, and within those constraints, you make trade-offs between simplicity and capability.</p>
<h2 id="exploringthesepatternsinteractively">Exploring these patterns interactively</h2>
<p>If you'd rather explore how these building blocks compose than assemble them by hand, <a href="https://mlunadia.github.io/otel-blueprints/">OpenTelemetry Blueprints</a> is an open-source tool that generates reference architectures from your requirements.</p>
<p>Toggle your environment, signals, volume, resilience, and processing needs — and get a composed diagram with animated data flow, interactive tooltips, and reference collector configurations you can open directly in <a href="https://www.otelbin.io">OTelBin</a> for validation.</p>
<p><a href="https://mlunadia.github.io/otel-blueprints/"><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5253df89f15ea6d5/6a7f19003ce8e27730cf5789/architecture.png" alt="Screenshot of a composed architecture diagram showing a Kubernetes cluster with DaemonSet agent, processing tier, and observability backend" /></a></p>
<p>The generated configurations export via OTLP, so they work with any OTLP-compatible backend — including <a href="https://www.elastic.co/docs/reference/opentelemetry/motlp">Elastic Observability</a>, which natively accepts and stores OTLP traces, metrics, and logs.</p>
<p>The architectures Blueprints generates are reference compositions — starting points for understanding how the building blocks fit together, not turnkey deployments. Every architecture should be adapted to your organisation's scale, security, networking, and compliance requirements. The patterns might combine or overlap differently in your environment than in anyone else's, and that's the point.</p>
<h2 id="getstarted">Get started</h2>
<p>The architectures described here export over OTLP, so they work with any compatible backend. If you don't have one yet, the fastest way to see your telemetry flowing end-to-end is with Elastic Observability — it natively ingests OTLP traces, metrics, and logs with no additional configuration.</p>
<ol>
<li><a href="https://cloud.elastic.co/serverless-registration?onboarding_token=observability">Start a free trial</a> on Elastic Cloud Serverless — no credit card required.</li>
<li>Point your collector's OTLP exporter at the managed OTLP endpoint.</li>
<li>Explore your traces, metrics, and logs in Kibana within minutes.</li>
</ol>
<p>Check out these resources to go further:</p>
<ul>
<li><a href="https://www.elastic.co/docs/reference/opentelemetry/motlp">Elastic's managed OTLP endpoint documentation</a></li>
<li><a href="https://www.elastic.co/docs/reference/opentelemetry/edot-collector">EDOT Collector — Elastic's distribution of the OpenTelemetry Collector</a></li>
<li><a href="https://mlunadia.github.io/otel-blueprints/">OpenTelemetry Blueprints — generate reference architectures interactively</a></li>
</ul>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/opentelemetry-collector-reference-architectures</link>
    <guid isPermaLink="false">opentelemetry-collector-reference-architectures</guid>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[Kubernetes]]></category>
    <category><![CDATA[Infrastructure Monitoring]]></category>
    <dc:creator><![CDATA[Miguel Luna]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7d7f731ee0a1652e/6a7f19032f00b219dfefef09/opentelemetry-collector-reference-architectures.jpg" length="0" type="image/jpeg"/>
    <pubDate>Tue, 31 Mar 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[ML and AI Ops Observability with OpenTelemetry and Elastic]]></title>
    <description><![CDATA[Learn how to instrument ML and AI pipelines with OpenTelemetry and Elastic to correlate traces, logs, and metrics from notebooks to production inference services.]]></description>
    <content:encoded><![CDATA[<p>While isolated execution logs might work for local experiments, they are no longer enough for the new era of complex, production-ready Machine Learning (ML) pipelines and Artificial Intelligence (AI) agents. Modern ML and AI systems present three unique challenges:</p>
<ul>
<li><strong>Distributed components</strong>: A single request might hit an API gateway, retrieve data from a feature store, evaluate a predictive model in a Python inference service, query a vector database, and call an external LLM.</li>
<li><strong>Non-determinism</strong>: AI agents make autonomous decisions and tool calls. If an agent fails, you need a full trace to understand its reasoning loop and what external tools it tried to invoke.</li>
<li><strong>Context dependence</strong>: You don't just care <em>that</em> an error happened; you need to know <em>what model version</em> was running, <em>what hyperparameters</em> were used, <em>what the input data looked like</em>, <em>what</em> was the commit that made that change. Many of these attributes are custom to your app, and you need an Observability environment that has the flexibility of creating new parameters on the fly and use them to find and fix issues.</li>
</ul>
<p>On top of that, with the increased use of AI agents to generate code and make autonomous decisions, Observability becomes key to understanding what is working and what is not. It creates a critical feedback loop to quickly fix problems. More than ever, ML and AI applications need to adopt the best practices of mature software engineering systems to succeed.</p>
<p>This guide shows how to use OpenTelemetry and Elastic to correlate traces, logs, and metrics to track runs, compare model behavior, and trace requests across Python and Go services with one shared context.</p>
<h2 id="problemcontextwhyaisystemsarehardertodebug">Problem context: why AI systems are harder to debug</h2>
<p>Traditional services already have distributed failure modes, but ML and AI systems add more moving parts:</p>
<ul>
<li>notebook experiments and ad hoc jobs</li>
<li>batch training and evaluation pipelines</li>
<li>online inference services</li>
<li>external API calls, including LLM providers</li>
<li>changing model versions and hyperparameters</li>
</ul>
<p>When one prediction path gets slower or starts failing, plain isolated logs do not answer enough questions. You need to correlate:</p>
<ul>
<li><strong>what ran</strong> (run ID, model version, parameters)</li>
<li><strong>where time was spent</strong> (pipeline stage latencies)</li>
<li><strong>what was the result</strong> (model stats, predictions, API calls, compare with other runs)</li>
<li><strong>what changed</strong> (code, data, dependencies)</li>
</ul>
<p>In a future blog post, we'll show you how to set up automatic RCA and remediations with <a href="https://github.com/elastic/workflows/">Elastic Workflows</a> and our AI integrations. But as a first step, ML and AI pipelines need a robust Observability framework, which is very easy to set up with OpenTelemetry and Elastic.</p>
<h2 id="solutionoverview">Solution overview</h2>
<p>OpenTelemetry gives you a standard way to emit traces, metrics, and logs. Elastic provides full OpenTelemetry ingestion, giving you a single place to store and query that telemetry. Kibana's UI is fully integrated with OpenTelemetry, allowing you to explore your services, service dependencies, service latencies, spans, and metrics out-of-the-box.</p>
<p>You can start with two deployment options:</p>
<ul>
<li><strong>Cloud</strong>: send OpenTelemetry data directly to Elastic Cloud Managed OTLP Endpoint (<a href="https://www.elastic.co/docs/reference/opentelemetry/motlp">mOTLP docs</a>), without the overhead of managing collectors</li>
<li><strong>Local</strong>: run Elastic and the EDOT Collector with <a href="https://github.com/elastic/start-local?tab=readme-ov-file#install-the-elastic-distribution-of-opentelemetry-edot-collector">start-local</a>, the EDOT Collector will be automatically listening for OTLP data in <code>localhost:4317</code></li>
</ul>
<p>Both options let you keep your application code unchanged for the initial implementation.</p>
<h2 id="step1zerocodebaselineforpythonservices">Step 1: zero-code baseline for Python services</h2>
<p>Start by just installing the Elastic Distribution of OpenTelemetry Python (<a href="https://github.com/elastic/elastic-otel-python">EDOT Python</a>) package and using the <code>opentelemetry-instrument</code> wrapper to run your script. By simply running your script with this wrapper—without modifying your application code—your Python services begin emitting standard telemetry right away. This includes any logs exported via <code>logging</code>, alongside metrics and traces for auto-instrumented libraries. This data can be routed directly to Elastic's managed OTLP endpoint or a local EDOT collector.</p>
<pre><code>pip install elastic-opentelemetry
edot-bootstrap --action=install
</code></pre>
<p>Export the OpenTelemetry environment variables, then run <code>opentelemetry-instrument</code> on your script to enable auto-instrumentation.</p>
<pre><code>export OTEL_EXPORTER_OTLP_ENDPOINT="https://&lt;motlp-endpoint&gt;" # No need when using start-local with EDOT
export OTEL_EXPORTER_OTLP_HEADERS="Authorization=ApiKey &lt;key&gt;" # No need when using start-local with EDOT
export OTEL_RESOURCE_ATTRIBUTES="deployment.environment=prod,service.version=1.0.0" # Set the environment and version for your app
export OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED=true
export ELASTIC_OTEL_SYSTEM_METRICS_ENABLED=true
export OTEL_METRIC_EXPORT_INTERVAL=5000 # Choose the interval for your application metrics

opentelemetry-instrument --service_name=&lt;pipeline-name&gt; python3 &lt;your_python_script&gt;.py # Set your chosen name for your service
</code></pre>
<p>With this baseline, you can quickly get:</p>
<ul>
<li>Centralized logs with trace context. Any logs exported via <code>logging</code> will be searchable in Elastic and Kibana, with the ability to perform full-text search on your logs</li>
<li>Set alerting on log errors</li>
<li>Process and system metrics. System and process metrics from the execution will be automatically exported to Elastic. You can visualize them, and analyse memory usage (leaks, OOM errors), CPU utilization (Bottlenecks / Spikes), thread counts, disk I/O bottlenecks or network I/O saturation.</li>
<li>Set alerting on metrics</li>
<li>Spans for auto instrumented libraries</li>
<li>Service latency baselines and error trends</li>
<li>Set manual or Anomaly detection alerting on error rates, latencies or throughput</li>
<li>Correlate logs, metrics, and traces in a single shared context to quickly find the root cause of issues, using OpenTelemetry for instrumentation and Elastic for analysis.</li>
</ul>
<p>Once ingested, Kibana immediately populates out-of-the-box dashboards. You can explore full-text searchable logs, monitor system and process metrics, investigate auto-instrumented trace waterfalls, map out your ML dependencies with service maps, and easily set up alerts for latency spikes, memory or CPU usage or log errors.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6d8633610c0a2563/6a7f0d816693f803fe663f6f/step-1-logs.png" alt="Logs in Elastic" />
<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc993609820f94884/6a7f0d842f00b2803eefeb9e/step-1-log-errors.png" alt="Log errors in Elastic" />
<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9b8fe0bf9a75775c/6a7f0d88e3a219eee399f4ee/step-1-alerts-on-log-errors.png" alt="Alerts on log errors" />
<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0b5713d6d819aa11/6a7f0d8bb437702a264d6cbf/step-1-metrics.png" alt="System and process metrics" />
<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltcc0af73d41e51f83/6a7f0d8f77b0343ab23ff4fc/step-1-auto-instrumented-traces.png" alt="Auto instrumented traces" />
<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt462cfb3d689097c3/6a7f0d92e02facc0505d65c4/step-1-service-map.png" alt="Service map in Elastic" />
<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte694763a9f9e9b49/6a7f0d9505b7b54d8f18b9b4/step-1-alerts-on-latencies.png" alt="Alerts on latencies" /></p>
<p>For LLM-specific observability, OpenTelemetry provides official <a href="https://opentelemetry.io/docs/specs/semconv/gen-ai/">Semantic Conventions for Generative AI</a> to standardize how you track token usage, model names, and prompts. These semantic conventions are still in development and not stable yet. Some instrumentations for the most used libraries in this space are being developed as part of the <a href="https://github.com/open-telemetry/opentelemetry-python-contrib/tree/main/instrumentation-genai">OpenTelemetry Python Contrib repository</a>.
Alternatively you can implement these conventions manually in your custom spans. LLM related OpenTelemetry logs, metrics and traces sent to Elastic will be in context and automatically correlated with the rest of your application or stack of applications.</p>
<h2 id="step2addmlspecificcontextwithcustomspansandlogfields">Step 2: add ML-specific context with custom spans and log fields</h2>
<p>Auto-instrumentation is a starting point. For ML and AI Ops, add explicit spans around business stages and attach run metadata. Elastic's schema flexibility and dynamic mappings make it a perfect fit for custom attributes or metrics that are exclusive to your pipelines or specific experiments. There is no need to know what the data will look like before writing it. You have the flexibility of creating new parameters on the fly, Elastic maps them automatically, and you can track them instantly.</p>
<p>Add custom fields and metric-like values as structured log fields so you can chart and alert on them later:</p>
<pre><code>logger.info("training metrics", extra={
    "ml.run_id": run_id,
    "ml.training_accuracy": train_accuracy,
    "ml.validation_accuracy": val_accuracy,
    "ml.drift_detected": drift_detected,
})
</code></pre>
<p>Because Elastic handles dynamic mapping, any custom metrics or attributes you log, like model ids, training accuracy or drift detection, are instantly indexed and available to search in Discover or visualize via Dashboards.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdd807b3c06794f24/6a7f0d99448e4e92995c0757/step-2-custom-log-attributes.png" alt="Custom log attributes" /></p>
<p>This makes dashboards and rules practical:</p>
<ul>
<li>alert when <code>ml.validation_accuracy &lt; 0.8</code></li>
<li>alert when <code>ml.drift_detected == true</code></li>
<li>compare stage latency by <code>ml.model_version</code></li>
</ul>
<p>You can use these custom attributes to build targeted visualizations, and trigger alerts when ML-specific metrics like validation accuracy drop below a critical threshold.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4d712336438d643b/6a7f0d9cbd219853427580f1/step-2-charts-from-custom-log-attributes.png" alt="Charts from custom log attributes" /></p>
<p>Adding custom spans allows you to break down the specific stages of your ML pipelines, such as data loading and model training, wrapping them in their own measurable execution blocks, and analyze average latency or error rates for specific pipeline stages.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt85b6d3abe4b55617/6a7f0d9fbdcff07e50c42e91/step-2-custom-spans.png" alt="Custom spans in code" /></p>
<pre><code>from opentelemetry import trace

tracer = trace.get_tracer("ml.pipeline")

with tracer.start_as_current_span("load_data") as span:
    span.set_attribute("ml.run_id", run_id)
    span.set_attribute("ml.dataset", dataset_source)
    load_data()

with tracer.start_as_current_span("train_model") as span:
    span.set_attribute("ml.model_version", model_version)
    span.set_attribute("ml.learning_rate", learning_rate)
    train_model()
</code></pre>
<p>Custom spans will be reflected in the APM UI alongside your traces. So you can explore their latency, impact in total execution, stack traces, error rates.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd4c919f38ce841ec/6a7f0da296b5a6f4b487b4ad/step-2-custom-spans-ui-in-elastic.png" alt="Custom spans UI in Elastic" />
<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc8f5c2baa1f9c4fb/6a7f0da6bdcff0091fc42e95/step-2-analysing-spans.png" alt="Analysing spans" />
<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt33ebc4ec394dc17e/6a7f0da9bdcff070afc42e9b/step-2-latency-and-avg-latency-of-spans.png" alt="Latency and avg latency of spans" />
<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc3653db7a7074fa7/6a7f0dacb6b7340f57e48e2e/step-2-alerts-on-custom-log-metrics.png" alt="Alerts on custom log metrics" /></p>
<h2 id="step3traceacrosspythonandgoinproduction">Step 3: trace across Python and Go in production</h2>
<p>Real inference paths often cross service boundaries. For example:</p>
<p>In a production environment, a user request might pass through a Go-based API before hitting your Python ML inference service. OpenTelemetry ensures tracing context is preserved seamlessly across these boundaries.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdfad2556fa72468e/6a7f0db0b6b7342bdde48e36/step-3-service-map-with-multiple-services.png" alt="Service map with multiple services" /></p>
<p>In our example, we have a simple Go HTTP service that acts as the entry point and demonstrates OpenTelemetry instrumentation in Go. This REST API service stores and retrieves ML predictions by querying Elasticsearch based on data IDs from the source dataset. All of its endpoints are natively instrumented with OTel spans.</p>
<p>The full request lifecycle looks like this:</p>
<ol>
<li>The Go API receives the client request.</li>
<li>It searches Elasticsearch for an existing prediction or calls the Python model service to run inference.</li>
<li>The Python service loads features, runs the model, and returns predictions.</li>
</ol>
<p>When both services use OpenTelemetry, trace context is propagated automatically through headers. In Elastic, you can inspect one end-to-end trace and locate latency or errors by service and span.</p>
<p>The resulting distributed trace in Elastic pieces the entire journey together. You can see the exact breakdown of time spent in the Go API versus the Python model, and correlate logs from both services in a single unified view.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3c5b971aef4bba7b/6a7f0db3b6b7341cdbe48e3c/step-3-multiple-services.png" alt="Multiple services request flow" />
<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte34455a40908ffd9/6a7f0db62f00b2ca9aefebac/step-3-spans-per-service.png" alt="Spans per service" />
<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7400c486e102c8f5/6a7f0dba2f00b22726efebb0/step-3-go-service-logs.png" alt="Go service logs" />
<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltfb1810d0aed82332/6a7f0dbdde2315e666fd7c8d/step-3-go-traces-in-discover.png" alt="Go traces in discover" /></p>
<h2 id="validationchecklist">Validation checklist</h2>
<p>After instrumentation, validate with a short runbook:</p>
<ol>
<li>Confirm logs, metrics, and traces arrive for each service.</li>
<li>Verify your custom attributes (e.g. <code>run_id</code>, <code>model_version</code>, <code>llm_ground_truth_score</code>) are present in traces and logs.</li>
<li>Compare p95 latency per stage (<code>load_data</code>, <code>train_model</code>, <code>predict</code>).</li>
<li>Trigger a controlled failure and confirm error traces include stack context.</li>
<li>Test one rule for errors, one rule for latency spikes, and one rule for model-quality fields. Set up a connector and attach it to the rule to reach you in Slack, email, or trigger an auto-remediation workflow.</li>
</ol>
<h2 id="conclusionandnextsteps">Conclusion and next steps</h2>
<p>OpenTelemetry gives ML and AI teams a unified telemetry layer, while Elastic makes that data instantly queryable and actionable across your entire lifecycle—from notebook experiments to production inference. By starting with zero-code instrumentation and incrementally adding ML-specific attributes and cross-language tracing, your team can easily adopt the Observability best practices of mature software engineering systems and succeed in the new era of complex AI operations.</p>
<p>Try this setup in <a href="https://cloud.elastic.co/registration">Elastic Cloud</a>, and use <a href="https://www.elastic.co/docs/reference/opentelemetry/motlp">mOTLP</a> for a managed ingest path. If you want a local sandbox first, start with <a href="https://github.com/elastic/start-local?tab=readme-ov-file#install-the-elastic-distribution-of-opentelemetry-edot-collector">Elastic start-local + EDOT Collector</a>.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/ml-ai-ops-observability-opentelemetry-elastic</link>
    <guid isPermaLink="false">ml-ai-ops-observability-opentelemetry-elastic</guid>
    <category><![CDATA[Machine Learning]]></category>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[APM]]></category>
    <dc:creator><![CDATA[Almudena Sanz Olivé]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb49a2f7887e6d598/6a7f0dc0eab5bee1bb20a731/header.jpg" length="0" type="image/jpeg"/>
    <pubDate>Tue, 31 Mar 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[OpenTelemetry Profiles Signal Enters Alpha: Elastic’s Continuous Commitment to Profiling]]></title>
    <description><![CDATA[OpenTelemetry Profiles has officially reached Alpha, entrenching profiling as the fourth observability signal. Elastic's core contribution of its eBPF profiling agent, continued OpenTelemetry Profiles signal work and commitment to a vendor-agnostic ecosystem are driving this industry-wide standard forward.]]></description>
    <content:encoded><![CDATA[<p>Following intensive collaboration between Elastic and the OpenTelemetry community, we are thrilled to announce that the OpenTelemetry Profiles signal has officially entered public Alpha.
This milestone is a testament to the community's dedication and marks a significant step towards establishing profiling as the fourth key observability signal in OpenTelemetry, alongside logs, metrics and traces.</p>
<p>As a core contributor, Elastic is proud to have accelerated this effort by previously donating its Universal Profiling™ eBPF-based continuous profiling agent to OpenTelemetry.
This production-grade agent enables whole-system visibility across all applications, covering a multitude of programming languages and runtimes including third-party libraries and kernel operations with minimal overhead.
It allows SREs and developers to quickly identify performance bottlenecks, maximize resource utilization, and optimize cloud spend.</p>
<p>Additionally, over the last two years, Elastic has been heavily contributing to the OpenTelemetry Collector, Semantic Conventions and Profiling Special Interest Groups (SIGs) to lay the technical foundation for the promotion of Profiles to Alpha.</p>
<p>This Alpha milestone not only boosts the standardization of continuous profiling but also accelerates the practical adoption of profiling as the fourth key signal in observability.
Customers now have a vendor-agnostic way of collecting profiling data and enabling correlation with existing signals, like logs, metrics and traces, unveiling new potential for observability insights and a more efficient troubleshooting experience.</p>
<h2 id="whatiscontinuousprofiling">What is continuous profiling?</h2>
<p>Profiling is a technique used to understand the behavior of a software application by collecting information about its execution.
This includes tracking the duration of function calls, memory usage, CPU usage, and other system resources. </p>
<p>However, traditional profiling solutions have significant drawbacks limiting adoption in production environments:</p>
<ul>
<li>Significant cost and performance overhead due to code instrumentation</li>
<li>Disruptive service restarts</li>
<li>Inability to get visibility into third-party libraries</li>
</ul>
<p>Unlike traditional profiling, which is often done only in a specific development phase or under controlled test conditions, continuous profiling runs in the background with minimal overhead, eliminating the need for service restarts or manual intervention.
This provides real-time, actionable insights without replicating issues in separate environments.
SREs, DevOps, and developers can see how code affects performance and cost, making code and infrastructure improvements easier.</p>
<h2 id="elasticscontributionpoweringthealpha">Elastic's contribution: Powering the Alpha</h2>
<p>The Elastic-donated profiler now forms the reference eBPF-based profiler implementation within OpenTelemetry: <a href="https://github.com/open-telemetry/opentelemetry-ebpf-profiler/">opentelemetry-ebpf-profiler</a>.
With the Alpha release, the eBPF profiler operates as an OpenTelemetry Collector receiver and contains numerous improvements such as automatic Go symbolization and support for new language runtimes.
Operating as an OpenTelemetry Collector receiver enables the profiler to seamlessly leverage existing OpenTelemetry processing and filtering pipelines. </p>
<p>For example, the <a href="https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/processor/k8sattributesprocessor">k8sattributesprocessor</a> can use the <code>container.id</code> resource attribute to automatically enrich every profile with its corresponding Kubernetes context.
This means you don't just see a raw stack trace; you see exactly which namespace, pod, and deployment produced it.</p>
<pre><code>receivers:
  # Profiling receiver
  profiling: {}

processors:
  k8sattributes:
    passthrough: false 
    pod_association:
      - sources:
          - from: resource_attribute
            name: container.id
    extract:
      metadata:
        - "k8s.namespace.name"
        - "k8s.deployment.name"
        - "k8s.replicaset.name"
        - "k8s.statefulset.name"
        - "k8s.daemonset.name"
        - "k8s.node.name"
        - "k8s.pod.name"
        - "k8s.pod.ip"
        - "k8s.pod.uid"
</code></pre>
<p>Besides improvements to the eBPF profiler, Elastic has made significant contributions to:</p>
<ul>
<li>Correlating profiles with the information produced by OpenTelemetry eBPF instrumentation (<a href="https://opentelemetry.io/docs/zero-code/obi/">OBI</a>), a powerful auto-instrumentation tool that can enable distributed tracing. </li>
<li><a href="https://github.com/open-telemetry/opentelemetry-specification/pull/4719">Process Context Sharing OTEP</a> which is designed to bridge the gap between application SDKs and the profiler. This mechanism will allow OpenTelemetry SDKs to "publish" their resource attributes (like <code>service.name</code>) into a small, standardized memory region. Because this data is stored in the process's own memory map, the eBPF Profiler can instantly discover and associate it with its corresponding Profile.</li>
<li>Semantic conventions and integration of OpenTelemetry Profiles with Google's pprof format (transparent conversion)</li>
<li>OpenTelemetry Collector processing pipelines, allowing it to better integrate with the profiling receiver</li>
</ul>
<h2 id="elasticsnextgenerationprofilingdevelopment">Elastic's Next-Generation Profiling Development</h2>
<p>Elastic remains deeply committed to OpenTelemetry's vision and is pushing the boundaries of what is possible with profiling data.
We are dedicating a team of profiling domain experts to co-maintain and advance profiling capabilities within OpenTelemetry, while simultaneously working on groundbreaking features built on this new open standard.</p>
<p>Exciting areas of internal profiling-specific development include:</p>
<ul>
<li>OpenTelemetry Profiles derived Metrics: We are developing innovative ways to automatically generate actionable performance metrics directly from the raw OTel Profiles data, providing a new dimension for infrastructure modeling and alerting.</li>
<li>Rapid Integration with the Elastic Stack: We are making swift progress on first-class support for OTLP Profiles within the Elastic Stack, ensuring seamless ingestion (the ebpf-profiler receiver is already integrated with the <a href="https://github.com/elastic/elastic-agent/tree/main/internal/edot#components">Elastic Distributions of OpenTelemetry (EDOT) collector</a>), storage, and visualization of this new signal alongside your existing logs, metrics and traces.</li>
<li>AI-Powered Workflows: We are leveraging the deep insights provided by continuous profiling data to power new AI-driven workflows, enabling automatic root-cause analysis, anomaly detection, and intelligent optimization suggestions for both code and infrastructure.</li>
</ul>
<p>While the Alpha release marks a significant milestone, it is just the beginning.
We encourage the community to start testing early preview versions of the OTel Profiles integration and contribute to the ongoing profiling work.
To get started with an actual, local deployment, you can use the <a href="https://github.com/open-telemetry/opentelemetry-ebpf-profiler">OpenTelemetry eBPF profiler</a> in combination with a self-hosted <a href="https://www.elastic.co/docs/solutions/observability">Elastic Observability Stack</a> or <a href="https://github.com/elastic/devfiler">devfiler</a>, a standalone desktop application that acts as an OpenTelemetry Profiles compliant backend aimed at experimentation and development.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/otel-profiling-alpha</link>
    <guid isPermaLink="false">otel-profiling-alpha</guid>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[APM]]></category>
    <dc:creator><![CDATA[Christos Kalkanis,Florian Lehner,Roger Coll]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt710a2b45a0da343d/6a7f1978bd21986753758495/header.jpg" length="0" type="image/jpeg"/>
    <pubDate>Wed, 25 Mar 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Centrally Managing OTel Collectors with Elastic Agent and Fleet]]></title>
    <description><![CDATA[How Elastic Agent 9.3 unifies Beats and OpenTelemetry (OTel) data collection and delivers central management with Elastic Fleet.]]></description>
    <content:encoded><![CDATA[<p>"The dream of OpenTelemetry is vendor-neutral, standardised observability.
The challenge nobody mentions is how you operate hundreds, or thousands, of those collectors in production."</p>
<p>OpenTelemetry has won the hearts of the industry.
Adoption is accelerating: the CNCF's 2024 Observability survey found OTel to be the fastest-growing project in the foundation's history, with the OTel Collector registering hundreds of millions of downloads.
The proposition is compelling: write instrumentation once, ship it anywhere, avoid lock-in.</p>
<p>But here is what every platform team discovers once they cross into production: the collector sprawl problem.
Hundreds of collector instances deployed across regions, Kubernetes namespaces, and bare-metal hosts. Configuration drift creeping in.
An upgrade that has to be co-ordinated across a fleet of independent processes. A security patch that someone has to manually roll out to each one.
And zero visibility into which collectors are running, healthy, or stuck.</p>
<p>This is the gap between "deploying OpenTelemetry" and "operating OpenTelemetry at scale."
With Elastic 9.3, Elastic Agent closes that gap entirely.
The Elastic Agent is now built on Elastic's Distribution of the OpenTelemetry Collector (EDOT) and, when managed by Fleet, gives platform teams a single control plane for configuring, updating, and monitoring every OTel collector in their estate — all while remaining compatible with the Beats-based integrations they already rely on.</p>
<h2 id="thecollectorsprawlproblemandwhyitmatters">The Collector Sprawl Problem and Why It Matters</h2>
<p>OpenTelemetry's success has created a quiet operational debt for many organisations.
Individual teams adopt the collector for their services: logs here, metrics there, a custom pipeline for the new microservice.
Without a centralised management layer, each of these collectors becomes an independent snowflake: its own config file, its own upgrade cycle, its own failure domain.</p>
<p>The consequences are predictable.
Configuration drift means collectors running different versions of the same pipeline, producing subtly incompatible data.
Compliance teams ask "show me all the places data is collected and where it goes", and the honest answer is a spreadsheet that's already out of date.</p>
<p>This isn't a niche problem.
A Gartner analysis of enterprise observability programmes consistently identifies operational overhead as the top barrier to expanding OTel adoption beyond initial pilots.
The technology works. The tooling to manage it at scale is what's been missing.</p>
<h2 id="howelasticagentbecameanotelcollector">How Elastic Agent Became an OTel Collector</h2>
<p>To understand the significance of this, it helps to understand what Elastic Agent used to be, and what it is now.</p>
<p>Elastic Agent acts as a supervisor process: Before version 9.3, it managed a collection of separate Beats sub-processes (Filebeat, Metricbeat, Winlogbeat and so on), each running its own input/output lifecycle, each consuming its own memory footprint.
The agent coordinated them, but the fundamental model was a collection of discrete daemons running under a parent.</p>
<p>With 9.3, that model has been replaced.
Elastic Agent is now itself an instance of the EDOT Collector: Elastic's hardened, production-supported distribution of the upstream OTel Collector.
The architectural shift has three important consequences.</p>
<p><strong>First</strong>, the process model simplifies dramatically.
Instead of a supervisor managing multiple sub-process lifecycles, there is a single EDOT Collector process.
This means a smaller memory footprint, fewer things that can fail independently, and fewer processes to observe for health and performance.</p>
<p><strong>Second</strong>, Beats functionality is preserved, not discarded.
Rather than forcing a breaking migration, Elastic has introduced <em>Beats Receivers</em>: beat inputs and processors re-packaged as native OTel receiver components.
A Filestream input is enabled by a <code>filebeatreceiver</code>.
The same Filebeat configuration YAML you write today is automatically translated into the corresponding EDOT receiver configuration at runtime.
Existing integrations, dashboards, and ingest pipelines continue to work without modification.</p>
<p><strong>Third</strong>, the agent is now a first-class participant in the OTel ecosystem.
It speaks OTLP natively, it runs standard OTel receivers, and it can be configured to sit alongside any other OTel-compatible tool in a modern observability pipeline.</p>
<h2 id="centralmanagementwithfleetconfigurationlifecycleandvisibility">Central Management with Fleet: Configuration, Lifecycle, and Visibility</h2>
<p>The architectural shift above would be valuable on its own. But it becomes transformative when combined with Elastic Fleet, the centralised management plane for Elastic Agents.</p>
<p>Fleet gives platform and SRE teams a single console from which to manage every Elastic Agent (and by extension, every EDOT Collector instance) in their estate.
The capabilities break into three categories: configuration management, lifecycle management, and fleet-wide observability.</p>
<h3 id="configurationmanagementatscale">Configuration management at scale</h3>
<p>With Fleet, you define an <em>Agent Policy</em> — a declarative description of what a collector should do.
What data should it collect?
Via which receivers?
Where should it export?
The policy is authored once in Fleet's UI (or via its API), and pushed automatically to every agent enrolled in that policy.
Change the policy, and every affected collector receives the update.
No SSH.
No Ansible playbook to maintain.
No configuration drift.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2e1a842db5fc2e33/6a7f042e9090b0979584e674/policy-health.jpg" alt="Fleet Policy Health" /></p>
<p>Fleet pushes policies to enrolled agents across any environment. Agents send heartbeat and health data back, giving a live inventory of every collector in the estate.</p>
<h3 id="lifecyclemanagementupgradesenrolmentandremediation">Lifecycle management: upgrades, enrolment, and remediation</h3>
<p>Perhaps the most operationally significant benefit of Fleet management is lifecycle control.
With Fleet, upgrading a collector is a policy action: select the target version, select the scope (all agents, a specific policy group, a canary subset), and click.
Fleet orchestrates the rolling upgrade, tracking status per agent and surfacing failures immediately.</p>
<p>This changes the security calculus fundamentally.
When a vulnerability is disclosed in the OTel Collector binary, patching is a Fleet operation measured in minutes, not a change-management ceremony measured in days across SSH sessions to individual hosts.</p>
<p>Fleet also handles enrolment and de-enrolment.
New hosts added to your infrastructure can be auto-enrolled into the appropriate policy based on tags or deployment tooling.
Agents on decommissioned hosts can be removed from Fleet's inventory, ensuring your observability map reflects your actual infrastructure.</p>
<h3 id="fleetwideobservabilityofyourcollectors">Fleet-wide observability of your collectors</h3>
<p>Every Fleet-managed Elastic Agent ships monitoring telemetry about itself: CPU and memory consumption, event throughput, error rates, pipeline latency.
This data flows into Elastic and is surfaced in the Fleet UI, giving you a live dashboard of every collector in your estate, not just the ones you happen to be watching.</p>
<p>For the first time, "how healthy is my observability pipeline?" becomes a question with a real-time, fleet-wide answer.
You can identify agents that have stopped sending data, agents consuming unexpectedly high resources, and agents that have fallen behind on queue processing — before those problems surface as gaps in your monitoring data.</p>
<p>In the near future this capability will be offered to non-Fleet managed agents (aka standalone) and/or 3rd party OTel collectors provided by other vendors.
These collectors can be configured via some other means but be monitored in Fleet - from both resource consumption and/or component pipeline health.</p>
<h2 id="thehybridagentbeatsdataandoteldatasimultaneously">The Hybrid Agent: Beats Data and OTel Data, Simultaneously</h2>
<p>One of the most practically significant capabilities introduced in 9.3 is what Elastic calls the <em>Hybrid Agent</em>: an Elastic Agent that can run both Beats-based receivers and native OTel receivers in the same pipeline, at the same time.
This does not change anything for existing installations.</p>
<p>This matters enormously for real-world adoption. Most organisations arriving at OTel in 2025 and 2026 are not starting from a blank slate.
They have years of investment in Beats-based integrations: Filebeat-powered log collection, Metricbeat-powered host metrics, bespoke ingest pipelines in Elasticsearch that normalise and enrich that data into ECS (Elastic Common Schema) format.
The business value locked in those integrations (the dashboards, the alerts, the correlation logic) is not something they can afford to throw away in order to "go OTel."</p>
<p>The Hybrid Agent solves this by making the two worlds coexist.
For example, in a single agent policy you can simultaneously configure:</p>
<ul>
<li>A <code>filebeatreceiver</code> collecting application logs in ECS format, routed through your existing ingest pipeline to its existing data stream</li>
<li>A native OTel <code>filelog</code> receiver collecting OTel-native telemetry from your new services instrumented with the OTel SDK, stored in OTel-native data streams without touching ingest pipelines</li>
<li>An OTel <code>hostmetrics</code> receiver collecting system metrics in semantic convention format alongside your existing Metricbeat-derived system metrics</li>
</ul>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdc770c11207e91b9/6a7f0431b6b7349d41e48a0d/hybrid-agent.jpg" alt="Hybrid Agent" /> </p>
<p>The two lanes are independent.
Beats-receiver data travels through ingest pipelines and lands in ECS-formatted data streams, exactly as it always has.
Native OTel data follows OTel semantic conventions and is stored directly in OTel-native data streams, bypassing ingest pipelines.
Your existing dashboards and alerts continue to work. Your new OTel-native workloads get the full OTel experience.
The same agent, the same Fleet policy, the same management console.</p>
<p>This co-existence is the practical answer to the question every platform team eventually faces: "We want to adopt OTel properly but we can't break what we already have."
The Hybrid Agent lets you migrate incrementally, service by service, on your timeline.</p>
<h2 id="theintegrationcatalogueturningconfigurationintoaoneclickoperation">The Integration Catalogue: Turning Configuration into a One-Click Operation</h2>
<p>Configuration management at scale is only as good as the configurations themselves.
Elastic's integration catalogue — over 500 packages covering everything from NGINX and PostgreSQL to AWS CloudTrail and Kubernetes — extends naturally to the Hybrid Agent model.</p>
<p>From 9.3 onwards, the catalogue includes <em>OTel integration packages</em> alongside the existing Beats-based ones. Each OTel package contains two components:</p>
<ul>
<li>An <em>Input package</em>: the configuration for the corresponding OTel receiver (receivers, processors, pipeline wiring), ready to be applied to a Hybrid Agent policy</li>
<li>A <em>Content package</em>: the assets associated with the application: pre-built dashboards, alerts, index templates, and saved queries, all calibrated for OTel semantic convention data</li>
</ul>
<p>When an operator adds an OTel integration to an Agent Policy in Fleet, the receiver configuration is pushed to all enrolled agents.
When those agents start ingesting data and it arrives in Elasticsearch, the content package assets are automatically installed based on metadata in the data received.
The dashboard is ready before you've had time to wonder where it is.</p>
<p>The same policy can hold both OTel integrations and legacy Beats integrations.
A real-world agent policy might simultaneously collect system metrics via the OTel <code>hostmetrics</code> receiver, application logs via <code>filebeat</code> receiver, and APM data via OTLP — all from one policy, all managed from Fleet, all visible in a unified Kibana experience.</p>
<p>A technical walk through of how this is done for NGINX data collection can be found <a href="https://www.elastic.co/observability-labs/blog/hybrid-elastic-agent-opentelemetry-integration">here</a> for reference.
Currently management of Elastic Agents is done via existing Fleet protocols, however in the near future this will move over to OPAMP so that Fleet will be able to provide management to 3rd party OTel collectors as well.</p>
<p>For organisations on platforms not yet in Elastic's OS support matrix, 3rd-party OTel Collectors (such as Red Hat's OpenShift-native collector) can send data to Elastic using the OTLP exporter and be observed  alongside all other collectors in their fleet.</p>
<h2 id="whatthismeansinpracticeamigrationstory">What This Means in Practice: A Migration Story</h2>
<p>Consider a mid-sized platform team operating 200 Linux hosts across three regions, currently running Elastic Agent 8.x with a mix of Filebeat and Metricbeat integrations.
Their new services are being instrumented with the OTel SDK and they want to standardise on OTel going forward without disrupting the monitoring coverage they already have.</p>
<p>With a Fleet-managed upgrade to 9.3, their existing agents become Hybrid Agents automatically.
Their Filebeat and Metricbeat configurations are internally translated to Beats receiver configurations and continue to run unmodified.
Their existing dashboards still populate. Their ingest pipelines still fire. Nothing breaks.</p>
<p>They then add OTel integration packages to their Fleet policies for each new service. The OTel-instrumented microservices start sending OTLP data, received by native OTel receivers in the same agents.
OTel-native dashboards appear automatically in Kibana. They now have both data universes in one place, managed from one console, visible in one interface.</p>
<p>Over the following quarters, as Beats-based integrations for their remaining services are superseded by OTel equivalents in the catalogue, they migrate them one by one, updating the Agent Policy in Fleet and watching the transition happen across all 200 hosts simultaneously, without touching a single one directly.</p>
<h2 id="lookingforward">Looking Forward</h2>
<p>Elastic has made a clear architectural bet: OpenTelemetry is the future of observability data collection, and the right response to that future is not to build a parallel OTel tool alongside the existing stack — it is to evolve the existing stack into OTel.
The Hybrid Agent and EDOT Collector are the result of that bet.</p>
<p>Fleet central management is the operational layer that makes that bet practical at scale.
OpenTelemetry gives you standardised, vendor-neutral instrumentation.
Fleet gives you the operational control plane to manage those collectors like the production infrastructure they are, not like artisanal YAML files scattered across your estate.</p>
<p>The collector sprawl problem is solvable.
The answer is a managed, policy-driven, centrally observable fleet of EDOT Collectors, and in Elastic 9.3, that answer is production-ready today.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/centrally-managed-otel-collectors-with-elastic-fleet</link>
    <guid isPermaLink="false">centrally-managed-otel-collectors-with-elastic-fleet</guid>
    <category><![CDATA[OpenTelemetry]]></category>
    <dc:creator><![CDATA[Nima Rezainia]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb7bd3640a2e19035/6a7f0435eab5be18e220a318/header.jpg" length="0" type="image/jpeg"/>
    <pubDate>Tue, 24 Mar 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Developer's Guide to Easy Ops: Demystifying OpenTelemetry's Magic]]></title>
    <description><![CDATA[A Go-based Developer's 101 Guide to Easy Ops with OpenTelemetry and Elastic Observability.]]></description>
    <content:encoded><![CDATA[<h2 id="theintroductionfromcodetodashdemystified">The Introduction: From Code to Dash, Demystified</h2>
<p>Observability for developers has lately been distilled into implementing auto-instrumentation, allowing you to instantly connect your code with the larger observability world. This way of utilizing an upstream SDK is certainly the simplest and most production-ready, and works efficiently with the <a href="https://www.elastic.co/docs/solutions/observability/get-started/quickstart-elastic-cloud-otel-endpoint">Elastic Cloud Managed OTLP Endpoint</a>.</p>
<p>But what if you could not only add powerful tracing to your Go service but also <em>truly</em> understand how the magic works, rather than just copy-pasting configuration files or a line of code? In the same way that you build your knowledge of software development systems, observability, modernized by OpenTelemetry (OTel) standardization, is a rich, broad system that is valuable to understand. Here is an in-depth technical breakdown of every piece of simple OTel instrumentation using the Elastic Distributions of OpenTelemetry (EDOT) and Golang, from the ground up.</p>
<p>Telemetry is the automated collection, transmission and analysis of data from your application, which can apply to any observable distributed system. This data can range from regular health check calls with your application to real-time information about user interactions, requests, and transactions. Using the example application repository <a href="https://github.com/sophia-solo/otel-go-demo">here</a>, we’ll build a strong observability foundation to start observing our applications with confidence.</p>
<h2 id="understandingtheopentelemetryflow">Understanding the OpenTelemetry Flow</h2>
<p>Below, you will see the basic flow of your data when implementing observability with OTel in your system. Before we dive in, let’s explain some of the key terms within OTel. Let’s go over these base key players that we need to implement observability solutions with OTel:</p>
<ul>
<li><p><a href="https://www.elastic.co/docs/solutions/observability/apm/spans"><strong>Span</strong></a>: This is a single, timed unit of a distributed trace that can represent a specific operation, such as a database query or an HTTP handler.</p></li>
<li><p><a href="https://www.elastic.co/docs/solutions/observability/apm/traces"><strong>Trace</strong></a>: This is a detailed record of a single request’s journey through your system, AKA a hierarchy of your spans.</p></li>
<li><p><a href="https://opentelemetry.io/docs/specs/otel/trace/api/#tracer"><strong>Tracer</strong></a>: This is the handle for generating spans. You will typically have one per instrumentation library, for example myapp/http.</p></li>
<li><p><a href="https://opentelemetry.io/docs/specs/otel/trace/api/#tracerprovider"><strong>Tracer Provider</strong></a>: This is the cornerstone of the SDK. This creates Tracer instances, and you can configure it on application start up.</p></li>
<li><p><a href="https://www.elastic.co/docs/reference/apm/agents/go/custom-instrumentation-propagation"><strong>Context Propagation</strong></a>: The mechanism for passing trace context between operations and services, maintaining the relationship between parent and child spans.</p></li>
<li><p><a href="https://www.elastic.co/docs/deploy-manage/monitor/stack-monitoring/es-monitoring-exporters"><strong>Exporter</strong></a>: This is the part that is responsible for sending your telemetry data to a vendor backend, and you can decide if you are sending it to the OTel Collector, EDOT Collector or an OTLP Endpoint.</p></li>
</ul>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte6fcb14e0997e272/6a7f05342f00b22f3fefe840/otel-flow.png" alt="Go OpenTelemetry App Flow" /></p>
<h2 id="installingthemagicinstrumentationstyle">Installing the Magic, Instrumentation Style</h2>
<p>OpenTelemetry provides instrumentation libraries that handle much of the tracing complexity for you. These libraries wrap common frameworks and libraries (like <code>net/http/otelhttp</code>) and automatically capture telemetry without requiring you to manually create spans for every operation.</p>
<p>However, before you're able to send any telemetry, OTel needs to know <em>who</em> (which service) is sending that data.</p>
<p>A <a href="https://opentelemetry.io/docs/concepts/resources/">resource</a> represents the specific entity, in this case <code>"simple-go-service"</code>, that is producing your telemetry data. Its identity is recorded as resource attributes, and resource attributes can include pod names, service names or instances, deployment environments; Basically <em>anything</em> important to identifying your resource. This resource is your service's identity card that gets attached to every span and metric that it emits with its attributes. Once your trace arrives, these attributes can answer <em>"what version was running?"</em> or <em>"which service is this from?"</em></p>
<pre><code>func initOTel(ctx context.Context, endpoint string) (func(context.Context) error, error) {
res, err := resource.New(ctx,
        resource.WithAttributes(
            semconv.ServiceName("simple-go-service"),
            semconv.ServiceVersion("1.0.0"),
        ),
    )
    if err != nil {
        return nil, err
    }
</code></pre>
<p>In the code above, <code>resource.New()</code> constructs the "identity card" of our Go service. The attributes that will be attached to it will use semantic conventions(<code>semconv</code>), standardized names for common metadata fields. These <a href="https://opentelemetry.io/docs/concepts/semantic-conventions/">semantic conventions</a> make sure that every single OTel-compatible observability backend knows their meaning.</p>
<p>Now that we've bootstrapped our application with the <code>initOtel</code> function, we can continue to configure everything else!</p>
<p>Let’s begin instrumenting this application by building all the app components that we will need to implement modern observability tools. Below is our instrumentation using <code>otelhttp</code>, which will handle span creation after calling the specified API routes. </p>
<pre><code>http.Handle("/hello", otelhttp.NewHandler(http.HandlerFunc(handleHello), "hello"))
http.Handle("/api/data", otelhttp.NewHandler(http.HandlerFunc(handleData), "data"))
http.HandleFunc("/health", handleHealth)

// Example of a tracer within our handleHello() function
tracer = tp.Tracer("simple-go-service")

ctx, span := tracer.Start(ctx, "process-hello")
defer span.End()
</code></pre>
<p>The key insight here is that <code>otelhttp.NewHandler</code> handles all the span lifecycle management for HTTP requests. You don't need to manually call <code>tracer.Start()</code>or <code>span.End()</code> for basic HTTP tracing since the library does this for you.</p>
<p>On application start up, the SDK will use the tracer provider set up below in order to create Tracer instances. These instances help create and manage the spans contained within traces.</p>
<pre><code>traceExporter, err := otlptracegrpc.New(ctx,
    &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;otlptracegrpc.WithEndpoint(endpoint),
    &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;otlptracegrpc.WithInsecure(),
    )

    tp := sdktrace.NewTracerProvider(
    &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;sdktrace.WithBatcher(traceExporter),
    &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;sdktrace.WithResource(res),
    )
    otel.SetTracerProvider(tp)
    tracer = tp.Tracer("simple-go-service")
</code></pre>
<p>Within our <code>initOTel</code> function, we will set up one of our most important signals: logs. First, we initialize the logExporter that will send logs to our OTel Collector using gRPC protocol. Then the <code>LoggerProvider</code> will create the base of the <code>logExporter</code> that batches log entries together before sending those batches to your exporter, attaching metadata about the services along the way. Lastly, the <code>LoggerProvider</code> also creates a standard Go structured logger (slog) that automatically includes trace context (such as span IDs) and batches your log with other logs. These are sent to your observability backend through the exporter along with your metrics and traces. </p>
<pre><code>logExporter, err := otlploggrpc.New(ctx,
        otlploggrpc.WithEndpoint(endpoint),
        otlploggrpc.WithInsecure(),
    )
    if err != nil {
        return nil, err
    }

    lp := sdklog.NewLoggerProvider(
        sdklog.WithProcessor(sdklog.NewBatchProcessor(logExporter)),
        sdklog.WithResource(res),
    )
    logger = slog.New(otelslog.NewHandler("simple-go-service", otelslog.WithLoggerProvider(lp)))
</code></pre>
<p>Below you can see how you can view your logs through Kibana in the APM UI. These logs are also color - coordinated; Coded warnings are in yellow, errors are in red, and regular logs are in green.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte40c8a47ca47ad2e/6a7f0537b4377041fe4d6963/log-viewer.png" alt="Viewing your logs in the APM UI" /></p>
<p><a href="https://www.elastic.co/docs/solutions/observability/apm/metrics">Metrics</a> are set up in the next part of our code. Metrics are telemetry signals that track the quantitative data from your application, such as response times and request counts. The metric exporter is initialized to send metric data to our EDOT Collector then to our observability backend, Elastic Observability in this case, using gRPC. The meter provider in the next portion periodically collects and exports our metrics data and measurements, the same as the tracer provider creates tracers. The only difference between the two providers is that the meter provider works on a timer while the trace provider exports spans as they complete.</p>
<pre><code>metricExporter, err := otlpmetricgrpc.New(ctx,
        otlpmetricgrpc.WithEndpoint(endpoint),
        otlpmetricgrpc.WithInsecure(),
    )
    if err != nil {
        return nil, err
    }

    mp := metric.NewMeterProvider(
        metric.WithReader(metric.NewPeriodicReader(metricExporter)),
        metric.WithResource(res),
    )
    otel.SetMeterProvider(mp)

    meter := mp.Meter("simple-go-service")
    requestCounter,  = meter.Int64Counter("http.requests")
    requestDuration,  = meter.Float64Histogram("http.duration")
</code></pre>
<p>In order to finish initializing OpenTelemetry, we set up our propagators for context propagation. The set text map propagator automatically injects the trace ID and the span ID of your service making an outbound HTTP request to another service, following the <a href="https://www.w3.org/TR/trace-context/">W3C Trace Context</a> standard. In short, this maintains the parent-child relationship between spans.</p>
<pre><code>otel.SetTextMapPropagator(propagation.TraceContext{})

    return func(ctx context.Context) error {
        tp.Shutdown(ctx)
        mp.Shutdown(ctx)
        lp.Shutdown(ctx)
        return nil
    }, nil
</code></pre>
<p>Now that you know how these pieces work together, try to run the repository linked <a href="https://github.com/sophia-solo/otel-go-demo">here</a>, using the readme as your guide.</p>
<h3 id="sidenoteaddingcustomspans">Sidenote: Adding Custom Spans</h3>
<p>For getting an application emitting traces, this instrumentation works great! If you visit localhost:8080/hello after starting the docker containers, the <code>otelhttp</code> middleware automatically creates spans for each HTTP request. However, basic instrumentation only shows essential application telemetry, such as response duration, URL paths, and status codes. You won’t know what happens between the request coming in and request completion. The moment OpenTelemetry truly gains power is when you add custom spans. Unlike auto-instrumentation where spans are created as well as closed automatically, custom spans require you to explicitly start and stop them.</p>
<p>Custom spans can track your application’s logic, such as specific business events or marking expensive operations, using a detailed hierarchy within each trace. In the <a href="https://github.com/sophia-solo/otel-go-demo">application</a> for this article, there are several custom spans that were created to track important operations:</p>
<ul>
<li><p><code>background-work</code>: This traces asynchronous processing that happens with the main request.</p></li>
<li><p><code>computation:</code> This measures computations and then captures those results, and the computation type.</p></li>
</ul>
<p>Custom spans add granular visibility into your application's behavior. For example, in <code>performComputation</code>:</p>
<pre><code>ctx, span := tracer.Start(ctx, "computation")
defer span.End()

result := rand.Float64()
span.SetAttributes(
    attribute.String("comp.type", compType),
    attribute.Float64("comp.result", result),
    )

    logger.InfoContext(ctx, "Computation completed", "type", compType, "result", result)

if result &lt; 0.3 {
span.AddEvent("Low confidence result")
    logger.WarnContext(ctx, "Low confidence computation", "result", result)
}
}
</code></pre>
<p>The attributes set above become searchable and filterable in our Elastic Observability backend, allowing for attribute filtering by <code>attribute.result</code> and <code>attribute.compType</code>. If you query your data with “show me all computations where results are less than 0.3,” then you will notice the span event <code>span.AddEvent(“Low confidence result”)</code> tacked on with a timestamped marker. This appears on your trace timeline as well, adding even more visibility to any unusual events. Below is a small example of the filtering that Kibana can accomplish from custom spans.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta0fe36324e168b50/6a7f053b6c6eac182ef13dc5/computations.png" alt="Filtering attribute.Result to review borderline Low Confidence results" /></p>
<h2 id="thedatapipelinefromcodetoirl">The Data Pipeline: From Code to IRL</h2>
<p>Now that you can export your custom spans and data to OTLP which sends it to the EDOT Collector and then to an observability backend, the best hub for your telemetry data will be the [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/. It is a simple, standalone process that is able to receive, process and export all of your telemetry data. Within this project, we use the Elastic Distributions of OpenTelemetry (<a href="https://www.elastic.co/docs/solutions/observability/get-started/opentelemetry/quickstart/self-managed/docker">EDOT</a>) Collector, an optimized Collector for usage within your Elastic Stack. Since this is a self-managed Elastic instance, this article and connected repository utilize the EDOT Collector through <code>elasticapm</code>, but for Elastic Cloud or Serverless projects, you can use the Elastic Managed OpenTelemetry Protocol (OTLP) Endpoint. As noted in the quickstart documentation <a href="https://www.elastic.co/docs/solutions/observability/get-started/quickstart-elastic-cloud-otel-endpoint">here</a>, the Elastic Cloud Managed OTLP Endpoint endpoint helps get your data quickly and efficiently into your Elastic Stack through OTLP, without schema translation! This means that your telemetry hits Elastic instantly and your telemetry data remains vendor-neutral.</p>
<p>For most developers and SREs, this Collector is an amazing tool. It allows you to decouple your code from the observability backend. Your application does not need to know its final destination, it can just send the data to the Collector. Your observability backend can change constantly without it even touching your code. The OpenTelemetry Collector also acts as a gateway for multiple streams of data, and is able to accept various formats in order to unify them for exportation. Lastly, the OpenTelemetry Collector is able to offload processing power from your application - tasks such as retries, batching and filtering can happen in the Collector, not your application.</p>
<p>After trying out this article’s repository, try auto-instrumenting your application with <a href="https://www.elastic.co/docs/reference/opentelemetry">Elastic Distributions of OpenTelemetry</a> (EDOT) so that you can utilize the APM UI to its full potential! With the latest version of Elasticsearch and Kibana <a href="https://github.com/elastic/start-local"><code>start-local</code></a>, you can use <a href="https://www.docker.com/">Docker</a> to install and run the services and instantly start monitoring your application. </p>
<h3 id="understandingthecollectorconfiguration">Understanding the Collector Configuration</h3>
<p>The Collector's behavior is defined in a configuration file (<code>otel-collector-config.yaml</code>). Let's break down each component.</p>
<p><strong>Receivers</strong> define how the Collector accepts telemetry data. Here, we're listening for both gRPC and HTTP traffic.</p>
<pre><code>receivers:
&amp;nbsp;&amp;nbsp;# Receives data from other Collectors in Agent mode
&amp;nbsp;&amp;nbsp;otlp:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;protocols:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;grpc:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;endpoint: 0.0.0.0:4317
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;http:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;endpoint: 0.0.0.0:4318
</code></pre>
<p><strong>Connectors</strong> are specialized components that sit in between pipelines, and in this case, we are using the <code>elasticapm</code> Connector. This APM Connector exports our metrics, logs, and traces, while simultaneously acting as a receiver for the metrics/aggregated-otel-metrics pipeline (see below). Without it, your raw OTLP data lands in Elasticsearch, but the APM UI has nothing to build its views from.</p>
<pre><code>connectors:
  elasticapm: {} # Elastic APM Connector
</code></pre>
<p><strong>Processors</strong> transform, filter, or enrich data as it passes through the EDOT Collector. The batch processor aggregates spans before export, reducing network overhead and improving efficiency, as well as limiting batch sizes. The batch/metrics processor does this as well, but for APM metrics. Lastly, there is the Elastic APM processor. This processor ensures that your spans fields are aligned, your traces views are complete, and  it overall bridges the gap between Elastic's expectations and OpenTelemetry's formatting of your traces.</p>
<pre><code>processors:
  batch:
    send_batch_size: 1000
    timeout: 1s
    send_batch_max_size: 1500
  batch/metrics:
    send_batch_max_size: 0 # Explicitly set to 0 to avoid splitting metrics requests
    timeout: 1s
  elasticapm: {} # Elastic APM Processor
</code></pre>
<p>As mentioned previously in the article, <strong>exporters</strong> send data to your observability backend. The debug exporter logs telemetry to the console (useful for development), while the Elasticsearch exporter sends traces to your Elastic stack.</p>
<pre><code>exporters:
  debug: {}
  elasticsearch/otel:
    endpoints:
      - ${ELASTIC_ENDPOINT} # Will be populated from environment variable
    user: elastic
    password: ${ELASTIC_PASSWORD}
    tls:
      ca_file: /config/certs/ca/ca.crt
    mapping:
      mode: otel
</code></pre>
<p><strong>Pipelines</strong> connect receivers, processors, and exporters into a data flow. These EDOT Collector pipelines receive OTLP traces, batches them, and exports to the <code>debug</code>, <code>elasticapm</code> and <code>elasticsearch/otel</code> exporters. It also exports metrics to the <code>debug</code> and <code>elasticsearch/otel</code> exporters.</p>
<pre><code>service:
  pipelines:
    metrics:
      receivers: [otlp]
      processors: [batch/metrics]
      exporters: [debug, elasticsearch/otel]
    logs:
      receivers: [otlp]
      processors: [batch]
      exporters: [debug, elasticapm, elasticsearch/otel]
    traces:
      receivers: [otlp]
      processors: [batch, elasticapm]
      exporters: [debug, elasticapm, elasticsearch/otel]
    metrics/aggregated-otel-metrics:
      receivers:
        - elasticapm
      processors: [] # No processors defined in the original for this pipeline
      exporters:
        - debug
        - elasticsearch/otel
</code></pre>
<h2 id="debuggingyourcodewithconfidenceinkibana">Debugging Your Code with Confidence in Kibana</h2>
<p>Elastic Observability, utilizing Kibana and Streams, has native support for the OTLP Endpoint through the EDOT Collector, which was used in this project. Below, you can see that your data is automatically connected to Streams from the beginning, requiring no extra leg work! You can add conditions or any Grok processors as your data is streaming in, and you'll be able to instantly see your data's schema and data quality.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7f0d96abddaada00/6a7f053dde23158fa4fd786d/streams-connection.png" alt="Streams built-in connection" /></p>
<p>Elastic also provides the Elastic Cloud Managed Endpoint for even easier storage, data-processing, and scaling. If you use this Managed Endpoint, it means that you can configure OpenTelemetry to send data directly to Elasticsearch, without ANY specialized Collectors. Any way you choose, once your traces are flowing, Kibana’s APM UI provides powerful visualization and analysis capabilities will be everything you need to debug your code. You are able to drill down into individual requests, identify bottlenecks, find anomalies and troubleshoot any issues that arise with confidence.</p>
<p>Here is one span of interest from this repository. Within Kibana, you can immediately filter by the Trace ID, finding other spans with the same Trace ID to visually see the entire trace.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt07ee960435778536/6a7f054173d9bddcb129d7f8/pre-filter-traces.png" alt="A span of interest among many" />
<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltff0f71d463353c0c/6a7f0544ea068d7b00f09b4c/post-filter-traces.png" alt="The entire trace of the span" /></p>
<p>Kibana Discover also allows you to switch indices instantly without losing your filters, ensuring that you can also see the logs that correspond with the same Trace ID.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt77ba55b402496814/6a7f05482f00b2d0c8efe852/log-trace.png" alt="Logs matching the Trace ID" /></p>
<p>In addition to the manually checking your traces, you can automatically check them within the APM UI (shown below). This is easy trace visualization using the Kibana APM UI is readily available while using the <code>elasticapm</code> connector. Below is a visualization of a trace comprised of spans within our project. Knowing both methods of correlating spans is beneficial to build the foundation of utilizing Kibana and the APM UI for observability.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8ca3abbac39f6f65/6a7f054bbd2198b235757d48/automatic-apm-trace.png" alt="Automatic trace span hierarchy in Kibana APM" /></p>
<p>Here is a fully built out dashboard built from the repository featured in this article. The possibilities with Elastic Observability are endless!</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf6d064cb610d0f80/6a7f054eead8ec624cbaa4df/kibana-dashboard.png" alt="Full Kibana Dashboard" /></p>
<h2 id="congratsyourenotjustadeveloperanymore">Congrats, You’re Not “Just” a Developer Anymore!</h2>
<p>We’ve broken down the why and how behind OpenTelemetry’s basic components, including the TraceProvider, the span, the exporter and the Collector. Here, you’ve done more than just implement your tracing tool. You now understand the complete data flow from your code to the graphs on your dashboard.</p>
<p>You can now speak the language of observability with confidence, not because you memorized a configuration file, but because you now understand the data flow from your code to the graph on your dashboard. You understand how telemetry moves through your system. You aren’t “just” a developer anymore; you’re now a developer who can truly see.</p>
<p>Try out the code repo above! Included in the [repository]() is a generate-traffic.sh script file. You can run this repeatedly in order to generate logs, traces, and metrics for you to play with within the APM UI. Also, check out our latest <a href="https://www.elastic.co/docs/release-notes/elasticsearch">releases</a> in our release docs page for exciting Elastic updates.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/developers-guide-to-easy-ops</link>
    <guid isPermaLink="false">developers-guide-to-easy-ops</guid>
    <category><![CDATA[Infrastructure Monitoring]]></category>
    <category><![CDATA[OpenTelemetry]]></category>
    <dc:creator><![CDATA[Sophia Solomon]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt76532418dd498412/6a7f055196b5a66f0087b133/blog-header.png" length="0" type="image/png"/>
    <pubDate>Tue, 17 Mar 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Why Elastic donated its OpenTelemetry PHP distribution]]></title>
    <description><![CDATA[Learn what the OpenTelemetry PHP distro donation changes for package-managed PHP environments, how it compares to existing options, and what contributors can do next.]]></description>
    <content:encoded><![CDATA[<p>We <a href="https://www.elastic.co/observability-labs/blog/opentelemetry-accepts-elastics-donation-of-edot">donated EDOT PHP</a> to make OpenTelemetry for PHP as easy to deploy as any other runtime.
PHP powers a significant number of websites and SaaS platforms, and we hope our contribution helps more teams adopt OpenTelemetry.
In many production environments, runtimes are locked down and building native extensions during deploy is not possible, so we focused on an OS-package-first path (<code>deb</code>, <code>rpm</code>, <code>apk</code>) for zero-code instrumentation.</p>
<p>Since we announcement of the donation, we have been actively working on the project and are about to release a first beta version.</p>
<p>In this post, we will walk through what was donated, why it matters for production PHP systems, how it relates to existing OpenTelemetry PHP projects, and what is the status of the project.</p>
<h2 id="whyphpobservabilitycanstillbehard">Why PHP observability can still be hard</h2>
<p>OpenTelemetry gives us a common standard, but deployment reality still matters. In many PHP environments, the blocker is not instrumentation APIs. The blocker is operations.</p>
<p>At Elastic, we are committed to open standards and to OpenTelemetry as the industry standard for observability data collection. To help the community with these operational constraints, we donated our EDOT PHP distribution to OpenTelemetry.</p>
<p>Common constraints include:</p>
<ul>
<li>Shared hosting or hardened servers without build toolchains</li>
<li>Production images that cannot be rebuilt frequently</li>
<li>Package-managed PHP runtimes where OS-native install flows are required</li>
<li>Teams that need adoption without app code changes</li>
</ul>
<p>This is where an OS-package distribution helps. If you can install a package and restart PHP, you can usually start collecting telemetry.</p>
<h2 id="whattheopentelemetryphpdistroprovides">What the OpenTelemetry PHP distro provides</h2>
<p>The project we donated combines native and PHP runtime components into one production path.
The key features we announced in our original donation proposal are implemented and we are close to a first beta release. 
This includes:</p>
<ul>
<li>Native extension and loader artifacts so teams can install prebuilt components instead of compiling in restricted environments.</li>
<li>Runtime/bootstrap logic for auto-instrumentation so applications can emit telemetry with little or no code changes.</li>
<li>Packaging support for <code>deb</code>, <code>rpm</code>, and <code>apk</code> so rollout fits existing Linux package management and operations workflows.</li>
<li>Background telemetry sending and automatic root span behavior so trace data is captured consistently without custom bootstrapping logic or blocking of the main flow.</li>
<li>OTLP protobuf serialization works out of the box, with no need for the <code>ext-protobuf</code> extension. This means teams don't have to install extra dependencies, which is especially important in PHP environments where adding new extensions is difficult or restricted.</li>
<li>Inferred spans so users get added visibility into work that is not explicitly instrumented in application code.</li>
<li>URL grouping for transaction/root spans so high-cardinality route data is easier to aggregate and analyze.</li>
<li>Built-in OpAMP support utilizing an OpAMP client already present in the agent.</li>
</ul>
<p>For teams running PHP <code>8.1</code> through <code>8.4</code>, this gives a practical onboarding path that fits existing OS package operations.</p>
<h2 id="howinstallationlooksinpractice">How installation looks in practice</h2>
<p>A typical flow is simple:</p>
<ol>
<li>Install distro package for your Linux platform.</li>
<li>Set exporter endpoint and auth headers.</li>
<li>Restart PHP processes.</li>
<li>Verify traces in your collector or backend.</li>
</ol>
<p>Example environment variables:</p>
<pre><code>export OTEL_EXPORTER_OTLP_ENDPOINT="https://your-collector.example:4318"
export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer &lt;token&gt;"
</code></pre>
<p>The key idea is predictable rollout through standard operational controls rather than custom build steps in each application pipeline.</p>
<p>For a detailed guide on how to setup the distro, see the <a href="https://github.com/open-telemetry/opentelemetry-php-distro/blob/main/docs/getting-started/setup.md">setup documentation</a>.</p>
<h2 id="relationshiptoexistingopentelemetryphpinstrumentation">Relationship to existing OpenTelemetry PHP instrumentation</h2>
<p>The current message from maintainers is coexistence with clear differentiation:</p>
<ul>
<li><strong>Distro path</strong>: package-managed, production-first, zero-code onboarding</li>
<li><strong>Composer-centric path</strong>: more manual control and portability where that is needed</li>
</ul>
<p>This distinction matters for users choosing a starting point. If you control application packaging tightly and can build extensions as part of app install, Composer-oriented paths can still be a fit. If you need an operations-first rollout through OS packages, the distro can reduce adoption friction.</p>
<p>The donation discussion also raised an important usability concern: too many overlapping options can confuse users. That is why compatibility and long-term alignment across projects is a key follow-up topic. The original proposal details are available in the <a href="https://github.com/open-telemetry/community/issues/2846">OpenTelemetry community donation issue</a>.</p>
<h2 id="whattovalidatebeforebroadproductionrollout">What to validate before broad production rollout</h2>
<p>If you want to test this path in your own environment, validate these points early:</p>
<ul>
<li><strong>Runtime coverage</strong>: verify your PHP version and SAPI mode (PHP-FPM, Apache <code>mod_php</code>, CLI)</li>
<li><strong>Packaging fit</strong>: confirm your distro package format and architecture support</li>
<li><strong>Telemetry behavior</strong>: check span completeness, service naming, and exporter reliability</li>
<li><strong>Operational safety</strong>: verify restart procedures, rollback steps, and version pinning policy</li>
</ul>
<p>A lightweight validation matrix can save rework later, especially when multiple runtime profiles exist in the same organization.</p>
<h2 id="thecurrentstatusandwhatsnext">The current status and what's next</h2>
<p>With the completion of the initially-announced, above-mentioned features, we reached a significant milestone and are about to release a first beta version.</p>
<p>However, the work does not stop here. More enhancements and features are planned, including:</p>
<h3 id="classshadowing">Class Shadowing</h3>
<p>In some situations it is possible that the PHP distro loads classes that are already loaded by the application itself. 
This can lead to collisions and unexpected behavior. 
We are working on implementing shadowing of classes and namespaces of the PHP distro dependencies to avoid collisions.
This new feature will increase the stability and reliability of the PHP distro across a wider range of applications.</p>
<h3 id="declarativeconfigurationsupport">Declarative Configuration Support</h3>
<p>Just recently the OpenTelemetry community announced the <a href="https://opentelemetry.io/blog/2026/stable-declarative-config/">stability of the declarative configuration specification</a>. 
Including native support for the declarative configuration specification in the PHP distro is a feature that is on our roadmap and one of the next major features we will be working on.
With declarative configuration support, teams will be able to configure the PHP distro in a more flexible way without having to modify their application code.</p>
<p>We are working on implementing declarative configuration support in the distro to allow for more fine-grained configuration of the agent.
This new feature will increase the flexibility and usability of the PHP distro.</p>
<h3 id="php85support">PHP 8.5 Support</h3>
<p>In November 2025, PHP 8.5 was released with major changes to the language and runtime.
We are working on supporting it in the PHP distro which will expand the scope of compatibility and usability of the PHP distro across a wider range of applications and environments.</p>
<h3 id="centralconfigurationsupport">Central Configuration Support</h3>
<p>Both, the declarative configuration as well as the <a href="https://github.com/open-telemetry/opentelemetry-specification/pull/4738">new proposal around telemetry policies</a> are potential and promising enablers for dynamic, central configuration of OTel SDKs and distors.
Once the discussion around telemetry policies is resolved, we will be able to implement central configuration support in the PHP distro.
This will combine the concepts around declarative configuration, telemetry policies and the OpenTelemetry Agent Management Protocol (OpAMP) to provide a more flexible and powerful way to configure the PHP distro centrally.</p>
<h3 id="baseedotontheupstreamdistribution">Base EDOT on the upstream distribution</h3>
<p>With the new OpenTelemetry PHP distro reaching a first beta release, we are working on basing Elastic's OTel PHP distribution (EDOT PHP) on the upstream distribution to increase the compatibility and avoid feature drift.
This will ensure that the PHP distro is always up to date with the latest features and bug fixes from the OpenTelemetry community.</p>
<h2 id="conclusion">Conclusion</h2>
<p>Our OpenTelemetry PHP distro donation is mainly about operational accessibility. It gives PHP teams a package-native way to adopt OpenTelemetry where build-time instrumentation is difficult.
As community alignment progresses, we expect this to become a clearer and lower-friction option for production PHP observability. Try out the <a href="https://github.com/open-telemetry/opentelemetry-php-distro">OpenTelemetry PHP distro repository</a>, document gaps, and feed findings back to the maintainers.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/otel-php-distro-donation</link>
    <guid isPermaLink="false">otel-php-distro-donation</guid>
    <category><![CDATA[OpenTelemetry]]></category>
    <dc:creator><![CDATA[Pawel Filipczak]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt750d8298af04ae7c/6a7f1973b437702d614d70e2/header.jpg" length="0" type="image/jpeg"/>
    <pubDate>Tue, 10 Mar 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Network monitoring with Elastic: Unifying network observability]]></title>
    <description><![CDATA[Learn how to unify network monitoring using Elastic observability and AI. We'll showcase how to correlate network data, identify root causes and fix issues.]]></description>
    <content:encoded><![CDATA[<h2 id="introductionthenetworkmonitoringfragmentationproblem">Introduction: The Network Monitoring Fragmentation Problem</h2>
<p>In five years working with Enterprise accounts at Elastic, I have heard the same challenge again and again:</p>
<p><strong>"We have several network monitoring tools, and we would love to correlate all of them into one platform."</strong></p>
<p>For many organizations, the barrier to true correlation isn't a lack of data, but where that data lives. Frequently, we see SNMP metrics, flow data, and logs isolated in purpose-built silos or dashboards. Without a unified data store and a proper correlation engine, piecing together the full narrative — from a topology change to a performance degradation — becomes a manual, time-consuming puzzle.</p>
<p>When an incident happens, engineers become <strong>human correlation engines</strong> — manually jumping between systems, copying timestamps, cross-referencing device names, and trying to piece together what actually happened. A simple question like "Did this interface failure impact application performance?" requires querying multiple tools and mentally correlating the results.</p>
<p>The real cost isn't the tool licenses — it's the time lost during critical incidents.</p>
<p>This lab is my answer to a fundamental question: <strong>Can Elastic become the unified foundation that actually correlates network data?</strong></p>
<p>More importantly, it demonstrates that Elastic is fully ready for network operations — capable of ingesting diverse telemetry and using AI to correlate relationships, identify root causes, and resolve issues in seconds instead of hours.</p>
<h2 id="theproblemnetworkobservabilityisbroken">The Problem: Network Observability is Broken</h2>
<p>Let me paint a typical scenario I encounter with enterprise network teams:</p>
<p><strong>The Fragmented Reality:</strong></p>
<ul>
<li>No single source of truth</li>
<li>Manual correlation during incidents (15-30 minutes per event)</li>
<li>Fragmented teams (network vs. platform engineers)</li>
<li>Limited automation capabilities</li>
<li>No AI-powered analysis</li>
</ul>
<p><strong>When a link goes down at 2 AM:</strong></p>
<ul>
<li>Notice the alert - 2 minutes</li>
<li>Log into monitoring tool to see the metric - 3 minutes</li>
<li>Switch to traffic analyzer to check impact - 5 minutes</li>
<li>Open log management to search for related messages - 10 minutes</li>
<li>Manually correlate timestamps across systems - 8 minutes</li>
<li>Create a ticket and copy context from multiple tools - 8 minutes</li>
</ul>
<p><strong>Time to initial diagnosis: 36 minutes</strong></p>
<p>This workflow is expensive, error-prone, and doesn't scale.</p>
<h2 id="thevisionelasticasaunifiednetworkobservabilityplatform">The Vision: Elastic as a Unified Network Observability Platform</h2>
<p>What if you could:</p>
<ul>
<li>Collect SNMP metrics, NetFlow, traps, and topology data in <strong>one platform</strong></li>
<li>Correlate network events with application performance <strong>automatically</strong></li>
<li>Generate executive dashboards without separate BI tools</li>
<li>Use <strong>AI to analyze incidents in seconds</strong>, not hours</li>
<li>Trigger alerting from network events</li>
</ul>
<p>This is what this lab aims to demonstrate.</p>
<h2 id="whatibuiltaproductiongradenetworksimulation">What I Built: A Production-Grade Network Simulation</h2>
<p>To demonstrate how Elastic unifies network data, I needed a realistic environment that generates real-world telemetry. Enter <strong>Containerlab</strong>  —  a Docker-based solution that enables us to create a network simulation framework.</p>
<h3 id="labarchitecture">Lab Architecture</h3>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf2d5eece08d2850c/6a7f0e7796b5a6bf6087b4eb/lab-topology.jpg" alt="Lab Topology" /></p>
<p>I simulated a Service Provider core network with:</p>
<ul>
<li><strong>7 FRR routers</strong> forming an OSPF Area 0 mesh</li>
<li><strong>2 Ubuntu hosts</strong> for additional use cases</li>
<li><strong>2 Layer 2 switches</strong> for access layer segmentation</li>
<li><strong>3 telemetry collectors</strong> feeding Elastic Cloud</li>
</ul>
<p><strong>Total containers:</strong> 14</p>
<p><strong>Deployment time:</strong> 12-15 minutes (fully automated)</p>
<p><strong>Full deployment instructions and topology details are available in the <a href="https://github.com/DeBaker1974/Containerlab-OSPF">GitHub repository README</a>.</strong></p>
<h2 id="thethreetelemetrypipelinesprovingmultisourcecorrelation">The Three Telemetry Pipelines: Proving Multi-Source Correlation</h2>
<p>What makes this lab production-ready is its <strong>hybrid observability approach</strong> — proving that Elastic can unify disparate network data sources.</p>
<p>| Pipeline | Data Type | Collection Method | Collector | Use Case |
| :---- | :---- | :---- | :---- | :---- |
| <strong>SNMP Metrics</strong> | Interface stats, system health, LLDP topology | Active polling  | OTEL Collector | Capacity planning, trend analysis |
| <strong>NetFlow</strong> | Traffic flows | Push-based export | Elastic Agent | Top talkers, security investigation |
| <strong>SNMP Traps</strong> | Interface up/down events | Event-driven | Logstash | Real-time incident detection |</p>
<p>This unified architecture proves Elastic can replace multiple specialized network monitoring tools with a single platform.</p>
<h2 id="thepowerofcorrelationoneplatformonequery">The Power of Correlation: One Platform, One Query</h2>
<p>When a network incident occurs, you need to answer questions like:</p>
<ul>
<li>Which interface failed? <em>(SNMP metrics)</em></li>
<li>What traffic was affected? <em>(NetFlow)</em></li>
<li>What was the sequence of events? <em>(SNMP traps)</em></li>
<li>Which devices are downstream? <em>(LLDP topology)</em></li>
</ul>
<p><strong>The Problem:</strong> modern tools offer separate modules glued together, forcing users to navigate different spaces for different sets of data.</p>
<p><strong>The Reality:</strong> You still have to pivot. You see a spike in the Metrics module, but to see why, you have to open the Logs module and manually align the time picker. The data lives in different tables or backends, making true correlation impossible without human intervention.</p>
<p><strong>The Elastic Difference:</strong> One Store, One Language, One AI</p>
<p>Elastic makes it simple. Whether it's an SNMP counter (metric), a NetFlow record (flow), or a Syslog message (log), it is all stored in a unified datastore powered by the Elasticsearch engine. This allows users to easily search across multiple datasets in a single query.</p>
<pre><code>FROM logs-*
| WHERE host.name == "csr23" AND interface.name == "eth1"
</code></pre>
<p><strong>Time required: 3 seconds</strong></p>
<p>Furthermore, as you will see later, the exact location of the data becomes agnostic to the user when leveraging the AI Assistant.</p>
<h2 id="datatransformationfromcrypticoidstoactionableintelligence">Data Transformation: From Cryptic OIDs to Actionable Intelligence</h2>
<p>Raw SNMP traps are notoriously difficult to interpret at a glance. In our current lab setup, the data arrives looking like this:</p>
<pre><code>OID: 1.3.6.1.6.3.1.1.5.3
ifIndex: 2
ifDescr: eth1
</code></pre>
<p>While traditional Network Management Platforms (NMPs) handle OID translation natively, bringing that clarity into Elastic requires a specific configuration.</p>
<p>In this initial lab, we are intentionally working with this raw data to demonstrate how AI assistants can interpret these events even without pre-existing context.</p>
<p>However, the strategy for the next phase of this project is to implement Elasticsearch Ingest Pipelines. This will allow us to map raw OIDs to human-readable names. This step is crucial for bridging the gap between Network tools and Application Observability platforms, allowing network events to be instantly correlated with application errors and infrastructure logs.</p>
<p><strong>The Target State</strong></p>
<p>Once the pipeline is implemented in the next lab, we will transform that raw trap into searchable, meaningful data:</p>
<pre><code>{
  "event.action": "interface-down",
  "host.name": "csr23",
  "interface.name": "eth1",
  "interface.oper_status_text": "Link Down"
}
</code></pre>
<p><strong>The result:</strong></p>
<ul>
<li>Human-readable fields</li>
<li>Searchable dimensions for filtering</li>
<li>Context for automation rules and dashboards</li>
<li>Correlation keys for joining with metrics and flows</li>
</ul>
<p>In our next blog post, we will walk through building the ingest pipeline that performs this transformation — step by step.</p>
<h2 id="intelligentalertingfromnoisetoactionableintelligence">Intelligent Alerting: From Noise to Actionable Intelligence</h2>
<p>Traditional network monitoring relies on simple threshold alerts — "interface down," "high CPU." These alerts flood your inbox but provide <strong>zero context</strong> about root cause, impact, or remediation.</p>
<h3 id="thelabsapproachesqlaiassistant">The Lab's Approach: ES|QL + AI Assistant</h3>
<p><strong>1. Semantic Detection with ES|QL</strong></p>
<p>Instead of generic threshold alerts, the lab uses ES|QL to detect specific event patterns:</p>
<pre><code>FROM logs-snmp.trap-prod
| WHERE snmp.trap_oid == "1.3.6.1.6.3.1.1.5.3"
| KEEP @timestamp, host.name, interface.name, message
</code></pre>
<p><strong>2. Automatic AI-Powered Investigation</strong></p>
<p>When the alert triggers, it invokes the <strong>Observability AI Assistant</strong> with a structured investigation prompt that:</p>
<ul>
<li>Performs immediate triage (which device, which interface, when)</li>
<li>Assesses OSPF impact and traffic rerouting</li>
<li>Correlates with other recent failures</li>
<li>Generates severity assessment and recommended actions</li>
</ul>
<h3 id="thetransformation">The Transformation</h3>
<p>| Traditional Alerting | Intelligent Alerting (Elastic) |
| :---: | :---: |
| <strong>Email: "Interface down on csr23"</strong> | Structured analysis with device context |
| <strong>Manual investigation: 20-30 min</strong> | AI-automated investigation: 90 seconds |
| <strong>Engineer correlates across tools</strong> | Automatic cross-source correlation |
| <strong>No business impact assessment</strong> | Severity + recommended actions included |</p>
<h2 id="acceleratingincidentresponsewiththeelasticaiassistant">Accelerating Incident Response with the Elastic AI Assistant</h2>
<p>This is where the Elastic AI Assistant demonstrates its operational value — moving beyond passive data collection to actively interpret and explain network events in real-time</p>
<p>When an engineer views a trap document in Discover and asks:</p>
<p><strong><em>"Explain this log message"</em></strong></p>
<p>The AI Assistant provides comprehensive analysis including:</p>
<ul>
<li><strong>What happened:</strong> Plain-language explanation of the SNMP trap</li>
<li><strong>Device context:</strong> Router role, interface purpose, network position</li>
<li><strong>Impact analysis:</strong> OSPF neighbor status, traffic rerouting assessment</li>
<li><strong>Root cause possibilities:</strong> Physical layer, link layer, administrative causes</li>
<li><strong>Recommended actions:</strong> Immediate steps, investigation queries, validation checks</li>
<li><strong>Severity assessment:</strong> Business and technical impact rating</li>
</ul>
<h3 id="manualtriagevsaiassistedinvestigation">Manual Triage vs. AI-Assisted Investigation</h3>
<p>| Before | After (Elastic AI) |
| :---- | :---- |
| <strong>Google the OID → 5 min</strong> | Click "Explain this log" → 20 seconds |
| <strong>Open network diagram → 3 min</strong> | Topology context auto-provided |
| <strong>Query multiple tools → 10 min</strong> | Cross-source correlation instant |
| <strong>Assess business impact → 5 min</strong> | Impact analysis auto-generated |
| <strong>Total: ~28 minutes</strong> | <strong>Total: ~20 seconds</strong> |</p>
<h2 id="thevaluepropositiononeplatformonedatamodeloneai">The Value Proposition: One Platform, One Data Model, One AI</h2>
<h3 id="whatthislabdemonstrates">What This Lab Demonstrates</h3>
<p>Elastic provides:</p>
<ul>
<li><strong>One unified platform</strong> for metrics, logs, flows</li>
<li><strong>One data model</strong> (SemConv) for consistent correlation</li>
<li><strong>One search interface</strong> (Kibana) for all network data</li>
<li><strong>One AI assistant</strong> that understands all your network telemetry</li>
<li><strong>AI-powered alerting</strong> with automated investigation</li>
</ul>
<h3 id="businessimpact">Business Impact</h3>
<p><strong>Efficiency Gains:</strong></p>
<ul>
<li><strong>85% reduction in MTTR</strong> (36 min → 5 min for initial diagnosis)</li>
<li><strong>90% reduction</strong> in manual correlation time</li>
<li>Junior engineers gain access to <strong>AI-powered expert analysis</strong></li>
</ul>
<p><strong>Operational Benefits:</strong></p>
<ul>
<li>Network engineers focus on <strong>strategy, not tool-switching</strong></li>
<li><strong>Cross-functional collaboration</strong> in one platform</li>
<li><strong>Reduced tool sprawl</strong> and management overhead</li>
</ul>
<h2 id="lessonslearned">Lessons Learned</h2>
<p>After building this lab, several key insights emerged regarding how network data fits into the broader observability ecosystem:</p>
<p><strong>1. Extending Observability to the Network</strong></p>
<p>Elastic is already the gold standard for high-volume logs and application traces. This lab demonstrates that the same engine seamlessly handles network telemetry without needing a separate, siloed tool.</p>
<ul>
<li>Scale: The same architecture that ingests petabytes of application logs easily handles millions of interface counters.</li>
<li>Structure: Native support for complex nested documents allows for rich SNMP trap data (variable bindings) without flattening or losing context.</li>
<li>Speed: Real-time search applies equally to network events, enabling sub-second troubleshooting.</li>
</ul>
<p><strong>2. OpenTelemetry Semantic Conventions (SemConv) as the Universal Translator</strong></p>
<p>The power isn't just in storing the data, but in standardizing it. By mapping SNMP and NetFlow to the <strong>OpenTelemetry Semantic Conventions (SemConv)</strong>, network data finally speaks the same language as the rest of the stack.</p>
<ul>
<li><strong>Unified Search:</strong> Query across firewall logs, server metrics, and switch telemetry in a single search bar.</li>
<li><strong>Instant Visualization:</strong> Pre-built dashboards work immediately because the field names are standardized.</li>
<li><strong>Cross-Domain Correlation</strong>: Easily correlates a spike in application latency with a specific interface saturation event.</li>
</ul>
<p><strong>3. AI Assistants Thrive on Context</strong></p>
<p>While the AI in this lab was powerful on its own, the experiment highlighted a critical realization: an AI Assistant becomes exponentially more effective when coupled with a specific Knowledge Base.</p>
<p><strong>Context is King:</strong> The AI delivers better root cause analysis when provided with rich metadata, such as device roles and topology maps. Without it, the advice remains generic.</p>
<p><strong>Pro Tip (and What’s Next):</strong></p>
<p>To get organization-specific advice rather than generic suggestions, you need to feed the AI your documentation.</p>
<ul>
<li><strong>The Goal:</strong> Create a Knowledge Base containing device roles, network topology diagrams, and troubleshooting procedures.</li>
<li><strong>The Next Step:</strong> In my next blog post, I will demonstrate exactly how to do this — connecting a Knowledge Base to the AI Assistant to enable fully context-aware troubleshooting.</li>
</ul>
<h2 id="conclusioncompletingtheobservabilitypicture">Conclusion: Completing the Observability Picture</h2>
<p>Elastic is already widely recognized as the standard for Application and Security observability. The goal of this lab wasn't to ask if Elastic can handle networking, but to demonstrate the immense value of bringing network data into that existing ecosystem.</p>
<p>The verdict is clear: Elastic acts as that unified foundation. It effectively breaks down the silo between Network Engineering and the rest of IT.</p>
<p>This isn't just about consolidating dashboards or replacing legacy tools. It is about establishing the Elasticsearch AI Platform as the single source of truth where network telemetry sits right alongside application and infrastructure data.</p>
<p>By treating network data as a first-class citizen in the observability stack, we unlock automated correlation, AI-assisted investigation, and the speed required to resolve incidents before they impact the business. The capabilities are in place, and the foundation is solid — Elastic is ready to unify your network with the rest of your digital business.</p>
<h2 id="readytotryityourself">Ready to Try It Yourself?</h2>
<p>Check out <a href="https://github.com/DeBaker1974/Containerlab-OSPF">github.com/DeBaker1974/Containerlab-OSPF</a></p>
<p>The repository includes:</p>
<ul>
<li>Complete deployment scripts (12-15 minute automated setup)</li>
<li>Pre-configured telemetry pipelines</li>
<li>Kibana dashboards</li>
<li>Alert rules with AI Assistant integration</li>
<li>Detailed README</li>
</ul>
<p><strong>Not ready to build? Try Elastic Serverless:</strong> <a href="https://cloud.elastic.co/registration">Start a free 14-day trial</a> and explore AI-powered observability with your own data.</p>
<p><strong>Special thanks to the Containerlab and FRRouting communities for their incredible open-source tools, and to Sheriff Lawal (CCIE, CISSP), Sr. Manager, Solutions Architecture at Elastic, for mentoring on this project.</strong></p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/network-monitoring-with-elastic-unifying-network-observability</link>
    <guid isPermaLink="false">network-monitoring-with-elastic-unifying-network-observability</guid>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[Metrics]]></category>
    <category><![CDATA[AI]]></category>
    <dc:creator><![CDATA[Patrick Boulanger]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt644de3218af4a6a1/6a7f0e7a73d9bd4b1129dbc1/article-image.jpg" length="0" type="image/jpeg"/>
    <pubDate>Mon, 16 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[Elastic's metrics analytics gets 5x faster]]></title>
    <description><![CDATA[Explore Elastic's metrics analytics enhancements, including faster ES|QL queries, TSDS updates and OpenTelemetry exponential histogram support.]]></description>
    <content:encoded><![CDATA[<p>In our <a href="https://www.elastic.co/observability-labs/blog/metrics-explore-analyze-with-esql-discover">previous blog in this series</a>, we explored the fundamentals of analyzing metrics using the Elasticsearch Query Language (ES|QL) and the interactive power of Discover. Building on that foundation, we are excited to announce a suite of powerful enhancements to Time Series Data Streams (Elastic’s TSDB) and ES|QL designed to provide even more comprehensive and blazingly faster metrics analytics capabilities!</p>
<p>These latest updates, available in v9.3 and in Serverless, introduce significant performance gains, sophisticated time series functions, and native OpenTelemetry exponential histogram support that directly benefit SREs and Observability practitioners.</p>
<h2 id="queryperformanceandstorageoptimizations">Query Performance and Storage Optimizations</h2>
<p>Speed is paramount when diagnosing incidents. Compared to prior releases, we have achieved a 5x+ improvement in query latency when wildcarding or filtering by dimensions. Additionally, storage efficiency for OpenTelemetry metrics data has improved by approximately 2x, significantly reducing the infrastructure footprint required to retain high-volume observability data. If you’re hungry to learn more about what architectural updates are driving these optimizations, stay tuned… Tech blogs are on their way! </p>
<h2 id="expandedtimeseriesanalyticsinesql">Expanded Time Series Analytics in ES|QL</h2>
<p>The <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/ts">ESQL TS source command</a>, which targets time series indices and enables <a href="https://www.elastic.co/docs/reference/query-languages/esql/functions-operators/time-series-aggregation-functions">time series aggregation functions</a>, has been significantly enhanced to support complex analytics capabilities.</p>
<p>We have expanded the <a href="https://www.elastic.co/docs/reference/query-languages/esql/esql-functions-operators">library of time series functions</a> to include essential tools for identifying anomalies and trends.</p>
<ul>
<li><code>PERCENTILE_OVER_TIME</code>, <code>STDDEV_OVER_TIME</code>, <code>VARIANCE_OVER_TIME</code>: Calculate the percentile, standard deviation, or variance of a field over time, which is critical for understanding distribution and variability in service latency or resource usage.</li>
</ul>
<p>Example: Seeing the worst-case latency in 5-minute intervals.</p>
<pre><code>TS metrics*  | STATS MAX(PERCENTILE_OVER_TIME(kafka.consumer.fetch_latency_avg, 99))
&amp;nbsp; BY TBUCKET(5m)
</code></pre>
<ul>
<li><code>DERIV</code>: This command calculates the derivative of a numeric field over time using linear regression, useful for analyzing the rate of change in system metrics.</li>
</ul>
<p>Example: trending gauge values over time.</p>
<pre><code>TS metrics*  | STATS AVG(DERIV(container.memory.available))
&amp;nbsp; BY TBUCKET(1 hour)
</code></pre>
<ul>
<li><code>CLAMP</code>: To handle noisy data or outliers, this function limits sample values to a specified lower and upper bound.</li>
</ul>
<p>Example: handling saturation metrics (like CPU or Memory utilization) where spikes or measurement errors can occasionally report values over 100%, making the rest of the data look like a flat line at the bottom of the chart.\</p>
<pre><code>TS metrics*  | STATS AVG(CLAMP(k8s.pod.memory.node.utilization, 0, 100))
&amp;nbsp; BY k8s.pod.name
</code></pre>
<ul>
<li><code>TRANGE</code>: This new filter function allows you to filter data for a specific time range using the <code>@timestamp</code> attribute, simplifying query syntax for time-bound investigations.</li>
</ul>
<p>Example: Filtering and showing metrics for the last 4 hours.</p>
<pre><code>TS metrics*  | WHERE TRANGE(4h) | STATS AVG(host.cpu.pct)
&amp;nbsp; BY TBUCKET(5m)
</code></pre>
<p><strong>Window Functions</strong> To smoothen results over specific periods, ES|QL now introduces window functions. Most time series aggregation functions now accept an optional second argument that specifies a sliding time window. For example, you can calculate a rate over a 10-minute sliding window while bucketing results by minute.</p>
<p>Example: Calculating the average rate of requests per host for every minute, using values over a sliding window of 5 minutes.</p>
<pre><code>TS metrics*  | STATS AVG(RATE(app.frontend.requests, 5m))
&amp;nbsp; BY TBUCKET(1m)
</code></pre>
<p>Accepted window values are currently limited to multiples of the time bucket interval in the BY clause. Windows that are smaller than the time bucket interval or larger but not a multiple of the time bucket interval will be supported in feature releases. </p>
<h2 id="nativeopentelemetryexponentialhistograms">Native OpenTelemetry Exponential Histograms</h2>
<p>Elastic now provides native support for OpenTelemetry exponential histograms, enabling efficient ingest, querying, and downsampling of high-fidelity distribution data.</p>
<p>We have introduced a new <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/exponential-histogram">exponential_histogram</a> field type designed to capture distributions with fixed, exponentially spaced bucket boundaries. Because these fields are primarily intended for aggregations, the histogram is stored as compact doc values and is not indexed, optimizing storage efficiency. These fields are fully supported in ES|QL aggregation functions such as <code>PERCENTILES</code>, <code>AVG</code>, <code>MIN</code>, <code>MAX</code>, and <code>SUM</code>.</p>
<p>You can index documents with exponential histograms automatically through our <a href="https://www.elastic.co/docs/manage-data/data-store/data-streams/tsds-ingest-otlp#configure-histogram-handling">OTLP endpoint</a> or manually. For example, let’s create an index with an exponential histogram field and a keyword field:</p>
<pre><code>PUT my-index-000001
{
&amp;nbsp;&amp;nbsp;"settings": {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"index": {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"mode": "time_series",
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"routing_path": ["http.path"],
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"time_series": {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"start_time": "2026-01-21T00:00:00Z",
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"end_time": "2026-01-25T00:00:00Z"
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&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;"@timestamp": {
&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;"http.path": {
&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;"time_series_dimension": 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;"responseTime": {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"type": "exponential_histogram",
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"time_series_metric": "histogram"
&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>Index a document with a full exponential histogram payload:</p>
<pre><code>POST my-index-000001/_doc
{
&amp;nbsp;&amp;nbsp;"@timestamp": "2026-01-22T21:25:00.000Z",
&amp;nbsp;&amp;nbsp;"http.path": "/foo",
&amp;nbsp;&amp;nbsp;"responseTime": {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"scale":3,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"sum":73.2,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"min":3.12,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"max":7.02,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"positive": {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"indices":[13,14,15,16,17,18,19,20,21,22],
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"counts":[1,1,2,2,1,2,1,3,1,1]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;}
}

POST my-index-000001/_doc
{
&amp;nbsp;&amp;nbsp;"@timestamp": "2026-01-22T21:26:00.000Z",
&amp;nbsp;&amp;nbsp;"http.path": "/bar",
&amp;nbsp;&amp;nbsp;"responseTime": {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"scale":3,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"sum":45.86,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"min":2.15,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"max":5.1,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"positive": {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"indices":[8,9,10,11,12,13,14,15,16,17,18],
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;"counts":[1,1,1,1,1,1,1,2,1,1,2]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;}
}
</code></pre>
<p>And finally, query the time series index using ES|QL and the TS source command:</p>
<pre><code>TS my-index-000001  | STATS MIN(responseTime), MAX(responseTime),
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; AVG(responseTime), MEDIAN(responseTime),
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; PERCENTILE(responseTime, 90)
&amp;nbsp; BY http.path
</code></pre>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4f2a02723540ef2e/6a7f08276693f85d04663d71/exponential_histogram_esql_example.png" alt="Alt text" /></p>
<h2 id="enhanceddownsampling">Enhanced Downsampling</h2>
<p>Downsampling is essential for long-term data retention. We have introduced a new <a href="https://www.elastic.co/docs/manage-data/data-store/data-streams/downsampling-concepts#downsampling-methods">"last value" downsampling mode</a>. This method exchanges accuracy for storage efficiency and performance by keeping only the last sample value, providing a lightweight alternative to calculating aggregate metrics.</p>
<p>You can <a href="https://www.elastic.co/docs/manage-data/data-store/data-streams/run-downsampling">configure a time series data stream</a> for last value downsampling in a similar way as regular downsampling, just by setting the <code>downsampling_method</code> to <code>last_value</code>. For example, by using a data stream lifecycle:</p>
<pre><code>PUT _data_stream/my-data-stream/_lifecycle
{
&amp;nbsp; "data_retention": "7d",
&amp;nbsp; "downsampling_method": "last_value",
&amp;nbsp; "downsampling": [
 &amp;nbsp; &amp;nbsp; {
 &amp;nbsp; &amp;nbsp; &amp;nbsp; "after": "1m",
 &amp;nbsp; &amp;nbsp; &amp;nbsp; "fixed_interval": "10m"
&amp;nbsp; &amp;nbsp; &amp;nbsp; },
&amp;nbsp; &amp;nbsp; &amp;nbsp; {
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; "after": "1d",
&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; "fixed_interval": "1h"
&amp;nbsp; &amp;nbsp; &amp;nbsp; }
 &amp;nbsp; ]
}
</code></pre>
<h2 id="inconclusion">In Conclusion</h2>
<p>These enhancements mark a significant step forward in Elastic's metrics analytics capabilities, delivering 5x+ faster query latency, 2x storage efficiency and specialized commands like <code>DERIV</code>, <code>CLAMP</code>, and <code>PERCENTILE_OVER_TIME</code>. With native support for OpenTelemetry exponential histograms and expanded downsampling options, SREs can now perform richer, more cost-effective analysis on their observability data. This release empowers teams to detect anomalies faster and manage long-term metrics retention with greater efficiency.</p>
<p>We welcome you to <a href="https://cloud.elastic.co/serverless-registration?onboarding_token=observability">try the new features</a> today!</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/elastic-metrics-analytics</link>
    <guid isPermaLink="false">elastic-metrics-analytics</guid>
    <category><![CDATA[Metrics]]></category>
    <category><![CDATA[OpenTelemetry]]></category>
    <dc:creator><![CDATA[Vinay Chandrasekhar,Yannis Roussos]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt13437afca64e5c55/6a7f082aead8ec35f6baa678/elastic_metrics_leaner_blog_image.jpg" length="0" type="image/jpeg"/>
    <pubDate>Wed, 28 Jan 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[A train ride away from a million events per second with EDOT Cloud Forwarder]]></title>
    <description><![CDATA[EDOT Cloud Forwarder for AWS from Elastic Observability is now Generally Available. Deploying EDOT Cloud Forwarder and reliably handling one million events per second with zero intervention, zero data loss, and zero idle cost.]]></description>
    <content:encoded><![CDATA[<p>Infrastructure observability is critical for maintaining uptime, optimizing cloud environments, and securing the cloud perimeter. Cloud environments generate observability data at massive scale. VPC Flow Logs, ELB Access Logs, CloudTrail and CloudWatch logs can easily reach hundreds of thousands of events per second. Dealing with scale like this is a complex problem all by itself.</p>
<p>Today, we introduce <strong>EDOT Cloud Forwarder</strong>, built on OTel Collector, it is the simplest, fastest, and possibly most boring way to connect your Cloud environment to Elastic Observability, and it is <strong>now Generally Available on AWS</strong>. With EDOT Cloud Forwarder you can get started in seconds, get observability across your entire cloud estate, and easily handle telemetry at any volume.</p>
<h2 id="deployingcloudforwarderfromadistrictlinetrain">Deploying Cloud Forwarder from a District Line train</h2>
<p>So, I got to work deploying EDOT Cloud Forwarder in my AWS account. I was doing it on the commute, using nothing more than a decent 4G signal and hoping that my 27% battery would be enough.</p>
<p>I hit deploy on the terraform template and waited. I started seeing events flowing into Elastic Observability.</p>
<p>As the train pulled into Putney Bridge, the flow of logs peaked, and one million events per second scrolled across my screen.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt349584b4d4f5955a/6a7f0ef777b03403873ff574/putney_bridge_1MEPS.png" alt="Putney_Bridge" /></p>
<p>Once deployed, I got the best three zeros I could expect:</p>
<ul>
<li><strong>Zero Intervention:</strong> I watched as traffic ramped up to a full 1M EPS. Lambda functions automatically scaled out from a few instances to the 60-65 concurrent executions needed. There were <strong>zero manual adjustments required</strong>. The scaling was instant and hands-free.</li>
<li><strong>Zero Data Loss:</strong> It achieved a consistent processing rate, with every single event indexed in Elasticsearch.</li>
<li><strong>Zero Idle Cost:</strong> When there are no events, Cloud Forwarder scales to zero - it has no fixed infrastructure cost. You only pay for the moment data is being processed, not for permanently over-provisioned servers sitting idle.</li>
</ul>
<p>Right before the train came to a standstill, I looked at the total cost for running Cloud Forwarder for the two minutes between Parsons Green and Putney Bridge - we forwarded about 120GB of telemetry and the total cost was below £0.10. Well, unless you count the £2.50 train ticket!</p>
<h2 id="makingobservabilityeasyatanyscale">Making Observability easy at any scale</h2>
<p>Getting started observing your infrastructure is hard and once it's observable, deriving actionable value from it requires sifting and winnowing through massive volumes of telemetry data, sometimes millions of events per second.</p>
<p>The new EDOT Cloud Forwarder for AWS (also available in Preview for GCP and Azure) is easy to deploy, with just a Terraform template. To make sure that it was easy to get started with, we designed it to be as close to a <a href="https://www.elastic.co/docs/reference/opentelemetry/edot-cloud-forwarder/aws#quick-deployment-direct-link">single-click deployment</a> as possible:</p>
<p>Just click the link below to launch the CloudFormation stack in your AWS account:</p>
<p><a href="https://console.aws.amazon.com/cloudformation/home?%23/stacks/new?templateURL=https%3A%2F%2Fedot-cloud-forwarder.s3.amazonaws.com%2Fv1%2Flatest%2Fcloudformation%2Fs3_logs-cloudformation.yaml"><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta2249df99c16d510/6a7f0efa5967e55bc95dd3ab/cloudformation-launch-stack.png" alt="Launch_stack" /></a></p>
<p>The best part? The fastest way to get started with Elastic Observability scales to any size workload! With EDOT Cloud Forwarder you have one solution which automatically scales down to zero and up to millions of events per second.</p>
<h2 id="sowhatisedotcloudforwarder">So, what is EDOT Cloud Forwarder?</h2>
<p>EDOT Cloud Forwarder is a serverless OpenTelemetry Collector that, on AWS, runs as a Lambda function. In AWS, it is triggered by events and processes logs and metrics from services such as VPC Flow Logs, ELB Access Logs, CloudTrail, CloudWatch Logs and CloudWatch Metrics.</p>
<p>It has the following core capabilities:</p>
<ul>
<li>Collects observability and security data from Cloud Service Providers</li>
<li>Parses data into native OpenTelemetry format</li>
<li>Forwards data over OTLP</li>
<li>Scales up and down based on traffic</li>
</ul>
<p>ECF for AWS is a pure serverless solution, no VMs, containers, or Kubernetes control planes to manage.</p>
<h2 id="offthetrainamorecontrolledscenario">Off the train, a more controlled scenario</h2>
<p>For a more controlled testing scenario, we used synthetic VPC Flow Log data generated to show how easy EDOT Cloud Forwarder can sustain a million events per second reliably and without data loss.</p>
<p>For the configuration, we left all EDOT Cloud Forwarder configuration/settings at their <a href="https://www.elastic.co/docs/reference/opentelemetry/edot-cloud-forwarder/aws#optional-settings">defaults</a>. In AWS, the Lambda max concurrency defaults to 5. If left to the default of 5, up to around 50k EPS could be expected. For our scenario, we bumped this to 100 to ensure we'd have plenty of headroom for our test.</p>
<p>We ran the scenario in 10 minute stages, with each stage resulting in a larger data volume. We held ingest flat during the stage to provide short-term steady state windows for us to grab metrics.</p>
<p>We experienced no errors across all stages of the scenarios, no retries outside expected behavior, no data loss across 5.4 billion ingested events.</p>
<h2 id="statsforobservabilitynerds">Stats for Observability Nerds</h2>
<p>Because we know you love them:</p>
<h3 id="incrementalloadstages">Incremental Load Stages</h3>
<p>We tested EDOT Cloud Forwarder using incremental load stages, gradually increasing traffic from approximately 300,000 events per second to over 1 million events per second.</p>
<p>The graph below shows the Elasticsearch ingestion rate throughout the entire test duration. You can see the clear progression as we ramped up through six distinct stages, with each plateau representing a 10 minute stabilization period.</p>
<p>The system handled each traffic increase smoothly, culminating in sustained 1 million documents per second ingestion with no bottlenecks or data loss.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0347a4f147111e38/6a7f0efdde2315fe3cfd7d1b/es-ingestion-rate.png" alt="ES Ingestion Rate" /></p>
<h3 id="lambda">Lambda</h3>
<p>As seen in CloudWatch metrics (no manual adjustments required). No errors and no throttles were seen during the full duration of the test.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt93d4377526e21313/6a7f0f011967ea48ff33082d/lambda-errors-throttles.png" alt="Lambda errors and throttles" /></p>
<h4 id="concurrentexecutions">Concurrent executions</h4>
<p>60 to 65 instances running at the same time.
<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb9dd8e54aae0185f/6a7f0f0396b5a6640587b523/lambda-instances.png" alt="Lambda concurrent executions" /></p>
<h4 id="averageexecutiontimeperlambda">Average execution time per Lambda</h4>
<p>Each execution is taking about 5 seconds.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt15de9c99c7d107d6/6a7f0f0673d9bd135c29dbfd/lambda-execution-time.png" alt="Lambda average execution time" /></p>
<h4 id="memoryusage">Memory Usage</h4>
<p>Memory use stabilized at around 450 MB, below the default limit of 512 MB.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2dab09e2769e9dd7/6a7f0f09ead8ec7babbaa958/lambda-memory-used.png" alt="Lambda memory usage" /></p>
<h3 id="elasticsearchindexing">Elasticsearch Indexing</h3>
<p>Elasticsearch indexed one million documents per second, with events visible in Discover within seconds and no indexing delays or bottlenecks.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb78d36a7d0dd78c2/6a7f0f0cead8ec5cc3baa95c/es-1m-eps.png" alt="1M EPS" /></p>
<h2 id="efficientbydesignlambdaat1mepsfroms3">Efficient by design: Lambda at 1M EPS from S3</h2>
<p>Running ECF for AWS at 1M EPS costs about <strong>$3.87 per hour</strong>. Around 66 percent ($2.57 per hour) is data transfer (same region), 34 percent ($1.32 per hour) is Lambda compute, and less than 1 percent is S3 requests.</p>
<p>This is fully serverless with no idle cost. You only pay while events are forwarded. With S3, data arrives pre-batched in large objects, which keeps Lambda invocations low and compute costs tightly controlled. At sustained throughput, Lambda costs are comparable to EKS—but without cluster management or idle capacity.</p>
<h3 id="otheroptionsotelcollectoroneksat1meventspersecond">Other options: OTel Collector on EKS at 1M events per second</h3>
<p>An OTel Collector on EKS sized for 1M EPS has a baseline cost of about <strong>$3.69 per hour</strong>. Roughly $0.33 per hour of this is compute related, EC2 nodes, EKS control plane, and EBS. The rest comes from data transfer and SQS, which scale with traffic and do not change with utilization.</p>
<h4 id="idlecomputeimpactoneksrealcosts">Idle compute impact on EKS real costs</h4>
<p>Considering EKS is typically provisioned for peak load, the real cost of the compute portion is affected by idle capacity. At <strong>100 percent utilization</strong>, total cost is <strong>$3.69 per hour</strong>. At <strong>50 percent utilization</strong>, a common baseline to absorb burstiness, total cost rises to about <strong>$4.02 per hour</strong>. At <strong>30 percent utilization</strong>, it increases further to about <strong>$4.46 per hour</strong>.</p>
<h4 id="payforworkvspayforcapacity">Pay for Work vs Pay for Capacity</h4>
<p>ECF for AWS delivers 1M EPS at a cost comparable to EKS at peak utilization, with no idle compute or capacity planning required. EKS can reach the same peak throughput, but total cost increases further as average utilization drops because compute capacity must be provisioned in advance.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt726e3a92780a3de7/6a7f0f0f448e4e0bb25c07ed/otel-collector-lambda.png" alt="Collector-lambda" /></p>
<h2 id="conclusion">Conclusion</h2>
<p>The boring truth: to the EDOT Cloud Forwarder, a million events per second is no different from any other workload.</p>
<p>With no infrastructure to deploy, no idle cost and no manual scaling, I guess the best thing to do is to stop overthinking and start forwarding! It's like stepping onto the fastest train on the line: you just get on and you're instantly en route to your destination, effortlessly handling any distance or in this case, any volume.</p>
<p>So, we're shipping it. ECF for AWS is now Generally Available.</p>
<h2 id="getstarted">Get Started</h2>
<ol>
<li>Deploy EDOT Cloud Forwarder via CloudFormation (below) or using the AWS Serverless Application Repository</li>
</ol>
<p><a href="https://console.aws.amazon.com/cloudformation/home?%23/stacks/new?templateURL=https%3A%2F%2Fedot-cloud-forwarder.s3.amazonaws.com%2Fv1%2Flatest%2Fcloudformation%2Fs3_logs-cloudformation.yaml"><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta2249df99c16d510/6a7f0efa5967e55bc95dd3ab/cloudformation-launch-stack.png" alt="Launch_stack" /></a></p>
<ol>
<li>Create an Observability project using an <a href="https://cloud.elastic.co/login?redirectTo=%2Fhome">Elastic Cloud</a> free trial or deploy locally with start-local if you don't already have an Elastic project or deployment.</li>
</ol>
<p>Visit EDOT Cloud Forwarder for AWS <a href="https://www.elastic.co/docs/reference/opentelemetry/edot-cloud-forwarder/aws">Documentation</a> for more details.</p>
<p>Check out these other resources on OpenTelemetry at Elastic</p>
<p><a href="https://www.elastic.co/observability-labs/blog/elastic-agent-pivot-opentelemetry">Discover how Elastic is evolving data ingestion with OpenTelemetry</a></p>
<p><a href="https://www.elastic.co/observability-labs/blog/elastic-distribution-opentelemetry-sdk-central-configuration-opamp">Learn how OpAMP enables centralized configuration of OpenTelemetry SDKs</a></p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/one-million-events-per-second-with-edot-cloud-forwarder</link>
    <guid isPermaLink="false">one-million-events-per-second-with-edot-cloud-forwarder</guid>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[Infrastructure Monitoring]]></category>
    <dc:creator><![CDATA[Michalis Katsoulis,Andreas Gkizas,Miguel Luna]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9c152dbb9b2e77fa/6a7f0f1363e959a51e73debc/ecf-for-aws.jpg" length="0" type="image/jpeg"/>
    <pubDate>Tue, 20 Jan 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[A Practical Guide to end-to-end distributed tracing for Nginx with OpenTelemetry in Elastic]]></title>
    <description><![CDATA[Instrument Nginx with the OpenTelemetry tracing module and export spans to Elastic Observability's APM for full end-to-end distributed tracing.]]></description>
    <content:encoded><![CDATA[<p>Nginx sits at the very front of most modern architectures: handling SSL, routing, load balancing, authentication, and more. Yet, despite its central role, it is often absent from distributed traces.<br />
That gap creates blind spots that impact performance debugging, user experience analysis, and system reliability.</p>
<p>This article explains <strong>why Nginx tracing is important</strong> in an application context, and provides a <strong>practical guide</strong> to enable the Nginx <a href="https://nginx.org/en/docs/ngx_otel_module.html">Otel</a> tracing module exporting spans directly to <a href="https://www.elastic.co/docs/solutions/observability/apm">Elastic APM</a>.</p>
<h2 id="whynginxtracingmattersformodernobservability">Why Nginx Tracing Matters for Modern Observability</h2>
<p>Instrumenting only backend services gives you only half the picture.<br />
Nginx sees:</p>
<ul>
<li>every incoming request  </li>
<li>client trace context  </li>
<li>TLS negotiation  </li>
<li>upstream errors (502, 504)  </li>
<li>edge-layer latency  </li>
<li>routing decisions  </li>
</ul>
<p>If Nginx is not in your traces, your distributed trace is incomplete.</p>
<p>By adding OpenTelemetry tracing at this ingress layer, you unlock:</p>
<p><em>1. Full trace continuity</em> : From browser → Nginx → backend → database.
<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc30b2ea6cee5fdff/6a7f0eaa96b5a68e9c87b50b/document_elastic_nginx_otel_instrumentation_1.png" alt="Nginx Trace Continuity" /></p>
<p><em>2. Accurate latency attribution</em> : Edge delays vs. backend delays are clearly separated which unlock Elastic <a href="https://www.elastic.co/docs/solutions/observability/apm/machine-learning">APM Latency</a> anomaly detection for proactive detection.
<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt234adca13afbd446/6a7f0eadbd219835c475815b/document_elastic_nginx_otel_instrumentation_2.png" alt="Nginx Latency Detection" /></p>
<p><em>3. Error root-cause clarity</em> : Nginx errors appear as spans instead of backend “mystery gaps”.</p>
<p><em>4. Complete service topology</em> : Your APM service map finally shows the real architecture.
<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt40ad01ea2ad51be3/6a7f0eb005b7b55b5618ba0e/document_elastic_nginx_otel_instrumentation_4.png" alt="Nginx APM Service Map" /></p>
<h2 id="integratingnginxwithopentelemetryondebian">Integrating Nginx with OpenTelemetry on Debian</h2>
<p>This guide provides a comprehensive overview of why, how to install and configure the Nginx OpenTelemetry module on a Debian-based system. The configuration examples are tailored to send telemetry data directly to an Elastic APM endpoint whether it's an <a href="https://www.elastic.co/docs/reference/opentelemetry">EDOT</a> Collector or <a href="https://www.elastic.co/observability-labs/blog/elastic-managed-otlp-endpoint-for-opentelemetry">mOtel</a> in case of our serverless, enabling end-to-end distributed tracing.</p>
<h3 id="installationondebian">Installation on Debian</h3>
<p>The Nginx OTEL module is not included in the standard Nginx packages. It must be installed along with a working nginx configuration.</p>
<h4 id="prerequisites">Prerequisites</h4>
<p>First, install the necessary tools for compiling software and the Nginx development dependencies.</p>
<pre><code>sudo apt update
sudo apt install -y apt install nginx-module-otel
</code></pre>
<h4 id="loadthemoduleinnginx">Load the Module in Nginx</h4>
<p>Edit your main <code>/etc/nginx/nginx.conf</code> file to load the new module. This directive must be at the top level, before the <code>http</code> block.</p>
<pre><code># /etc/nginx/nginx.conf

load_module modules/ngx_otel_module.so;

events {
    # ...
}

http {
    # ...
}
</code></pre>
<p>Now, test your configuration and restart Nginx.</p>
<pre><code>sudo nginx -t
sudo systemctl restart nginx
</code></pre>
<h3 id="configuration">Configuration</h3>
<p>Configuration is split between the main <code>nginx.conf</code> file (for global settings) and your site-specific server block files.</p>
<h4 id="globalconfigurationetcnginxnginxconf">Global Configuration (<code>/etc/nginx/nginx.conf</code>)</h4>
<p>This configuration sets up the destination for your telemetry data and defines global variables used for CORS and tracing. These settings are placed inside the <code>http</code> block.</p>
<pre><code>http {
    ...

    # --- OpenTelemetry Exporter Configuration ---
    # Defines where Nginx will send its telemetry data directly to Elastic APM or EDOT.
    otel_exporter {
        endpoint https://&lt;ELASTIC_URL&gt;:443;
        header Authorization "Bearer &lt;TOKEN&gt;";
    }

    # --- OpenTelemetry Service Metadata ---
    # These attributes identify Nginx as a unique service in the APM UI.
    otel_service_name nginx;
    otel_resource_attr service.version 1.28.0;
    otel_resource_attr deployment.environment production;
    otel_trace_context propagate; # Needed to propagate the RUM traces to the backend

    # --- Helper Variables for Tracing and CORS ---
    # Creates the $trace_flags variable needed to build the outgoing traceparent header.
    map $otel_parent_sampled $trace_flags {
        default "00"; # Not sampled
        "1"     "01"; # Sampled
    }

    # Creates the $cors_origin variable for secure, multi-origin CORS handling.
    map $http_origin $cors_origin {
        default "";
        "http://&lt;URL_ORIGIN_1&gt;/" $http_origin; # Add your Origin here to allow CORS
        "https://&lt;URL_ORIGIN_2&gt;/" $http_origin; # Add your others Origin here to allow CORS
    }
...
}
</code></pre>
<h4 id="serverblockconfigurationetcnginxconfdsiteconf">Server Block Configuration (<code>/etc/nginx/conf.d/site.conf</code>)</h4>
<p>This configuration enables tracing for a specific site, handles CORS preflight requests, and propagates the trace context to the backend service.</p>
<pre><code>server {
    listen 443 ssl;
    server_name &lt;WEBSITE_URL&gt;;

    # --- OpenTelemetry Module Activation ---
    # Enable tracing for this server block.
    otel_trace on;
    otel_trace_context propagate;

    location / {
        # --- CORS Preflight (OPTIONS) Handling ---
        # Intercepts preflight requests and returns the correct CORS headers,
        # allowing the browser to proceed with the actual request.
        if ($request_method = 'OPTIONS') {
            add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always;
            add_header 'Access-Control-Allow-Headers' 'Content-Type, traceparent, tracestate' always;
            add_header 'Access-Control-Max-Age' 86400;
            add_header 'Access-Control-Allow-Origin' "$cors_origin" always;
            return 204;
        }

        # --- OpenTelemetry Trace Context Propagation ---
        # Manually constructs the W3C traceparent header and passes the tracestate
        # header to the backend, linking this trace to the upstream service.
        proxy_set_header traceparent      "00-$otel_trace_id-$otel_span_id-$trace_flags";
        proxy_set_header tracestate       $http_tracestate;

        # --- Standard Proxy Headers ---
        proxy_set_header Host             $host;
        proxy_set_header X-Real-IP        $remote_addr;
        proxy_set_header X-Forwarded-For  $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # --- Forward to Backend ---
        # Passes the request to the actual application (eg. localhost in this example).
        proxy_pass http://&lt;BACKEND_URL&gt;:8080;
    }
}
</code></pre>
<p>Test your configuration and restart Nginx.</p>
<pre><code>sudo nginx -t
sudo systemctl restart nginx
</code></pre>
<h2 id="conclusionturningnginxintoafirstclassobservabilitysignal">Conclusion: Turning Nginx into a First-Class Observability Signal</h2>
<p>By enabling OpenTelemetry tracing directly in Nginx and exporting spans to Elastic APM (via EDOT or Elastic’s managed OTLP endpoint), you bring your ingress layer into the same observability model as the rest of your stack. The result is:</p>
<ul>
<li>true end-to-end trace continuity from the browser to backend services  </li>
<li>clear separation between edge latency and application latency  </li>
<li>immediate visibility into gateway-level failures and retries  </li>
<li>accurate service maps that reflect real production traffic  </li>
</ul>
<p>Most importantly, this approach aligns Nginx with modern observability standards. It avoids proprietary instrumentation, fits naturally into OpenTelemetry-based architectures, and scales consistently across hybrid and cloud-native environments.</p>
<h2 id="tryitout">Try it out!</h2>
<p>Once Nginx tracing is in place, several natural extensions can further improve your observability posture:</p>
<ul>
<li>correlate Nginx traces with application <a href="https://www.elastic.co/docs/reference/apm/agents/go/log-correlation">logs and metrics using</a> Elastic’s unified observability  </li>
<li>add Real User Monitoring (<a href="https://www.elastic.co/docs/solutions/observability/apm/apm-agents/real-user-monitoring-rum">RUM</a>) to close the loop from frontend to backend  </li>
<li>introduce <a href="https://www.elastic.co/docs/solutions/observability/apm/transaction-sampling">sampling and tail-based</a> decisions at the collector level for cost control  </li>
<li>use Elastic <a href="https://www.elastic.co/docs/solutions/observability/apm/service-map">APM service maps</a> and <a href="https://www.elastic.co/docs/reference/machine-learning/ootb-ml-jobs-apm">anomaly detection</a> to proactively detect edge-related issues  </li>
</ul>
<p>Instrumenting Nginx is often the missing link in distributed tracing strategies. With OpenTelemetry and Elastic, that gap can now be closed in a clean, standards-based, and production-ready way.</p>
<p>If you want to experiment with this setup quickly, Elastic Serverless provides the fastest way to get started.
Sign up and try it out in just a few minutes using our trial environment available at https://cloud.elastic.co/ .</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/nginx-opentelemetry-end-to-end-tracing</link>
    <guid isPermaLink="false">nginx-opentelemetry-end-to-end-tracing</guid>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[APM]]></category>
    <category><![CDATA[Infrastructure Monitoring]]></category>
    <dc:creator><![CDATA[Frederic Maussion]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt40ad01ea2ad51be3/6a7f0eb005b7b55b5618ba0e/document_elastic_nginx_otel_instrumentation_4.png" length="0" type="image/png"/>
    <pubDate>Tue, 13 Jan 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Accelerate Otel Adoption with Elastic Agent Hybrid Ingestion]]></title>
    <description><![CDATA[Elastic Agent 9.2 brings hybrid ingestion to Elastic Observability, unifying native integrations and OpenTelemetry receivers to simplify large-scale OTel adoption without disruption.]]></description>
    <content:encoded><![CDATA[<h2 id="hybridelasticagentthemostpracticalpathtoopentelemetryadoption">Hybrid Elastic Agent: The Most Practical Path to OpenTelemetry Adoption</h2>
<p>OpenTelemetry is quickly becoming the standard foundation for modern observability. Organizations want its open ecosystem, unified model, and vendor-neutral instrumentation—but moving a mature production environment to OTel is rarely straightforward.</p>
<p>Most teams already rely on battle-tested pipelines for logs, metrics, and security signals. They have dashboards tuned over years, operational practices built around existing data flows, and mission-critical systems where disruption simply isn’t an option.</p>
<p>This means the question isn’t "Why OpenTelemetry?"
It’s "How do we get there without breaking what already works?"</p>
<p>Elastic Observability introduces a way to ingest telemetry without disrupting existing data and dashboards with Hybrid ingestion. Released in Elastic 9.2, its a low-friction way to adopt OTel receivers alongside existing native Elastic integrations-managed centrally through Fleet.</p>
<p>This hybrid approach offers one of the most pragmatic and operationally safe routes to OTel adoption available today.</p>
<h3 id="thechallengeadoptingotelwithoutdisruptingthepresent">The Challenge: Adopting OTel Without Disrupting the Present</h3>
<p>For many organizations, the path to OTel adoption is complicated by realities such as:</p>
<ul>
<li>Established log pipelines powering critical alerting</li>
<li>Legacy infrastructure that isn’t easily re-instrumented</li>
<li>Existing dashboards and visualizations built on Elastic-native datasets</li>
<li>Teams with different levels of OTel experience</li>
<li>Risk constraints that make large changes difficult to roll out</li>
</ul>
<p>Standardizing on OTel is the right long-term direction, but replacing everything at once is neither realistic nor desirable.</p>
<p>Teams need a way to bring OTel into their environment incrementally, while preserving continuity, reliability, and central governance.</p>
<h3 id="elasticagent92hybridingestionasabridgetothefuture">Elastic Agent 9.2+: Hybrid Ingestion as a Bridge to the Future</h3>
<p>Elastic Agent now supports two fully supported ingestion paths, both running inside the same unified agent:</p>
<ol>
<li>Elastic-native integrations</li>
</ol>
<p>Perfect for logs and host-level telemetry, with mature dashboards, alerts, and ECS mappings.</p>
<ol>
<li>OpenTelemetry input integrations (OTel receivers)</li>
</ol>
<p>Powered by upstream OTel Collector components, managed directly from Fleet.</p>
<p>And crucially:</p>
<p>You can use both, simultaneously, on the same agent.</p>
<p>This hybrid ingestion model allows teams to:</p>
<ul>
<li>Continue collecting logs using native Elastic integrations</li>
<li>Begin collecting metrics or traces via OTel receivers</li>
<li>Maintain full control through Fleet</li>
<li>Introduce OTel exactly where and when it makes sense</li>
<li>Avoid running parallel agents or duplicate pipelines</li>
</ul>
<p>It’s a way to evolve—not replace—your observability strategy.</p>
<h3 id="apracticalexampleaddingotelinputswhilekeepingyourexistingpipelines">A Practical Example: Adding OTel Inputs While Keeping Your Existing Pipelines</h3>
<p>Imagine a system where NGINX logs are already handled via Elastic-native integrations. These pipelines drive dashboards, audits, and critical alerts. Interrupting them isn’t an option.</p>
<p>At the same time, your platform team wants to standardize metrics and service telemetry using OpenTelemetry.</p>
<p>With Elastic Agent hybrid ingestion, both goals align:</p>
<ol>
<li>Keep your existing log integration in Fleet</li>
<li>Add an OTel input integration (e.g., OTel <a href="https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/receiver/nginxreceiver">nginxreceiver</a>)</li>
<li>Fleet deploys both inside the same Elastic Agent</li>
<li>Deployment is done at scale across your infrastructure from a single management console</li>
<li>Logs and OTel metrics flow into Elasticsearch side-by-side</li>
</ol>
<p>No re-instrumentation.
No duplicate agents.
No loss of historical visibility.
No new tooling for operations.
No external deployment tool.</p>
<p>Whether the component is a web server, reverse proxy, database, JVM runtime, or custom service already instrumented in OTel, the workflow is the same.</p>
<h3 id="whythishybridapproachmattersstrategically">Why This Hybrid Approach Matters Strategically</h3>
<p>Hybrid ingestion is not simply a technical capability—it’s an organizational enabler for OpenTelemetry transformation.</p>
<p><strong>Incremental migration without downtime</strong></p>
<p>Teams can begin adopting OTel at the exact pace they’re comfortable with.
Existing collection signals remain stable. OTel metrics or logs are added progressively.</p>
<p><strong>Fleet remains your single control plane</strong></p>
<p>Fleet continues to manage:</p>
<ul>
<li>agent lifecycle</li>
<li>policy management</li>
<li>version upgrades</li>
<li>diagnostics and monitoring</li>
</ul>
<p>Even as OTel becomes part of your ingestion strategy.</p>
<p><strong>Consistent semantics across teams</strong></p>
<p>Adopting OTel receivers through <a href="https://www.elastic.co/docs/reference/edot-collector">EDOT</a> helps harmonize telemetry models across microservices, infrastructure, and applications.</p>
<p>OTel becomes the shared language—Elastic becomes the scalable backend.</p>
<p><strong>Future-proof flexibility</strong></p>
<p>When the day comes that a team needs advanced OTel features, custom pipelines, custom processors, or additional exporters, they can build their own <a href="https://www.elastic.co/docs/reference/edot-collector/custom-collector">EDOT custom collector</a> flavor and use it in their elastic-agent in hybrid mode.</p>
<p>This allows deep customization without abandoning the Elastic Agent runtime.</p>
<p><strong>No vendor lock-in—full ecosystem alignment</strong></p>
<p>Hybrid ingestion leverages upstream OpenTelemetry components directly.
This reinforces the open, vendor-neutral ecosystem organizations prefer when standardizing observability across teams while being supported by Elastic.</p>
<h3 id="whataboutstandalonemodeadvancedusecases">What About Standalone Mode? (Advanced Use Cases)</h3>
<p>While Fleet-managed hybrid ingestion will meet the needs of most users, Elastic Agent in hybrid mode also support standalone deployment with the same functions as the managed version.</p>
<ul>
<li>native integrations support</li>
<li>full control over Otel receivers, processors, and exporters</li>
<li>Elasticsearch output as the backend</li>
</ul>
<p>This is particularly useful for platform teams testing advanced OTel deployments or building custom telemetry strategies.</p>
<p>But it remains optional—the managed experience is still the default path.</p>
<h3 id="conclusionamodernflexiblepathtowardopentelemetry">Conclusion: A Modern, Flexible Path Toward OpenTelemetry</h3>
<p>Migrating to OpenTelemetry is a journey, not a switch. With hybrid ingestion, Elastic provides a realistic, scalable, and low-risk pathway for organizations that want to adopt OTel gradually while maintaining operational continuity.</p>
<p>Elastic Agent 9.2+ enables teams to:</p>
<ul>
<li>retain reliable log integrations</li>
<li>introduce OTel inputs seamlessly</li>
<li>manage everything from Fleet</li>
<li>reduce complexity and operational overhead</li>
<li>expand into OTel at the right pace</li>
<li>stay aligned with open standards and best practices</li>
</ul>
<p>It brings the best of both worlds—Elastic-native richness and OTel-standard flexibility—into a single agent and a unified operational model.</p>
<p>Hybrid isn’t a workaround.
It’s the strategic bridge between where your observability platform is today and where it needs to go next.</p>
<h2 id="technicalwalkthroughdeployinghybridelasticagentedotinfleet">Technical Walkthrough: Deploying Hybrid Elastic Agent + EDOT in Fleet</h2>
<p>Before we close, let’s look at what this actually looks like in practice.
Conceptual advantages are important, but many teams want to see how hybrid ingestion works when deployed through Fleet.</p>
<p>The example below walks through a simple, production-ready setup using Elastic Agent 9.2, combining a native integration and an OTel input integration inside a single agent,  the same approach you can apply to any service across your environment.</p>
<p>Here is a step-by-step guide showing how to deploy Elastic Agent 9.2 in <strong>Fleet-managed hybrid mode</strong>, using the OTel nginxreceiver as one concrete example.
This applies to any service with an OTel receiver (Redis, HAProxy, Kafka, JVM, etc.).</p>
<h3 id="requirements">Requirements</h3>
<ul>
<li>Elastic Stack <strong>9.2+</strong></li>
<li>Elastic Agent <strong>9.2+</strong></li>
<li>Fleet configured in Kibana</li>
<li>A host running your workload (NGINX in this example)</li>
<li>NGINX <code>stub_status</code> endpoint or any equivalent OTel metrics endpoint</li>
<li>API key with ingest privileges</li>
</ul>
<h2 id="1createorselectanagentpolicy">1. Create or Select an Agent Policy</h2>
<ol>
<li>In Kibana → <strong>Management → Fleet → Agent policies</strong></li>
<li>Create a new policy: <code>nginx-o11y</code></li>
<li>Enable system monitoring (recommended)</li>
<li>Save</li>
</ol>
<h2 id="2enrollelasticagentintothepolicy">2. Enroll Elastic Agent into the Policy</h2>
<p>From the policy page:</p>
<ol>
<li>Click <strong>Add agent</strong></li>
<li>Choose your OS</li>
<li>Copy the installation command</li>
<li>Run:</li>
</ol>
<pre><code>sudo elastic-agent install \
  --url=&lt;FLEET_URL&gt; \
  --enrollment-token=&lt;ENROLLMENT_TOKEN&gt;
</code></pre>
<p>You should soon see the agent appear as Healthy in Fleet.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt95c77390dd561094/6a85c73811893c73fba7aac3/image1.png" alt="" /></p>
<h2 id="3addthenativeintegrationlogs">3. Add the Native Integration (Logs)</h2>
<ol>
<li>In Fleet, go to Integrations.</li>
<li>Search for NGINX.</li>
<li>Click Add NGINX.</li>
<li>Select your <code>nginx-o11y</code> policy.</li>
<li>Only enable log collection (access + error logs).</li>
<li>Save and deploy.</li>
</ol>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf68a8556fde5f467/6a85c73cbc5bb37b1cf81a09/image2.png" alt="" /></p>
<h2 id="4validatelogcollection">4. Validate Log Collection</h2>
<ol>
<li>In Kibana, go to Analytics → Discover and search for:</li>
</ol>
<pre><code>data_stream.dataset : "nginx.access" or "nginx.error"
</code></pre>
<ol>
<li>Or open the built-in dashboard:</li>
</ol>
<pre><code>Analytics → Dashboards → [Logs Nginx] Access and error logs
</code></pre>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0ec392a12356c191/6a85c73f43c0b78b1e2f0561/image3.png" alt="" /></p>
<h3 id="5collectingnginxmetricsviatheotelnginxreceiver">5. Collecting NGINX Metrics via the OTel NGINX Receiver</h3>
<p>Elastic Agent 9.2+ allows Fleet to deploy OTel input integrations.<br />
This scenario uses the OpenTelemetry <code>nginxreceiver</code> through a Fleet-managed integration.</p>
<h4 id="51installthenginxopentelemetryintegrationcontent">5.1. Install the NGINX OpenTelemetry Integration Content</h4>
<ol>
<li>In Kibana, go to Management → Fleet → Integrations.  </li>
<li>Search for NGINX OpenTelemetry Assets.  </li>
<li>Click Add Integration.</li>
</ol>
<h4 id="52installthenginxopentelemetryinputintegration">5.2. Install the NGINX OpenTelemetry Input Integration</h4>
<ol>
<li>In Kibana, go to Management → Fleet → Integrations.  </li>
<li>Search for NGINX OpenTelemetry Input Package.  </li>
<li>Click Add Integration.  </li>
<li>Assign it to your agent <code>nginx-o11y</code> policy.</li>
</ol>
<p>Provide the endpoint for the NGINX status page:</p>
<ul>
<li><strong>Endpoint</strong>: <code>http://localhost/status</code>  </li>
<li><strong>Collection interval</strong>: <code>10s</code>  </li>
</ul>
<p>Click <strong>Add integration</strong>.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt558458bae654e9a0/6a85c74299083f364440f91c/image4.png" alt="" /></p>
<h3 id="6validateotelmetrics">6. Validate OTel Metrics</h3>
<ol>
<li>Go to <strong>Analytics → Dashboards</strong>.  </li>
<li>Open: <strong>[Metrics Nginx OTEL Overview]</strong> Dashboard</li>
</ol>
<p>You should see metrics such as active connections, writes, reads, waiting, and request counts.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltbdf88740e1bb6391/6a85c74599083ff71e40f920/image5.png" alt="" /></p>
<h3 id="7closingthoughts">7. Closing thoughts</h3>
<p>This example highlights how straightforward hybrid ingestion becomes with Elastic Agent 9.2. By combining native integrations and OTel receivers within a single, centrally managed policy, you gain the flexibility to adopt OpenTelemetry where it adds the most value without disrupting existing pipelines or introducing operational overhead.</p>
<p>Whether you extend this pattern to additional services, experiment with other OTel receivers, or scale it across your fleet, the deployment model remains consistent, repeatable, and production-ready.</p>
<p>For more information and other innovations Elastic Observability has made check out:</p>
<ul>
<li><p><a href="https://www.elastic.co/observability-labs/blog/elastic-agent-pivot-opentelemetry">Discover how Elastic is evolving data ingestion with OpenTelemetry</a></p></li>
<li><p><a href="https://www.elastic.co/observability-labs/blog/elastic-distribution-opentelemetry-sdk-central-configuration-opamp">Learn how OpAMP enables centralized configuration of OpenTelemetry SDKs</a></p></li>
<li><p><a href="https://www.elastic.co/observability-labs/blog/elastic-observability-streams-ai-logs-investigations">Explore how Streams reshape AI-driven log investigation workflows</a></p></li>
</ul>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/hybrid-elastic-agent-opentelemetry-integration</link>
    <guid isPermaLink="false">hybrid-elastic-agent-opentelemetry-integration</guid>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[Infrastructure Monitoring]]></category>
    <dc:creator><![CDATA[Frederic Maussion]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf5cebbf4c5cf399f/6a85c7479d2b71a165f938b6/feature-image.png" length="0" type="image/png"/>
    <pubDate>Fri, 09 Jan 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Find answers quickly, correlate OpenTelemetry traces with existing ECS logs in Elastic Observability]]></title>
    <description><![CDATA[In this blog we will discuss how EDOT enables you to collect existing ECS logs while ensuring a seamless and transparent move to OTel semantic conventions. The key benefit is that applications can continue sending logs as they do today, which minimizes the effort and impact on application developers.]]></description>
    <content:encoded><![CDATA[<p>OpenTelemetry (OTel) is the undisputed standard for vendor-neutral instrumentation. However, most established organizations don't start from a blank slate. You likely have a mature ecosystem of applications already logging in Elastic Common Schema (ECS), supported by years of refined dashboards and alerting rules.</p>
<p><strong>The challenge is clear:</strong> How do you adopt OTel’s unified observability without abandoning your proven ECS-based logging?</p>
<p>In this guide, we’ll demonstrate how to bridge this gap using the <strong>Elastic Distribution of OpenTelemetry (EDOT)</strong>. We will first show you how to leverage the EDOT Collector to ingest your logs into Elasticsearch, ensuring a seamless transition that unlocks the full power of OTel’s distributed tracing without breaking your current workflows.</p>
<p>Once the data is flowing, we will explore how Elasticsearch's underlying mapping architecture to allow that your existing filters and visualizations remain fully functional through two key features:</p>
<ul>
<li><p><strong>Field Aliases:</strong> We’ll explain how Elastic uses aliases to ensure that legacy dashboards looking for <code>log.level</code> (ECS) still work perfectly, even as your new telemetry arrives as <code>severity_text</code> (OTel).</p></li>
<li><p><strong>Passthrough Fields:</strong> We’ll show how Elastic’s native OTel mapping structures use passthrough fields to handle OTel attributes. This ensures your data remains searchable and performant without the need for complex, manual schema migrations.</p></li>
</ul>
<p>By combining EDOT for ingestion with these intelligent mapping structures, you can maintain your existing Java ECS logging while evolving toward a unified, OTel-native future.</p>
<h2 id="theecsfoundation">The ECS Foundation</h2>
<p>We begin with a Java application using <strong>Log4j2</strong> and the <strong>ecs-java-plugin</strong>. This setup generates structured JSON logs in the <a href="https://www.elastic.co/docs/reference/ecs">Elastic Common Schema (ECS)</a> that Elastic handles natively leveraging the ECS logging plugins that easily integrate with common logging libraries across various programming languages. </p>
<p>The following provides a <strong>Log4j2 Configuration Extract</strong> and this setup assumes prior configuration of Log4j2 dependencies to include the required ECS plugin libraries:</p>
<pre><code>&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;Configuration status="DEBUG"&gt;
    &lt;Appenders&gt;
        &lt;Console name="LogToConsole" target="SYSTEM_OUT"&gt;
            &lt;EcsLayout serviceName="logger-app" serviceVersion="v1.0.0"/&gt;
        &lt;/Console&gt;
    &lt;/Appenders&gt;
    &lt;Loggers&gt;
        &lt;Root level="info"&gt;
            &lt;AppenderRef ref="LogToConsole"/&gt;
        &lt;/Root&gt;
    &lt;/Loggers&gt;
&lt;/Configuration&gt;
</code></pre>
<p><strong>Note:</strong> <code>&lt;EcsLayout serviceName="logger-app" serviceVersion="v1.0.0"/&gt;</code> we will come back to this setting later in the blog article, as with Kubernets deployments these values can be automatically populated by the EDOT Collector and the setting could be simplified to <code>&lt;EcsLayout/&gt;</code></p>
<h2 id="introducingtheelasticdistributionofopentelemetryedot">Introducing the Elastic Distribution of OpenTelemetry (EDOT)</h2>
<p>The <a href="https://www.elastic.co/docs/reference/opentelemetry">Elastic Distribution of OpenTelemetry (EDOT)</a> is more than just a repackaging; it is a curated set of OTel components (Collector and SDKs) optimized for Elastic Observability. Released in v8.15, it allows you to collect traces, metrics, and logs using standard OTel receivers while benefiting from Elastic-contributed enhancements like powerful log parsing and Kubernetes metadata enrichment.</p>
<p>EDOT's Primary Benefits:</p>
<p><strong>Deliver Enhanced Features Earlier:</strong> Provides features not yet available in "vanilla" OTel components, which Elastic continuously contributes upstream.</p>
<p><strong>Enhanced OTel Support:</strong> Offers enterprise-grade support and maintenance for fixes outside of standard OTel release cycles.</p>
<p>The question then becomes: How can users transition their ingestion architecture to an OTel-native approach while maintaining the ability to collect logs in ECS format?</p>
<p>This involves replacing classic collection and instrumentation components (like Elastic Agent and the Elastic APM Java Agent). Let us show you how this can be done step by step replacing it with the full suite of components provided by EDOT. A comprehensive view of the EDOT architecture components in Kubernets is shown below.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt274ae7c75410bd05/6a7f19574c4bfb37a2ccd8d8/architecture.png" alt="EDOT reference Architecure in K8s" /></p>
<p>In a Kubernetes environment, EDOT components are typically installed via an OTel Operator and HELM chart. The main components are:</p>
<ul>
<li><strong>EDOT Collector Cluster:</strong> deployment used to collect cluster-wide metrics.</li>
<li><strong>EDOT Collector Daemon:</strong> daemonset used to collect node metrics, logs, and application telemetry data.</li>
<li><strong>EDOT Collector Gateway:</strong> performs pre-processing, aggregation, and ingestion of data into Elastic.</li>
</ul>
<p>Elastic provides a curated configuration file for all the EDOT components available as part of the the OpenTelemetry Operator using the <code>opentelemetry-kube-stack</code> Helm chart. Downloadable from <a href="https://github.com/elastic/elastic-agent/blob/main/deploy/helm/edot-collector/kube-stack/values.yml">here</a>.</p>
<h2 id="achievingcorrelationsdkloggingcontext">Achieving Correlation: SDK + Logging Context</h2>
<p>To link a log line to a specific trace, the <a href="https://www.elastic.co/docs/reference/opentelemetry/edot-sdks/java">EDOT Java SDK</a> performs a "handshake" with your logging library.
When a trace is active, the SDK extracts the <code>trace_id</code> and <code>span_id</code> and injects them into the <strong>Mapped Diagnostic Context (MDC)</strong> of Log4j2. Even though your logs are in ECS format, they now carry the OTel DNA required for correlation.
While the EDOT SDK can collect logs directly, a generally more resilient approach is to stick to file collection. This is important because if the OTel Collector is down, logs written to a file are buffered locally on the disk, preventing the data loss that can occur if the SDK's in-memory queue reaches its limit and starts discarding new logs. For an in-depth discussion on this topic we refer to the <a href="https://opentelemetry.io/docs/languages/java/instrumentation/#log-instrumentation">OpenTelemetry Documentation</a>.</p>
<h2 id="zerocodeinstrumentation">Zero-Code Instrumentation</h2>
<p>The EDOT Java SDK is a customized version of the OpenTelemetry Java Agent. In Kubernetes, zero-code Java autoinstrumentation is supported by adding an <a href="https://www.elastic.co/docs/reference/opentelemetry/edot-sdks/java/setup/k8s">annotation</a> in the pod template configuration in the deployment manifest:</p>
<pre><code>apiVersion: apps/v1
kind: Deployment
...
spec:
  ..
  template:
    metadata:
      # Auto-Instrumentation
      annotations:
        instrumentation.opentelemetry.io/inject-java: "opentelemetry-operator-system/elastic-instrumentation"
</code></pre>
<h2 id="collectingandprocessinglogswiththeedotcollector">Collecting and Processing Logs with the EDOT Collector</h2>
<p>This is the most critical step. Our logs are now JSON, they are in the console output, and they contain trace IDs. Now, we need the EDOT Collector to pick them up and map them to the <strong>OpenTelemetry Log Data Model</strong>.</p>
<h3 id="edotcollectorconfigurationdynamicworkloaddiscoveryandfilelogreceiver">EDOT Collector Configuration: Dynamic Workload Discovery and filelog receiver</h3>
<p>Applications running on containers become moving targets for monitoring systems. To handle this, we rely on <a href="https://www.elastic.co/observability-labs/blog/k8s-discovery-with-EDOT-collector">Dynamic workload discovery on Kubernetes</a>. This allows the EDOT Collector to track pod lifecycles and dynamically attach log collection configurations based on specific annotations relying on the <code>k8s_observer</code> and the <code>receiver_creator</code> component.</p>
<p>In our example, we have a Deployment with a Pod consisting of one container. We use Kubernetes annotations to:</p>
<ol>
<li><p>Enable auto-instrumentation (Java).</p></li>
<li><p>Enable log collection for this pod.</p></li>
<li><p>Instruct the collector to parse the output as JSON immediately (json-parser configuration).</p></li>
<li><p>Add custom attributes (e.g. identify the Application souce code)</p></li>
</ol>
<h4 id="deploymentmanifestexample">Deployment Manifest Example</h4>
<pre><code>apiVersion: apps/v1
kind: Deployment
metadata:
  name: logger-app-deployment
  labels:
    app: logger-app
spec:
  replicas: 1
  selector:
    matchLabels:
      app: logger-app
  template:
    metadata:
      annotations:
        # 1. Turn on Auto-Instrumentation
        instrumentation.opentelemetry.io/inject-java: "opentelemetry-operator-system/elastic-instrumentation"
        # 2. Enable Log Collection for this pod
        io.opentelemetry.discovery.logs/enabled: "true"
        # 3. Provide the parsing "hint" (Treat logs as JSON)
        io.opentelemetry.discovery.logs.ecs-log-producer/config: |
            operators:
            - type: container
              id: container-parser
            - type: json_parser
              id: json-parser
         # 4. Identify this application as Java (To allow for user interface rendering in Kibana)
        resource.opentelemetry.io/telemetry.sdk.language: "java"
      ...
</code></pre>
<p>This setup provides a bare-minimum configuration for ingesting ECS library logs.
Crucially, it decouples log collection from application logic. Developers simply need to provide a hint via annotations that their logs are in JSON format (structurally guaranteed by the ECS libraries). We then define the standardized enrichment and processing rules centrally at the <a href="https://www.elastic.co/docs/reference/edot-collector/components">processor</a> level in the (Daemon) EDOT Collector.</p>
<p>This centralization ensures consistency across the platform: if we need to update our standard formatting or enrichment strategies later, we apply the change once in the collector, and it automatically propagates to all services without developers needing to touch their manifests.</p>
<h4 id="daemonedotcollectorconfiguration">(Daemon) EDOT Collector Configuration</h4>
<p>To enable this, we configure a Receiver Creator in the Daemon Collector. This component uses the <code>k8s_observer</code> extension to monitor the Kubernetes environment and automatically discover the target pods based on the annotations above.</p>
<pre><code>daemon:
  ...
  config:
    ...
    extensions:
      extensions:
        k8s_observer:
          auth_type: serviceAccount
          node: ${env:K8S_NODE_NAME}
          observe_nodes: true
          observe_pods: true
          observe_services: true
          ...
    receivers:
        receiver_creator/logs:
          watch_observers: [k8s_observer]
          discovery:
            enabled: true
    ...
...
</code></pre>
<p>Finally, we reference the <code>receiver_creator</code> in the pipeline instead of a static filelog receiver and we make sure to include the <code>k8s_observer</code> extension:</p>
<pre><code>daemon:
  ...
  config:
    ...
    service:
      extensions:
      - k8s_observer
      pipelines:
        # Pipeline for node-level logs
        logs/node:
          receivers:
            # - filelog             # We disable direct filelog receiver
            - receiver_creator/logs # Using the configured receiver_creator instead of filelog
          processors:
            - batch
            - k8sattributes
            - resourcedetection/system
          exporters:
            - otlp/gateway # Forward to the Gateway Collector for ingestion
</code></pre>
<h3 id="thetransformationlayer">The Transformation Layer</h3>
<p>While the logs are structured, OTel sees them as generic attributes. We use the OpenTelemetry Transformation Language (OTTL) within a <code>transform</code> processor to "promote" ECS fields to top-level OTel fields.
To finalize the pipeline, we use the <a href="https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/processor/transformprocessor/README.md">transform processor</a>, which allows us to modify and restructure telemetry signals using the OpenTelemetry Transformation Language (OTTL).</p>
<p>We use the processor to promote specific ECS fields into the top-level OpenTelemetry fields and renaming attributes according to OpenTelemetry Semantic Conventions:</p>
<ul>
<li>Promote the <code>message</code> attribute to the top-level <code>Body</code> field.</li>
<li>Promote the <code>log.level</code> attribute to the OTel <code>SeverityText</code> field.</li>
<li>Move the <code>@timestamp</code> attribute to the OTel <code>Time</code> field.</li>
<li>Map <code>trace_id</code> and <code>span_id</code> to the right log context.</li>
</ul>
<p>The following provides a sample <code>transform</code> configuration:</p>
<pre><code> processors:
    transform/ecs_handler:
      log_statements:
      - context: log
        conditions:
          - log.attributes["ecs.version"] != nil
        statements:
          # Map ECS fields to OTel Log Model
          - set(log.body, log.attributes["message"])
          - set(log.time, Time(log.attributes["@timestamp"], "%Y-%m-%dT%H:%M:%SZ"))
          - set(log.trace_id.string, log.attributes["trace_id"])
          - set(log.span_id.string, log.attributes["span_id"])
          - set(log.severity_text, log.attributes["log.level"])
          # Cleanup original keys to save space
          - delete_key(log.attributes, "message")
          - delete_key(log.attributes, "trace_id")
          - delete_key(log.attributes, "span_id")

          # Add here additional transformations as needed...
</code></pre>
<p><strong>Note:</strong> When working with EDOT Collector and the OpenTelemetry Kube-Stack Helm Chart, resource attributes such as <code>service.name</code> and <code>service.version</code> are automatically populated based on a set of <a href="https://opentelemetry.io/docs/specs/semconv/non-normative/k8s-attributes/">well-defined</a>
rules by the <code>k8sattributes</code> processor. Thus, on Kubernetes we do not need to extract those fields from the log content itself.</p>
<p>Make sure to use the newly created processor in the logs pipeline for the Daemon Collector:</p>
<pre><code>service:
  pipelines:
    logs/node:
      receivers:
        - receiver_creator/logs
      processors:
        - batch
        - k8sattributes
        - resourcedetection/system
        - transform/ecs_handler          # Newly created transform processor
      exporters:
        - otlp/gateway
</code></pre>
<h2 id="thecompatibilitylayerbridgingecsandotel">The Compatibility Layer: Bridging ECS and OTel</h2>
<p>To bridge the gap between the Elastic Common Schema (ECS) and OpenTelemetry (OTel), Elastic provides a "compatibility layer" built directly into its Observability solution relying on existing index templates and mappings. This architecture allows you to send OTel-native data while still using your legacy ECS-based dashboards, saved searches, and other associated objects.</p>
<p>This "bridge" relies on two key features:</p>
<ul>
<li><p><strong>Bridging ECS and OTel with Passthrough:</strong> OpenTelemetry (OTel) data often uses deeply nested structures (e.g., <code>resource.attributes.*</code>). Elasticsearch uses the <strong><a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/passthrough">Passthrough</a></strong> object type to "promote" these nested attributes to the top level when performing a search query. Any new metadata added by the OTel collector is automatically searchable without the user needing to know the full JSON path. This creates a "virtual flattening" layer and makes sure that all fields that match in name are automatically compatible, even though thery're stored in different namespaces (attributes/resource.attributes for OTel vs top-level for ECS). To learn more about fields and attributes alignment between ECS and Otel SemanticConvention refer to this <a href="https://www.elastic.co/docs/reference/ecs/ecs-otel-alignment-details">page</a>.</p></li>
<li><p><strong>Bridging with Field Aliases</strong>: Elastic relies on OTel mapping templates that include <code>Field Aliases</code>. These aliases link OTel semantic names back to their equivalent ECS fields at query to handle fields that do not align with Otel naming convention.</p></li>
</ul>
<p><em>The Benefit:</em> If you have an existing dashboard looking for <code>message</code> (ECS), but your data is now indexed as <code>body.text</code> (OTEL), an alias allows the dashboard to aggregate and visualize data from both sources simultaneously. This ensures that your existing filters and KQL queries also work flawlessly whether the data originated from a Filebeat agent or a modern OTel SDK Agent.</p>
<p>Some more details about field aliases and pass-through objects can be found <a href="https://www.elastic.co/docs/reference/opentelemetry/compatibility/data-streams#query-compatibility-with-classic-apm-data-streams">here</a>.</p>
<p>Here is an example of the provided mapping template:</p>
<pre><code>{
  "mappings": {
    ...
    "properties": {
      "log": {
          "properties": {
            "level": {
              "type": "alias",
              "path": "severity_text"
            }
          }
        },
      "message": {
        "type": "alias",
        "path": "body.text"
      }
    ...
    }
  }
 }
</code></pre>
<p>This architectural approach provides three major advantages for teams in transition:</p>
<ul>
<li><p><strong>Zero Reindexing:</strong> You don't have to rewrite or migrate old data. Aliases resolve at query time, meaning your old indices and new indices can coexist in the same visualization.</p></li>
<li><p><strong>Future-Proofing:</strong> As OTel becomes the primary standard (following the donation of ECS to the OTel project), Elastic is shifting its native UI to look for OTel fields first. These mappings ensure that your legacy ECS-native data still appears in OTel-native views.</p></li>
<li><p><strong>Unified Observability:</strong> It enables "Correlation by Default." Because the aliases link trace_id (OTel) and trace.id (ECS), you can jump from a legacy log to a modern OTel trace without losing context or breaking the drill-down path.</p></li>
</ul>
<h2 id="sendingdatatoelasticsearch">Sending data to Elasticsearch</h2>
<p>If you are running Elastic Serverless or the latest Elastic Cloud Hosted (ECH) v9.2+, you now have access to a managed OTLP endpoint. This native functionality allows you to route telemetry directly from your Collector Gateway to Elasticsearch using the OTLP protocol.</p>
<p>Because we mapped our ECS fields to the OTel model in the collector, Elasticsearch recognizes the correlation immediately. You get the best of both worlds:
<strong><em>Legacy Compatibility:</em></strong> Your old ECS-based dashboards still work (with minor tweaks).
<strong><em>Modern Power:</em></strong> You can now click "View Trace" directly from a log entry in Kibana's Observability UI.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2765d381ee814272/6a7f195ac2cc09588524999e/discovery.jpg" alt="Discover" /></p>
<h2 id="conclusion">Conclusion</h2>
<p>Transitioning to OpenTelemetry doesn't have to be a "big bang" migration. By using the EDOT SDK and Collector, you can:
<strong><em>Protect your investment</em></strong> in ECS-based logging libraries.
<strong><em>Centralize complexity</em></strong> by handling schema translation in the collector rather than the application.
<strong><em>Enable full correlation</em></strong> between traces and logs with zero code changes.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/otel-ecs-unification-elastic</link>
    <guid isPermaLink="false">otel-ecs-unification-elastic</guid>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[APM]]></category>
    <dc:creator><![CDATA[Mirko Bez,Alessandro Brofferio]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt754e4d41f9adb854/6a7f195e227b1c7c165989e9/blog-image.jpg" length="0" type="image/jpeg"/>
    <pubDate>Thu, 04 Dec 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[OpenTelemetry for PHP: EDOT PHP joins the OpenTelemetry project]]></title>
    <description><![CDATA[Explore Elastic’s donation of its EDOT PHP to the OpenTelemetry community and discover how it makes OpenTelemetry for PHP simpler and more accessible.]]></description>
    <content:encoded><![CDATA[<p>The OpenTelemetry community has officially accepted Elastic's proposal to contribute the <strong>Elastic Distribution of OpenTelemetry for PHP (EDOT PHP)</strong> — marking an important milestone in bringing first-class observability to one of the web's most widely used languages.</p>
<p>For decades, PHP has powered everything from small business websites to large-scale SaaS platforms. Yet observability in PHP has often required manual setup, compilers, custom extensions, or changes to application code — challenges that limited adoption in production environments.
This upcoming donation aims to change that, by making OpenTelemetry for PHP <strong>as easy to deploy as any other runtime</strong>.</p>
<h2 id="whatscoming">What's coming</h2>
<p>Once the contribution process is complete, EDOT PHP will become part of the OpenTelemetry project — providing a <strong>complete, production-ready distribution</strong> that's optimized for performance, simplicity, and scalability.</p>
<p>EDOT PHP introduces a new approach to PHP observability:</p>
<ul>
<li><strong>Simple installation</strong> - installing OpenTelemetry for PHP will be as straightforward as installing a standard system package. From that point, the agent automatically detects and instruments PHP applications — no code changes, no manual setup.</li>
<li><strong>Automatic agent loading</strong> - works transparently in cloud and container environments without modifying application deployments.</li>
<li><strong>Zero configuration</strong> - ships as a single, self-contained binary; no need to install or compile any external extensions.</li>
<li><strong>Native C++ performance</strong> - a built-in serializer written in C++ reduces telemetry overhead by up to <strong>5×</strong>.</li>
<li><strong>Automatic instrumentation</strong> - instruments popular frameworks and libraries out of the box.</li>
<li><strong>Inferred spans</strong> - reveals the behavior of even uninstrumented code paths, providing full trace coverage.</li>
<li><strong>Automatic root spans</strong> - ensures complete traces, even in legacy or partially instrumented applications.</li>
<li><strong>OpAMP readiness</strong> - while the OpenTelemetry community continues to standardize configuration schemas and management workflows, the implementation in EDOT PHP is fully prepared to support these upcoming specifications — ensuring seamless adoption once the OpAMP ecosystem matures.</li>
<li><strong>Asynchronous backend communication</strong> - telemetry data is exported to the OpenTelemetry Collector or backend <strong>asynchronously</strong>, without blocking the instrumented application.
This ensures that span and metric exports do not add latency to user requests or impact response times, even under heavy load.</li>
</ul>
<p>Together, these features make EDOT PHP the first truly <strong>zero-effort observability solution for PHP</strong> — from local testing to cloud-scale production systems.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt340e08b58598fdb7/6a7f0f6ce88c651ef700b7ca/performance.png" alt="Performance comparision" /></p>
<blockquote>
  <p>The native C++ serializer and asynchronous export pipeline in EDOT PHP reduce average request time from <strong>49 ms</strong> to <strong>23 ms</strong>, more than <strong>2× faster</strong> than the pure PHP implementation.</p>
</blockquote>
<h2 id="buildingontheexistingfoundation">Building on the existing foundation</h2>
<p>EDOT PHP doesn't replace the existing OpenTelemetry PHP SDK — it <strong>extends and strengthens it</strong>.
It packages the SDK, automatic instrumentation, and native extension into a single, unified agent package that works seamlessly with existing OpenTelemetry specifications and APIs.</p>
<p>By contributing this work, Elastic helps the OpenTelemetry community accelerate PHP adoption, align implementations across languages, and make distributed tracing truly universal.</p>
<blockquote>
  <p>“This isn't a hand-off — it's a collaboration.
  We're contributing years of development to help OpenTelemetry for PHP evolve faster, run more efficiently, and reach more users in every environment.”</p>
  <ul>
  <li><em>Elastic Observability team</em></li>
  </ul>
</blockquote>
<h2 id="ongoingimprovements">Ongoing improvements</h2>
<p>Elastic continues to invest in advancing EDOT PHP ahead of its integration into OpenTelemetry.
The team is currently focused on <strong>reducing resource usage and memory footprint</strong>, particularly in <strong>multi-worker server environments</strong> such as PHP-FPM or Apache prefork.
These optimizations aim to make the agent more predictable and efficient under heavy load — ensuring that telemetry remains lightweight even in large-scale production deployments.</p>
<p>Beyond that, we're exploring further improvements that can enhance both performance and interoperability.
Areas under investigation include smarter coordination in high-concurrency scenarios, better sharing of telemetry resources across workers, and future alignment with additional OpenTelemetry signals such as metrics and logs.</p>
<p>Together, these efforts will help make EDOT PHP not only faster, but also more adaptable and seamlessly integrated into diverse runtime architectures.</p>
<h2 id="whyitmatters">Why it matters</h2>
<p>This contribution is about more than performance — it's about <strong>removing barriers</strong>.
By making OpenTelemetry for PHP installable as a simple system package and automatically loaded into running applications, the project opens observability to every PHP developer, operator, and platform provider.</p>
<p>For the OpenTelemetry ecosystem, it fills one of the last major language gaps, extending visibility to a vast portion of the internet — all under open governance and community collaboration.</p>
<h2 id="lookingahead">Looking ahead</h2>
<p>In the months ahead, Elastic and the OpenTelemetry PHP SIG will work closely on the technical integration, documentation, and community onboarding process.
Once the transition is complete, developers will gain a fully open, community-driven, and production-ready OpenTelemetry agent that “just works” — without friction, configuration, or code changes.</p>
<p>Together, we're building a future where <strong>observability just works — for every language, every framework, and every environment</strong>.</p>
<p>For more information:</p>
<p><a href="https://www.elastic.co/docs/reference/opentelemetry">EDOT documentation</a><br />
<a href="https://www.elastic.co/observability-labs/blog/elastic-managed-otlp-endpoint-for-opentelemetry">Learn about</a> OTLP Endpoint</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/opentelemetry-accepts-elastics-donation-of-edot</link>
    <guid isPermaLink="false">opentelemetry-accepts-elastics-donation-of-edot</guid>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[APM]]></category>
    <dc:creator><![CDATA[Pawel Filipczak]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9499757f899f3979/6a7f18da227b1c46675989e1/otel-php.jpg" length="0" type="image/jpeg"/>
    <pubDate>Mon, 10 Nov 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[OpenTelemetry Data Quality Insights with the Instrumentation Score and Elastic]]></title>
    <description><![CDATA[This post explores the Instrumentation Score for OpenTelemetry data quality, sharing practical insights, key learnings, and a hands-on look at implementing this approach with Elastic's powerful observability features.]]></description>
    <content:encoded><![CDATA[<p>OpenTelemetry adoption is rapidly increasing and more companies rely on OpenTelemetry to collect observability data.
While OpenTelemetry offers clear specifications and semantic conventions to guide telemetry data collection, it also introduces significant flexibility.
With high flexibility comes high responsibility — many things can go wrong with OTel-based data collection, easily resulting in mediocre or low-quality telemetry.
Poor data quality can hinder backend analysis, confuse users, and degrade system performance.
To unlock actionable insights from OpenTelemetry data, maintaining high data quality is essential.
The <a href="https://instrumentation-score.com/">Instrumentation Score</a> initiative addresses this challenge by providing a standardized way to measure OpenTelemetry data quality.
Although the specification and tooling are still evolving, the underlying concepts are already compelling.
In this blog post, I’ll share my experience experimenting with the Instrumentation Score concept and demonstrate how to use the Elastic Stack — utlizing ES|QL, Kibana Task Manager, and Dashboards — to build a POC for data quality analysis based on this approach within Elastic Observability.</p>
<h2 id="instrumentationscorethepowerofrulebaseddataqualityanalysis">Instrumentation Score - The Power of Rule-based Data Quality Analysis</h2>
<p>When you first hear the term "Instrumentation Score", your initial reaction might be: "OK, there's a <em>single</em>, percentage-like metric that tells me my instrumentation (i.e. OTel data) has a score of 60 out of 100. 
So what? How does it help me?"</p>
<p>However, the Instrumentation Score is much more than just a single number.
Its power lies in the individual rules from which the score is calculated.
The rule definitions' <code>rationale</code>, <code>impact level</code>, and <code>criteria</code> provide an evaluation framework that enables you to drill down into data quality issues and identify specific areas for improvement.
Also, the Instrumentation Score specification does not mandate specific tools and implementation details for calculating the score and rule evaluations.</p>
<p>As I explored the Instrumentation Score concepts, I developed the following mental model for deriving actionable insights.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt375841c9034caae4/6a7f1962e88c6553e700bae4/inst-score-drill-down.png" alt="Instrumentation Score Drill-down" /></p>
<h5 id="thescore">The Score</h5>
<p>The score itself is an indicator of the quality of your telemetry data. The lower the number, the more room for improvement with your data quality.
In general, if a score falls below 75, you should consider fixing your instrumentation and data collection.</p>
<h5 id="breakdownbyinstrumentationscorerules">Breakdown by Instrumentation Score Rules</h5>
<p>Exploring the evaluation results of individual Instrumentation Score <em>rules</em> will give you insights into <em>what</em> is wrong with your data quality.
In addition, the rules' rationales explain <em>why</em> the violation of a rule is problematic.</p>
<p>As an example, let's take the <a href="https://github.com/instrumentation-score/spec/blob/main/rules/SPA-002.md"><code>SPA-002 rule</code></a>:</p>
<blockquote>
  <p><strong>Description</strong>:</p>
  <p>Traces do not contain orphan spans.</p>
  <p><strong>Rationale</strong>:</p>
  <p>Orphaned spans indicate potential issues in tracing instrumentation or data integrity. This can lead to incomplete or misleading trace data, hindering effective troubleshooting and performance analysis.</p>
</blockquote>
<p>If your data violates the <code>SPA-002</code> rule, you know <em>what</em> is wrong (i.e. you have broken traces), and the rationale explains why that is an issue (i.e. degraded analysis capabilities).</p>
<h5 id="breakdownbyservices">Breakdown by Services</h5>
<p>When you have a large system with hundreds or maybe even thousands of entities (such as services, Kubernetes pods, etc.), a binary signal on all of the data — such as "has a certain rule been passed or not" — is not really actionable.
Is the data from all services violating a certain rule, or just a small subset of services?</p>
<p>Breaking down rule evaluation by services (and potentially other entity types) may help you to identify <em>where</em> there are issues with data quality.
For example, let's assume only one service — the <code>cart-service</code> — (out of your fifty services) is affected by the violation of rule <code>SPA-002</code>.
With that information, you can focus on fixing the instrumentation for the <code>cart-service</code> instead of having to check all fifty services.</p>
<p>Once you know which services (or other entities) violate which Instrumentation Score rules, you're very close to actionable insights.
However, there are two more things that I found to be extremely useful for data quality analysis when I was experimenting with the Instrumentation Score evaluation: (1) a quantitative indication of the extent, and (2) concrete examples of rule violation occurrences in your data.</p>
<h5 id="quantifyingtheruleviolationextent">Quantifying the Rule Violation Extent</h5>
<p>The Instrumentation Score spec already defines an impact level (e.g. <code>NORMAL</code>, <code>IMPORTANT</code>, <code>CRITICAL</code>) per rule.
However, this only covers the "importance" of the rule itself, not the extent of a rule violation.
For example, if a single trace (out of a million traces) on your service has an orphan span, technically speaking the rule <code>SPA-002</code> is violated.
But is it really a relevant issue if only one out of a million traces is affected? Probably not. It definitely would be if half of your traces were broken.</p>
<p>Hence, having a quantitative indication of the extent of a rule violation per service — e.g. "40% of your traces violate <code>SPA-002</code>" — would provide additional information on how severe a rule violation actually is.</p>
<h5 id="tangibleexamples">Tangible Examples</h5>
<p>Finally, nothing is as meaningful and self-explanatory as tangible, concrete examples from your own data.
If the telemetry data of your <code>cart-service</code> violates <code>SPA-002</code> (i.e., has traces with orphan spans), wouldn't you want to see a concrete trace from that service that demonstrates the rule violation?
Analyzing concrete examples may give you hints about the root cause of broken traces — or, more generally, why your data violates Instrumentation Score rules.</p>
<h2 id="instrumentationscorewithelastic">Instrumentation Score with Elastic</h2>
<p>The Instrumentation Score spec does not prescribe tool usage or implementation details for the calculation of the score and evaluation of the rules.
This allows for integrating the Instrumentation Score concept with whatever backend your OpenTelemetry data is being sent to.</p>
<p>With the goal of building a POC for an end-to-end integration of the Instrumentation Score with Elastic Observability, I combined the powerful capabilities of ES|QL with Kibana's task manager and dashboarding features.</p>
<p>Each Instrumentation Score rule can be formulated as an ES|QL query that covers the steps described above:</p>
<ul>
<li>rule passed or not</li>
<li>breakdown by services</li>
<li>calculation of the extent</li>
<li>sampling of an example occurrence</li>
</ul>
<p>Here is an example query for the <code>LOG-002</code> rule that checks the validity of the <code>severity_number</code> field:</p>
<pre><code>FROM logs-*.otel-* METADATA _id
| WHERE data_stream.type == "logs"
    AND @timestamp &gt; NOW() - 1h
| EVAL no_sev = severity_number IS NULL OR severity_number == 0
| STATS 
    logs_wo_severity = COUNT(*) WHERE no_sev,
    example = SAMPLE(_id, 1) WHERE no_sev,
    total = COUNT(*)
      BY service.name
| EVAL rule_passed = (logs_wo_severity == 0),
    extent = CASE(total != 0, logs_wo_severity / total, 0.0)
| KEEP rule_passed, service.name, example, extent
</code></pre>
<p>These rule evaluation queries are wrapped in a Kibana <code>instrumentation-score</code> plugin that utilizes the task manager for regular execution.
The <code>instrumentation-score</code> plugin then takes the results from all the evaluation queries for the different rules and calculates the final instrumentation score value (overall and broken down by service) following the <a href="https://github.com/instrumentation-score/spec/blob/main/specification.md#score-calculation-formula">Instrumentation Score spec's calculation formula</a>.
The resulting instrumentation score values, as well as the rule evaluation results (with the examples and extent) are then stored in separate Elasticsearch indices for consumption. </p>
<p>With the results stored in dedicated Elasticsearch indices, we can build Dashboards to visualize the Instrumentation Score insights and allow users to troubleshoot their data quality issues.</p>
<p>In this POC I implement subet of instrumentation score rules to prove out the approach.</p>
<p>The Instrumentation Score concept accommodates extension with your own custom rules.
I did that in my POC as well to test some quality rules that are not yet formalized as rules in the Instrumentation Score spec,
but are important for Elastic Observability to provide the maximum value from the OTel data.</p>
<h2 id="applyingtheinstrumentationscoreontheopentelemetrydemo">Applying the Instrumentation Score on the OpenTelemetry Demo</h2>
<p>The <a href="https://github.com/open-telemetry/opentelemetry-demo">OpenTelemetry Demo</a> is the most-used environment to play around with and showcase OpenTelemetry capabilities.
Initially, I thought the demo would be the worst environment to test my Instrumentation Score implementation.
After all, it's the showcase environment for OpenTelemetry, and I expected it to have an Instrumentation Score close to 100.
Surprisingly, that wasn't the case.</p>
<p>Let's start with the overview.</p>
<h3 id="theoverview">The Overview</h3>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt83442d26e7434cc2/6a7f1966de23156342fd8081/dashboard-overview.png" alt="Dashboard Overview" /></p>
<p>This dashboard shows an overview of the Instrumentation Score results for the OpenTelemetry Demo environment.
The first thing you might notice is the very low overall score <code>35</code> (top-left corner).
The table in the bottom-left corner shows a breakdown of the score by services.
Somewhat surprisingly, all the service scores are higher than the overall score.
How is that possible?</p>
<p>The main reason is that Instrumentation Score rules have, by definition, a binary result — passed or not.
So it can happen that each service fails a single but distinct rule. Hence, the service score is not perfect but also not too bad.
But, from the overall perspective, many rules have failed (each by a different service), hence, leading to a very low overall score.</p>
<p>In the table on the right, we see the results for the individual rules with their description, impact level, and example occurrences.
We see that 7 out of 11 implemented rules have failed. Let's pick our favorite example from earlier — <code>SPA-002</code> (in row 5), the orphan spans rule.</p>
<p>With the dashboard indicating that the rule <code>SPA-002</code> has failed, we know that there are orphan spans somewhere in our OTel traces. But where exactly?</p>
<p>For further analysis, we have two ways to drill down: (1) into a specific rule to see which services violate a specific rule, or (2) into a specific service to see which rules are violated by that service.</p>
<h3 id="ruledrilldown">Rule Drilldown</h3>
<p>The following dashboard shows a detailed view into the rule evaluation results for individual rules.
In this case we selected rule <code>SPA-002</code> at the top.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2deddc098f9095bc/6a7f19694c4bfb4d33ccd8dc/dashboard-rule-spa-002.png" alt="Dashboard Overview" /></p>
<p>In addition to the rule's meta information, such as its description, rationale, and criteria, we see some statistics on the right.
For example, we see that 2 services have failed that rule, 16 passed, and for 19 services this rule is not applicable (e.g., because those don't have tracing data).
In the table below, we see which two services are impacted by this rule violation: the <code>frontend</code> and <code>frontend-proxy</code> services.
For each service, we also see the <em>extent</em>. In the case of the <code>frontend</code> service, around 20% of traces have orphan spans.
This information is crucial as it gives an indication of how severe the rule violation actually is.
If it had been under 1%, this problem might have been negligible, but with one trace out of five being broken, it definitely needs to be fixed.
Also, for each of the services, we have an example <code>span.id</code> for which no spans could be found but that are referenced in the <code>parent.id</code> by other spans.
This allows us to perform further analyses (e.g., by investigating the referring spans in Kibana's Discover) on concrete example cases.</p>
<p>With that view, we now know that the <code>frontend</code> service has a good amount of broken traces.
But is that service also violating other rules? And, if yes, which?</p>
<h3 id="servicedrilldown">Service Drilldown</h3>
<p>To answer the above question we can switch to the <code>Per Service</code> Dashboard.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte23fad5ba6cee485/6a7f196c5967e51c3b5dd6a1/dashboard-service-frontend.png" alt="Dashboard Overview" /></p>
<p>In this dashboard, we see similar information as on the overview dashboard, however, filtered on a single selected service (e.g., <code>frontend</code> service in this example).
In the table, we see that the <code>frontend</code> service violates three rules. We already know about <code>SPA-002</code> from the previous section.
In addition, the violation of the custom rule <code>SPA-C-001</code> shows that around 99% of transaction span names have high cardinality.
In Elastic Observability, <code>transactions</code> refer to service-local root spans (i.e., entry points into services).
In the example value, we see directly why the <code>span.name</code>s (here referred to as <code>transaction.name</code>s) have high cardinality.
The span name contains unique identifiers (here the session ID) as part of the URL that the span name is constructed from in the instrumentation.
As the <a href="https://www.elastic.co/docs/reference/edot-collector">EDOT Collector</a> derives metrics for transaction-type spans, we also can observe a violation of the <code>MET-001</code> which requires bound cardinality on metric dimensions.</p>
<p>As you can see, with the Instrumentation Score concept and a few different breakdown views, we were able to pinpoint data quality issues and identify which services and instrumentations need improvement to fix the issues.</p>
<h2 id="learningsandobservations">Learnings and Observations</h2>
<p>My experimentation with the Instrumentation Score was very insightful and showed me the power of this concept — though it's still in its early phase.
It is particularly insightful if the implementation and calculation include breakdowns by meaningful entities, such as services, K8s pods, hosts, etc.
With such a breakdown, you can narrow down data quality issues to a manageable scope, instead of having to sift through huge amounts of data and entities.</p>
<p>Furthermore, I realized that having some notion of problem extent (per rule and service), as well as concrete examples, helps make the problem more tangible.</p>
<p>Thinking further about the idea of rule violation <code>extent</code>, there might even be a way to incorporate that into the score formula itself.
In my humble opinion, this would make the score significantly more comparable and indicative of the actual impact.
I <a href="https://github.com/instrumentation-score/spec/issues/43">proposed this idea in an issue</a> on the Instrumentation Score project.</p>
<h2 id="conclusion">Conclusion</h2>
<p>The Instrumentation Score is a powerful approach to ensuring a high level of data quality with OpenTelemetry.</p>
<p>Thank you to the maintainers — Antoine Toulme, Daniel Gomez Blanco, Juraci Paixão Kröhling, and Michele Mancioppi — for bringing this great project to life, and to all the contributors for their participation!</p>
<p>With proper implementation of the rules and score calculation, users can easily get actionable insights into what they need to fix in their instrumentation and data collection.
The Instrumentation Score rules are in an early stage and are steadily improved and extended.
I'm looking forward to what the community will build in the scope of this project in the future, and I hope to intensify my contributions as well.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/otel-instrumentation-score</link>
    <guid isPermaLink="false">otel-instrumentation-score</guid>
    <category><![CDATA[OpenTelemetry]]></category>
    <dc:creator><![CDATA[Alexander Wert]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3851f3d00f4dc179/6a7f196fe88c65307a00bae8/otel-instrumentation-grade.jpg" length="0" type="image/jpeg"/>
    <pubDate>Thu, 06 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[Explore and Analyze Metrics with Ease in Elastic Observability]]></title>
    <description><![CDATA[The latest enhancements to ES|QL and Discover based metrics exploration unleash a potent set of tools for quick and effective metrics analytics.]]></description>
    <content:encoded><![CDATA[<h2 id="metricsarecriticalinidentifyingthewhat">Metrics are critical in identifying the “what”</h2>
<p>As a core pillar of Observability, metrics offer a highly structured, quantitative view of system performance and health. They provide a crucial symptomatic perspective—revealing <em>what</em> is happening, such as high application latency, increasing service errors, or spiking container CPU utilization, which is essential for initiating alerting and triaging efforts. This capability for effective monitoring, alerting, and triaging is paramount to ensuring robust service delivery and achieving successful business outcomes.</p>
<p>Elastic Observability provides a comprehensive, end-to-end experience for metrics data. Elastic ensures that metrics data can be collected from numerous sources, enriched as needed and shipped to the Elastic Stack. Elastic efficiently stores this time series data, including high-cardinality metrics, utilizing the <a href="https://www.elastic.co/observability-labs/blog/time-series-data-streams-observability-metrics">TSDS index mode</a> (Time Series Data Stream), introduced in <a href="https://www.elastic.co/blog/whats-new-elasticsearch-8-7-0#efficient-storage-of-metrics-with-tsdb,-now-generally-available">prior versions</a> and used across Elastic time series <a href="https://www.elastic.co/blog/70-percent-storage-savings-for-metrics-with-elastic-observability">integrations</a>. This foundation ensures comprehensive observability through out-of-the-box dashboards, alerts, SLOs, and streamlined data management.</p>
<p>Elastic Observability 9.2 provides enhancements to metrics exploration and analysis through powerful query language extensions and expanded UI capabilities. These enhancements focus on making analysis on TSDS data via counter rates and common aggregations over time easier and faster than ever before.</p>
<p>The main metrics enhancements center on these key features, offered as Tech Preview:</p>
<ol>
<li>Metrics analytics with TSDS and ES|QL</li>
<li>Interactive metrics exploration in Discover</li>
<li>OTLP endpoint for metrics</li>
</ol>
<h2 id="metricsanalyticswithtsdsandesql">Metrics analytics with TSDS and ES|QL</h2>
<p>The introduction of the new <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/ts"><code>TS</code> source command</a> in <a href="https://www.elastic.co/docs/reference/query-languages/esql">ES|QL</a> (Elasticsearch Query Language) on TSDS metrics dramatically simplifies time series analysis.</p>
<p>The <code>TS</code> command is specifically designed to target only time series indices, differentiating it from the general <code>FROM</code> command. Its core power lies in enabling a dedicated suite of time series aggregation functions within the <code>STATS</code> command.</p>
<p>This mechanism utilizes a dual aggregation paradigm, which is standard for time series querying. These queries involve two aggregation functions:</p>
<ul>
<li><p><strong>Inner (Time Series) function:</strong> Applied implicitly per time series, often over bucketed time intervals.</p></li>
<li><p><strong>Outer (Regular) function:</strong> Used to aggregate the results of the inner function across groups. For instance, if you use <code>STATS SUM(RATE(search_requests)) BY TBUCKET(1 hour), host</code>, the <code>RATE()</code> function is the inner function applied per time series in hourly buckets, and <code>SUM()</code> is the outer function, summing these rates for each host and hourly bucket.</p></li>
</ul>
<p>If an ES|QL query using the <code>TS</code> command is missing an inner (time series) aggregation function, <code>LAST_OVER_TIME()</code> is implicitly assumed and used. For example, <code>TS metrics | STATS AVG(memory_usage)</code> is equivalent to <code>TS metrics | STATS AVG(LAST_OVER_TIME(memory_usage))</code>.</p>
<h3 id="keytimeseriesaggregationfunctionsavailableinesqlviatscommand">Key time series aggregation functions available in ES|QL via <code>TS</code> command</h3>
<p>These functions allow for powerful analysis on time-series data:</p>
<p>|                                                        |                                                                                                                                                                                                                                                                                                                                       |                                                               |
| :----------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------: |
|                      <strong>Function</strong>                      |                                                                                                                                                            <strong>Description</strong>                                                                                                                                                            |                      <strong>Example Use Case</strong>                     |
|                <code>RATE()</code> <strong>/</strong> <code>IRATE()</code>                | Calculates the per-second average rate of increase of a counter (<code>RATE</code>), accounting for non-monotonic breaks like counter resets, making it the most appropriate function for counters, or the per-second rate of increase between the last two data points (<code>IRATE</code>), ignoring all but the last two points for high responsiveness. |      Calculating request per second (RPS) or throughput.      |
|                    <code>AVG_OVER_TIME()</code>                   |                                                                                                                                 Calculates the average of a numeric field over the defined time range.                                                                                                                                |        Determining average resource usage over an hour.       |
|                    <code>SUM_OVER_TIME()</code>                   |                                                                                                                                           Calculates the sum of a field over the time range.                                                                                                                                          |           Total errors over a specific time window.           |
|        <code>MAX_OVER_TIME()</code> <strong>/</strong> <code>MIN_OVER_TIME()</code>       |                                                                                                                                     Calculates the maximum or minimum value of a field over time.                                                                                                                                     |             Identifying peak resource consumption.            |
|               <code>DELTA()</code> <strong>/</strong> <code>IDELTA()</code>               |                                                                      Calculates the absolute change of a gauge field over a time window (<code>DELTA</code>) or specifically between the last two data points (<code>IDELTA</code>), making <code>IDELTA</code> more responsive to recent changes.                                                                     | Tracking changes in system gauge metrics (e.g., buffer size). |
|                      <code>INCREASE()</code>                      |                                                                                                                                      Calculates the absolute increase of a counter (<code>INCREASE</code>).                                                                                                                                      |   Analyzing immediate rate changes in fast-moving counters.   |
|      <code>FIRST_OVER_TIME()</code> <strong>/</strong> <code>LAST_OVER_TIME()</code>      |                                                                                                                   Calculates the earliest or latest recorded value of a field, determined by the <code>@timestamp</code> field.                                                                                                                  |  Inspecting initial and final metric states within a bucket.  |
|    <code>ABSENT_OVER_TIME()</code> <strong>/</strong> <code>PRESENT_OVER_TIME()</code>    |                                                                                                                            Calculates the absence or presence of a field in the result over the time range.                                                                                                                           |             Identifying monitoring coverage gaps.             |
| <code>COUNT_OVER_TIME()</code> <strong>/</strong> <code>COUNT_DISTINCT_OVER_TIME()</code> |                                                                                                                            Calculates the total count or the count of distinct values of a field over time.                                                                                                                           |          Measuring frequency or cardinality changes.          |</p>
<p>These functions, available with the <code>TS</code> command, allow SREs and Ops teams to easily perform rate calculations and other common aggregations, enabling efficient metrics analysis as a routine part of observability workflows. And it’s much faster, too! Internal performance testing has revealed that TS commands outperform other ways of querying metrics data by an order of magnitude or more, and consistently! </p>
<h2 id="interactivemetricsexplorationindiscover">Interactive metrics exploration in Discover</h2>
<p>The 9.2 release introduces the capability to explore and analyze metrics directly and interactively within the Discover interface. In addition to exploring and analyzing logs and raw events, Discover now provides a dedicated environment for metrics exploration:</p>
<ul>
<li><p><strong>Easy start:</strong> Begin exploration simply by querying metrics ingested via <code>TS metrics-*</code>.</p></li>
<li><p><strong>Grid view and pre-applied aggregations:</strong> This command displays all metrics in a grid format at a glance, immediately applying the appropriate aggregations based on the metric type, such as <code>rate</code> versus <code>avg</code>.</p></li>
<li><p><strong>Search and group-by:</strong> Quickly search for specific metrics by name. Also easily group and analyze metrics by dimensions (labels) and specific values. This allows narrowing down to metrics and dimensions of choice for targeted analysis.</p></li>
<li><p><strong>Quick access to details:</strong> Furthermore, the interface provides access to crucial details, including query and response details, the underlying ES|QL commands, the metric field type, and applicable dimensions, for each metric.</p></li>
<li><p><strong>Easy tweaking and dashboarding:</strong> The system automatically populates ES|QL queries, aiding in making easy tweaks, slicing, and dicing the data. Once analyzed, metrics and resulting analyses can be added to new or existing dashboards with ease.</p></li>
</ul>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt58ccd6deb4688879/6a7f0d5cc2cc0979e92495bc/metrics-discover-ts-command.png" alt="Interactive metrics exploration in Discover" /></p>
<h2 id="otlpendpointformetrics">OTLP endpoint for metrics</h2>
<p>We are also introducing a native OpenTelemetry Protocol (OTLP) endpoint specifically for metrics ingest directly into Elasticsearch. The endpoint especially benefits self-managed customers, and will be integrated into our <a href="https://www.elastic.co/docs/reference/opentelemetry/motlp">Elastic Cloud Managed OTLP Endpoint</a> for Elastic-managed offerings. The native endpoint and related updates improve ingest performance and scalability of OTel metrics, providing up to 60% higher throughput via <code>_otlp</code>, and up to 25% higher throughput when using classic <code>_bulk</code> methods. </p>
<h2 id="inconclusion">In Conclusion</h2>
<p>By merging the power of ES|QL's new time series aggregations with the familiar interactive experience of Discover, Elastic 9.2 enables a potent set of metrics analytics tools. The tools significantly boost the exploration and analysis phase of any observability workflow. And we’re just getting started on unleashing the full power of metrics in Elastic Observability!</p>
<p>We welcome you to <a href="https://cloud.elastic.co/serverless-registration?onboarding_token=observability">try the new features</a> today!</p>
<p>Also learn more about how we provide metrics analytics for AWS, Azure, GCP, Kubernetes, and LLMs on <a href="https://www.elastic.co/observability-labs">Observability Labs</a></p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/metrics-explore-analyze-with-esql-discover</link>
    <guid isPermaLink="false">metrics-explore-analyze-with-esql-discover</guid>
    <category><![CDATA[Metrics]]></category>
    <category><![CDATA[OpenTelemetry]]></category>
    <dc:creator><![CDATA[Vinay Chandrasekhar]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc8fdb910165be324/6a7f0d5f63e959271573de1a/metrics-blog-image-ts-discover.jpg" length="0" type="image/jpeg"/>
    <pubDate>Thu, 23 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[Web Frontend Instrumentation and Monitoring with OpenTelemetry and Elastic]]></title>
    <description><![CDATA[Learn how frontend instrumentation differs to backend, and the current state of client web instrumentation in OpenTelemetry]]></description>
    <content:encoded><![CDATA[<p>DevOps, SRE and software engineering teams all require telemetry data to understand what's going on across their infrastructure and full-stack applications. Indeed we have covered instrumentation of backend services in several language ecosystems using <a href="http://opentelemetry.io">OpenTelemetry</a> (OTel) in the past. Yet for frontend tools, teams are often still relying on RUM agents, or sadly no instrumentation at all, due to the subtle differences in metrics that are needed to understand what's going on.</p>
<p>In this blog, we will discuss the current state of client instrumentation for the browser, along with an example showing how to instrument a simple JavaScript frontend using <a href="https://opentelemetry.io/docs/languages/js/getting-started/browser/">the OpenTelemetry browser instrumentation</a>. Furthermore, we'll also share how the baggage propagators help us build a full picture of what is going on across the entire application by connecting backend traces with frontend signals. If you want to dive straight into the code, check out the repo <a href="https://github.com/carlyrichmond/otel-record-store">here</a>.</p>
<h2 id="applicationoverview">Application Overview</h2>
<p>The application that we use for this blog is called <a href="https://github.com/carlyrichmond/otel-record-store">OTel Record Store</a>, a simple web application written with Svelte and JavaScript (albeit our implementation is compatible with other web frameworks), communicating with a Java backend. Both send telemetry signals to an Elastic backend.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt02b90450407c16cd/6a7f1c6f77b03459aa3ff943/1-otel-frontend-sample-architecture.png" alt="Architecture" /></p>
<p>Eagle-eyed readers will noticed that signals from our frontend pass through a proxy and collector. The proxy is required to ensure that the appropriate Cross-Origin headers are populated to allow the signals to pass into Elastic, as well as the traditional reasons such as security, privacy and access control: </p>
<pre><code>events {}

http {

  server {

    listen 8123; 

    # Traces endpoint exposed as example, others available in code repo
    location /v1/traces {
      proxy_pass http://host.docker.internal:4318;
      # Apply CORS headers to ALL responses, including POST
      add_header 'Access-Control-Allow-Origin' 'http://localhost:4173' always;
      add_header 'Access-Control-Allow-Methods' 'POST, OPTIONS' always;
      add_header 'Access-Control-Allow-Headers' 'Content-Type' always;
      add_header 'Access-Control-Allow-Credentials' 'true' always;

      # Preflight requests receive a 204 No Content response
      if ($request_method = OPTIONS) {
        return 204;
      }
    }
  }
}
</code></pre>
<p>While collectors can also be used to add headers, we have left this example to perform traditional tasks such as routing and processing.</p>
<h2 id="prerequisites">Prerequisites</h2>
<p>This example requires an Elastic cluster, run either locally via <a href="https://github.com/elastic/start-local">start-local</a>, via Elastic Cloud or Serverless. Here we use the Managed OLTP endpoint in Elastic Serverless. Any mechanism requires you to specify several key environment variables, listed in the <a href="https://github.com/carlyrichmond/otel-record-store/blob/main/.env-example">.env-example file</a>:</p>
<pre><code>ELASTIC_ENDPOINT=https://my-elastic-endpoint:443
ELASTIC_API_KEY=my-api-key
</code></pre>
<h3 id="runningtheapplication">Running the application</h3>
<p>To run our example, follow the steps in the <a href="https://github.com/carlyrichmond/otel-record-store/blob/main/README.md">project README</a>, summarized below:</p>
<pre><code># Terminal 1: backend service, proxy and collector
docker-compose build
docker-compose up

# Terminal 2: frontend and sample telemetry data
cd records-ui
npm install
npm run generate
</code></pre>
<h2 id="javabackendinstrumentation">Java Backend Instrumentation</h2>
<p>We will not cover the specifics of instrumentation of Java services with EDOT as there is already a great guide to get started <a href="https://github.com/elastic/elastic-otel-java">in the <code>elastic-otel-java</code> README</a>. The example is here purely for showcasing propagation that is important for investigating UI issues. All you need to know is that we make use of automatic instrumentation, sending logs, metrics and traces via <a href="https://opentelemetry.io/docs/specs/otel/protocol/">OpenTelemetry Protocol, or OTLP</a> using the below environment variables:</p>
<pre><code>OTEL_RESOURCE_ATTRIBUTES=service.version=1,deployment.environment=dev
OTEL_SERVICE_NAME=record-store-server-java
OTEL_EXPORTER_OTLP_ENDPOINT=$ELASTIC_ENDPOINT
OTEL_EXPORTER_OTLP_HEADERS="Authorization=ApiKey ${ELASTIC_API_KEY}"
OTEL_TRACES_EXPORTER=otlp
OTEL_METRICS_EXPORTER=otlp
OTEL_LOGS_EXPORTER=otlp
</code></pre>
<p>The instrumentation is then initialized using the <code>-javaagent</code> option:</p>
<pre><code>ENV JAVA_TOOL_OPTIONS="-javaagent:./elastic-otel-javaagent-1.2.1.jar"
</code></pre>
<h2 id="clientinstrumentation">Client Instrumentation</h2>
<p>Now that we have established our prerequisites, let's dive into the instrumentation code for our simple web application. Although we'll cover the implementation in sections, the full solution is available <a href="https://github.com/carlyrichmond/otel-record-store/blob/main/records-ui/src/lib/telemetry/frontend.tracer.ts">here in <code>frontend.tracer.ts</code></a>.</p>
<h3 id="stateofotelclientinstrumentation">State of OTel Client Instrumentation</h3>
<p>At time of writing, the <a href="https://opentelemetry.io/docs/languages/js/">OpenTelemetry JavaScript SDK</a> has stable support for metrics and traces, with logs currently under development and therefore subject to breaking changes <a href="https://opentelemetry.io/docs/languages/js/">as listed in their documentation</a>:</p>
<p>| Traces | Metrics | Logs        |
| ------ | ------- | ----------- |
| Stable | Stable     | Development |</p>
<p>What differs from many other SDKs is the note warning that client instrumentation for the browser is experimental and mostly unspecified. It is subject to breaking change, and many pieces such as plugin support for measuring Google Core Web Vitals are in progress as reflected in the <a href="https://github.com/orgs/open-telemetry/projects/19/views/1">Client Instrumentation SIG project board</a>. In subsequent sections we'll show examples for signal capture, and also browser specific instrumentations including document load, user interaction and Core Web Vitals capture.</p>
<h3 id="resourcedefinition">Resource Definition</h3>
<p>When instrumenting web UIs, we need to establish our UI as an OpenTelemetry <a href="https://opentelemetry.io/docs/languages/js/resources/">Resource</a>. By definition, resources are entites that produce telemetry information. We want to see our UI as an entity in our system that interacts with other entities, which can be specified using the following code:</p>
<pre><code>// Defines a Resource to include metadata like service.name, required by Elastic
import { resourceFromAttributes, detectResources } from '@opentelemetry/resources';

// Experimental detector for browser environment
import { browserDetector } from '@opentelemetry/opentelemetry-browser-detector';

// Provides standard semantic keys for attributes, like service.name
import { ATTR_SERVICE_NAME } from '@opentelemetry/semantic-conventions';

const detectedResources = detectResources({ detectors: [browserDetector] });
let resource = resourceFromAttributes({
    [ATTR_SERVICE_NAME]: 'records-ui-web',
    'service.version': 1,
    'deployment.environment': 'dev'
});
resource = resource.merge(detectedResources);
</code></pre>
<p>A unique identifier for the service is required, and is common to all SDKs. What differs from other implementations is the inclusion of the <a href="https://www.npmjs.com/package/@opentelemetry/opentelemetry-browser-detector"><code>browserDetector</code></a> which, when merged with our defined resource attributes adds browser attributes such as platform, brands (e.g. Chrome versus Edge) and whether a mobile browser is being used:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte1476364f28a7b6c/6a7f1c7233fa8a4c8b202bbf/2-otel-browser-attributes.png" alt="Sample Span JSON with Resource and Browser Attributes" /></p>
<p>Having this information on spans and errors is useful in diagnostic situations in identifying application and dependency compatibility issues with certain browsers (such as Internet Explorer from my time as an engineer 🤦).</p>
<h3 id="logs">Logs</h3>
<p>Traditionally, frontend engineers rely on the DevTools console of their favourite browser to examine logs. With UI log messages only being accessible within your browser rather than forwarded to a file somewhere, which is the common pattern with backend services, we lose visibility of this resource when triaging user issues. </p>
<p>OpenTelemetry defines the concept of an <a href="https://opentelemetry.io/docs/concepts/signals/logs/#log-record-exporter">exporter</a> that allow us to send signals to a particular destination, such as logs.</p>
<pre><code>// Get logger and severity constant imports
import { logs, SeverityNumber } from '@opentelemetry/api-logs';

// Provider and batch processor for sending logs
import { BatchLogRecordProcessor, LoggerProvider } from '@opentelemetry/sdk-logs';

// Export logs via OTLP
import { OTLPLogExporter } from '@opentelemetry/exporter-logs-otlp-http';

// Configure logging to send to the collector via nginx
const logExporter = new OTLPLogExporter({
    url: 'http://localhost:8123/v1/logs' // nginx proxy
});

const loggerProvider = new LoggerProvider({
    resource: resource, // see resource initialisation above
    processors: [new BatchLogRecordProcessor(logExporter)]
});

logs.setGlobalLoggerProvider(loggerProvider);
</code></pre>
<p>Once the provider has been initialized, we need to get a hold of the logger to send our traces to Elastic rather than using good ol' <code>console.log('Help!')</code>:</p>
<pre><code>// Example gets logger and sends a message to Elastic
const logger = logs.getLogger('default', '1.0.0');
logger.emit({
    severityNumber: SeverityNumber.INFO,
    severityText: 'INFO',
    body: 'Logger initialized'
});
</code></pre>
<p>They will now be visible in Discover and the Logs views, allowing us to search for relevant outages as part of investigations and incidents:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt10bfba64e82f4568/6a7f1c75c2e9141b34017032/3-otel-log-discover.png" alt="Sample Logs in Discover" /></p>
<h3 id="traces">Traces</h3>
<p>The power of traces in diagnosing issues in the UI is in the visibility of not just what is going on within the web application, but seeing the connections and time taken to make calls to the labyrinth of services behind. To instrument a web-based application, we need to make use of the <code>WebTraceProvider</code> using the <code>OTLPTraceExporter</code> in a similar way to how exporters work for logs and metrics:</p>
<pre><code>/* Packages for exporting traces */

// Import the WebTracerProvider, which is the core provider for browser-based tracing
import { WebTracerProvider } from '@opentelemetry/sdk-trace-web';

// BatchSpanProcessor forwards spans to the exporter in batches to prevent flooding
import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base';

// Import the OTLP HTTP exporter for sending traces to the collector over HTTP
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';

// Configure the OTLP exporter to talk to the collector via nginx
const exporter = new OTLPTraceExporter({
    url: 'http://localhost:8123/v1/traces' // nginx proxy
});

// Instantiate the trace provider and inject the resource
const provider = new WebTracerProvider({
    resource: resource,
    spanProcessors: [
        // Send each completed span through the OTLP exporter
        new BatchSpanProcessor(exporter)
    ]
});
</code></pre>
<p>Next we need to register our provider. One thing that's slightly different in the web world is how we configure propagation. <a href="https://opentelemetry.io/docs/concepts/context-propagation/">Context propagation</a> in OpenTelemetry refers to the concept of moving context between services and processes which, in our case, allows us to correlate the web signals with those of backend services. Often this is done automatically. As you will see from the below snippet, there are 3 concepts that help us with propagation:</p>
<pre><code>// This context manager ensures span context is maintained across async boundaries in the browser
import { ZoneContextManager } from '@opentelemetry/context-zone';

// Context Propagation across signals
import {
    CompositePropagator,
    W3CBaggagePropagator,
    W3CTraceContextPropagator
} from '@opentelemetry/core';

// Provider instantiation code omitted

// Register the provider with propagation and set up the async context manager for spans
provider.register({
    contextManager: new ZoneContextManager(),
    propagator: new CompositePropagator({
        propagators: [new W3CBaggagePropagator(), new W3CTraceContextPropagator()]
    })
});
</code></pre>
<p>The first is the <code>ZoneContextManager</code> which propagates context such as spans and traces across asynchronous operations. Web developers will be familiar with <a href="https://www.npmjs.com/package/zone.js?activeTab=readme">zone.js</a>, the framework used by many JS frameworks to provide an execution context that persists across async tasks.</p>
<p>Additionally, we have combined the <code>W3CBaggagePropagator</code> and <code>W3CTraceContextPropagator</code> using the <code>CompositePropagator</code> to ensure key value pair attributes are passed between signals as per the <a href="https://w3c.github.io/baggage/">W3C specification defined here</a>. In the case of the <code>W3CTraceContextPropagator</code>, it allows the propagation of the <code>traceparent</code> and <code>tracestate</code> HTTP headers as per the <a href="https://www.w3.org/TR/trace-context-2/">specification located here</a>.</p>
<h4 id="autoinstrumentation">Auto Instrumentation</h4>
<p>The simplest way to start instrumenting a web application is to register the web auto-instrumentations. At time of writing <a href="https://github.com/open-telemetry/opentelemetry-js-contrib/tree/main/packages/auto-instrumentations-web#readme">the documentation</a> states that the following instrumentations can be configured via this approach:</p>
<ol>
<li><a href="https://www.npmjs.com/package/@opentelemetry/instrumentation-document-load">@opentelemetry/instrumentation-document-load</a></li>
<li><a href="https://www.npmjs.com/package/@opentelemetry/instrumentation-fetch">@opentelemetry/instrumentation-fetch</a></li>
<li><a href="https://www.npmjs.com/package/@opentelemetry/instrumentation-user-interaction">@opentelemetry/instrumentation-user-interaction</a></li>
<li><a href="https://www.npmjs.com/package/@opentelemetry/instrumentation-xml-http-request">@opentelemetry/instrumentation-xml-http-request</a></li>
</ol>
<p>Configuration for each configuration can be passed as configuration to <code>registerInstrumentations</code> as shown in the below example configuring the fetch and XMLHTTPRequest instrumentations:</p>
<pre><code>// Used to auto-register built-in instrumentations
import { registerInstrumentations } from '@opentelemetry/instrumentation';

// Import the auto-instrumentations for web, which includes common libraries, frameworks and document load
import { getWebAutoInstrumentations } from '@opentelemetry/auto-instrumentations-web';

// Enable automatic span generation for document load and user click interactions
registerInstrumentations({
  instrumentations: [
    getWebAutoInstrumentations({
      '@opentelemetry/instrumentation-fetch': {
        propagateTraceHeaderCorsUrls: /.*/,
        clearTimingResources: true
        },
        '@opentelemetry/instrumentation-xml-http-request': {
          propagateTraceHeaderCorsUrls: /.*/
          }
      })
    ]
});
</code></pre>
<p>Taking the @opentelemetry/instrumentation-fetch instrumentation as an example, we are able to see traces for HTTP requests, and the propagators also ensure that the spans can connect with our Java backend services to give a full picture of the amount of time taken to process the request at each stage:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt48b6284177039351/6a7f1c78de23157d77fd80d7/4-otel-http-get-sample-trace.png" alt="Sample HTTP GET Trace" /></p>
<p>While auto-instrumentations is agreat way to get common instrumentations, we can also instantiate instrumentations directly, as we'll see in the remainder of this article.</p>
<h4 id="documentloadinstrumentation">Document Load Instrumentation</h4>
<p>Another consideration unique to web frontend is the time taken to load assets such as images, JavaScript files and even stylesheets. Such assets taking considerable time to load can impact metrics such as <a href="https://web.dev/articles/fcp">First Contentful Paint</a>, and therefore the user experience. The <a href="https://www.npmjs.com/package/@opentelemetry/instrumentation-document-load">OTel Document Load instrumentation</a> allows for automatic instrumentation of the time taken to load assets when using the <a href="https://www.npmjs.com/package/@opentelemetry/sdk-trace-web">@opentelemetry/sdk-trace-web</a> package.</p>
<p>It is simply a case of adding the instrumentation to the <code>instrumentations</code> array we have provided to our provider using <code>registerInstrumentations</code>:</p>
<pre><code>// Used to auto-register built-in instrumentations like page load and user interaction
import { registerInstrumentations } from '@opentelemetry/instrumentation';

// Document Load Instrumentation automatically creates spans for document load events
import { DocumentLoadInstrumentation } from '@opentelemetry/instrumentation-document-load';

// Configuration discussed above omitted

// Enable automatic span generation for document load and user click interactions
registerInstrumentations({
  instrumentations: [
    // Automatically tracks when the document loads
    new DocumentLoadInstrumentation({
      ignoreNetworkEvents: false,
      ignorePerformancePaintEvents: false
      }),
      // Other instrumentations omitted
  ]
});
</code></pre>
<p>This configuration will create a new trace conventiently named <code>documentLoad</code>, that will show us the time taken to load resources within the document, similar to the following:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta9c0a10fd57e6fe8/6a7f1c7b73d9bd60ec29dfab/5-otel-document-load-example-trace.png" alt="Sample &lt;code&gt;documentLoad&lt;/code&gt; Trace" /></p>
<p>Each span will have metadata attached to help us identify which resources are taking considerable time to load, such as this image example, where the resource takes <strong>837ms</strong> to load:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt89e403e808045a99/6a7f1c7f227b1c8761598a27/6-otel-document-load-http-url-metadata.png" alt="&lt;code&gt;documentLoad&lt;/code&gt; Trace Metadata" /></p>
<h4 id="clickevents">Click Events</h4>
<p>You may wonder why we want to capture user interactions with web applications for diagnostic purposes. Being able to see the trigger points for errors can be useful in incidents to establish a timeline of what happened, and determine if users are indeed being impact as is the case for Real Ueer Monitoring tools. But if we also consider the field of Digital Experience Monitoring, or DEM, software teams need details on usage of application features to understand the user journey and how it could possibly being improved in a data-drive way. Capturing user events is required for both.</p>
<p>The <a href="https://www.npmjs.com/package/@opentelemetry/instrumentation-user-interaction">OTel UserInteraction instrumentation for web</a> is how we capture these events. Similar to the document load instrumentation it depends on the <a href="https://www.npmjs.com/package/@opentelemetry/sdk-trace-web">@opentelemetry/sdk-trace-web</a> package, and when used with <code>zone-js</code> and the <code>ZoneContextManager</code> it also supports async operations.</p>
<p>Like other instrumentations it is added via <code>registerInstrumentations</code>:</p>
<pre><code>// Used to auto-register built-in instrumentations like page load and user interaction
import { registerInstrumentations } from '@opentelemetry/instrumentation';

// Automatically creates spans for user interactions like clicks
import { UserInteractionInstrumentation } from '@opentelemetry/instrumentation-user-interaction';

// Configuration discussed above omitted

// Enable automatic span generation for document load and user click interactions
registerInstrumentations({
  instrumentations: [
    // User events
    new UserInteractionInstrumentation({
      eventNames: ['click', 'input'] // instrument click and input events only
    }),
    // Other instrumentations omitted
  ]
});
</code></pre>
<p>It will capture and label spans for the user events we configure, and leveraging the propagators configured previously can connect spans from other resources to the user event, similar to the below example where we see the service call to get records when the user adds a search term to the <code>input</code> box:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt13ac18437db9cdc4/6a7f1c819090b0032384ee83/7-otel-user-interaction-input-sample-trace.png" alt="User Interaction &lt;code&gt;input&lt;/code&gt; Sample Trace" /></p>
<h3 id="metrics">Metrics</h3>
<p>There are numerous different measurements that are helpful in capturing useful indicators of availability and performace of web applications, such as latency, throughput or the number of 404 errors. <a href="https://developers.google.com/search/docs/appearance/core-web-vitals">Google Core Web Vitals</a> are a set of standard metrics used by web developers to measure real-world user experience of web sites, including loading performance, reactivity to user input and visual stability. Given at time of writing <a href="https://github.com/open-telemetry/opentelemetry-js-contrib/issues/1461">the Core Web Vitals Plugin for OTel Browser is on the backlog</a>, let's try building our own custom instrumentation using the <a href="https://www.npmjs.com/package/web-vitals">web-vitals JS library</a> to capture these as <a href="https://opentelemetry.io/docs/concepts/signals/metrics/">OTel metrics</a>.</p>
<p>In OpenTelemetry you can create your own custom instrumentation by extending the <code>InstrumentationBase</code>, overriding the <code>constructor</code> to create the <code>MeterProvider</code>, <code>Meter</code> and <code>OTLPMetricExporter</code> that will allow us to send our Core Web Vital measurements to Elastic via our proxy, as presented in <a href="https://github.com/carlyrichmond/otel-record-store/blob/main/records-ui/src/lib/telemetry/web-vitals.instrumentation.ts"><code>web-vitals.instrumentation.ts</code></a>. Note that below we show only the LCP meter for succinctness, but the full example <a href="https://github.com/carlyrichmond/otel-record-store/blob/main/records-ui/src/lib/telemetry/web-vitals.instrumentation.ts">here</a> measures all web vitals.</p>
<pre><code>/* OpenTelemetry JS packages */
// Instrumentation base to create a custom Instrumentation for our provider
import {
    InstrumentationBase,
    type InstrumentationConfig,
    type InstrumentationModuleDefinition
} from '@opentelemetry/instrumentation';

// Metrics API
import {
    metrics,
    type ObservableGauge,
    type Meter,
    type Attributes,
    type ObservableResult,

} from '@opentelemetry/api';

export class WebVitalsInstrumentation extends InstrumentationBase {

  // Meter captures measurements at runtime
    private cwvMeter: Meter;

    /* Core Web Vitals Measures, LCP provided, others omitted */
    private lcp: ObservableGauge;

    constructor(config: InstrumentationConfig, resource: Resource) {
        super('WebVitalsInstrumentation', '1.0', config);

    // Create metric reader to process metrics and export using OTLP
        const metricReader = new PeriodicExportingMetricReader({
            exporter: new OTLPMetricExporter({
                url: 'http://localhost:8123/v1/metrics' // nginx proxy
            }),
            // Default is 60000ms (60 seconds).
            // Set to 10 seconds for demo purposes only.
            exportIntervalMillis: 10000
        });

    // Creating Meter Provider factory to send metrics
        const myServiceMeterProvider = new MeterProvider({
            resource: resource,
            readers: [metricReader]
        });
        metrics.setGlobalMeterProvider(myServiceMeterProvider);

    // Create web vitals meter
        this.cwvMeter = metrics.getMeter('core-web-vitals', '1.0.0');

        // Initialising CWV metric gauge instruments (LCP given as example, others omitted here)
        this.lcp = this.cwvMeter.createObservableGauge('lcp', { unit: 'ms', description: 'Largest Contentful Paint' });
    }

    protected init(): InstrumentationModuleDefinition | InstrumentationModuleDefinition[] | void {}

  // Other steps discussed later
}
</code></pre>
<p>You'll notice in our LCP example we have created an <code>ObservableGauge</code> to capture the value at the time it is read via a callback function. This can be setup when we <code>enable</code> our custom instrumentation, specifying when the LCP event is triggered the value will be sent via <code>result.observe</code>:</p>
<pre><code>/* Web Vitals Frontend package, LCP shown as example*/
import { onLCP, type LCPMetric } from 'web-vitals';

/* OpenTelemetry JS packages */
// Instrumentation base to create a custom Instrumentation for our provider
import {
    InstrumentationBase,
    type InstrumentationConfig,
    type InstrumentationModuleDefinition
} from '@opentelemetry/instrumentation';

// Metrics API
import {
    metrics,
    type ObservableGauge,
    type Meter,
    type Attributes,
    type ObservableResult,

} from '@opentelemetry/api';

// Other OTel Metrics imports omitted

// Time calculator via performance component
import { hrTime } from '@opentelemetry/core';

type CWVMetric = LCPMetric | CLSMetric | INPMetric | TTFBMetric | FCPMetric;

export class WebVitalsInstrumentation extends InstrumentationBase {

    /* Core Web Vitals Measures */
    private lcp: ObservableGauge;

    // Constructor and Initialization omitted

    enable() {
        // Capture Largest Contentful Paint, other vitals omitted
        onLCP(
            (metric) =&gt; {
                this.lcp.addCallback((result) =&gt; {
                    this.sendMetric(metric, result);
                });
            },
            { reportAllChanges: true }
        );
    }

  // Callback utility to add attributes and send captured metric
    private sendMetric(metric: CWVMetric, result: ObservableResult&lt;Attributes&gt;): void {
        const now = hrTime();

        const attributes = {
            startTime: now,
            'web_vital.name': metric.name,
            'web_vital.id': metric.id,
            'web_vital.navigationType': metric.navigationType,
            'web_vital.delta': metric.delta,
            'web_vital.value': metric.value,
            'web_vital.rating': metric.rating,
            // metric specific attributes
            'web_vital.entries': JSON.stringify(metric.entries)
        };

        result.observe(metric.value, attributes);
    }
}
</code></pre>
<p>To use our own instrumentation, we need to register our instrumentation just like we did in <code>frontend.tracer.ts</code> for the available web instrumentations to capture document and user event instrumentations:</p>
<pre><code>registerInstrumentations({
  instrumentations: [
    // Other web instrumentations omitted
    // Custom Web Vitals instrumentation
    new WebVitalsInstrumentation({}, resource)
    ]
});
</code></pre>
<p>The <code>lcp</code> metric, along with the attributes we specified as part of our <code>sendMetric</code> function will be sent to our Elastic cluster:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf31d8c484a66c726/6a7f1c84bdcff03bacc43311/8-otel-metric-elastic-discover-view.png" alt="LCP Metric in Discover" /></p>
<p>These metrics will not feed into the <a href="https://www.elastic.co/docs/solutions/observability/applications/user-experience">User Experience dashboard</a> due to compatibility, but we can create a dashboard leveraging the values to show the trends of each of our vitals:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt07ad8a1b10b97772/6a7f1c88c2e9141c12017038/9-otel-core-web-vitals-dashboard.png" alt="Sample Core Web Vitals Dashboard" /></p>
<h2 id="summary">Summary</h2>
<p>In this blog, we presented the current state of client instrumentation for the browser, along with an example showing how to instrument a simple JavaScript frontend using <a href="https://opentelemetry.io/docs/languages/js/getting-started/browser/">the OpenTelemetry browser instrumentation</a>. To reflect back on the code, check out the repo <a href="https://github.com/carlyrichmond/otel-record-store">here</a>. If you have any questions or want to learn from other developers connect with the <a href="https://www.elastic.co/community">Elastic Community</a>.</p>
<blockquote>
  <p>Developer resources:</p>
  <ul>
  <li><a href="https://github.com/carlyrichmond/otel-record-store">OTel Record Store Application</a></li>
  <li><a href="https://opentelemetry.io/docs/languages/js/getting-started/browser/">JavaScript Browser Instrumentation</a></li>
  </ul>
</blockquote>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/web-frontend-instrumentation-with-opentelemetry</link>
    <guid isPermaLink="false">web-frontend-instrumentation-with-opentelemetry</guid>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[Infrastructure Monitoring]]></category>
    <dc:creator><![CDATA[Carly Richmond]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt99bb683acc7af2e6/6a7f1c8ae02fac60e05d69ff/web-blog-header.jpg" length="0" type="image/jpeg"/>
    <pubDate>Mon, 04 Aug 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[The next evolution of observability: unifying data with OpenTelemetry and generative AI]]></title>
    <description><![CDATA[Generative AI and machine learning are revolutionizing observability, but siloed data hinders their true potential. This article explores how to break down data silos by unifying logs, metrics, and traces with OpenTelemetry, unlocking the full power of GenAI for natural language investigations, automated root cause analysis, and proactive issue resolution.]]></description>
    <content:encoded><![CDATA[<p>The Observability industry today stands at a critical juncture. While our applications generate more telemetry data than ever before, this wealth of information typically exists in siloed tools, separate systems for logs, metrics, and traces. Meanwhile, Generative AI is hurtling toward us like an asteroid about to make a tremendous impact on our industry.</p>
<p>As SREs, we've grown accustomed to jumping between dashboards, log aggregators, and trace visualizers when troubleshooting issues. But what if there was a better way? What if AI could analyze all your observability data holistically, answering complex questions in natural language, and identifying root causes automatically?</p>
<p>This is the next evolution of observability. But to harness this power, we need to rethink how we collect, store, and analyze our telemetry data.</p>
<h2 id="theproblemsiloeddatalimitsaieffectiveness">The problem: siloed data limits AI effectiveness</h2>
<p>Traditional observability setups separate data into distinct types:</p>
<ul>
<li>Metrics: Numeric measurements over time (CPU, memory, request rates)</li>
<li>Logs: Detailed event records with timestamps and context</li>
<li>Traces: Request journeys through distributed systems</li>
<li>Profiles: Code-level execution patterns showing resource consumption and performance bottlenecks at the function/line level</li>
</ul>
<p>This separation made sense historically due to the way the industry evolved. Different data types have traditionally had different cardinality, structure, access patterns and volume characteristics. However, this approach creates significant challenges for AI-powered analysis:</p>
<pre><code>Metrics (Prometheus) → "CPU spiked at 09:17:00"
Logs (ELK) → "Exception in checkout service at 09:17:32" 
Traces (Jaeger) → "Slow DB queries in order-service at 09:17:28"
Profiles (pyroscope) -&gt; "calculate_discount() is taking 75% of CPU time"
</code></pre>
<p>When these data sources live in separate systems, AI tools must either:</p>
<ol>
<li>Work with an incomplete picture (seeing only metrics but not the related logs)</li>
<li>Rely on complex, brittle integrations that often introduce timing skew</li>
<li>Force developers to manually correlate information across tools</li>
</ol>
<p>Imagine asking an AI, "Why did checkout latency spike at 09:17?" To answer comprehensively, it needs access to logs (to see the stack trace), traces (to understand the service path), and metrics (to identify resource strain). With siloed tools, the AI either sees only fragments of the story or requires complex ETL jobs that are slower than the incident itself.</p>
<h2 id="whytraditionalmachinelearningmlfallsshort">Why traditional machine learning (ML) falls short</h2>
<p>Traditional machine learning for observability typically focuses on anomaly detection within a single data dimension. It can tell you when metrics deviate from normal patterns, but struggles to provide context or root cause.</p>
<p>ML models trained on metrics alone might flag a latency spike, but can't connect it to a recent deployment (found in logs) or identify that it only affects requests to a specific database endpoint (found in traces). They behave like humans with extreme tunnel vision, seeing only a fraction of the relevant information and only the information that a specific vendor has given you an opinionated view into.</p>
<p>This limitation becomes particularly problematic in modern microservice architectures where problems frequently cascade across services. Without a unified view, traditional ML can detect symptoms but struggles to identify the underlying cause.</p>
<h2 id="thesolutionunifieddatawithenrichedlogs">The solution: unified data with enriched logs</h2>
<p>The solution is conceptually simple but transformative: unify metrics, logs, and traces into a single data store, ideally with enriched logs that contain all signals about a request in a single JSON document. We're about to see a merging of signals.</p>
<p>Think of traditional logs as simple text lines:</p>
<pre><code>[2025-05-19 09:17:32] ERROR OrderService - Failed to process checkout for user 12345
</code></pre>
<p>Now imagine an enriched log that contains not just the error message, but also:</p>
<ul>
<li>The complete distributed trace context</li>
<li>Related metrics at that moment</li>
<li>System environment details</li>
<li>Business context (user ID, cart value, etc.)</li>
</ul>
<p>This approach creates a holistic view where every signal about the same event sits side-by-side, perfect for AI analysis.</p>
<h2 id="howgenerativeaichangesthings">How generative AI changes things</h2>
<p>Generative AI differs fundamentally from traditional ML in its ability to:</p>
<ol>
<li>Process unstructured data: Understanding free-form log messages and error text</li>
<li>Maintain context: Connecting related events across time and services</li>
<li>Answer natural language queries: Translating human questions into complex data analysis</li>
<li>Generate explanations: Providing reasoning alongside conclusions</li>
<li>Surface hidden patterns: Discovering correlations and anomalies in log data that would be impractical to find through manual analysis or traditional querying</li>
</ol>
<p>With access to unified observability data, GenAI can analyze complete system behavior patterns and correlate across previously disconnected signals.</p>
<p>For example, when asked "Why is our checkout service slow?" a GenAI model with access to unified data can:</p>
<ul>
<li>Analyze unified enriched logs to identify which specific operations are slow and to find errors or warnings in those components</li>
<li>Check attached metrics to understand resource utilization</li>
<li>Correlate all these signals with deployment events or configuration changes</li>
<li>Present a coherent explanation in natural language with supporting graphs and visualizations</li>
</ul>
<h2 id="implementingunifiedobservabilitywithopentelemetry">Implementing unified observability with OpenTelemetry</h2>
<p>OpenTelemetry provides the perfect foundation for unified observability with its consistent schema across metrics, logs, and traces. Here's how to implement enriched logs in a Java application:</p>
<pre><code>import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.metrics.Meter;
import io.opentelemetry.api.metrics.DoubleHistogram;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.context.Scope;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import java.lang.management.ManagementFactory;
import java.lang.management.OperatingSystemMXBean;

public class OrderProcessor {
    private static final Logger logger = LoggerFactory.getLogger(OrderProcessor.class);
    private final Tracer tracer;
    private final DoubleHistogram cpuUsageHistogram;
    private final OperatingSystemMXBean osBean;

    public OrderProcessor(OpenTelemetry openTelemetry) {
        this.tracer = openTelemetry.getTracer("order-processor");
        Meter meter = openTelemetry.getMeter("order-processor");
        this.cpuUsageHistogram = meter.histogramBuilder("system.cpu.load")
                                      .setDescription("System CPU load")
                                      .setUnit("1")
                                      .build();
        this.osBean = ManagementFactory.getOperatingSystemMXBean();
    }

    public void processOrder(String orderId, double amount, String userId) {
        Span span = tracer.spanBuilder("processOrder").startSpan();
        try (Scope scope = span.makeCurrent()) {
            // Add attributes to the span
            span.setAttribute("order.id", orderId);
            span.setAttribute("order.amount", amount);
            span.setAttribute("user.id", userId);
            // Populate MDC for structured logging
            MDC.put("trace_id", span.getSpanContext().getTraceId());
            MDC.put("span_id", span.getSpanContext().getSpanId());
            MDC.put("order_id", orderId);
            MDC.put("order_amount", String.valueOf(amount));
            MDC.put("user_id", userId);
            // Record CPU usage metric associated with the current trace context
            double cpuLoad = osBean.getSystemLoadAverage();
            if (cpuLoad &gt;= 0) {
                cpuUsageHistogram.record(cpuLoad);
                MDC.put("cpu_load", String.valueOf(cpuLoad));
            }
            // Log a structured message
            logger.info("Processing order");
            // Simulate business logic
            // ...
            span.setAttribute("order.status", "completed");
            logger.info("Order processed successfully");
        } catch (Exception e) {
            span.recordException(e);
            span.setAttribute("order.status", "failed");
            logger.error("Order processing failed", e);
        } finally {
            MDC.clear();
            span.end();
        }
    }
}
</code></pre>
<p>This code demonstrates how to:</p>
<ol>
<li>Create a span for the operation</li>
<li>Add business attributes</li>
<li>Add current CPU usage</li>
<li>Link everything with consistent IDs</li>
<li>Record exceptions and outcomes in the backend system</li>
</ol>
<p>When configured with an appropriate exporter, this creates enriched logs that contain both application events and their complete context.</p>
<h2 id="powerfulqueriesacrosspreviouslyseparatedata">Powerful queries across previously separate data</h2>
<p>With data that has not yet been enriched, there is still hope. Firstly with GenAI powered ingestion it is possible to extract key fields to help correlate data such as a session id's. This will help you enrich your logs so they get the structure they need to behave like other signals. Below we can see Elastic's Auto Import mechanism that will automatically generate ingest pipelines and pull unstructured information from logs into a structured format perfect for analytics.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt49ea53cd2cb13c82/6a7f1b8cea068d2deaf0a2df/image4.png" alt="" /></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt51470fc777215fdd/6a7f1b8f6c6eac7075f145bd/image2.png" alt="" /></p>
<p>Once you have this data in the same data store, you can perform powerful join queries that were previously impossible. For example, finding slow database queries that affected specific API endpoints:</p>
<pre><code>FROM logs-nginx.access-default 
| LOOKUP JOIN .ds-logs-mysql.slowlog-default-2025.05.01-000002 ON request_id 
| KEEP request_id, mysql.slowlog.query, url.query 
| WHERE mysql.slowlog.query IS NOT NULL
</code></pre>
<p>This query joins web server logs with database slow query logs, allowing you to directly correlate user-facing performance with database operations.</p>
<p>For GenAI interfaces, these complex queries can be generated automatically from natural language questions:</p>
<p>"Show me all checkout failures that coincided with slow database queries"</p>
<p>The AI translates this into appropriate queries across your unified data store, correlating application errors with database performance.</p>
<h2 id="realworldapplicationsandusecases">Real-world applications and use cases</h2>
<h3 id="naturallanguageinvestigation">Natural language investigation</h3>
<p>Imagine asking your observability system:</p>
<p>"Why did checkout latency spike at 09:17 yesterday?"</p>
<p>A GenAI-powered system with unified data could respond:</p>
<p>"Checkout latency increased by 230% at 09:17:32 following deployment v2.4.1 at 09:15. The root cause appears to be increased MySQL query times in the inventory-service. Specifically, queries to the 'product_availability' table are taking an average of 2300ms compared to the normal 95ms. This coincides with a CPU spike on database host db-03 and 24 'Lock wait timeout' errors in the inventory service logs."</p>
<p>Here's an example of Claude Desktop connected to <a href="https://github.com/elastic/mcp-server-elasticsearch">Elastic's MCP (Model Context Protocol) Server</a> which demonstrates how powerful natural language investigations can be. Here we ask Claude "analyze my web traffic patterns" and as you can see it has correctly identified that this is in our demo environment.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5f22b476cc3e5cb4/6a7f1b9263e959e91b73e281/image3.png" alt="" /></p>
<h3 id="unknownproblemdetection">Unknown problem detection</h3>
<p>GenAI can identify subtle patterns by correlating signals that would be missed in siloed systems. For example, it might notice that a specific customer ID appears in error logs only when a particular network path is taken through your microservices—indicating a data corruption issue affecting only certain user flows.</p>
<h3 id="predictivemaintenance">Predictive maintenance</h3>
<p>By analyzing the unified historical patterns leading up to previous incidents, GenAI can identify emerging problems before they cause outages:</p>
<p>"Warning: Current load pattern on authentication-service combined with increasing error rates in user-profile-service matches 87% of the signature that preceded the April 3rd outage. Recommend scaling user-profile-service pods immediately."</p>
<h2 id="thefutureagenticaiforobservability">The future: agentic AI for observability</h2>
<p>The next frontier is agentic AI, systems that not only analyze but take action automatically.</p>
<p>These AI agents could:</p>
<ol>
<li>Continuously monitor all observability signals</li>
<li>Autonomously investigate anomalies</li>
<li>Implement fixes for known patterns</li>
<li>Learn from the effectiveness of previous interventions</li>
</ol>
<p>For example, an observability agent might:</p>
<ul>
<li>Detect increased error rates in a service</li>
<li>Analyze logs and traces to identify a memory leak</li>
<li>Correlate with recent code changes</li>
<li>Increase the memory limit temporarily</li>
<li>Create a detailed ticket with the root cause analysis</li>
<li>Monitor the fix effectiveness</li>
</ul>
<p>This is about creating systems that understand your application's behavior patterns deeply enough to maintain them proactively. See how this works in Elastic Observability, in the screenshot at the end of the RCA we are sending an email summary but this could trigger any action.  </p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd7066e2bb8f06113/6a7f1b959090b0011b84ee37/image1.png" alt="" /></p>
<h2 id="businessoutcomes">Business outcomes</h2>
<p>Unifying observability data for GenAI analysis delivers concrete benefits:</p>
<ul>
<li>Faster resolution times: Problems that previously required hours of manual correlation can be diagnosed in seconds</li>
<li>Fewer escalations: Junior engineers can leverage AI to investigate complex issues before involving specialists</li>
<li>Improved system reliability: Earlier detection and resolution of emerging issues</li>
<li>Better developer experience: Less time spent context-switching between tools</li>
<li>Enhanced capacity planning: More accurate prediction of resource needs</li>
</ul>
<h2 id="implementationsteps">Implementation steps</h2>
<p>Ready to start your observability transformation? Here's a practical roadmap:</p>
<ol>
<li>Adopt OpenTelemetry: Standardize on OpenTelemetry for all telemetry data collection and use it to generate enriched logs.</li>
<li>Choose a unified storage solution: Select a platform that can efficiently store and query metrics, logs, traces and enriched logs together</li>
<li>Enrich your telemetry: Update application instrumentation to include relevant context</li>
<li>Create correlation IDs: Ensure every request has identifiers</li>
<li>Implement semantic conventions: Follow consistent naming patterns across your telemetry data</li>
<li>Start with focused use cases: Begin with high-value scenarios like checkout flows or critical APIs</li>
<li>Leverage GenAI tools: Integrate tools that can analyze your unified data and respond to natural language queries</li>
</ol>
<p>Remember, AI can only be as smart as the data you feed it. The quality and completeness of your telemetry data will determine the effectiveness of your AI-powered observability.</p>
<h2 id="generativeaianevolutionarycatalystforobservability">Generative AI: an evolutionary catalyst for observability</h2>
<p>The unification of observability data for GenAI analysis represents an evolutionary leap forward comparable to the transition from Internet 1.0 to 2.0. Early adopters will gain a significant competitive advantage through faster problem resolution, improved system reliability, and more efficient operations. GAI is a huge step for increasing observability maturity and moving your team to a more proactive stance.</p>
<p>Think of traditional observability as a doctor trying to diagnose a patient while only able to see their heart rate. Unified observability with GenAI is like giving that doctor a complete health picture, vital signs, lab results, medical history, and genetic data all accessible through natural conversation.</p>
<p>As SREs, we stand at the threshold of a new era in system observability. The asteroid of GenAI isn't a threat to be feared, it's an opportunity to evolve our practices and tools to build more reliable, understandable systems. The question isn't whether this transformation will happen, but who will lead it.</p>
<p>Will you?</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/the-next-evolution-of-observability-unifying-data-with-opentelemetry-and-generative-ai</link>
    <guid isPermaLink="false">the-next-evolution-of-observability-unifying-data-with-opentelemetry-and-generative-ai</guid>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[Agentic Observability]]></category>
    <category><![CDATA[Machine Learning]]></category>
    <dc:creator><![CDATA[David Hope]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltccc14cece0d58b74/6a7f1b99bdcff0587cc432c3/title.png" length="0" type="image/png"/>
    <pubDate>Wed, 11 Jun 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Pivoting Elastic's Data Ingestion to OpenTelemetry]]></title>
    <description><![CDATA[Elastic has fully embraced OpenTelemetry as the backbone of its data ingestion strategy, aligning with the open-source community and contributing to make it the best data collection platform for a broad user base. This move benefits users by providing enhanced flexibility, efficiency, and control over telemetry data.]]></description>
    <content:encoded><![CDATA[<p>Elastic has fully embraced OpenTelemetry as the backbone of its data ingestion strategy, aligning with the open-source community and contributing to make it the best data collection platform for a broad user base. This move benefits users by providing enhanced flexibility, efficiency, and control over telemetry data.</p>
<h2 id="whyopentelemetry">Why OpenTelemetry?</h2>
<p>OpenTelemetry provides a powerful set of capabilities that make it a compelling choice for open-source-focused users. Elastic is re-architecting its data ingest tools around OpenTelemetry to offer users vendor-agnostic flexibility, performance optimization through OTel's efficient data model for correlating telemetry, and enhanced flexibility and control over data pipelines. This move brings the benefits of open-source telemetry to Elastic users.</p>
<p>Elastic engineers are active contributors to the Otel project in several areas of the project. Demonstrating its commitment to open source, Elastic continues to make significant <a href="https://opentelemetry.devstats.cncf.io/d/5/companies-table?orgId=1%5C&amp;var-period_name=Last%20year&amp;var-metric=contributions">contributions to OpenTelemetry</a>.</p>
<h2 id="opentelemetryasthecoreofelasticsdataingestion">OpenTelemetry as the Core of Elastic's Data Ingestion</h2>
<p>Elastic is transforming its data ingestion strategy by basing all ingestion mechanisms on the OpenTelemetry components. Elastic currently supports the following OTel based ingest architecture, which support OTel SDKs and Collectors from OTel or Elastic's Distribution of OpenTelemetry (EDOT). </p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt274ee07c460ad240/6a7f06003ce8e286bdcf50d4/edot-components.png" alt="EDOT components" /></p>
<p>This marks a fundamental shift, ensuring a more standardized and scalable telemetry pipeline. All the existing Elastic ingest components will become OTel based.</p>
<p>|                              |                                                                                                 |
| ---------------------------- | ----------------------------------------------------------------------------------------------- |
| <strong>Beats</strong>                    | Beats architecture will be based on OTel.                                                       |
| <strong>Elastic Agent</strong>            | Agent architecture will be based on OTel to support both beats based inputs and OTel receivers. |
| <strong>Integrations</strong>             | Integrations catalogue will additionally include OTel based modules for ease of configuration.  |
| <strong>Fleet central management</strong> | Fleet will support monitoring of Elastic OTel collectors.                                       |</p>
<p>Let's discuss how each component of Elastic's data ingestion platform will be based on an OpenTelemetry collector whilst still providing the same functionality to the user.</p>
<h3 id="beats">Beats</h3>
<p>Elastic's traditional data shippers will be re-architected as OpenTelemetry Collectors, aligning with OTel's extensibility model. Current Beat architecture is essentially made up of a few stages in its pipeline, as shown in the diagram below. It consists of an Input, Processors for enrichments, Queuing of events and Output for batching and writing the data to a specific output.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8449b5e3334e82a0/6a7f060233fa8a7cc32023e8/filebeat.png" alt="filebeat" /></p>
<h4 id="beatreceiverconcept">Beatreceiver Concept</h4>
<p>To ensure a smooth transition without major disruptions, a "beatsreceiver" concept is being implemented. These <code>beatreceivers</code> (like <code>filebeatreceiver</code> or <code>metricbeatreceiver</code>) act as dedicated Beat inputs integrated into the OpenTelemetry Collector as native receivers. They support all existing inputs and processors, guaranteeing that the final architecture accepts the user's current configuration and delivers the same functionality as today's Beats, all without introducing any breaking changes.</p>
<p>An OTel based Beats architecture will see the Input phase embedded as an OTel receiver (eg.  <code>filebeatreceiver</code> to represent the functionality of <code>filebeat</code>). This receiver would only be available as part of Elastic's distribution of OTel in support of our current user base and not a functionality that would be available upstream.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9b96ac33c2582cb0/6a7f06059090b0591984e72b/filebeatreceiver.png" alt="filebeat" /></p>
<p>All the remaining components of the pipeline will be based on OTel. The new Beat will accept the same filebeat configuration (as an example) and will transform it to an OTel based configuration in order to avoid any deployment disruption. It should be noted that in this architecture the Beats will continue to only support ECS formatted data. In order to keep the Beat functionality inline with what exists today, the Elasticsearch exporter (as an example) will output ECS formatted data only.</p>
<p>The following diagram illustrates the <code>beatreceiver</code> concept by showing how a basic <code>filebeat</code> configuration is automatically translated into an OpenTelemetry-based configuration. This new configuration retains the original inputs and processors but leverages the native OpenTelemetry pipeline and exporter to achieve the same overall <code>filebeat</code> functionality. Existing <code>filebeat</code> configurations will be automatically converted, eliminating the need for manual adjustments or introducing breaking changes.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf043398fe986d14a/6a7f060873d9bd339329d8a8/elastic-agent-otel-config.png" alt="Filebeat OTel config" /></p>
<h3 id="elasticagent">Elastic Agent</h3>
<p>Elastic Agent is a unified agent for data collection, security, and observability. It can also be deployed in an OpenTelemetry only mode, enabling native OTel workflows. Elastic Agent is a supervisor that manages many other Beats as sub-processes in order to provide a more comprehensive data collection tool. It is capable of translating Agent Policy received from Fleet into configuration acceptable by the various sub-processes.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt58723135028419eb/6a7f060bbdcff0806ec42b5d/elastic-agent-architecture.png" alt="Elastic Agent Architecture" /></p>
<p>Expanding on the Beat receiver concept described above, the Elastic Agent, which currently can be deployed as an OTel collector (see <a href="https://www.elastic.co/observability-labs/blog/elastic-distributions-opentelemetry-ga">blog</a>), will be also modified to a much simpler OTel based architecture based on these receivers. As shown below, this architecture will streamline the components within the Elastic Agent and remove duplicated functionality such as queuing and output. Whilst supporting the current functionality, these changes will reduce the agent footprint and also present a reduction in number of connections opened to pipeline elements egress of the agent (such as Elasticsearch clusters, Logstash or Kafka brokers).</p>
<p>By moving to an OTel based architecture Elastic Agent is now able to operate as a truly hybrid Elastic Agent which provides not only the Beat functionality but also allows our users to create OTel native pipelines and take advantage of plethora of functionality available as part of the open source project.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb64f338f976e9efa/6a7f060d1967ea49ed33043d/elastic-agent-otel-architecture.png" alt="Elastic Agent OTel Architecture" /></p>
<p>Elastic's commitment to OpenTelemetry will deepen through increased contributions, resulting in OpenTelemetry receivers gradually superseding Beats receiver features. This evolution will eventually reduce the need for a distinct Beats receiver within the Elastic Agent architecture. The envisioned architecture will empower the Elastic Agent to transmit data in OTLP format as well, granting users the flexibility to select any OTLP-compatible backend, thereby upholding the principle of vendor neutrality.</p>
<h3 id="fleetintegrationsmanagingopentelemetryatscale">Fleet &amp; Integrations: Managing OpenTelemetry at Scale</h3>
<p>Elastic's centralized management system will support OpenTelemetry-based configurations, making large-scale deployments easier to manage. Managing thousands of telemetry agents at scale presents a significant challenge. Elastic's <strong>Fleet &amp; Integrations</strong> simplify this process by providing robust lifecycle management for these new OpenTelemetry-based Elastic agents.</p>
<p><strong>Key Capabilities Offered:</strong></p>
<ul>
<li><p><strong>Scalability:</strong> Manage up to 100K+ agents across distributed environments.</p></li>
<li><p><strong>Automated Upgrades:</strong> Staged rollouts and automatic upgrades ensure minimal downtime.</p></li>
<li><p><strong>Monitoring &amp; Diagnostics:</strong> Real-time status updates, failure detection, and diagnostic downloads improve system reliability.</p></li>
<li><p><strong>Policy-Based Configuration Management:</strong> Enables centralized control over agent configurations, improving consistency across deployments.</p></li>
<li><p><strong>Pre-Built Integrations:</strong> Elastic offers a catalog of <strong>470+ pre-built integrations</strong>, allowing users to ingest data seamlessly from various sources. These will also include OTel based packages making configuration much more efficient across a large deployment.</p></li>
</ul>
<p>The goal is for Fleet to also provide monitoring capabilities for native OTel collectors as well in a vendor agnostic fashion.</p>
<h2 id="conclusion">Conclusion</h2>
<p>Elastic's adoption of OpenTelemetry marks a significant milestone in the evolution of open-source observability. By standardizing on OpenTelemetry, Elastic is ensuring that its data ingestion strategy remains <strong>open, scalable, and future-proof</strong>.</p>
<p>For open-source users, this shift means:</p>
<ul>
<li><p>Greater interoperability across observability tools.</p></li>
<li><p>Enhanced flexibility in choosing telemetry backends.</p></li>
<li><p>A stronger commitment to <strong>community-driven</strong> observability standards.</p></li>
<li><p>Existing Beats and Elastic Agent users can <strong>seamlessly adopt OpenTelemetry</strong> without rearchitecting their pipelines.</p></li>
<li><p>OpenTelemetry users can <strong>integrate with Elastic's observability stack</strong> without additional complexity.</p></li>
</ul>
<p>Stay tuned for more updates as Elastic continues to expand its OpenTelemetry-based data collection capabilities! In the mean time here are some other references:</p>
<ul>
<li><p><a href="https://www.elastic.co/observability-labs/blog/elastic-distributions-opentelemetry-ga">Elastic Distributions of OpenTelemetry (EDOT) Now GA</a></p></li>
<li><p><a href="https://www.elastic.co/observability-labs/blog/k8s-discovery-with-EDOT-collector">Dynamic workload discovery on Kubernetes now supported with EDOT Collector</a></p></li>
<li><p><a href="https://www.elastic.co/observability-labs/blog/introducing-the-ottl-playground-for-opentelemetry">Introducing the OTTL Playground for OpenTelemetry</a></p></li>
</ul>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/elastic-agent-pivot-opentelemetry</link>
    <guid isPermaLink="false">elastic-agent-pivot-opentelemetry</guid>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[APM]]></category>
    <dc:creator><![CDATA[Nima Rezainia]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltefce6a6a29657306/6a7f06101967ea2e1f330443/self-service-blog-image-templates.jpg" length="0" type="image/jpeg"/>
    <pubDate>Tue, 03 Jun 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[End to end LLM observability with Elastic: seeing into the opaque world of generative AI applications]]></title>
    <description><![CDATA[Elastic’s LLM Observability delivers end-to-end visibility into the performance, reliability, cost, and compliance of LLMs across Amazon Bedrock, Azure OpenAI, Google Vertex AI, and OpenAI, empowering SREs to optimize and troubleshoot AI-powered applications.]]></description>
    <content:encoded><![CDATA[<p>In the ever-evolving landscape of artificial intelligence, Large Language Models (LLMs) stand as beacons of innovation, offering unprecedented capabilities across industries. From generating human-like text and translating languages to providing personalized customer interactions, the possibilities with LLMs are vast and increasingly indispensable. Enterprises are deploying these models for everything, from automating customer support systems to enhancing creative writing processes. Imagine a virtual assistant not only answering questions but also drafting business proposals or a customer service bot that understands and responds with empathy—all powered by LLMs. However, with great power comes the need for great oversight.</p>
<p>Despite the transformative potential, LLMs introduce complex challenges that necessitate a new level of observability as LLMs are notoriously opaque. Enter LLM observability: a crucial component in the lifecycle management of LLMs. This aspect becomes vital for Service Reliability Engineers (SREs) and other key stakeholders tasked with ensuring seamless, error-free operations, cost control, and minimizing the risks associated with the unpredictable nature of LLM generated responses. SREs need insights into performance metrics, error frequencies, latency issues, the cost implications of running these sophisticated models, and the prompt and response exchange with the model. Traditional monitoring tools fall short in this high-stakes environment; what’s needed is a nuanced approach to address the unique observability demands that LLMs introduce.</p>
<h3 id="elasticsllmobservabilitycapabilitiesaddressthesechallenges">Elastic's LLM Observability Capabilities Address These Challenges</h3>
<p>With Elastic’s end-to-end LLM observability you can cover a wide range of use cases. To achieve this, you can onboard two types of integrations - API-based logs and metrics and via APM instrumentation. Depending on your use case, you can also choose to use of the LLM integrations.</p>
<ol>
<li><p><strong>High level overview</strong>: via API-based logs and metrics. Monitoring LLM services from providers by ingesting a curated set of service metrics and logs like latency, invocation frequency, tokens, errors, and prompts and responses. Each LLM integration comes with out-of-the-box dashboards.</p></li>
<li><p><strong>Troubleshooting applications</strong>: via APM instrumentation. Fully OTel-native tracing and auto-instrumentation for LLM-based applications through Elastic Distributions of OpenTelemetry (EDOT). Additionally, you can use third party libraries (Langtrace, OpenLit, OpenLLMetry) together with Elastic to extend the coverage to additional LLM-related technologies. </p></li>
</ol>
<h4 id="highleveloverviewllmobservabilityforleadingproviders">High level overview: LLM Observability for Leading Providers</h4>
<p>Elastic offers tailored API-based integrations for four major LLM hosting providers:</p>
<ul>
<li><p>Azure OpenAI</p></li>
<li><p>OpenAI</p></li>
<li><p>Amazon Bedrock</p></li>
<li><p>Google Vertex AI</p></li>
</ul>
<p>These integrations bring a curated set of logs and metrics collection tailored to each provider. What this means for SREs is straightforward access to pre-configured dashboards that highlight the prompts and responses, usage patterns, performance metrics, and cost details across different models and providers.</p>
<p>For instance, SREs keen on identifying which LLM generates the most errors or insights about the models in terms of latency, cost, or usage frequency can leverage these integrations. Imagine having the capability to instantly visualize which LLM is slowing down processes or incurring high costs, thus enabling data-driven decisions to optimize operations.</p>
<h4 id="troubleshootingapplicationstracingandautoinstrumentationofopenaiamazonbedrockandgooglevertexaimodels">Troubleshooting applications: Tracing and Auto-Instrumentation of OpenAI, Amazon Bedrock and Google Vertex AI models</h4>
<p>Elastic supports OTLP tracing capabilities in EDOT for applications using OpenAI models and models hosted on Amazon Bedrock and Google Vertex AI. In addition, Elastic also supports LLM tracing from third party libraries (Langtrace, OpenLIT, OpenLLMetry). </p>
<p>Tracing offers a comprehensive map of an application's request flow, pinpointing granular details about each call within the system. For each transaction and span of a request, tracing shows critical information such as specific models utilized, request duration, errors encountered, tokens used per request, and the prompts and responses between the LLM.</p>
<p>Tracing helps SREs troubleshoot performance issues with applications developed in languages like Python, Node.js and Java." If an SRE needs to investigate latency or error issues, LLM tracing provides a zoomed-in view into the request lifecycle and allows for profound insights into whether a delay is application-specific, model-specific or systemic across deployments.</p>
<h3 id="usecasesbringingelasticsobservabilityfeaturestolife">Use Cases: Bringing Elastic's Observability Features to Life</h3>
<p>Let’s explore some practical scenarios where Elastic’s observability tools shine:</p>
<h4 id="1understandingllmperformanceandreliability">1. Understanding LLM Performance and Reliability</h4>
<p>An SRE team looking to optimize a customer support system powered by Azure OpenAI can utilize Elastic’s <a href="https://www.elastic.co/guide/en/integrations/current/azure_openai.html">Azure OpenAI integration</a> to quickly ascertain which model variants incur higher latency or error rates. This enhances decision-making regarding model deployment or even switching providers based on performance metrics.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltba6dd852a703d452/6a7f0cc0fc63ab0f2b64cc37/Azure-OpenAI.png" alt="Azure OpenAI" /></p>
<p>Similarly SREs can also use in parallel integrations for <a href="https://www.elastic.co/guide/en/integrations/current/gcp_vertexai.html">Google Vertex AI</a>, <a href="https://www.elastic.co/guide/en/integrations/current/aws_bedrock.html">Amazon Bedrock</a>, and <a href="https://www.elastic.co/guide/en/integrations/current/openai.html">OpenAI</a> for other applications using models hosted on these providers.</p>
<h4 id="2troubleshootingopenaipoweredapplications">2. Troubleshooting OpenAI-Powered Applications</h4>
<p>Consider an enterprise utilizing an OpenAI model for real-time user interactions. Encountering unexplained delays, an SRE can use OpenAI tracing to dissect the transaction pathway, identifying if one specific API call or model invocation is the bottleneck. The SRE can also check the out-of-the-box OpenAI integration dashboard to verify if the latency is only affecting this application or all model invocations across the organization.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1a8db67903037838/6a7f0cc363e9595fb873ddda/OpenAI-tracing.png" alt="OpenAI Tracing" /></p>
<p>An engineer troubleshooting the LLM-based application can also check to see what were the prompt and response exchanges with the LLM during this request so they can rule out possible impact on performance due to the input. </p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf636f72199ddf561/6a7f0cc5e02fac383b5d6576/OpenAI-trace.png" alt="OpenAI Trace sample with logs " /></p>
<h4 id="3addressingcostandusageconcerns">3. Addressing Cost and Usage Concerns</h4>
<p>SREs are generally acutely aware of which LLM configurations are less cost-effective than required. Elastic’s integration dashboards, pre-configured to display model usage patterns, help mitigate unnecessary spending effectively. You can find out-of-the box dashboards for Azure OpenAI, OpenAI, Amazon Bedrock, and Google VertexAI models. These dashboards show key cost and usage information such as total invocations and tokens, as well as time series breakdown by model and endpoint. In addition, some integrations show more advanced usage information such as provisioned throughput units (PTU) as well as billing cost.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte81a390cd24036bd/6a7f0cc83cab1c66e30e4868/GCP-Vertex-AI.png" alt="GCP Vertex AI" /></p>
<h4 id="4understandingllmcompliancenbsp">4. Understanding LLM Compliance </h4>
<p>With the Elastic Amazon Bedrock integration for Guardrails, and Azure OpenAI integration for content filtering, SREs can swiftly address security concerns, like verifying if certain user interactions prompt policy violations. Elastic's observability logs clarify whether guardrails rightly blocked potentially harmful responses, bolstering compliance assurance.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd27317f296118f10/6a7f0ccbfc63ab8c1964cc3f/Bedrock-Guardrails.png" alt="Bedrock-Guardrails.png" /></p>
<h3 id="conclusion">Conclusion</h3>
<p>As LLMs continue to revolutionize the capabilities of modern applications, the role of observability becomes increasingly paramount. Elastic’s comprehensive observability framework empowers enterprises to harness the full potential of LLMs while maintaining robust operational insight and control. The integration with prominent LLM hosting providers and advanced tracing for OpenAI, Amazon Bedrock and Google Vertex AI models, equips SREs with the necessary arsenal to navigate the complex landscape of LLM-driven applications, ensuring they remain safe, reliable, efficient, and cost-effective.</p>
<p>In this new era of AI, balancing innovation with observability isn't just beneficial—it's essential. Whether optimizing performance, troubleshooting intricacies, or managing costs and compliance, Elastic stands at the forefront, ensuring your LLM journey is as seamless as it is groundbreaking.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/llm-observability-elastic</link>
    <guid isPermaLink="false">llm-observability-elastic</guid>
    <category><![CDATA[LLM Observability]]></category>
    <category><![CDATA[OpenTelemetry]]></category>
    <dc:creator><![CDATA[Daniela Tzvetkova,Bahubali Shetti]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4a75b88b6d752066/6a7f0cceeab5bec2c720a6d7/llm-e2e.jpg" length="0" type="image/jpeg"/>
    <pubDate>Wed, 02 Apr 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[2025 observability trends: Maturing beyond the hype]]></title>
    <description><![CDATA[Discover what 500+ decision-makers revealed about OpenTelemetry adoption, GenAI integration, and LLM monitoring—insights that separate innovators from followers in Elastic's 2025 observability survey.]]></description>
    <content:encoded><![CDATA[<p>Our latest survey of over 500 observability decision-makers reveals how dramatically the landscape has evolved as we move through 2025. What strikes me most is how observability has moved beyond its technical roots to become a true business imperative. Let’s dive into what we're seeing in the industry.</p>
<h2 id="theinvestmentparadoxofobservabilityin2025">The investment paradox of observability in 2025</h2>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltcd28ab8962baff2e/6a7f0a4dbd2198132e757fb9/image5.png" alt="" /></p>
<p>Here's something fascinating: 96% of executives in our survey expect observability to remain a key investment area. Yet almost all of them (97%) are hitting roadblocks in realizing full value. And surprisingly, the primary hurdles for observability are not technical or complicated in nature, can you guess what they might be?</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt01eaab94507227dc/6a7f0a50bdcff03a43c42d0b/image10.png" alt="" /></p>
<p>For 2025, IT leaders are challenged with financial hurdles for their observability. I'm seeing this tension play out constantly in conversations with leaders - they know they need to invest, but they're grappling with budget constraints, licensing costs, and proving ROI for their organizations. This creates an interesting dynamic where organizations must carefully balance increasing investment with rigorous cost optimization and business metrics.</p>
<p>What's particularly interesting is how this paradox is forcing organizations to become more strategic about their investments. Leaders are no longer just throwing money at the problem - they're thinking carefully about how to maximize value from every dollar spent.</p>
<h2 id="whyobservabilitymaturityismakingallthedifference">Why observability maturity is making all the difference</h2>
<p>The data really jumps out at me here. The gap between observability experts and newcomers tells a compelling story that I wasn't expecting to see. Expert organizations are significantly outperforming their peers across every key metric:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt05166add0381eda7/6a7f0a543ce8e231bacf52b5/image9.png" alt="" /></p>
<ul>
<li>91% of expert organizations are deploying applications and infrastructure faster (compared to just 34% of those in early stages)</li>
</ul>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt22f788515e7afd45/6a7f0a57ead8ecd41cbaa75d/image11.png" alt="" /></p>
<ul>
<li>82% are successfully reducing operational costs (versus 56% of early-stage organizations)</li>
</ul>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt35ca13bf87e472be/6a7f0a5aea068d31caf09d63/image4.png" alt="" /></p>
<ul>
<li>71% achieve better MTTR for incidents (while only 40% of early-stage organizations do)</li>
</ul>
<p>What I find particularly fascinating is how some benefits go beyond just maturity levels. About 80% of organizations report better customer issue response times regardless of their maturity stage. It tells me that even basic observability delivers immediate customer-facing value. This is crucial information for organizations just starting their observability journey - they can expect to see tangible benefits right from the start. But the overarching story may be that observability maturity leads teams from reactive to proactive and allows them to focus on higher level, value-add activities.</p>
<h2 id="costmanagementthenewimperative">Cost management: the new imperative</h2>
<p>The numbers around cost management paint a clear picture of where the industry is heading - 97% of IT decision-makers are actively managing observability costs, and 86% feel personally responsible for business outcomes.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt47754196fbd6a537/6a7f0a5d33fa8a2ff82025c2/image2.png" alt="" /></p>
<p>I'm seeing a clear trend where leaders are taking concrete steps in their day to day work:</p>
<ul>
<li>Consolidating their observability toolset while maintaining capabilities, they don’t want to lose anything</li>
<li>Implementing usage-based pricing models</li>
<li>Establishing clear ROI metrics</li>
<li>Creating cross-functional teams to optimize spending</li>
</ul>
<p>This isn't just about cutting costs - it's about being smarter with resources. Organizations are learning that more tools don't necessarily mean better observability.</p>
<h2 id="twotechnologiesreshapingtheobservabilitylandscape">Two technologies reshaping the observability landscape</h2>
<h3 id="aisgrowingimpact">AI's growing impact</h3>
<p>The enthusiasm for AI is remarkable - 94% of respondents see its tremendous potential. What fascinates me is how concerns about Generative AI reliability have actually decreased from 64% to 55% over the past year.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf5138bff0bb780b6/6a7f0a5fe88c6544d100b58e/image7.png" alt="" /></p>
<p>Leaders are particularly excited about:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt92708af419f7bb2e/6a7f0a62bd21987622757fc9/image1.png" alt="" /></p>
<ul>
<li>Automated correlation of logs, metrics, and traces (72% of respondents)</li>
<li>Predictive analytics for preventing outages</li>
<li>Natural language interfaces for querying observability data</li>
<li>Automated root cause analysis</li>
</ul>
<p>The key shift I'm seeing for the upcoming year is the move from AI as a buzzword to AI as a practical tool delivering real value in observability workflows.  </p>
<p>Generative AI capabilities paired with retrieval augmented generation (RAG) capabilities allow organizations to leverage the power of LLMs and private data (e.g., runbooks, alerts, business data) to deliver relevant and meaningful results and identify and solve problems faster while reducing noise.</p>
<h3 id="opentelemetryscontinuedmomentum">OpenTelemetry's continued momentum</h3>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltcb6abc9011647535/6a7f0a64e3a219169e99f37e/image3.png" alt="" /></p>
<p>Looking at expert organizations, 80% are either experimenting with or have deployed OpenTelemetry. This isn't just about technology adoption - it's about building for the future with open standards. The correlation between OpenTelemetry adoption and overall observability maturity is correlated and unmistakable.</p>
<p>What's particularly interesting is how OpenTelemetry is changing the vendor landscape. Organizations are increasingly demanding OpenTelemetry support from their vendors, seeing it as a way to future-proof their observability investments and avoid vendor lock-in. Thinking back to how Linux shifted the server landscape, can we expect to see the same in the observability domain?</p>
<h2 id="businessintegrationandinsightsdeepens">Business integration and insights deepens</h2>
<hr />
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt91f467debde60ec5/6a7f0a67c2cc0922c0249464/image8.png" alt="" /></p>
<p>Here's what I find most compelling: 64% of expert organizations are frequently correlating operational data with business outcomes, while only 9% of early-stage organizations do the same. This represents a fundamental shift from technical monitoring to business observability.</p>
<p>This isn't just about uptime anymore - organizations are increasingly using observability data to:</p>
<ul>
<li>Make informed business decisions</li>
<li>Improve customer experience</li>
<li>Optimize resource allocation</li>
<li>Drive innovation</li>
</ul>
<h2 id="lookingahead">Looking ahead</h2>
<p>As we continue through 2025, I'm seeing observability mature beyond its initial promise. Organizations are focusing less on basic implementation and more on delivering real business value through:</p>
<ul>
<li>Deeper business integration, like mapping system performance directly to revenue metrics</li>
<li>Optimized cost management through new data lake technology, efficient storage and intelligent retention</li>
<li>AI-enhanced capabilities powered by LLMs and Agentic AI</li>
<li>Standardized instrumentation through OpenTelemetry, reducing vendor lock-in</li>
</ul>
<p>The path to success in 2025 isn't just about having the right tools - it's about building mature practices that deliver measurable business value while managing costs effectively. The organizations that can balance these competing demands while maintaining focus on business outcomes are the ones pulling ahead.</p>
<p>What are you seeing in your organization's observability journey? Are these trends aligning with your experience? </p>
<p>If you would like to dig in deeper on emerging observability trends, download <a href="https://www.elastic.co/resources/observability/report/landscape-observability-report">our full report</a> or watch the on-demand webinar, <a href="https://www.elastic.co/virtual-events/observability-trends-2025">2025 Observability trends: Maturing beyond the hype and delivering results</a>!</p>
<p>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.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/emerging-trends-in-observability-2025</link>
    <guid isPermaLink="false">emerging-trends-in-observability-2025</guid>
    <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/blta5afa8cac1d6450e/6a7f0a6b77b03421db3ff3c7/trends.png" length="0" type="image/png"/>
    <pubDate>Thu, 27 Feb 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[Tracing a RAG based Chatbot with Elastic Distributions of OpenTelemetry and Langtrace]]></title>
    <description><![CDATA[How to observe a OpenAI RAG based application using Elastic. Instrument the app, collect logs, traces, metrics, and understand how well the LLM is performing with Elastic Distributions of OpenTelemetry on Kubernetes with Langtrace.]]></description>
    <content:encoded><![CDATA[<p>Most AI-driven applications are currently focusing around increasing the value an end user, such as an SRE gets from AI. The main use case is the creation of various chatbots. These chatbots not only use large language models (LLMs), but are also using frameworks such as LangChain, and search to improve contextual information during a conversation (Retrieval Augmented Generation). Elastic’s sample <a href="https://github.com/elastic/elasticsearch-labs/tree/main/example-apps/chatbot-rag-app">RAG based Chatbot application</a>, showcases how to use Elasticsearch with local data that has embeddings, enabling search to properly pull out the most contextual information during a query with a chatbot connected to an LLM of your choice. It's a great example of how to build out a RAG based application with Elasticsearch. However, what about monitoring the application?</p>
<p>Elastic provides the ability to ingest OpenTelemetry data with native OTel SDKs, the off the shelf OTel collector, or even Elastic’s Distributions of OpenTelemetry (EDOT). EDOT enables you to bring in logs, metrics and traces for your GenAI application and for K8s. However you will also generally need libraries to help trace specific components in your application. In tracing GenAI applications you can pick from a large set of libraries.</p>
<ul>
<li><p><a href="https://github.com/open-telemetry/opentelemetry-python-contrib/tree/main/instrumentation-genai/opentelemetry-instrumentation-openai-v2">OpenTelemetry OpenAI Instrumentation-v2</a> - allows tracing LLM requests and logging of messages made by the OpenAI Python API library. (note v2 is built by OpenTelemetry, the non v2 version is from a specific vendor and not OpenTelemetry)</p></li>
<li><p><a href="https://github.com/open-telemetry/opentelemetry-python-contrib/tree/main/instrumentation-genai/opentelemetry-instrumentation-vertexai">OpenTelemetry VertexAI Instrumentation</a> - allows tracing LLM requests and logging of messages made by the VertexAI Python API library</p></li>
<li><p><a href="https://docs.langtrace.ai/introduction">Langtrace</a> - commercially available library which supports all LLMs in one library, and all traces are also OTel native.</p></li>
<li><p>Elastic’s EDOT - which recently added tracing. See <a href="https://www.elastic.co/observability-labs/blog/openai-tracing-elastic-opentelemetry">blog</a>.</p></li>
</ul>
<p>As you can see OpenTelemetry is the defacto mechanism that is converging to collect and ingest. OpenTelemetry is growing its support for this but it is also early days.</p>
<p>In this blog, we will walk through how to, with minimal code, observe a RAG based chatbot application with tracing using Langtrace. We previously covered Langtrace in a <a href="https://www.elastic.co/observability-labs/blog/elastic-opentelemetry-langchain-tracing-langtrace">blog</a> to highlight tracing Langchain.</p>
<p>In this blog we used langtrace OpenAI, Amazon Bedrock, Cohere, and others in one library.</p>
<h2 id="prerequisites">Pre-requisites:</h2>
<p>In order to follow along, these few pre-requisites are needed</p>
<ul>
<li><p>An Elastic Cloud account — sign up now, and become familiar with Elastic’s OpenTelemetry configuration. With Serverless no version required. With regular cloud minimally 8.17</p></li>
<li><p>Git clone the <a href="https://github.com/elastic/elasticsearch-labs/tree/main/example-apps/chatbot-rag-app">RAG based Chatbot application</a> and go through the <a href="https://www.elastic.co/search-labs/tutorials/chatbot-tutorial/welcome">tutorial</a> on how to bring it up and become more familiar.</p></li>
<li><p>An account on your favorite LLM (OpenAI, AzureOpen AI, etc), with API keys</p></li>
<li><p>Be familiar with EDOT to understand how we bring in logs, metrics, and traces from the application through the OTel Collector</p></li>
<li><p>Kubernetes cluster - I’ll be using Amazon EKS</p></li>
<li><p>Look at <a href="https://docs.langtrace.ai/introduction">Langtrace</a> documentation also.</p></li>
</ul>
<h2 id="applicationopentelemetryoutputinelastic">Application OpenTelemetry output in Elastic</h2>
<h3 id="chatbotragapp">Chatbot-rag-app</h3>
<p>The first item that you will need to get up and running is the ChatBotApp, and once up you should see the following:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt880964dd83511be5/6a7f0f443ce8e2feb5cf5471/Chatbotapp-general.png" alt="Chatbot app main page" /></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6d932574c6143415/6a7f0f48ead8ecb92fbaa976/Chatbotapp-details.png" alt="Chatbot app working" /></p>
<p>As you select some of the questions you will set a response based on the index that was created in Elasticsearch when the app initializes. Additionally there will be queries that are made to LLMs.</p>
<h3 id="traceslogsandmetricsfromedotinelastic">Traces, logs, and metrics from EDOT in Elastic</h3>
<p>Once you have OTel Collector with EDOT configuration on your K8s cluster, and Elastic Cloud up and running you should see the following:</p>
<h4 id="logs">Logs:</h4>
<p>In Discover you will see logs from the Chatbotapp, and be able to analyze the application logs, any specific log patterns (saves you time in analysis), and view logs from K8s.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta108497f956043e0/6a7f0f4a1967ea4e31330847/Chatbotapp-logs.png" alt="Chatbot-logs" /></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt47cfac93de9cc224/6a7f0f4d5967e535e15dd3cd/Chatbotapp-log-patterns.png" alt="Chatbot-log-patterns" /></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltef3b11c21b429f54/6a7f0f5063e95922cc73dedd/Chatbotapp-logs-detailed.png" alt="Chatbot-log-details" /></p>
<h4 id="traces">Traces:</h4>
<p>In Elastic Observability APM, you can also see tha chatbot details, which include transactions, dependencies, logs, errors, etc.</p>
<p>When you look at traces, you will be able to see the chatbot interactions in the trace.</p>
<ol>
<li><p>You will see the end to end http call</p></li>
<li><p>Individual calls to elasticsearch</p></li>
<li><p>Specific calls such as invoke actions, and calls to the LLM</p></li>
</ol>
<p>You can also get individual details of the traces, and look at related logs, and metrics related to that trace,</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2a985aa53fe6e887/6a7f0f536693f8a83a66402b/Chatbotapp-service-traces.png" alt="CHatbot-traces" /></p>
<h4 id="metrics">Metrics:</h4>
<p>In addition to logs, and traces, any instrumented metrics will also get ingested into Elastic.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3757e1e587a76239/6a7f0f564c4bfb17ddccd60d/chatbot-reg-metrics.png" alt="Chatbot app metrics" /></p>
<h2 id="settingitallup">Setting it all up</h2>
<p>In order to properly set up the Chatbot-app on K8s with telemetry sent over to Elastic, a few things must be set up:</p>
<ol>
<li><p>Git clone the chatbot-rag-app, and modify one of the python files.</p></li>
<li><p>Next create a docker container that can be used in Kubernetes. The Docker build <a href="https://github.com/elastic/elasticsearch-labs/blob/main/example-apps/chatbot-rag-app/Dockerfile">here</a> in the Chatbot-app is good to use.</p></li>
<li><p>Collect all needed env variables. In this example we are using OpenAI, but the files can be modified for any of the LLMs. Hence you will have to get a few environmental variables loaded into the cluster. In the github repo there is a env.example for docker. You can pick and chose what is needed or not needed and adjust appropriately in the K8s file below.</p></li>
<li><p>Set up your K8s Cluster, and then install the OpenTelemetry collector with the appropriate yaml file and credentials. This will help collect K8s cluster logs and metrics also.</p></li>
<li><p>Utilize the two yaml files listed below to ensure you can run it on Kubernetes.</p></li>
</ol>
<ul>
<li><p>Init-index-job.yaml - Initiates the index in elasticsearch with the local corporate information</p></li>
<li><p>k8s-deployment-chatbot-rag-app.yaml - initializes the application frontend and backend.</p></li>
</ul>
<ol>
<li><p>Open the app on the load balancer URL against the chatbot-app service in K8s</p></li>
<li><p>Go to Elasticsearch and look at Discover for logs, go to APM and look for your chatbot-app and review the traces, and finally.</p></li>
</ol>
<h3 id="modifythecodefortracingwithlangtrace">Modify the code for tracing with Langtrace</h3>
<p>Once you curl the app and untar, go to the chatbot-rag-app directory:</p>
<pre><code>curl https://codeload.github.com/elastic/elasticsearch-labs/tar.gz/main | 
tar -xz --strip=2 elasticsearch-labs-main/example-apps/chatbot-rag-app
cd elasticsearch-labs-main/example-apps/chatbot-rag-app
</code></pre>
<p>Next open the <code>app.py</code> file in the <code>api</code> directory and add the following </p>
<pre><code>from opentelemetry.instrumentation.flask import FlaskInstrumentor

from langtrace_python_sdk import langtrace

langtrace.init(batch=False)

FlaskInstrumentor().instrument_app(app)
</code></pre>
<p>into the code:</p>
<pre><code>import os
import sys
from uuid import uuid4

from chat import ask_question
from flask import Flask, Response, jsonify, request
from flask_cors import CORS

from opentelemetry.instrumentation.flask import FlaskInstrumentor

from langtrace_python_sdk import langtrace

langtrace.init(batch=False)

app = Flask(__name__, static_folder="../frontend/build", static_url_path="/")
CORS(app)

FlaskInstrumentor().instrument_app(app)

@app.route("/")
</code></pre>
<p>See the items in <strong>BOLD</strong> which will add in the langtrace library, and the opentelemetry flask instrumentation. This combination will provide and end to end trace for the https call all the way down to the calls to Elasticsearch, and to OpenAI (or other LLMs).</p>
<h3 id="createthedockercontainer">Create the docker container</h3>
<p>Use the Dockerfile that is in the chatbot-rag-app directory as is and add the following line:</p>
<p><code>RUN pip3 install --no-cache-dir langtrace-python-sdk</code></p>
<p>into the Dockerfile:</p>
<pre><code>COPY requirements.txt ./requirements.txt
RUN pip3 install -r ./requirements.txt
RUN pip3 install --no-cache-dir langtrace-python-sdk
COPY api ./api
COPY data ./data

EXPOSE 4000
</code></pre>
<p>This enables the <code>langtrace-python-sdk</code> to be installed into the docker container so the langtrace libraries can be used properly.</p>
<h3 id="collectingtheproperenvvariables">Collecting the proper env variables:</h3>
<p>First collect the env variables from Elastic:</p>
<p>Envs for index initialization in Elastic:</p>
<pre><code>ELASTICSEARCH_URL=https://aws.us-west-2.aws.found.io
ELASTICSEARCH_USER=elastic
ELASTICSEARCH_PASSWORD=elastic

# The name of the Elasticsearch indexes
ES_INDEX=workplace-app-docs
ES_INDEX_CHAT_HISTORY=workplace-app-docs-chat-history
</code></pre>
<p>The <code>ELASTICSEARCH_URL</code> can be found in cloud.elastic.co when you bring up your instance.
The user and password, you will need to setup in Elastic. </p>
<p>Envs for sending the OTel instrumentation you will need the following:</p>
<pre><code>OTEL_EXPORTER_OTLP_ENDPOINT="https://123456789.apm.us-west-2.aws.cloud.es.io:443"
OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer xxxxx"
</code></pre>
<p>These credentials are found in Elastic under APM integration and under OpenTelemetry</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte5d0b7250dcc156d/6a7f0f5933fa8a1f732027f0/otel-credentials.png" alt="OTel credentials" /></p>
<p>Envs for LLMs</p>
<p>In this example we’re using OpenAI, hence only three variables are needed.</p>
<pre><code>LLM_TYPE=openai
OPENAI_API_KEY=XXXX
CHAT_MODEL=gpt-4o-mini
</code></pre>
<p>All these variables will be needed in the Kubernetes yamls in the next step</p>
<h3 id="setupk8sclusterandloadupotelcollectorwithedot">Setup K8s cluster and load up OTel Collector with EDOT</h3>
<p>This step is outlined in the following <a href="https://www.elastic.co/observability-labs/blog/elastic-opentelemetry-otel-operator">Blog</a>. It’s a simple three step process.</p>
<p>This step will bring in all the K8s cluster logs and metrics and setup the OTel collector.</p>
<h3 id="setupsecretsinitializeindicesandstarttheapp">Setup secrets, initialize indices, and start the app</h3>
<p>Now that the cluster is up, and you have your environmental variables, you will need to</p>
<ol>
<li><p>Install and run the <code>k8s-deployments.yaml</code> with the variables</p></li>
<li><p>Initialize the index</p></li>
</ol>
<p>Essentially run the following:</p>
<pre><code>kubectl create -f k8s-deployment.yaml
kubectl create -f init-index-job.yaml
</code></pre>
<p>Here are the two yamls you should use. Also found <a href="https://github.com/elastic/observability-examples/tree/main/chatbot-rag-app-observability">here</a></p>
<p>k8s-deployment.yaml</p>
<pre><code>apiVersion: v1
kind: Secret
metadata:
  name: genai-chatbot-langtrace-secrets
type: Opaque
stringData:
  OTEL_EXPORTER_OTLP_HEADERS: "Authorization=Bearer%20xxxx"
  OTEL_EXPORTER_OTLP_ENDPOINT: "https://1234567.apm.us-west-2.aws.cloud.es.io:443"
 ELASTICSEARCH_URL: "YOUR_ELASTIC_SEARCH_URL"
  ELASTICSEARCH_USER: "elastic"
  ELASTICSEARCH_PASSWORD: "elastic"
  OPENAI_API_KEY: "XXXXXXX"  

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: genai-chatbot-langtrace
spec:
  replicas: 2
  selector:
    matchLabels:
      app: genai-chatbot-langtrace
  template:
    metadata:
      labels:
        app: genai-chatbot-langtrace
    spec:
      containers:
      - name: genai-chatbot-langtrace
        image:65765.amazonaws.com/genai-chatbot-langtrace2:latest
        ports:
        - containerPort: 4000
        env:
        - name: LLM_TYPE
          value: "openai"
        - name: CHAT_MODEL
          value: "gpt-4o-mini"
        - name: OTEL_SDK_DISABLED
          value: "false"
        - name: OTEL_RESOURCE_ATTRIBUTES
          value: "service.name=genai-chatbot-langtrace,service.version=0.0.1,deployment.environment=dev"
        - name: OTEL_EXPORTER_OTLP_PROTOCOL
          value: "http/protobuf"
        envFrom:
        - secretRef:
            name: genai-chatbot-langtrace-secrets
        resources:
          requests:
            memory: "512Mi"
            cpu: "250m"
          limits:
            memory: "1Gi"
            cpu: "500m"

---
apiVersion: v1
kind: Service
metadata:
  name: genai-chatbot-langtrace-service
spec:
  selector:
    app: genai-chatbot-langtrace
  ports:
  - port: 80
    targetPort: 4000
  type: LoadBalancer
</code></pre>
<p>Init-index-job.yaml</p>
<pre><code>apiVersion: batch/v1
kind: Job
metadata:
  name: init-elasticsearch-index-test
spec:
  template:
    spec:
      containers:
      - name: init-index
#update your image location for chatbot rag app
        image: your-image-location:latest
        workingDir: /app/api
        command: ["python3", "-m", "flask", "--app", "app", "create-index"]
        env:
        - name: FLASK_APP
          value: "app"
        - name: LLM_TYPE
          value: "openai"
        - name: CHAT_MODEL
          value: "gpt-4o-mini"
        - name: ES_INDEX
          value: "workplace-app-docs"
        - name: ES_INDEX_CHAT_HISTORY
          value: "workplace-app-docs-chat-history"
        - name: ELASTICSEARCH_URL
          valueFrom:
            secretKeyRef:
              name: chatbot-regular-secrets
              key: ELASTICSEARCH_URL
        - name: ELASTICSEARCH_USER
          valueFrom:
            secretKeyRef:
              name: chatbot-regular-secrets
              key: ELASTICSEARCH_USER
        - name: ELASTICSEARCH_PASSWORD
          valueFrom:
            secretKeyRef:
              name: chatbot-regular-secrets
              key: ELASTICSEARCH_PASSWORD
        envFrom:
        - secretRef:
            name: chatbot-regular-secrets
      restartPolicy: Never
  backoffLimit: 4
</code></pre>
<h3 id="openappwithloadbalancerurl">Open App with LoadBalancer URL</h3>
<p>Run the kubectl get services command and get the URL for the chatbot app</p>
<pre><code>% kubectl get services
NAME                                 TYPE           CLUSTER-IP       EXTERNAL-IP                                                               PORT(S)                                                                     AGE
chatbot-langtrace-service            LoadBalancer   10.100.130.44    xxxxxxxxx-1515488226.us-west-2.elb.amazonaws.com   80:30748/TCP                                                                6d23h
</code></pre>
<p>Play with app and review telemetry in Elastic</p>
<p>Once you go to the URL, you should see all the screens we described earlier in the <a href="https://docs.google.com/document/d/1w_3VRDJV3CoLMjOj8Ktnng-6MuKgdzkhKs4CVBWkatc/edit?tab=t.0#bookmark=id.lrmf4nbl2twi">beginning of this blog</a>.</p>
<h2 id="conclusion">Conclusion</h2>
<p>With Elastic's Chatbot-rag-app you have an example of how to build out a OpenAI driven RAG based chat application. However, you still need to understand how well it performs, whether its working properly, etc. Using OTel, Elastic’s EDOT and Langtrace gives you the ability to achieve this. Additionally, you will generally run this application on Kubernetes. Hopefully this blog provides the outline of how to achieve this.</p>
<p>Here are the other Tracing blogs:</p>
<p>App Observability with LLM (Tracing)- </p>
<ul>
<li><p><a href="https://www.elastic.co/observability-labs/blog/elastic-opentelemetry-langchain-tracing-langtrace">Observing LangChain with Langtrace and OpenTelemetry</a></p></li>
<li><p><a href="https://www.elastic.co/observability-labs/blog/elastic-opentelemetry-langchain-openlit-tracing">Observing LangChain with OpenLit Tracing</a></p></li>
<li><p><a href="https://www.elastic.co/observability-labs/blog/elastic-opentelemetry-langchain-tracing">Instrumenting LangChain with OpenTelemetry</a> </p></li>
</ul>
<p>LLM Observability - </p>
<ul>
<li><p><a href="https://www.elastic.co/observability-labs/blog/elevate-llm-observability-with-gcp-vertex-ai-integration">Elevate LLM Observability with GCP Vertex AI Integration</a></p></li>
<li><p><a href="https://www.elastic.co/observability-labs/blog/llm-observability-aws-bedrock">LLM Observability on AWS Bedrock</a></p></li>
<li><p><a href="https://www.elastic.co/observability-labs/blog/llm-observability-azure-openai">LLM Observability for Azure OpenAI</a></p></li>
<li><p><a href="https://www.elastic.co/observability-labs/blog/llm-observability-azure-openai-v2">LLM Observability for Azure OpenAI v2</a></p></li>
</ul>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/openai-tracing-langtrace-elastic</link>
    <guid isPermaLink="false">openai-tracing-langtrace-elastic</guid>
    <category><![CDATA[LLM Observability]]></category>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[Metrics]]></category>
    <category><![CDATA[APM]]></category>
    <category><![CDATA[Kubernetes]]></category>
    <category><![CDATA[Infrastructure Monitoring]]></category>
    <dc:creator><![CDATA[Bahubali Shetti]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9d3180a0fd833d64/6a7f0f5c73d9bd264e29dc29/edot-openai-tracing.png" length="0" type="image/png"/>
    <pubDate>Thu, 06 Feb 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Tracing, logs, and metrics for a RAG based Chatbot with Elastic Distributions of OpenTelemetry]]></title>
    <description><![CDATA[How to observe a OpenAI RAG based application using Elastic. Instrument the app, collect logs, traces, metrics, and understand how well the LLM is performing with Elastic Distributions of OpenTelemetry on Kubernetes and Docker.]]></description>
    <content:encoded><![CDATA[<p>As discussed in the following post, <a href="https://www.elastic.co/observability-labs/blog/elastic-opentelemetry-openai">Elastic added instrumentation for OpenAI based applications in EDOT</a>. The main application most commonly using LLMs is known as a Chatbot. These chatbots not only use large language models (LLMs), but are also using frameworks such as LangChain, and search to improve contextual information during a conversation RAG (Retrieval Augmented Generation). Elastics's sample <a href="https://github.com/elastic/elasticsearch-labs/tree/main/example-apps/chatbot-rag-app">RAG based Chatbot application</a>, showcases how to use Elasticsearch with local data that has embeddings, enabling search to properly pull out the most contextual information during a query with a chatbot connected to an LLM of your choice. It's a great example of how to build out a RAG based application with Elasticsearch.</p>
<p>This app is also now insturmented with EDOT, and you can visualize the Chatbot's traces to OpenAI, as well as relevant logs, and metrics from the application. By running the app as instructed in the github repo with Docker you can see these traces on a local stack. But how about running it against serverless, Elastic cloud or even with Kubernetes?</p>
<p>In this blog we will walk through how to set up Elastic's RAG Based Chatbot application with Elastic cloud and Kubernetes.</p>
<h2 id="prerequisites">Prerequisites:</h2>
<p>In order to follow along, these few pre-requisites are needed</p>
<ul>
<li><p>An Elastic Cloud account — sign up now, and become familiar with Elastic's OpenTelemetry configuration. With Serverless no version required. With regular cloud minimally 8.17</p></li>
<li><p>Git clone the <a href="https://github.com/elastic/elasticsearch-labs/tree/main/example-apps/chatbot-rag-app">RAG based Chatbot application</a> and go through the <a href="https://www.elastic.co/search-labs/tutorials/chatbot-tutorial/welcome">tutorial</a> on how to bring it up and become more familiar and how to bring up the application using Docker.</p></li>
<li><p>An account on OpenAI with API keys</p></li>
<li><p>Kubernetes cluster to run the RAG based Chatbot app</p></li>
<li><p>The instructions in this blog are also found in <a href="https://github.com/elastic/observability-examples/tree/main/chatbot-rag-app-observability">observability-examples</a> in github.</p></li>
</ul>
<h2 id="applicationopentelemetryoutputinelastic">Application OpenTelemetry output in Elastic</h2>
<h3 id="chatbotragapp">Chatbot-rag-app</h3>
<p>The first item that you will need to get up and running is the ChatBotApp, and once up you should see the following:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdd100775bedb7fa2/6a7f0f2be3a2190cf999f57e/Chatbotapp-general.png" alt="Chatbot app main page" /></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt68a2dbea6d8f3991/6a7f0f2e4c4bfb9fbaccd603/Chatbotapp-details.png" alt="Chatbot app working" /></p>
<p>As you select some of the questions you will set a response based on the index that was created in Elasticsearch when the app initializes. Additionally there will be queries that are made to LLMs.</p>
<h3 id="traceslogsandmetricsfromedotinelastic">Traces, logs, and metrics from EDOT in Elastic</h3>
<p>Once you have the application running on your K8s cluster or with Docker, and Elastic Cloud up and running you should see the following:</p>
<h4 id="logs">Logs:</h4>
<p>In Discover you will see logs from the Chatbotapp, and be able to analyze the application logs, any specific log patterns, which saves you time in analysis.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt29660a76fd00a49c/6a7f0f316c6eac6076f14207/chatbot-reg-logs.png" alt="Chatbot-logs" /></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdc9a84779ba0d561/6a7f0f34ea068d10eff09f64/chatbot-reg-logs-patterns.png" alt="Chatbot-log-patterns" /></p>
<h4 id="traces">Traces:</h4>
<p>In Elastic Observability APM, you can also see tha chatbot details, which include transactions, dependencies, logs, errors, etc.</p>
<p>When you look at traces, you will be able to see the chatbot interactions in the trace.</p>
<ol>
<li><p>You will see the end to end http call</p></li>
<li><p>Individual calls to elasticsearch</p></li>
<li><p>Specific calls such as invoke actions, and calls to the LLM</p></li>
</ol>
<p>You can also get individual details of the traces, and look at related logs, and metrics related to that trace,</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt810b06d98a833d78/6a7f0f376693f8036f664023/chatbot-reg-trace.png" alt="CHatbot-traces" /></p>
<h4 id="metrics">Metrics:</h4>
<p>In addition to logs, and traces, any instrumented metrics will also get ingested into Elastic.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt16786031635f9d06/6a7f0f3a448e4eedc45c0803/chatbot-reg-metrics.png" alt="Chatbot app metrics" /></p>
<h2 id="settingitallupwithdocker">Setting it all up with Docker</h2>
<p>In order to properly set up the Chatbot-app on Docker with telemetry sent over to Elastic, a few things must be set up:</p>
<ol>
<li><p>Git clone the chatbot-rag-app</p></li>
<li><p>Modify the env file as noted in the github README with the following exception:</p></li>
</ol>
<p>Use your Elastic cloud's <code>OTEL_EXPORTER_OTLP_ENDPOINT</code> and <code>OTEL_EXPORTER_OTLP_HEADER</code> instead.</p>
<p>You can find these in the Elastic Cloud under <code>integrations-&gt;APM</code></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3720f7995587366b/6a7f0f3d3cab1c13600e494c/otel-credentials.png" alt="OTel credentials" /></p>
<p>Envs for sending the OTel instrumentation you will need the following:</p>
<pre><code>OTEL_EXPORTER_OTLP_ENDPOINT="https://123456789.apm.us-west-2.aws.cloud.es.io:443"
OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer%20xxxxx"
</code></pre>
<p>Notice the <code>%20</code> in the headers. This will be needed to account for the space in credentials.</p>
<ol>
<li><p>Set the following to false - <code>OTEL_SDK_DISABLED=false</code></p></li>
<li><p>Set the envs for LLMs </p></li>
</ol>
<p>In this example we're using OpenAI, hence only three variables are needed.</p>
<pre><code>LLM_TYPE=openai
OPENAI_API_KEY=XXXX
CHAT_MODEL=gpt-4o-mini
</code></pre>
<ol>
<li>Run the docker container as noted </li>
</ol>
<pre><code>docker compose up --build --force-recreate
</code></pre>
<ol>
<li><p>Play with the app at <code>localhost:4000</code></p></li>
<li><p>Then log into Elastic cloud and see the output as shown previously.</p></li>
</ol>
<h2 id="runchatbotragapponkubernetes">Run chatbot-rag-app on Kubernetes</h2>
<p>In order to set this up, you can follow the following repo on Observability-examples which has the Kubernetes yaml files being used. These will also point to Elastic Cloud.</p>
<ol>
<li><p>Set up the Kubernetes Cluster (we're using EKS)</p></li>
<li><p>Get the appropriate ENV variables:</p></li>
</ol>
<ul>
<li><p>Find the <code>OTEL_EXPORTER_OTLP_ENDPOINT/HEADER</code> variables as noted in the pervious for Docker.</p></li>
<li><p>Get your OpenAI Key</p></li>
<li><p>Elasticsearch URL, and username and password.</p></li>
</ul>
<ol>
<li>Follow the instructions in the following <a href="https://github.com/elastic/observability-examples/tree/main/chatbot-rag-app-observability">github repo in observability examples</a> to run two Kubernetes yaml files.</li>
</ol>
<p>Essentially you need only replace the secret variables in k8s-deployment.yaml, and run</p>
<pre><code>kubectl create -f k8s-deployment.yaml
kubectl create -f init-index-job.yaml
</code></pre>
<p>The app needs to be running first, then we use the app to initialize Elasticsearch with indices for the app.</p>
<p><strong><em>Init-index-job.yaml</em></strong></p>
<pre><code>apiVersion: batch/v1
kind: Job
metadata:
  name: init-elasticsearch-index-test
spec:
  template:
    spec:
      containers:
      - name: init-index
        image: ghcr.io/elastic/elasticsearch-labs/chatbot-rag-app:latest
        workingDir: /app/api
        command: ["python3", "-m", "flask", "--app", "app", "create-index"]
        env:
        - name: FLASK_APP
          value: "app"
        - name: LLM_TYPE
          value: "openai"
        - name: CHAT_MODEL
          value: "gpt-4o-mini"
        - name: ES_INDEX
          value: "workplace-app-docs"
        - name: ES_INDEX_CHAT_HISTORY
          value: "workplace-app-docs-chat-history"
        - name: ELASTICSEARCH_URL
          valueFrom:
            secretKeyRef:
              name: chatbot-regular-secrets
              key: ELASTICSEARCH_URL
        - name: ELASTICSEARCH_USER
          valueFrom:
            secretKeyRef:
              name: chatbot-regular-secrets
              key: ELASTICSEARCH_USER
        - name: ELASTICSEARCH_PASSWORD
          valueFrom:
            secretKeyRef:
              name: chatbot-regular-secrets
              key: ELASTICSEARCH_PASSWORD
        envFrom:
        - secretRef:
            name: chatbot-regular-secrets
      restartPolicy: Never
  backoffLimit: 4
</code></pre>
<p><strong><em>k8s-deployment.yaml</em></strong></p>
<pre><code>apiVersion: v1
kind: Secret
metadata:
  name: chatbot-regular-secrets
type: Opaque
stringData:
  ELASTICSEARCH_URL: "https://yourelasticcloud.es.us-west-2.aws.found.io"
  ELASTICSEARCH_USER: "elastic"
  ELASTICSEARCH_PASSWORD: "elastic"
  OTEL_EXPORTER_OTLP_HEADERS: "Authorization=Bearer%20xxxx"
  OTEL_EXPORTER_OTLP_ENDPOINT: "https://12345.apm.us-west-2.aws.cloud.es.io:443"
  OPENAI_API_KEY: "YYYYYYYY"

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: chatbot-regular
spec:
  replicas: 2
  selector:
    matchLabels:
      app: chatbot-regular
  template:
    metadata:
      labels:
        app: chatbot-regular
    spec:
      containers:
      - name: chatbot-regular
        image: ghcr.io/elastic/elasticsearch-labs/chatbot-rag-app:latest
        ports:
        - containerPort: 4000
        env:
        - name: LLM_TYPE
          value: "openai"
        - name: CHAT_MODEL
          value: "gpt-4o-mini"
        - name: OTEL_RESOURCE_ATTRIBUTES
          value: "service.name=chatbot-regular,service.version=0.0.1,deployment.environment=dev"
        - name: OTEL_SDK_DISABLED
          value: "false"
        - name: OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT
          value: "true"
        - name: OTEL_EXPERIMENTAL_RESOURCE_DETECTORS
          value: "process_runtime,os,otel,telemetry_distro"
        - name: OTEL_EXPORTER_OTLP_PROTOCOL
          value: "http/protobuf"
        - name: OTEL_METRIC_EXPORT_INTERVAL
          value: "3000"
        - name: OTEL_BSP_SCHEDULE_DELAY
          value: "3000"
        envFrom:
        - secretRef:
            name: chatbot-regular-secrets
        resources:
          requests:
            memory: "512Mi"
            cpu: "250m"
          limits:
            memory: "1Gi"
            cpu: "500m"

---
apiVersion: v1
kind: Service
metadata:
  name: chatbot-regular-service
spec:
  selector:
    app: chatbot-regular
  ports:
  - port: 80
    targetPort: 4000
  type: LoadBalancer
</code></pre>
<p><strong>Open App with LoadBalancer URL</strong></p>
<p>Run the kubectl get services command and get the URL for the chatbot app</p>
<pre><code>% kubectl get services
NAME                                 TYPE           CLUSTER-IP    EXTERNAL-IP                                                               PORT(S)                                                                     AGE
chatbot-regular-service            LoadBalancer   10.100.130.44    xxxxxxxxx-1515488226.us-west-2.elb.amazonaws.com   80:30748/TCP                                                                6d23h
</code></pre>
<ol>
<li><p>Play with app and review telemetry in Elastic</p></li>
<li><p>Once you go to the URL, you should see all the screens we described earlier in the beginning of this blog.</p></li>
</ol>
<h2 id="conclusion">Conclusion</h2>
<p>With Elastic's Chatbot-rag-app you have an example of how to build out a OpenAI driven RAG based chat application. However, you still need to understand how well it performs, whether its working properly, etc. Using OTel and Elastic’s EDOT gives you the ability to achieve this. Additionally, you will generally run this application on Kubernetes. Hopefully this blog provides the outline of how to achieve this.
Here are the other Tracing blogs:</p>
<p>App Observability with LLM (Tracing)- </p>
<ul>
<li><p><a href="https://www.elastic.co/observability-labs/blog/elastic-opentelemetry-langchain-tracing-langtrace">Observing LangChain with Langtrace and OpenTelemetry</a></p></li>
<li><p><a href="https://www.elastic.co/observability-labs/blog/elastic-opentelemetry-langchain-openlit-tracing">Observing LangChain with OpenLit Tracing</a></p></li>
<li><p><a href="https://www.elastic.co/observability-labs/blog/elastic-opentelemetry-langchain-tracing">Instrumenting LangChain with OpenTelemetry</a> </p></li>
</ul>
<p>LLM Observability - </p>
<ul>
<li><p><a href="https://www.elastic.co/observability-labs/blog/elevate-llm-observability-with-gcp-vertex-ai-integration">Elevate LLM Observability with GCP Vertex AI Integration</a></p></li>
<li><p><a href="https://www.elastic.co/observability-labs/blog/llm-observability-aws-bedrock">LLM Observability on AWS Bedrock</a></p></li>
<li><p><a href="https://www.elastic.co/observability-labs/blog/llm-observability-azure-openai">LLM Observability for Azure OpenAI</a></p></li>
<li><p><a href="https://www.elastic.co/observability-labs/blog/llm-observability-azure-openai-v2">LLM Observability for Azure OpenAI v2</a></p></li>
</ul>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/openai-tracing-elastic-opentelemetry</link>
    <guid isPermaLink="false">openai-tracing-elastic-opentelemetry</guid>
    <category><![CDATA[LLM Observability]]></category>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[Metrics]]></category>
    <category><![CDATA[APM]]></category>
    <category><![CDATA[Kubernetes]]></category>
    <category><![CDATA[Infrastructure Monitoring]]></category>
    <dc:creator><![CDATA[Bahubali Shetti]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt12854c40fcaa0e97/6a7f0f406c6eac23bbf1420f/edot-openai-tracing.jpg" length="0" type="image/jpeg"/>
    <pubDate>Fri, 24 Jan 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Instrumenting your OpenAI-powered Python, Node.js, and Java Applications with EDOT]]></title>
    <description><![CDATA[Elastic is proud to introduce OpenAI support in our Python, Node.js and Java EDOT SDKs. These add logs, metrics and tracing to applications that use OpenAI compatible services without any code change.]]></description>
    <content:encoded><![CDATA[<h2 id="introduction">Introduction</h2>
<p>Last year, <a href="https://www.elastic.co/blog/elastic-distributions-opentelemetry">we announced Elastic Distribution of OpenTelemetry</a> (a.k.a. EDOT) language SDKs, which collect logs, traces and metrics from applications. When this was announced, we didn’t yet support Large Language Model (LLM) providers such as OpenAI. This limited insight developers had into Generative AI (GenAI) applications.</p>
<p>In a <a href="https://www.elastic.co/observability-labs/blog/elastic-opentelemetry-langchain-tracing-langtrace">prior post</a>, we reviewed LLM observability focus, such as token usage, chat latency and knowing which tools (like DuckDuckGo) your application uses. With the right logs, traces and metrics, developers can answer questions like "Which version of a model generated this response?" or "What was the exact chat prompt created by my RAG application?"</p>
<p>In the last six months, Elastic invested a lot of energy alongside others in the OpenTelemetry community towards shared specifications on these areas, including code to collect LLM related logs, metrics and traces. Our goal was to extend the zero code (agent) approach EDOT brings to GenAI use cases.</p>
<p>Today, we announce our first GenAI instrumentation capability in the EDOT language SDKs: OpenAI. Below, you’ll see how to observe GenAI applications using our Python, Node.js and Java EDOT SDKs.</p>
<h2 id="exampleapplication">Example application</h2>
<p>Many of us may be familiar with <a href="https://chatgpt.com/">ChatGPT</a>, which is frontend for OpenAI’s GPT model family. Using this, you can ask a question and the assistant might reply correctly depending on what you ask and text the LLM was trained on.</p>
<p>Here’s an example of an esoteric question answered by ChatGPT:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltedb3a50448470314/6a7f08cbbd21986162757f1f/chatgpt-screenshot.png" alt="ChatGPT answer" /></p>
<p>Our example application will simply ask this predefined question and print the result. We’ll write it in three languages: Python, JavaScript and Java.</p>
<p>We’ll execute each with a "zero code" (agent) approach, so that logs, metrics and traces are captured and visible in an Elastic Stack configured with Kibana and APM server. If you don’t have a stack running, use <a href="https://github.com/elastic/elasticsearch-labs/tree/main/docker">instructions from Elasticsearch Labs</a> to set one up.</p>
<p>Regardless of programming language, three variables are needed: the OpenAI API key, the location of your Elastic APM server, and the service name of the application. You’ll write these to a file named <code>.env</code>.</p>
<pre><code>OPENAI_API_KEY=sk-YOUR_API_KEY
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:8200
OTEL_SERVICE_NAME=openai-example
</code></pre>
<p>By default instrumentations does not capture the content sent to the OpenAI API in the GenAI events sent to logs, if you want to capture it add the following:</p>
<pre><code>OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT=true
</code></pre>
<p>Each time the application is run, it sends logs, traces and metrics to the APM server, which you can find by querying Kibana like this for the application "openai-example"</p>
<p>http://localhost:5601/app/apm/services/openai-example/transactions</p>
<p>When you choose a trace, you’ll see the LLM request made by the OpenAI SDK, and HTTP traffic caused by it:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5987344c4d68ea32/6a7f08cf96b5a6107687b2cd/kibana-transaction-timeline.png" alt="Kibana transaction timeline" /></p>
<p>Select the logs tab to see the exact request and response to OpenAI. This data is critical for Q/A and evaluation use cases.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta35f22e88805b0ee/6a7f08d24c4bfbe008ccd395/kibana-transaction-logs.png" alt="Kibana transaction logs" /></p>
<p>You can also go to the Metrics Explorer and make a graph of "gen_ai.client.token.usage" or "gen_ai.client.operation.duration" over all the times you ran the application:</p>
<p>http://localhost:5601/app/metrics/explorer</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0561523ff7bc4b29/6a7f08d51967eac5b5330593/kibana-metrics-explorer.png" alt="Kibana Metrics Explorer" /></p>
<p>Continue to see exactly how this application looks and is run, in Python, Java and Node.js. Those already using our EDOT language SDKs will be familiar with how this works.</p>
<h2 id="python">Python</h2>
<p>Assuming you have python installed, the first thing would be to setup a virtual environment and install the required packages: the OpenAI client, a helper tool to read the <code>.env</code> file and our <a href="https://github.com/elastic/elastic-otel-python">EDOT Python</a> package:</p>
<pre><code>python3 -m venv .venv
source .venv/bin/activate
pip install openai "python-dotenv[cli]" elastic-opentelemetry
</code></pre>
<p>Next, run <code>edot-bootstrap</code> which analyzes the code to install any relevant instrumentation available:</p>
<pre><code>edot-bootstrap —-action=install
</code></pre>
<p>Now, create your <code>.env</code>file, as described earlier in this article, and the below source code in <code>chat.py</code></p>
<pre><code>import os

import openai

CHAT_MODEL = os.environ.get("CHAT_MODEL", "gpt-4o-mini")


def main():
  client = openai.Client()

  messages = [
    {
      "role": "user",
        "content": "Answer in up to 3 words: Which ocean contains Bouvet Island?",
    }
  ]

  chat_completion = client.chat.completions.create(model=CHAT_MODEL, messages=messages)
  print(chat_completion.choices[0].message.content)

if __name__ == "__main__":
  main()
</code></pre>
<p>Now you can run everything with:</p>
<pre><code>dotenv run -- opentelemetry-instrument python chat.py
</code></pre>
<p>Finally, look for a trace for the service named "openai-example" in Kibana. You should see a transaction named "chat gpt-4o-mini".</p>
<p>Rather than copy/pasting above, you can find a working copy of this example (along with the instructions) in the Python EDOT repository <a href="https://github.com/elastic/elastic-otel-python/tree/main/examples/openai">here</a>.</p>
<p>Finally, if you would like to try a more comprehensive example, take a look at <a href="https://github.com/elastic/elasticsearch-labs/tree/main/example-apps/chatbot-rag-app">chatbot-rag-app</a> which uses OpenAI with Elasticsearch’s <a href="https://www.elastic.co/guide/en/machine-learning/current/ml-nlp-elser.html">Elser</a> retrieval model.</p>
<h2 id="java">Java</h2>
<p>There are multiple popular ways to initialize a Java project. Since we are using OpenAI, the first step is to configure the dependency <a href="https://central.sonatype.com/artifact/com.openai/openai-java"><code>com.openai:openai-java</code></a> and write the below source as <code>Chat.java.</code></p>
<pre><code>package openai.example;

import com.openai.client.OpenAIClient;
import com.openai.client.okhttp.OpenAIOkHttpClient;
import com.openai.models.*;


final class Chat {

  public static void main(String[] args) {
    String chatModel = System.getenv().getOrDefault("CHAT_MODEL", "gpt-4o-mini");

    OpenAIClient client = OpenAIOkHttpClient.fromEnv();

    String message = "Answer in up to 3 words: Which ocean contains Bouvet Island?";
    ChatCompletionCreateParams params = ChatCompletionCreateParams.builder()
        .addMessage(ChatCompletionUserMessageParam.builder()
          .content(message)
          .build())
        .model(chatModel)
        .build();

    ChatCompletion chatCompletion = client.chat().completions().create(params);
    System.out.println(chatCompletion.choices().get(0).message().content().get());
  }
}
</code></pre>
<p>Build the project such that all dependencies are in a single jar. For example, if using Gradle, you would use the <code>com.gradleup.shadow</code>plugin.</p>
<p>Next, create your <code>.env</code>file, as described earlier, and download shdotenv which we’ll use to load it.</p>
<pre><code>curl -O -L https://github.com/ko1nksm/shdotenv/releases/download/v0.14.0/shdotenv
chmod +x ./shdotenv
</code></pre>
<p>At this point, you have a jar and configuration you can use to run the OpenAI example. The next step is to download the EDOT Java javaagent binary. This is the part that records and exports logs, metrics and traces.</p>
<pre><code>curl -o elastic-otel-javaagent.jar -L 'https://oss.sonatype.org/service/local/artifact/maven/redirect?r=snapshots&amp;g=co.elastic.otel&amp;a=elastic-otel-javaagent&amp;v=LATEST'
</code></pre>
<p>Assuming you assembled a file named <code>openai-example-all.jar</code>, run it with EDOT like this:</p>
<pre><code>./shdotenv java -javaagent:elastic-otel-javaagent.jar -jar openai-example-all.jar
</code></pre>
<p>Finally, look for a trace for the service named "openai-example" in Kibana. You should see a transaction named "chat gpt-4o-mini".</p>
<p>Rather than copy/pasting above, you can find a working copy of this example in the EDOT Java source repository <a href="https://github.com/elastic/elastic-otel-java/tree/main/examples/openai">here</a>.</p>
<h2 id="nodejs">Node.js</h2>
<p>Assuming you already have npm installed and configured, run the following commands to initialize a project for the example. This includes the <a href="https://www.npmjs.com/package/openai">openai</a> package and <a href="https://www.npmjs.com/package/@elastic/opentelemetry-node"><code>@elastic/opentelemetry-node</code></a> (EDOT Node.js)</p>
<pre><code>npm init -y
npm install openai @elastic/opentelemetry-node
</code></pre>
<p>Next, create your <code>.env</code> file, as described earlier in this article and the below source code in <code>index.js</code></p>
<pre><code>const {OpenAI} = require('openai');

let chatModel = process.env.CHAT_MODEL ?? 'gpt-4o-mini';

async function main() {
 const client = new OpenAI();
 const completion = await client.chat.completions.create({
  model: chatModel,
  messages: [
   {
    role: 'user',
    content: 'Answer in up to 3 words: Which ocean contains Bouvet Island?',
   },
  ],
 });
 console.log(completion.choices[0].message.content);
}

main();
</code></pre>
<p>With this in place, run the above source with EDOT like this:</p>
<pre><code>node --env-file .env --require @elastic/opentelemetry-node index.js
</code></pre>
<p>Finally, look for a trace for the service named "openai-example" in Kibana. You should see a transaction named "chat gpt-4o-mini".</p>
<p>Rather than copy/pasting above, you can find a working copy of this example in the EDOT Node.js source repository <a href="https://github.com/elastic/elastic-otel-node/tree/main/examples/openai">here</a>.</p>
<p>Finally, if you would like to try a more comprehensive example, take a look at <a href="https://github.com/elastic/elasticsearch-labs/tree/main/example-apps/openai-embeddings">openai-embeddings</a> which uses OpenAI with Elasticsearch as a vector database!</p>
<h2 id="closingnotes">Closing Notes</h2>
<p>Above you’ve seen how to observe the official OpenAI SDK in three different languages, using Elastic Distribution of OpenTelemetry (EDOT).</p>
<p>It is important to note that some of the OpenAI SDKs and also OpenTelemetry specifications around generative AI are experimental. If you find this helps you, or find glitches, please join our slack and let us know about it.</p>
<p>Several LLM platforms accept requests from the OpenAI client SDK, by setting <code>OPENAI_BASE_URL</code> and choosing relevant models. During development, we tested against OpenAI Platform and Azure OpenAI Service. We also ran integration tests against Ollama, contributing improvements its OpenAI support released in v0.5.12. Whatever your choice of OpenAI compatible platform, we hope this new tooling helps you understand your LLM usage.</p>
<p>Finally, while the first Generative AI SDK instrumented with EDOT is OpenAI, you’ll see more soon. We are already working on Bedrock, and collaborating with others in the OpenTelemetry community for other platforms. Keep watching this blog for exciting updates.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/elastic-opentelemetry-openai</link>
    <guid isPermaLink="false">elastic-opentelemetry-openai</guid>
    <category><![CDATA[LLM Observability]]></category>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[Metrics]]></category>
    <dc:creator><![CDATA[Adrian Cole]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt43126fbb328992ce/6a84041c5751aa67087e402a/elastic-opentelemetry-openai.png" length="0" type="image/png"/>
    <pubDate>Thu, 23 Jan 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Assembling an OpenTelemetry NGINX Ingress Controller Integration]]></title>
    <description><![CDATA[This blog post explores how to set up an OpenTelemetry integration for the NGINX Ingress Controller, detailing the configuration process, key transformations, and upcoming enhancements for modular configuration support.]]></description>
    <content:encoded><![CDATA[<p>Our vision is clear: to support OpenTelemetry within Elastic. A key aspect of
this transition are integrations — how can we seamlessly adapt all existing
integrations to fit the OpenTelemetry model?</p>
<p>Elastic integrations are designed to simplify observability by providing tools
to ingest application data, process it through Ingest pipelines, and deliver
prebuilt dashboards for visualization. With OpenTelemetry support, data
collection and processing will transition to the OpenTelemetry Collector, while
dashboards will need to adopt the OpenTelemetry data structure.</p>
<h2 id="fromalogtoanintegration">From a Log to an Integration</h2>
<p>Although the concept of an OpenTelemetry Integration has not yet been officially
defined, we envision it as a structured collection of artifacts that enables users
to start monitoring an application from scratch. Each artifact has a specific role;
for example, an OpenTelemetry Collector configuration file, which must be
integrated into the main Collector setup. This bundled configuration instructs
the Collector on how to gather and process data from the relevant application.</p>
<p>In the OpenTelemetry Collector, data collection is handled by the <a href="https://opentelemetry.io/docs/collector/configuration/#receivers">receivers</a>
component. Some receivers are tailored for specific applications, such as Kafka
or MySQL, while others are designed to support general data collection methods.
The specialized receivers combine data gathering and transformation within a
single component. For the more generic receivers, however, additional components
are needed to refine and transform the incoming data into a more
application-specific format. Let’s take a look at how we can build an
integration for monitoring a Nginx Ingress Controller.</p>
<p>The Ingress Nginx is an Ingress controller for Kubernetes, using NGINX as a
reverse proxy and load balancer. Widely adopted, it plays a crucial role in
directing external traffic into Kubernetes services, making its usage,
performance and health essential to observe. How can we start observing the external
requests done to our Ingress controller? Fortunately, the NGINX Ingress Controller
generates a structured log entry for each processed request. This structured
format ensures that each log entry follows a consistent structure, making it
straightforward to parse and generate consistent output.</p>
<pre><code>log_format upstreaminfo '$remote_addr - $remote_user [$time_local]
    "$request" ' '$status $body_bytes_sent "$http_referer" "$http_user_agent" '
    '$request_length $request_time [$proxy_upstream_name]
    [$proxy_alternative_upstream_name] $upstream_addr ' '$upstream_response_length
    $upstream_response_time $upstream_status $req_id';
</code></pre>
<p>All the field's definition can be found
<a href="https://github.com/kubernetes/ingress-nginx/blob/controller-v1.11.3/docs/user-guide/nginx-configuration/log-format.md">here</a>.</p>
<p>The OpenTelemetry Contrib Collector does not include a receiver capable of
reading and parsing all fields in an NGINX Ingress log. There are two primary
reasons for this:</p>
<ul>
<li><strong>Application Diversity</strong>: The landscape of applications is vast, with each
generating logs in unique formats. Developing and maintaining a dedicated
receiver for every application would be resource-intensive and difficult to
scale.</li>
<li><strong>Data Source Flexibility</strong>: Receivers are typically designed to collect data
from a specific source, like an HTTP endpoint. However, in some cases, we
may want to parse logs from an alternate source, such as an NGINX Ingress
log file stored in an AWS S3 bucket.</li>
</ul>
<p>These challenges can be addressed by combining receivers and processors.
Receivers handle the collection of raw data, while processors can extract
specific values when a known data structure is detected. Do we need a dedicated
processor to parse NGINX logs? Not necessarily. The transform processor can
handle this by modifying telemetry data according to a specified configuration.
This configuration is written in the OpenTelemetry Transformation Language
(OTTL), a language for transforming open telemetry data based on the
<a href="https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/rfcs/processing.md">OpenTelemetry Collector Processing
Exploration</a>.</p>
<p>The concept of processors in OpenTelemetry is quite similar to the Ingest
pipeline strategy currently used in Elastic integrations. The main challenge,
therefore, lies in migrating Ingest pipeline configurations to OpenTelemetry
Collector configurations. For a deeper dive into the challenges of such
migrations, check out this
<a href="https://www.elastic.co/observability-labs/blog/logstash-to-otel">article</a>.</p>
<p>For reference, you can view the current Elastic NGINX Ingress
Controller Ingest pipeline configuration in the following link: <a href="https://github.com/elastic/integrations/blob/main/packages/nginx_ingress_controller/data_stream/access/elasticsearch/ingest_pipeline/default.yml">Elastic NGINX
Ingress Controller Ingest
Pipeline</a>.</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>Let’s start with the data collection. By default, the NGINX Ingress Controller
logs to stdout, and Kubernetes captures and stores these logs in a file.
Assuming that the
OpenTelemetry Collector running the following configuration has access to the
Kubernetes Pod logs, we can use the <a href="https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/receiver/filelogreceiver">filelog
receiver</a>
to read the controller logs:</p>
<pre><code>receivers:
  filelog/nginx:
    include_file_path: true
    include: [/var/log/pods/*nginx-ingress-nginx-controller*/controller/*.log]
    operators:
      - id: container-parser
        type: container
</code></pre>
<p>This configuration is designed to exclusively read the controller's pod logs,
focusing on their default file path within a Kubernetes node. Furthermore, since
the Ingress controller does not inherently have access to its associated
Kubernetes metadata, the <code>container-parser</code> operator has been implemented to
bridge this gap. This operator appends Kubernetes-specific attributes, such as
<code>k8s.pod.name</code> and <code>k8s.namespace.name</code>, based solely on information available
from the filename. For a detailed overview of the <code>container-parser</code> operator, see
the following <a href="https://opentelemetry.io/blog/2024/otel-collector-container-log-parser/">OpenTelemetry blog
post</a>.</p>
<h3 id="avoidingduplicatedlogs">Avoiding duplicated logs</h3>
<p>The configuration outlined in this blog is designed for Kubernetes environments,
where the collector runs as a Kubernetes Pod. In such setups, handling Pod
restarts properly is crucial. By default, the <code>filelog</code> receiver reads the entire
content of log files on startup. This behavior can lead to duplicate log entries
being reprocessed and sent through the pipeline if the collector Pod is
restarted.</p>
<p>To make the configuration resilient to restarts, you can use a storage extension
to track file offsets. These offsets allow the <code>filelog</code> receiver to resume
reading from the last processed position in the log file after a restart. Below
is an example of how to add a <a href="https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/extension/storage/filestorage">file storage extension</a> and update the <code>filelog</code>
receiver configuration to store the offsets in a file:</p>
<pre><code>extensions:
  file_storage:
  directory: /var/lib/otelcol

receivers:
  filelog/nginx:
    storage: file_storage
    ...
</code></pre>
<p><strong>Important</strong>: The /var/lib/otelcol directory must be mounted as part of a
Kubernetes persistent volume to ensure the stored offsets persist across Pod
restarts.</p>
<h3 id="datatransformationwithopentelemetryprocessors">Data transformation with OpenTelemetry processors</h3>
<p>Now it’s time to parse the structured log fields and transform them into
queryable OpenTelemetry fields. Initially, we considered using regular
expressions with the <a href="https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/pkg/ottl/ottlfuncs#extract_patterns">extract_patterns
function</a>
available in the OpenTelemetry Transformation Language (OTTL). However, Elastic
recently contributed a new OTTL function,
<a href="https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/pkg/ottl/ottlfuncs#extractgrokpatterns">ExtractGrokPatterns</a>,
based on Grok—a regular expression dialect that supports reusable, aliased
expressions. The function’s underlying library <a href="https://github.com/elastic/go-grok">Elastic
Go-Grok</a> ships with numerous predefined grok
patterns that simplify working with pattern matching, like <code>%NUMBER</code>
that will match any number type; "123", "456.789", "-0.123".</p>
<p>Each Ingress Controller log entry begins with the client's source IP address
(which may be a single IP or a list of IPs) and the username provided via Basic
authentication, represented as “$remote_addr - $remote_user”. The Grok IP alias
can be used to parse either an IPv4 or IPv6 address from the remote_addr field,
while the <code>%GREEDYDATA</code> alias can capture the remote_user value.</p>
<p>For example, the following OTTL configuration will transform an unstructured
body message to a structured one with two fields:</p>
<ul>
<li>Parses a single IP address and assign it to the source.address key.</li>
<li>Delimited by a “-”, captures the optional value of the authenticated username
in the <code>user.name</code> key.</li>
</ul>
<pre><code>transform/parse_nginx_ingress_access/log:
  log_statements:
    - context: log
      statements:
        - set(body, ExtractGrokPatterns(body, "%{IP:source.address} - (-|%{GREEDYDATA:user.name})", true))
</code></pre>
<p>The screenshot below illustrates the transformation process, showing the
original input data alongside the resulting structured format (diff):</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf1c84f9d0271011b/6a7f02e833fa8a181e20223d/data-diff.png" alt="data-transform-diff" /></p>
<p>In real-world scenarios, NGINX Ingress Controller logs may begin with a list of
IP addresses or, at times, a domain name. These variations can be handled with
an extended Grok pattern. Similarly, we can use Grok to parse an HTTP UserAgent
and URL strings, but additional OTTL functions, such as
<a href="https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/pkg/ottl/ottlfuncs#url">URL</a>
or
<a href="https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/pkg/ottl/ottlfuncs#useragent">UserAgent</a>,
are required to extract meaningful data from these fields.</p>
<p>The complete configuration is available in the documentation for Elastic’s
OpenTelemetry NGINX Ingress Controller integration: <a href="https://github.com/elastic/integrations/blob/main/packages/nginx_ingress_controller_otel/docs/README.md">Integration
Documentation</a>.</p>
<h2 id="usage">Usage</h2>
<p>The Elastic OpenTelemetry NGINX Ingress Controller is currently on <strong>Technical
preview</strong>. To access it, you must enable the "Display beta integrations" toggle
in the <strong>Integrations</strong> menu within Kibana.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltfb3ad090d9931319/6a7f02ebb43770063c4d6867/kibana-integration.png" alt="kibana-nginx-integration" /></p>
<p>By installing the Elastic OpenTelemetry NGINX Ingress Controller integration, a
couple of dashboards will become available in your Kibana profile. One of these
dashboards provides insights into access events for the controller, displaying
information such as HTTP response status codes over time, request volume per
URL, distribution of incoming requests by browser, top requested pages, and
more. The screenshot below shows the NGINX Ingress Controller Access Logs
dashboard, displaying data from a controller routing requests to an
<a href="https://github.com/elastic/opentelemetry-demo">OpenTelemetry Demo</a> deployment:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb3ce0992bb121156/6a7f02eeead8ec75d5baa39f/main-dashboard.png" alt="nginx-ingress-controller-otel-access-dashboard" /></p>
<p>The second dashboard focuses on errors within the Nginx Ingress controller, highlighting the
volume of error events generated over time:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt13ae13e034223d6e/6a7f02f1227b1ced1e598194/error-access-dashboard.png" alt="nginx-ingress-controller-otel-error-access-dashboard" /></p>
<p>To start gathering and processing controller logs, we recommend incorporating
the OpenTelemetry Collector pipeline outlined in the integration’s documentation
into your collector configuration: <a href="https://www.elastic.co/guide/en/integrations/current/nginx_ingress_controller_otel.html">Integration
Documentation</a>.
Keep in mind that this configuration requires access to the Kubernetes node's
Pods logs,
typically stored in <code>/var/log/pods/*</code>. To ensure proper access, we recommend
deploying the OpenTelemetry Collector as a daemonset in Kubernetes, as this
deployment type allows the collector to access the necessary log directory on
each node.</p>
<p>The OpenTelemetry Collector configuration service pipeline should include a
similar configuration:</p>
<pre><code>service:
  extensions: [file_storage]
  pipelines:
    logs/nginx_ingress_controller:
      receivers:
        - filelog
      processors:
        - transform/parse_nginx_ingress_access/log
        - transform/parse_nginx_ingress_error/log
        - resourcedetection/system
      exporters:
        - elasticsearch
</code></pre>
<h3 id="addinggeoipmetadata">Adding GeoIP Metadata</h3>
<p>As an optional enhancement, the OpenTelemetry Collector <a href="https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/processor/geoipprocessor">GeoIP processor</a> can be configured and added to the pipeline to enrich each NGINX Ingress Controller log with geographical attributes, such as the request’s originating country, region, and city, enabling geo maps in Kibana to visualize traffic distribution and geographic patterns.</p>
<p>While the OpenTelemetry GeoIP processor is similar to <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/geoip-processor.html">Elastic's GeoIP
processor</a>,
it requires users to provide their own local GeoLite2 database. The following
configuration extends the Integration’s configuration to include the GeoIP
processor with a <a href="https://dev.maxmind.com/geoip/geolite2-free-geolocation-data/">MaxMind's database</a>.</p>
<pre><code>processors:
  geoip:
    context: record
    providers:
      maxmind:
        database_path: /tmp/GeoLite2-City.mmdb

service:
  extensions: [file_storage]
  pipelines:
    logs/nginx_ingress_controller:
      receivers:
        - filelog
      processors:
        - transform/parse_nginx_ingress_access/log
        - transform/parse_nginx_ingress_error/log
        - resourcedetection/system
        - geoip
      exporters:
        - elasticsearch
</code></pre>
<p>Sample Kibana Map with the OpenTelemetry Nginx Ingress Controller integration:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltfaac1676362f353d/6a7f02f4de23150e8bfd7726/geoip-map.png" alt="geoip-map-dashboard" /></p>
<h2 id="nextsteps">Next steps</h2>
<h3 id="opentelemetrylogevent">OpenTelemetry Log Event</h3>
<p>A closer look at the OTTL integration’s statements reveals that the raw log
message is replaced by the parsed fields. In other words, the configuration
transforms the body log field* from a string into a structured map of key-value
pairs, as seen in “set(body, ExtractGrokPatterns(body,…)”. This approach is
based on treating each NGINX Ingress Controller log entry as an <a href="https://opentelemetry.io/docs/specs/otel/logs/event-api/#event-data-model">OpenTelemetry
Event</a>—a
specialized type of LogRecord. Events are OpenTelemetry’s standardized semantic
formatting for LogRecords, containing an
“<a href="https://github.com/open-telemetry/semantic-conventions/blob/main/docs/general/events.md#event-definition">event.name</a>”
attribute which defines the structure of the body field. An NGINX Ingress
Controller log record aligns well with the OpenTelemetry Event data model. It
follows a structured format and clearly distinguishes between two event types:
access logs and error logs. There is an ongoing PR to incorporate the NGINX
Ingress controller log into the OpenTelemetry semantic convention:
https://github.com/open-telemetry/semantic-conventions/pull/982</p>
<h3 id="operatingsystembreakdown">Operating system breakdown</h3>
<p>Each controller log contains the source UserAgent, from which the integration
extracts the browser that originated the request. This information is valuable
for understanding user access patterns, as it provides insights into the types
of browsers commonly interacting with your services. Additionally, an <a href="https://github.com/open-telemetry/opentelemetry-collector-contrib/issues/35458">ongoing
pull
request</a>
into OTTL aims to extend this functionality by extracting operating system (OS)
details as well, providing even deeper insights into the environments
interacting with the NGINX Ingress Controller.</p>
<h3 id="configurationencapsulation">Configuration encapsulation</h3>
<p>Setting up the configuration for the NGINX Ingress Controller integration can be
somewhat tedious, as it involves adding several complex processor configurations
to the existing collector pipelines. This process can quickly become cumbersome,
especially for non-expert users or in cases where the collector configuration is
already quite complex. In an ideal scenario, users would simply reference a
pre-defined integration configuration, and the collector would automatically
"unwrap" all the necessary components into the corresponding pipelines. This
would significantly simplify the setup process, making it more accessible and
reducing the risk of misconfigurations. To address this, there is a
<a href="https://github.com/open-telemetry/opentelemetry-collector/pull/11631">RFC</a>
(Request for Comments) proposing support for shareable, modular configurations
within the OpenTelemetry Collector. This feature would allow users to easily
collect signals from specific services or applications by referencing modular
configurations, streamlining the setup and enhancing usability for complex
scenarios.</p>
<p>*The OpenTelemetry community is currently discussing whether structured
body-extracted information should be stored in the attributes or body field.
For details, see this <a href="https://github.com/open-telemetry/semantic-conventions/issues/1651">ongoing issue</a>.</p>
<blockquote>
  <p>This product includes GeoLite2 data created by MaxMind, available from https://www.maxmind.com</p>
</blockquote>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/assembling-an-opentelemetry-nginx-ingress-controller-integration</link>
    <guid isPermaLink="false">assembling-an-opentelemetry-nginx-ingress-controller-integration</guid>
    <category><![CDATA[OpenTelemetry]]></category>
    <dc:creator><![CDATA[Roger Coll]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3f19ad0ab3ff293a/6a7f02f76693f85a25663b37/ingress-controller.jpg" length="0" type="image/jpeg"/>
    <pubDate>Wed, 15 Jan 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Unlock possibilities with native OpenTelemetry: prioritize reliability, not proprietary limitations]]></title>
    <description><![CDATA[Elastic now supports Elastic Distributions of OpenTelemetry (EDOT) deployment and management on Kubernetes, using OTel Operator. SREs can now access out-of the-box configurations and dashboards designed to streamline collector deployment, application auto-instrumentation and lifecycle management with Elastic Observability.]]></description>
    <content:encoded><![CDATA[<p>OpenTelemetry (OTel) is emerging as the standard for data ingestion since it delivers a vendor-agnostic way to ingest data across all telemetry signals. Elastic Observability is leading the OTel evolution with the following announcements:</p>
<ul>
<li><p><strong>Native OTel Integrity:</strong> Elastic is now 100% OTel-native, retaining OTel data natively without requiring data translation This eliminates the need for SREs to handle tedious schema conversions and develop customized views. All Elastic Observability capabilities—such as entity discovery, entity-centric insights, APM, infrastructure monitoring, and AI-driven issue analysis— now seamlessly work with  native OTel data.</p></li>
<li><p><strong>Powerful end to end OTel based Kubernetes observability with</strong> <a href="https://www.elastic.co/observability-labs/blog/elastic-distributions-opentelemetry"><strong>Elastic Distributions of OpenTelemetry (EDOT)</strong></a><strong>:</strong> Elastic now supports EDOT deployment and management on Kubernetes via the OTel Operator, enabling streamlined EDOT collector deployment, application auto-instrumentation, and lifecycle management. With out-of-the-box OTel-based Kubernetes integration and dashboards, SREs gain instant, real-time visibility into cluster and application metrics, logs, and traces—with no manual configuration needed.</p></li>
</ul>
<p>For organizations, it signals our commitment to open standards, streamlined data collection, and delivering insights from native OpenTelemetry data. Bring the power of Elastic Observability to your Kubernetes and OpenTelemetry deployments for maximum visibility and performance. </p>
<h2 id="fullynativeotelarchitecturewithindepthdataanalysis">Fully native OTel architecture with in-depth data analysis</h2>
<p>Elastic’s OpenTelemetry-first architecture is 100% OTel-native, fully retaining the OTel data model, including OTel Semantic Conventions and Resource attributes, so your observability data remains in OpenTelemetry standards. OTel data in Elastic is also backward compatible with the Elastic Common Schema (ECS).</p>
<p>SREs now gain a holistic view of resources, as Elastic accurately identifies entities through OTel resource attributes. For example, in a Kubernetes environment, Elastic identifies containers, hosts, and services and connects these entities to logs, metrics, and traces.</p>
<p>Once OTel data is in Elastic’s scalable vector datastore, Elastic’s capabilities such as the AI Assistant, zero-config machine learning-based anomaly detection, pattern analysis, and latency correlation empower SREs to quickly analyze and pinpoint potential issues in production environments.</p>
<h2 id="kubernetesinsightswithelasticdistributionsofopentelemetryedot">Kubernetes insights with Elastic Distributions of OpenTelemetry (EDOT)</h2>
<p>EDOT reduces manual effort through automated onboarding and pre-configured dashboards. With EDOT and OpenTelemetry, Elastic makes Kubernetes monitoring straightforward and accessible for organizations of any size.</p>
<p>EDOT paired with Elasticsearch,  enables storage for all signal types—logs, metrics, traces, and soon profiling—while maintaining essential resource attributes and semantic conventions.</p>
<p>Elastic’s OpenTelemetry-native solution enables customers to quickly extract insights from their data rather than manage complex infrastructure to ingest data. Elastic automates the deployment and configuration of observability components to deliver a user experience focused on ease and scalability, making it well-suited for large-scale environments and diverse industry needs.</p>
<p>Let’s take a look at how Elastic’s EDOT enables visibility into Kubernetes environments.</p>
<h3 id="1simple3stepotelingestwithlifecyclemanagementandautoinstrumentationnbsp">1. Simple 3-step OTel ingest with lifecycle management and auto-instrumentation </h3>
<p>Elastic leverages the upstream OpenTelemetry Operator to automate its EDOT lifecycle management—including deployment, scaling, and updates—allowing customers to focus on visibility into their Kubernetes infrastructure and applications instead of their observability infrastructure for data collection.</p>
<p>The Operator integrates with the EDOT Collector and language SDKs to provide a consistent, vendor-agnostic experience. For instance, when customers deploy a new application, they don’t need to manually configure instrumentation for various languages; the OpenTelemetry Operator manages this through auto-instrumentation, as supported by the upstream OpenTelemetry project.</p>
<p>This integration simplifies observability by ensuring consistent application instrumentation across the Kubernetes environment. Elastic’s collaboration with the upstream OpenTelemetry project strengthens this automation, enabling users to benefit from the latest updates and improvements in the OpenTelemetry ecosystem. By relying on open source tools like the OpenTelemetry Operator, Elastic ensures that its solutions stay aligned with the latest advancements in the OpenTelemetry project, reinforcing its commitment to open standards and community-driven development.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt896dd27233a341a0/6a7f08c142a117bb2f95bd14/unified-otel-based-k8s-experience.png" alt="Unified OTel-based Kubernetes Experience" /></p>
<p>The diagram above shows how the operator can deploy multiple OTel collectors, helping SREs deploy individual EDOT Collectors for specific applications and infrastructure. This configuration improves availability for OTel ingest and the telemetry is sent directly to Elasticsearch servers via OTLP.</p>
<p><a href="https://www.elastic.co/observability-labs/blog/elastic-opentelemetry-otel-operator">Check out our recent blog on how to set this up</a>.</p>
<h3 id="2outoftheboxotelbasedkubernetesintegrationwithdashboards">2. Out-of-the-box OTel-based Kubernetes integration with dashboards</h3>
<p>Elastic delivers an OTel-based Kubernetes configuration for the OTel collector by packaging all necessary receivers, processors, and configurations for Kubernetes observability. This enables users to automatically collect, process, and analyze Kubernetes metrics, logs, and traces without the need to configure each component individually.</p>
<p>The OpenTelemetry Kubernetes Collector components provide essential building blocks, including receivers like the Kubernetes Receiver for cluster metrics, Kubeletstats Receiver for detailed node and container metrics, along with processors for data transformation and enrichment. By packaging these components, Elastic offers a turnkey solution that simplifies Kubernetes observability and eliminates the need for users to set up and configure individual collectors or processors.</p>
<p>This pre-packaged approach, which includes <a href="https://github.com/elastic/integrations/tree/main/packages/kubernetes_otel">OTel-native Kibana assets</a> such as dashboards, allows users to focus on analyzing their observability data rather than managing configuration details. Elastic’s Unified OpenTelemetry Experience ensures that users can harness OpenTelemetry’s full potential without needing deep expertise. Whether you’re monitoring resource usage, container health, or API server metrics, users gain comprehensive observability through EDOT.</p>
<p>For more details on OpenTelemetry Kubernetes Collector components, visit<a href="https://opentelemetry.io/docs/kubernetes/collector/components/"> OpenTelemetry Collector Components</a>.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc2f0763430cc1ad8/6a7f08c41967ea53bd330587/otel-based-k8s-dashboard.png" alt="OTel-based Kubernetes Dashboard" /></p>
<h3 id="3streamlinedingestarchitecturewithoteldataandelasticsearch">3. Streamlined ingest architecture with OTel data and Elasticsearch</h3>
<p>Elastic’s ingest architecture minimizes infrastructure overhead by enabling users to forward trace data directly into Elasticsearch with the EDOT Collector, removing the need for the Elastic APM server. This approach:</p>
<ul>
<li><p>Reduces the costs and complexity associated with maintaining additional infrastructure, allowing users to deploy, scale, and manage their observability solutions with fewer resources.</p></li>
<li><p>Allows all OTel data, metrics, logs, and traces to be ingested and stored in Elastic’s singular vector database store enabling further analysis with Elastic’s AI-driven capabilities.</p></li>
</ul>
<p>SREs can now reduce operational burdens while also gaining high performance analytics and observability insights provided by Elastic.</p>
<h2 id="elasticsongoingcommitmenttoopensourceandopentelemetry">Elastic’s ongoing commitment to open source and OpenTelemetry</h2>
<p>With <a href="https://www.elastic.co/blog/elasticsearch-is-open-source-again">Elasticsearch fully open source once again</a> under the AGPL license,  this change reinforces our deep commitment to open standards and the open source community. This aligns with Elastic’s OpenTelemetry-first approach to observability, where Elastic Distributions of OpenTelemetry (EDOT) streamline OTel ingestion and schema auto-detection, providing real-time insights for Kubernetes and application telemetry.</p>
<p>As users increasingly adopt OTel as their schema and data collection architecture for observability, Elastic’s Distribution of OpenTelemetry (EDOT), currently in tech preview, enhances standard OpenTelemetry capabilities and improves troubleshooting while also serving as a commercially supported OTel distribution. EDOT, together with Elastic’s recent contributions of the Elastic Profiling Agent and Elastic Common Schema (ECS) to OpenTelemetry, reinforces Elastic’s commitment to establishing OpenTelemetry as the industry standard.</p>
<p>Customers can now embrace open standards and enjoy the advantages of an open, extensible platform that integrates seamlessly with their environment. End result?  Reduced costs, greater visibility, and vendor independence.</p>
<h2 id="gettinghandsonwithelasticobservabilityandedot">Getting hands-on with Elastic Observability and EDOT</h2>
<p>Ready to try out the OTel Operator with EDOT collector and SDKs to see how Elastic utilizes ingested OTel data in APM, Discover, Analysis, and out-of-the-box dashboards? </p>
<ul>
<li><p><a href="https://cloud.elastic.co/">Get an account on Elastic Cloud</a></p></li>
<li><p><a href="https://www.elastic.co/observability-labs/blog/elastic-distributions-opentelemetry">Learn about Elastic Distributions of OpenTelemetry Overview</a></p></li>
<li><p><a href="https://www.elastic.co/observability-labs/blog/opentelemetry-demo-with-the-elastic-distributions-of-opentelemetry">Utilize the OpenTelemetry Demo with EDOT</a></p></li>
<li><p><a href="https://www.elastic.co/observability-labs/blog/infrastructure-monitoring-with-opentelemetry-in-elastic-observability">Understand how you can monitor Kubernetes with EDOT</a></p></li>
<li><p><a href="https://github.com/elastic/opentelemetry">Utilize the EDOT Operator </a>and the <a href="https://www.elastic.co/observability-labs/blog/elastic-distribution-opentelemetry-collector">EDOT OTel collector</a></p></li>
</ul>
<p>If you have your own application and want to configure EDOT the application with auto-instrumentation, read the following blogs on Go, Java, PHP, Python</p>
<ul>
<li><p><a href="https://www.elastic.co/observability-labs/blog/auto-instrumentation-go-applications-opentelemetry">Auto-Instrumenting Go Applications with OpenTelemetry</a></p></li>
<li><p><a href="https://www.elastic.co/observability-labs/blog/elastic-distribution-opentelemetry-java-agent">Elastic Distribution OpenTelemetry Java Agent</a></p></li>
<li><p><a href="https://www.elastic.co/observability-labs/blog/elastic-opentelemetry-distribution-php">Elastic OpenTelemetry Distribution for PHP</a></p></li>
<li><p><a href="https://www.elastic.co/observability-labs/blog/elastic-opentelemetry-distribution-python">Elastic OpenTelemetry Distribution for Python</a></p></li>
</ul>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/elastic-opentelemetry-native-kubernetes-observability</link>
    <guid isPermaLink="false">elastic-opentelemetry-native-kubernetes-observability</guid>
    <category><![CDATA[Kubernetes]]></category>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[Metrics]]></category>
    <dc:creator><![CDATA[Bahubali Shetti,Miguel Luna]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6f5ac53ca0bdff9f/6a7f08c7ead8ec5b79baa6c5/Kubecon-main-blog.jpg" length="0" type="image/jpeg"/>
    <pubDate>Tue, 12 Nov 2024 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[OpenTelemetry Demo with the Elastic Distributions of OpenTelemetry]]></title>
    <description><![CDATA[Discover how Elastic is dedicated to supporting users in their journey with OpenTelemetry. Explore our public deployment of the OpenTelemetry Demo and see how Elastic's solutions enhance your observability experience.]]></description>
    <content:encoded><![CDATA[<p>Recently, Elastic <a href="https://www.elastic.co/observability-labs/blog/elastic-distributions-opentelemetry">introduced the Elastic Distributions
(EDOT)</a>
for various OpenTelemetry components, we are proud to announce that these EDOT
components are now available in the <a href="https://github.com/elastic/opentelemetry-demo">Elastic's fork of the OpenTelemetry
Demo</a>. We've also made public a
<a href="https://ela.st/demo-otel">Kibana endpoint</a>, allowing you to dive into the
demo’s live data and explore its capabilities firsthand. In this blog post,
we'll elaborate on the reasons behind the fork and explore the powerful new
features it introduces. We'll also provide a comprehensive overview of how
these enhancements can be leveraged with the Elastic Distributions of
OpenTelemetry (EDOT) for advanced error detection, as well as the EDOT
Collector—a cutting-edge evolution of the Elastic Agent—for seamless data
collection and analysis.</p>
<h2 id="whatistheopentelemetrydemo">What is the OpenTelemetry Demo?</h2>
<p>The <a href="https://github.com/open-telemetry/opentelemetry-demo">OpenTelemetry Demo</a>
is a microservices-based application created by OpenTelemetry's community to
showcase its capabilities in a realistic, and distributed system environment.
This demo application, known as the OpenTelemetry Astronomy Shop, simulates an
e-commerce website composed of over 10 interconnected microservices (written in
multiple languages: Go, Java, .NET, Node.js, etc.), communicating via HTTP and
gRPC. Each service is fully instrumented with OpenTelemetry, generating
comprehensive traces, metrics, and logs. The demo serves as an invaluable
resource for understanding how to implement and use OpenTelemetry in real-world
applications.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt786e28cae6a3d1fc/6a85cb0233f2445e1649f50a/opentelemetry_demo_service_map.png" alt="1 - Service Map for the OpenTelemetry Demo Elastic
fork" /></p>
<p>One of the microservices, called <code>loadgenerator</code>, automatically starts
generating requests to the various endpoints of the demo, simulating a
real-world environment where multiple clients are interacting with the system.
This helps replicate the behavior of a busy, live application with concurrent
user activity.</p>
<h3 id="elasticsfork">Elastic's fork</h3>
<p>Elastic recognized an opportunity to enhance the OpenTelemetry Demo by forking
it and integrating advanced Elastic features for deeper observability and
simpler monitoring. While forking is the <a href="https://github.com/open-telemetry/opentelemetry-demo?tab=readme-ov-file#demos-featuring-the-astronomy-shop">recommended OpenTelemetry
approach</a>,
we aim to leverage the robust foundation and latest updates from the upstream
version as much as possible. To achieve this, Elastic’s fork of the
OpenTelemetry Demo performs daily pulls from upstream, seamlessly integrating
them with Elastic-specific changes. To avoid conflicts, we continuously
contribute upstream, ensuring Elastic's modifications are always additive or
configurable through environment variables. One such contribution is the
<a href="https://github.com/elastic/opentelemetry-demo/blob/main/.env.override">.env.override
file</a>,
designed exclusively for vendor forks to override the microservices images and
configuration files used in the demo.</p>
<h2 id="deeperinsightswithelasticdistributions">Deeper Insights with Elastic Distributions</h2>
<p>In our current update of Elastic's OpenTelemetry Demo fork, we have replaced
some of the microservices OTel SDKs used for instrumentation with Elastic's
specialized distributions. These changes ensure deeper integration with
Elastic's observability tools, offering richer insights and more robust
monitoring capabilities. These are some of the fork's changes:</p>
<p><strong>Java services:</strong> The Ad, Fraud Detection, and Kafka services now utilize the
Elastic distribution of the OpenTelemetry Java Agent. One of the included
features in the distribution are stack traces, which provides precise
information of where in the code path a span was originated. Learn more about
the Elastic Java Agent
<a href="https://www.elastic.co/observability-labs/blog/elastic-distribution-opentelemetry-java-agent">here</a>.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta9b95e4656860d7f/6a85cb056826665c9a1eabfd/adservice_span_stacktrace.png" alt="2 - Ad Service span stack trace
example" /></p>
<p>The <strong>Cart service</strong> has been upgraded to use the Elastic distribution of the
OpenTelemetry .NET Agent. This replacement gives visibility on how the Elastic
Distribution of OpenTelemetry .NET (EDOT .NET) can be used to get started using
OpenTelemetry in your .NET applications with zero code changes. Discover more
about the Elastic .NET Agent in <a href="https://www.elastic.co/observability-labs/blog/elastic-opentelemetry-distribution-dotnet-applications">this blog
post</a>.</p>
<p>In the <strong>Payment service</strong>, we've configured the Elastic distribution of the
OpenTelemetry Node.js Agent. The distribution ships with the host-metrics
extension, and Kibana provides a curated service metrics UI. Read more about
the Elastic Node.js Agent
<a href="https://www.elastic.co/observability-labs/blog/elastic-opentelemetry-distribution-node-js">here</a>.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltfd1a7d73cfff293b/6a85cb085c2790e01df59b1d/payment_service_host_metrics.png" alt="3 - Payment service host
metrics" /></p>
<p>The <strong>Recommendation service</strong> now leverages the EDOT Python, replacing the
standard OpenTelemetry Python agent. The Python distribution is another example
of a Zero-code (or Automatic) instrumentation, meaning that the distribution
will set up the OpenTelemetry SDK and enable all the recommended
instrumentations for you. Find out more about the Elastic Python Agent in <a href="https://www.elastic.co/observability-labs/blog/elastic-opentelemetry-distribution-python">this
blog
post</a>.</p>
<p>It's important to highlight that Elastic Distributions of OpenTelemetry don't
bundle proprietary software, they have been build on top of the vanilla OTel
SDKs but they offer some advantages, such as single package for installation,
easy auto-instrumentation with reasonable default configuration, automatic logs
telemetry sending, and many more. Along these lines, the ultimate goal is to
contribute as many features from EDOT's back to the upstream OpenTelemetry
agents; they are designed in such a way that the additional features, realized
as extensions, work directly with the OTel SDKs.</p>
<h2 id="collectingdatawiththeelasticcollectordistribution">Collecting Data with the Elastic Collector Distribution</h2>
<p>The OpenTelemetry Demo applications generate and send their signals to an
OpenTelemetry Collector OTLP endpoint. In the Demo's fork, the EDOT collector
is set up to forward all OTLP signals from the microservices to an <a href="https://www.elastic.co/guide/en/observability/current/apm.html">APM
server</a> OTLP
endpoint. Additionally, it sends all other metrics and logs collected by the
collector to an Elasticsearch endpoint.</p>
<p>If the fork is deployed in a Kubernetes environment, the collector will
automatically start collecting the system's metrics. The collector will be
configured to use the <a href="https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/receiver/hostmetricsreceiver">hostmetrics
receivers</a>
to monitor all the K8s node's metrics, the <a href="https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/receiver/kubeletstatsreceiver">kuebeletstats
receiver</a>
to retrieve Kubelet's metrics and the <a href="https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/receiver/filelogreceiver">filelog
receiver</a>,
that will collect all cluster's.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta05b08da80d30cd4/6a85cb0b43c0b760522f0618/node_host_metrics.png" alt="4 - Host
metrics" /></p>
<p>Both the signals generated by the microservices and those collected by the EDOT
collector are enriched with Kubernetes metadata, allowing users to correlate
them seamlessly. This makes it easy to track and observe which Kubernetes nodes
and pods each service is running on, providing deep insights into both
application performance and infrastructure health.</p>
<p>Learn more about the Elastic's OpenTelemetry Collector distribution:
https://www.elastic.co/observability-labs/blog/elastic-distribution-opentelemetry-collector</p>
<h2 id="errordetectionwithelastic">Error detection with Elastic</h2>
<p>The OpenTelemetry Demo incorporates <a href="https://flagd.dev/">flagd</a>, a feature flag
evaluation engine used to simulate error scenarios. For example, the
<code>paymentServiceFailure</code> flag will force an error for every request to the
payment service <code>charge</code> endpoint. Since the service is instrumented with
OpenTelemetry, the error will be captured in the generated traces. We can then
use Kibana's powerful visualization and search tools to trace the error back to
its root cause.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt73f9a645195e575c/6a85cb0eabdc290505122502/payment_error.png" alt="5 - Payment service
error" />
<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8f403208700f3316/6a85cb1199083f08ca40f9d3/payment_trace_error.png" alt="6 - Payment service trace
error" /></p>
<p>Another available flag is named <code>adServiceHighCpu</code>, which causes a high CPU
load in the ad service. This increased CPU usage can be monitored either
through the service's metrics or the related metrics of its Kubernetes pod:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0a990b3b7a69ee30/6a85cb13d7b2e7bbcafe84d4/adservice_high_cpu_error.png" alt="7 - AdService High CPU
error" /></p>
<p>The full list of simulated scenarios can be found at <a href="https://opentelemetry.io/docs/demo/feature-flags/">this
link</a>.</p>
<h2 id="startyourownexploration">Start your own exploration</h2>
<p>Ready to explore the OpenTelemetry Demo with Elastic and its enhanced
observability capabilities? Follow the link to Kibana and begin your own
exploration of how Elastic and OpenTelemetry can transform your approach to
observability.</p>
<p>Live demo: https://ela.st/demo-otel</p>
<p>But that's not all—if you want to take it a step further, you can deploy the
OpenTelemetry Demo directly with your own Elasticsearch stack. Follow the steps
provided <a href="https://github.com/elastic/opentelemetry-demo">here</a> to set it up and
start gaining valuable insights from your own environment.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/opentelemetry-demo-with-the-elastic-distributions-of-opentelemetry</link>
    <guid isPermaLink="false">opentelemetry-demo-with-the-elastic-distributions-of-opentelemetry</guid>
    <category><![CDATA[OpenTelemetry]]></category>
    <dc:creator><![CDATA[Roger Coll]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3f772ca5b5a7535d/6a85cb16bc5bb35d0ef81af9/elastic-oteldemo.jpg" length="0" type="image/jpeg"/>
    <pubDate>Mon, 07 Oct 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Auto-instrumentation of Go applications with OpenTelemetry]]></title>
    <description><![CDATA[Instrumenting Go applications with OpenTelemetry provides insights into application performance, dependencies, and errors. We'll show you how to automatically instrument a Go application using Docker, with no changes to your application code.]]></description>
    <content:encoded><![CDATA[<p>In the fast-paced universe of software development, especially in the
cloud-native realm, DevOps and SRE teams are increasingly emerging as essential
partners in application stability and growth.</p>
<p>DevOps engineers continuously optimize software delivery, while SRE teams act
as the stewards of application reliability, scalability, and top-tier
performance. The challenge? These teams require a cutting-edge observability
solution, one that encompasses full-stack insights, empowering them to rapidly
manage, monitor, and rectify potential disruptions before they culminate into
operational challenges.</p>
<p>Observability in our modern distributed software ecosystem goes beyond mere
monitoring — it demands limitless data collection, precision in processing, and
the correlation of this data into actionable insights. However, the road to
achieving this holistic view is paved with obstacles, from navigating version
incompatibilities to wrestling with restrictive proprietary code.</p>
<p>Enter <a href="https://opentelemetry.io/">OpenTelemetry (OTel)</a>, with the following
benefits for those who adopt it:</p>
<ul>
<li>Escape vendor constraints with OTel, freeing yourself from vendor lock-in and
ensuring top-notch observability.</li>
<li>See the harmony of unified logs, metrics, and traces come together to provide
a complete system view.</li>
<li>Improve your application oversight through richer and enhanced
instrumentations.</li>
<li>Embrace the benefits of backward compatibility to protect your prior
instrumentation investments.</li>
<li>Embark on the OpenTelemetry journey with an easy learning curve, simplifying
onboarding and scalability.</li>
<li>Rely on a proven, future-ready standard to boost your confidence in every
investment.</li>
</ul>
<p>In this blog, we will explore how you can use <a href="https://github.com/open-telemetry/opentelemetry-go-instrumentation/">automatic instrumentation in
your Go</a>
application using Docker, without the need to refactor any part of your
application code. We will use an <a href="https://github.com/elastic/observability-examples">application called
Elastiflix</a>, which helps
highlight auto-instrumentation in a simple way.</p>
<h2 id="applicationprerequisitesandconfig">Application, prerequisites, and config</h2>
<p>The application that we use for this blog is called
<a href="https://github.com/elastic/observability-examples">Elastiflix</a>, a
movie-streaming application. It consists of several micro-services written in
.NET, NodeJS, Go, and Python.</p>
<p>Before we instrument our sample application, we will first need to understand
how Elastic can receive the telemetry data.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5405335485c969f1/6a85c7acd6cf2918a7bb087e/elastic-blog-1-config.png" alt="Elastic configuration options for
OpenTelemetry" /></p>
<p>All of Elastic Observability’s APM capabilities are available with OTel data.
Some of these include:</p>
<ul>
<li>Service maps</li>
<li>Service details (latency, throughput, failed transactions)</li>
<li>Dependencies between services, distributed tracing</li>
<li>Transactions (traces)</li>
<li>Machine learning (ML) correlations</li>
<li>Log correlation</li>
</ul>
<p>In addition to Elastic’s APM and a unified view of the telemetry data, you will
also be able to use Elastic’s powerful machine learning capabilities to reduce
the analysis, and alerting to help reduce MTTR.</p>
<h3 id="prerequisites">Prerequisites</h3>
<ul>
<li>An Elastic Cloud account — <a href="https://cloud.elastic.co/">sign up now</a>.</li>
<li>A clone of the <a href="https://github.com/elastic/observability-examples">Elastiflix demo application</a>, or your own Go application</li>
<li>Basic understanding of Docker — potentially install <a href="https://www.docker.com/products/docker-desktop/">Docker Desktop</a></li>
<li>Basic understanding of Go</li>
</ul>
<h3 id="viewtheexamplesourcecode">View the example source code</h3>
<p>The full source code, including the Dockerfile used in this blog, can be found
on
<a href="https://github.com/elastic/observability-examples/tree/main/Elastiflix/go-favorite">GitHub</a>.</p>
<p>The following steps will show you how to instrument this application and run it
on the command line or in Docker. If you are interested in a more complete OTel
example, take a look at the docker-compose file
<a href="https://github.com/elastic/observability-examples/tree/main#start-the-app">here</a>,
which will bring up the full project.</p>
<h2 id="stepbystepguide">Step-by-step guide</h2>
<h3 id="step0logintoyourelasticcloudaccount">Step 0. Log in to your Elastic Cloud account</h3>
<p>This blog assumes you have an Elastic Cloud account — if not, follow the
<a href="https://cloud.elastic.co/registration?elektra=en-cloud-page">instructions to get started on Elastic
Cloud</a>.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdb92257a78bf2a8b/6a85c7afabdc296b2912247a/elastic-blog-2-trial.png" alt="free trial" /></p>
<h3 id="step1runthedockerimagewithautoinstrumentation">Step 1. Run the Docker Image with auto-instrumentation</h3>
<p>We are going to use automatic instrumentation with the Go service from the
<a href="https://github.com/elastic/observability-examples/tree/main/Elastiflix/go-favorite">Elastiflix demo
application</a>.</p>
<p>We will be using the following service from Elastiflix:</p>
<pre><code>Elastiflix/go-favorite
</code></pre>
<p>Per the <a href="https://github.com/open-telemetry/opentelemetry-go-instrumentation/blob/main/docs/getting-started.md">OpenTelemetry Automatic Instrumentation for Go
documentation</a>,
you will configure the application to be auto-instrumented using
docker-compose.</p>
<p>As specified in the <a href="https://github.com/open-telemetry/opentelemetry-go-instrumentation/blob/main/docs/getting-started.md">OTEL Go
documentation</a>,
we will use environment variables and pass in the configuration values to
enable it to connect with <a href="https://www.elastic.co/guide/en/observability/current/apm-open-telemetry.html">Elastic Observability’s APM
server</a>.</p>
<p>Because Elastic accepts OTLP natively, we just need to provide the Endpoint and
authentication where the OTEL Exporter needs to send the data, as well as some
other environment variables.</p>
<p><strong>Getting Elastic Cloud variables</strong>
You can copy the endpoints and token from Kibana under the path <code>/app/apm/onboarding?agent=openTelemetry</code>.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2da72f2943c5e320/6a85c7b29d2b71762ef938f3/elastic-blog-3-apm-agents.png" alt="apm agents" /></p>
<p>You will need to copy the following environment variables:</p>
<pre><code>OTEL_EXPORTER_OTLP_ENDPOINT
OTEL_EXPORTER_OTLP_HEADERS
</code></pre>
<p>Update the <code>docker-compose.yml</code> file at the top of the <code>Elastiflix</code> repository,
adding a <code>go-auto</code> service and updating the <code>favorite-go</code> one:</p>
<pre><code>  favorite-go:
    build: go-favorite/.
    image: docker.elastic.co/demos/workshop/observability/elastiflix-go-favorite:${ELASTIC_VERSION}-${BUILD_NUMBER}
    depends_on:
      - redis
    networks:
      - app-network
    ports:
      - "5001:5000"
    environment:
      - REDIS_HOST=redis
      - TOGGLE_SERVICE_DELAY=${TOGGLE_SERVICE_DELAY:-0}
      - TOGGLE_CANARY_DELAY=${TOGGLE_CANARY_DELAY:-0}
      - TOGGLE_CANARY_FAILURE=${TOGGLE_CANARY_FAILURE:-0}
    volumes:
      - favorite-go:/app
  go-auto:
    image: otel/autoinstrumentation-go
    privileged: true
    pid: "host"
    networks:
      - app-network
    environment:
      OTEL_EXPORTER_OTLP_ENDPOINT: "REPLACE WITH OTEL_EXPORTER_OTLP_ENDPOINT"
      OTEL_EXPORTER_OTLP_HEADERS: "REPLACE WITH OTEL_EXPORTER_OTLP_HEADERS"
      OTEL_GO_AUTO_TARGET_EXE: "/app/main"
      OTEL_SERVICE_NAME: "go-favorite"
      OTEL_PROPAGATORS: "tracecontext,baggage"
    volumes:
      - favorite-go:/app
      - /proc:/host/proc
</code></pre>
<p>And, at the bottom of the file:</p>
<pre><code>volumes:
  favorite-go:
networks:
  app-network:
    driver: bridge
</code></pre>
<p>Finally, in the configuration for the main node app, you will want to tell Elastiflix to call the Go favorites app by replacing the line:</p>
<pre><code>environment:
  - API_ENDPOINT_FAVORITES=favorite-java:5000
</code></pre>
<p>with:</p>
<pre><code>environment:
  - API_ENDPOINT_FAVORITES=favorite-go:5000
</code></pre>
<h3 id="step3exploretracesandlogsinelasticapm">Step 3: Explore traces and logs in Elastic APM</h3>
<p>Once you have this up and running, you can ping the endpoint for your
instrumented service (in our case, this is /favorites), and you should see the
app appear in Elastic APM, as shown below:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc01109d384575b47/6a85c7b5d6cf296423bb0886/elastic-blog-4-services.png" alt="services" /></p>
<p>It will begin by tracking throughput and latency critical metrics for SREs to
pay attention to.</p>
<p>Digging in, we can see an overview of all our Transactions.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt30c92e5b8c16cffb/6a85c7b89829264ca658385c/elastic-blog-5-services2.png" alt="services-2" /></p>
<p>And look at specific transactions:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb9cdf840d07ea72b/6a85c7bad7b2e746c7fe8446/elastic-blog-6-graph-colored.png" alt="graph colored lines" /></p>
<p>This gives you complete visibility across metrics, and traces!</p>
<h2 id="summary">Summary</h2>
<p>With this Dockerfile, you've transformed your simple Go application into one
that's automatically instrumented with OpenTelemetry. This will aid greatly in
understanding application performance, tracing errors, and gaining insights
into how users interact with your software.</p>
<p>Remember, observability is a crucial aspect of modern application development,
especially in distributed systems. With tools like OpenTelemetry, understanding
complex systems becomes a tad bit easier.</p>
<p>In this blog, we discussed the following:</p>
<ul>
<li>How to auto-instrument Go with OpenTelemetry.</li>
<li>Using standard commands in a Docker file, auto-instrumentation was done
efficiently and without adding code in multiple places enabling
manageability.</li>
<li>Using OpenTelemetry and its support for multiple languages, DevOps and SRE
teams can auto-instrument their applications with ease gaining immediate
insights into the health of the entire application stack and reduce mean time
to resolution (MTTR).</li>
</ul>
<p>Since Elastic can support a mix of methods for ingesting data, whether it be
using auto-instrumentation of open-source OpenTelemetry or manual
instrumentation with its native APM agents, you can plan your migration to OTel
by focusing on a few applications first and then using OpenTelemety across your
applications later on in a manner that best fits your business needs.</p>
<p>Developer resources:</p>
<ul>
<li><p><a href="https://www.elastic.co/observability-labs/blog/getting-started-opentelemetry-instrumentation-sample-app">Elastiflix application</a>, a guide to instrument different languages with OpenTelemetry</p></li>
<li><p>Python: <a href="https://www.elastic.co/observability-labs/blog/auto-instrumentation-python-applications-opentelemetry">Auto-instrumentation</a>, <a href="https://www.elastic.co/observability-labs/blog/manual-instrumentation-python-apps-opentelemetry">Manual-instrumentation</a></p></li>
<li><p>Java: <a href="https://www.elastic.co/observability-labs/blog/auto-instrumentation-java-applications-opentelemetry">Auto-instrumentation</a>, <a href="https://www.elastic.co/observability-labs/blog/manual-instrumentation-java-apps-opentelemetry">Manual-instrumentation</a></p></li>
<li><p>Node.js: <a href="https://www.elastic.co/observability-labs/blog/auto-instrument-nodejs-apps-opentelemetry">Auto-instrumentation</a>, <a href="https://www.elastic.co/observability-labs/blog/manual-instrumentation-nodejs-apps-opentelemetry">Manual-instrumentation</a></p></li>
<li><p>.NET: <a href="https://www.elastic.co/observability-labs/blog/auto-instrumentation-net-applications-opentelemetry">Auto-instrumentation</a>, <a href="https://www.elastic.co/observability-labs/blog/manual-instrumentation-net-apps-opentelemetry">Manual-instrumentation</a></p></li>
<li><p>Go: <a href="https://www.elastic.co/observability-labs/blog/auto-instrumentation-go-applications-opentelemetry">Auto-instrumentation</a> <a href="https://www.elastic.co/observability-labs/blog/manual-instrumentation-apps-opentelemetry">Manual-instrumentation</a></p></li>
<li><p><a href="https://www.elastic.co/observability-labs/blog/best-practices-instrumenting-opentelemetry">Best practices for instrumenting OpenTelemetry</a></p>
<p>General configuration and use case resources:</p></li>
<li><p><a href="https://www.elastic.co/blog/opentelemetry-observability">Independence with OpenTelemetry on Elastic</a></p></li>
<li><p><a href="https://www.elastic.co/blog/implementing-kubernetes-observability-security-opentelemetry">Modern observability and security on Kubernetes with Elastic and OpenTelemetry</a></p></li>
<li><p><a href="https://www.elastic.co/blog/3-models-logging-opentelemetry-elastic">3 models for logging with OpenTelemetry and Elastic</a></p></li>
<li><p><a href="https://www.elastic.co/blog/adding-free-and-open-elastic-apm-as-part-of-your-elastic-observability-deployment">Adding free and open Elastic APM as part of your Elastic Observability deployment</a></p></li>
<li><p><a href="https://www.elastic.co/blog/custom-metrics-app-code-java-agent-plugin">Capturing custom metrics through OpenTelemetry API in code with Elastic</a></p></li>
<li><p><a href="https://www.elastic.co/virtual-events/future-proof-your-observability-platform-with-opentelemetry-and-elastic">Future-proof your observability platform with OpenTelemetry and Elastic</a></p></li>
<li><p><a href="https://www.elastic.co/blog/kubernetes-k8s-observability-elasticsearch-cncf">Elastic Observability: Built for open technologies like Kubernetes, OpenTelemetry, Prometheus, Istio, and more</a></p></li>
</ul>
<p>Don’t have an Elastic Cloud account yet? <a href="https://cloud.elastic.co/registration">Sign up for Elastic Cloud</a> and try out the auto-instrumentation capabilities that I discussed above. I would be interested in getting your feedback about your experience in gaining visibility into your application stack with Elastic.</p>
<p>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._</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/auto-instrumentation-go-applications-opentelemetry</link>
    <guid isPermaLink="false">auto-instrumentation-go-applications-opentelemetry</guid>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[APM]]></category>
    <category><![CDATA[Infrastructure Monitoring]]></category>
    <dc:creator><![CDATA[Damien Mathieu]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt82a63d48c4106992/6a85c7bdbc5bb3efbcf81a4f/observability-launch-series-3-go-auto.jpg" length="0" type="image/jpeg"/>
    <pubDate>Wed, 02 Oct 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Observing Langchain applications with Elastic, OpenTelemetry, and Langtrace]]></title>
    <description><![CDATA[Langchain applications are growing in use. The ability to build out RAG-based applications, simple AI Assistants, and more is becoming the norm. Observing these applications is even harder. Given the various options that are out there, this blog shows how to use OpenTelemetry instrumentation with Langtrace and ingest it into Elastic Observability APM]]></description>
    <content:encoded><![CDATA[<p>As AI-driven applications become increasingly complex, the need for robust tools to monitor and optimize their performance is more critical than ever. LangChain has rapidly emerged as a crucial framework in the AI development landscape, particularly for building applications powered by large language models (LLMs). As its adoption has soared among developers, the need for effective debugging and performance optimization tools has become increasingly apparent. One such essential tool is the ability to obtain and analyze traces from Langchain applications. Tracing provides invaluable insights into the execution flow, helping developers understand and improve their AI-driven systems. <a href="https://www.elastic.co/observability/application-performance-monitoring">Elastic Observability's APM</a> provides an ability to trace your Langchain apps with OpenTelemetry, but you need third-party libraries.</p>
<p>There are several options to trace for Langchain. <a href="https://docs.langtrace.ai/introduction">Langtrace</a> is one such option. Langtrace is an <a href="https://github.com/Scale3-Labs/langtrace">open-source</a> observability software that lets you capture, debug and analyze traces and metrics from all your applications. Langtrace automatically captures traces from LLM APIs/inferences, Vector Databases, and LLM-based Frameworks. Langtrace stands out due to its seamless integration with popular LLM frameworks and its ability to provide deep insights into complex AI workflows without requiring extensive manual instrumentation.</p>
<p>Langtrace has an SDK, a lightweight library that can be installed and imported into your project to collect traces. The traces are OpenTelemetry-based and can be exported to Elastic without using a Langtrace API key.</p>
<p>OpenTelemetry (OTel) is now broadly accepted as the industry standard for tracing. As one of the major Cloud Native Computing Foundation (CNCF) projects, with as many commits as Kubernetes, it is gaining support from major ISVs and cloud providers delivering support for the framework. </p>
<p>Hence, many LangChain-based applications will have multiple components beyond just LLM interactions. Using OpenTelemetry with LangChain is essential. </p>
<p>This blog will cover how you can use Langtrace SDK to trace a simple LangChain Chat app connecting to Azure OpenAI, perform a search in DuckDuckGoSearch and export the output to Elastic.</p>
<h2 id="prerequisites">Pre-requisites:</h2>
<ul>
<li><p>An Elastic Cloud account — <a href="https://cloud.elastic.co/">sign up now</a>, and become familiar with <a href="https://www.elastic.co/guide/en/observability/current/apm-open-telemetry.html">Elastic’s OpenTelemetry configuration</a></p></li>
<li><p>Have a LangChain app to instrument</p></li>
<li><p>Be familiar with using <a href="https://opentelemetry.io/docs/languages/python/libraries/">OpenTelemetry’s Python SDK</a> </p></li>
<li><p>An account on your favorite LLM (AzureOpen AI), with API keys</p></li>
<li><p>The application we used in this blog, called <code>langchainChat</code> can be found in <a href="https://github.com/elastic/observability-examples/tree/main/langchainChat">Github langhcainChat</a>. It is built using Azure OpenAI and DuckDuckGo, but you can easily modify it for your LLM and search of choice.</p></li>
</ul>
<h2 id="appoverviewandoutputinelastic">App Overview and output in Elastic:</h2>
<p>To showcase the combined power of Langtrace and Elastic, we created a simple LangChain app that performs the following steps:</p>
<ol>
<li><p>Takes customer input on the command line. (Queries)</p></li>
<li><p>Sends these to the Azure OpenAI LLM via a LangChain.</p></li>
<li><p>Utilizes chain tools to perform a search using DuckDuckGo.</p></li>
<li><p>The LLM processes the search results and returns the relevant information to the user.</p></li>
</ol>
<p>Here is a sample interaction:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt76b2d020888007a7/6a7f08925967e5c1035dd10c/langchainchat-cli.png" alt="Chat Interaction" /></p>
<p>Here is what the service view looks like after we ran a few queries. </p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt16c8c79d875d3a11/6a7f0895b6b7348147e48c42/langchainchat-overview.png" alt="Service Overview" /></p>
<p>As you can see, Elastic Observability’s APM recognizes the LangChain app and also shows the average latency, throughput, and transactions. Our average latency is 30s since it takes that log for humans to type the query (twice).</p>
<p>You can also select other tabs to see, dependencies, errors, metrics, and more. One interesting part of Elastic APM is the ability to use universal profiling (eBPF) output also analyzed for this service. Here is what our service’s dependency is (Azure OpenAI) with its average latency, throughput, and failed transactions:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8736bb62fef3dec2/6a7f08982f00b26dbbefe9e3/langchainchat-dependency.png" alt="Dependencies" /></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltccf60cbec05ce73d/6a7f089a448e4e59fa5c0540/langchainchat-dependency-metrics.png" alt="Dependency-metric" /></p>
<p>We see Azure OpenAI is on average 4s to give us the results.</p>
<p>If we drill into transactions and look at the trace for our queries on Taylor Swift and Pittsburgh Steelers, we can see both queries and their corresponding spans.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5d3ebad3e3fdf95c/6a7f089e3cab1c74990e4694/langchainchat-trace.png" alt="Trace for two queries" /></p>
<p>In this trace:</p>
<ol>
<li><p>The user makes a query</p></li>
<li><p>Azure OpenAI is called, but it uses a tool (DuckDuckGo) to obtain some results</p></li>
<li><p>Azure OpenAI reviews and returns a summary to the end user</p></li>
<li><p>Repeats for another query</p></li>
</ol>
<p>We noticed that the other long span (other than Azure OpenAI) is Duckduckgo (~1000ms). We can individually look at the span and review the data:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt322bb5b1d8291b1e/6a7f08a12f00b27e54efe9e9/langchainchat-tools-span.png" alt="Span details" /></p>
<h2 id="configuration">Configuration:</h2>
<p>How do we make all this show up in Elastic? Let's go over the steps:</p>
<h3 id="opentelemetryconfiguration">OpenTelemetry Configuration</h3>
<p>To leverage the full capabilities of OpenTelemetry with Langtrace and Elastic, we need to configure the SDK to generate traces and properly set up Elastic’s endpoint and authorization. Detailed instructions can be found in the <a href="https://opentelemetry.io/docs/zero-code/python/#setup">OpenTelemetry Auto-Instrumentation setup documentation</a>.</p>
<h4 id="opentelemetryenvironmentvariables">OpenTelemetry Environment variables:</h4>
<p>For Elastic, you can set the following OpenTelemetry environment variables either in your Linux/Mac environment or directly in the code:</p>
<pre><code>OTEL_EXPORTER_OTLP_ENDPOINT=12345.apm.us-west-2.aws.cloud.es.io:443
OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer%20ZZZZZZZ"
OTEL_RESOURCE_ATTRIBUTES="service.name=langchainChat,service.version=1.0,deployment.environment=production"
</code></pre>
<p>In this setup:</p>
<ul>
<li><p><strong>OTEL_EXPORTER_OTLP_ENDPOINT</strong> is configured to send traces to Elastic.</p></li>
<li><p><strong>OTEL_EXPORTER_OTLP_HEADERS</strong> provides the necessary authorization for the Elastic APM server.</p></li>
<li><p><strong>OTEL_RESOURCE_ATTRIBUTES</strong> define key attributes like the service name, version, and deployment environment.</p></li>
</ul>
<p>These values can be easily obtained from Elastic’s APM configuration screen under the OpenTelemetry section.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltfb5cb2d3359013d1/6a7f08a442a1178c6b95bcfe/langchainchat-OTelAPMsetup.png" alt="Span details" /></p>
<p><strong>Note: No agent is required; the OTLP trace messages are sent directly to Elastic’s APM server, simplifying the setup process.</strong></p>
<h3 id="langtracelibrary">Langtrace Library:</h3>
<p>OpenTelemetry's auto-instrumentation can be extended to trace additional frameworks using instrumentation packages. For this blog post, you will need to install the Langtrace Python SDK:</p>
<pre><code>pip install langtrace-python-sdk 
</code></pre>
<p>After installation, you can add the following code to your project:</p>
<pre><code>from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter

from langtrace_python_sdk import langtrace, with_langtrace_root_span
</code></pre>
<h3 id="instrumentation">Instrumentation:</h3>
<p>Once the necessary libraries are installed and the environment variables are configured, you can use auto-instrumentation to trace your application. For example, run the following command to instrument your LangChain application with Elastic:</p>
<pre><code>opentelemetry-instrument python langtrace-elastic-demo.py
</code></pre>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5d3ebad3e3fdf95c/6a7f089e3cab1c74990e4694/langchainchat-trace.png" alt="Trace for two queries" /></p>
<p>The Langtrace OpenTelemetry library correctly captures the flow with minimal manual instrumentation, apart from integrating the OpenTelemetry library. Additionally, the LLM spans captured by Langtrace also include useful metadata such as token counts, model hyper-parameter settings etc. Note that the generated spans follow the OTEL GenAI semantics described <a href="https://opentelemetry.io/docs/specs/semconv/attributes-registry/gen-ai/">here</a>.</p>
<p>In summary, the instrumentation process involves:</p>
<ol>
<li><p>Capturing customer input from the command line (Queries).</p></li>
<li><p>Sending these queries to the Azure OpenAI LLM via a LangChain.</p></li>
<li><p>Utilizing chain tools, such as DuckDuckGo, to perform searches.</p></li>
<li><p>The LLM processes the results and returns the relevant information to the user.</p></li>
</ol>
<h2 id="conclusion">Conclusion</h2>
<p>By combining the power of <a href="https://langtrace.ai/">Langtrace</a> with Elastic, developers can achieve unparalleled visibility into their LangChain applications, ensuring optimized performance and quicker debugging. This powerful combination simplifies the complex task of monitoring AI-driven systems, enabling you to focus on what truly matters—delivering value to your users. Throughout this blog,we've covered the following essential steps and concepts:</p>
<ul>
<li><p>How to manually instrument Langchain with OpenTelemetry</p></li>
<li><p>How to properly initialize OpenTelemetry and add a custom span</p></li>
<li><p>How to easily set the OTLP ENDPOINT and OTLP HEADERS with Elastic without the need for a collector</p></li>
<li><p>How to view and analyze traces in Elastic Observability APM</p></li>
</ul>
<p>These steps provide a clear and actionable guide for developers looking to integrate robust tracing capabilities into their LangChain applications.</p>
<p>We hope this guide makes understanding and implementing OpenTelemetry tracing for LangChain simple, ensuring seamless integration with Elastic.</p>
<p><strong>Additional resources for OpenTelemetry with Elastic:</strong></p>
<ul>
<li><p><a href="https://www.elastic.co/blog/opentelemetry-observability">Independence with OpenTelemetry on Elastic</a></p></li>
<li><p><a href="https://www.elastic.co/blog/implementing-kubernetes-observability-security-opentelemetry">Modern observability and security on Kubernetes with Elastic and OpenTelemetry</a></p></li>
<li><p><a href="https://www.elastic.co/blog/3-models-logging-opentelemetry-elastic">3 models for logging with OpenTelemetry and Elastic</a></p></li>
<li><p><a href="https://www.elastic.co/blog/adding-free-and-open-elastic-apm-as-part-of-your-elastic-observability-deployment">Adding free and open Elastic APM as part of your Elastic Observability deployment</a></p></li>
<li><p><a href="https://www.elastic.co/blog/monitor-openai-api-gpt-models-opentelemetry-elastic">Monitor OpenAI API and GPT models with OpenTelemetry and Elastic</a></p></li>
<li><p>Futureproof<a href="https://www.elastic.co/virtual-events/future-proof-your-observability-platform-with-opentelemetry-and-elastic"> your observability platform with OpenTelemetry and Elastic</a></p></li>
<li><p>Instrumentation resources:</p></li>
<li><p>Python: <a href="https://www.elastic.co/blog/auto-instrumentation-of-python-applications-opentelemetry">Auto-instrumentation</a>, <a href="https://www.elastic.co/blog/manual-instrumentation-of-python-applications-opentelemetry">Manual instrumentation</a></p></li>
<li><p>Java: <a href="https://www.elastic.co/blog/auto-instrumentation-of-java-applications-opentelemetry">Auto-instrumentation</a>, <a href="https://www.elastic.co/blog/manual-instrumentation-of-java-applications-opentelemetry">Manual instrumentation </a></p></li>
<li><p>Node.js: <a href="https://www.elastic.co/blog/auto-instrument-nodejs-applications-opentelemetry">Auto-instrumentation</a>, <a href="https://www.elastic.co/blog/manual-instrumentation-of-nodejs-applications-opentelemetry">Manual instrumentation</a></p></li>
<li><p>.NET: <a href="https://www.elastic.co/blog/auto-instrumentation-of-net-applications-opentelemetry">Auto-instrumentation</a>, <a href="https://www.elastic.co/blog/manual-instrumentation-of-net-applications-opentelemetry">Manual instrumentation</a></p></li>
<li><p><a href="https://docs.langtrace.ai/supported-integrations/observability-tools/elastic">Elastic APM - Langtrace AI Docs</a></p></li>
</ul>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/elastic-opentelemetry-langchain-tracing-langtrace</link>
    <guid isPermaLink="false">elastic-opentelemetry-langchain-tracing-langtrace</guid>
    <category><![CDATA[LLM Observability]]></category>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[APM]]></category>
    <category><![CDATA[Infrastructure Monitoring]]></category>
    <dc:creator><![CDATA[Bahubali Shetti,Karthik Kalyanaraman,Yemi Adejumobi]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt405f53422a8d599e/6a7f08a81967ea8d8333057d/elastic-langtrace.jpg" length="0" type="image/jpeg"/>
    <pubDate>Mon, 02 Sep 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Tailoring span names and enriching spans without changing code with OpenTelemetry - Part 1]]></title>
    <description><![CDATA[The OpenTelemetry Collector offers powerful capabilities to enrich and refine telemetry data before it reaches your observability tools. In this blog post, we'll explore how to leverage the Collector to create more meaningful transaction names in Elastic Observability, significantly enhancing the value of your monitoring data.]]></description>
    <content:encoded><![CDATA[<p>The OpenTelemetry Collector offers powerful capabilities to enrich and refine telemetry data before it reaches your observability tools. In this blog post, we'll explore how to leverage the Collector to create more meaningful transaction names in Elastic Observability, significantly enhancing the value of your monitoring data.</p>
<p>Consider this scenario: You have a transaction labeled simply as "HTTP GET" with an average response time of 5ms. However, this generic label masks a variety of distinct operations – payment processing, user logins, and adding items to a cart. Does that 5ms average truly represent the performance of these diverse actions? Clearly not. </p>
<p>The other problem that happens is that span traces become all mixed up so that login spans and image serving spans all become part of the same bucket, this makes things like latency correlation analysis hard in Elastic. </p>
<p>We'll focus on a specific technique using the collector's attributes, and transform processors to extract meaningful information from HTTP URLs and use it to create more descriptive span names. This approach not only improves the accuracy of your metrics but also enhances your ability to quickly identify and troubleshoot performance issues across your microservices architecture.</p>
<p>By using these processors in combination, we can quickly address the issue of overly generic transaction names, creating more granular and informative identifiers that provide accurate visibility into your services' performance.</p>
<p>However, it's crucial to approach this technique with caution. While more detailed transaction names can significantly improve observability, they can also lead to an unexpected challenge: cardinality explosion. As we dive into the implementation details, we'll also discuss how to strike the right balance between granularity and manageability, ensuring that our solution enhances rather than overwhelms our observability stack.</p>
<p>In the following sections, we'll walk through the configuration step-by-step, explaining how each processor contributes to our goal, and highlighting best practices to avoid potential pitfalls like cardinality issues. Whether you're new to OpenTelemetry or looking to optimize your existing setup, this guide will help you unlock more meaningful insights from your telemetry data.</p>
<h2 id="prerequisitesandconfiguration">Prerequisites and configuration</h2>
<p>If you plan on following this blog, here are some of the components and details we used to set up the configuration:</p>
<ul>
<li>Ensure you have an account on Elastic Cloud and a deployed stack (see instructions <a href="https://www.elastic.co/cloud/">here</a>).</li>
<li>I am also using the OpenTelemetry demo in my environment, this is important to follow along with as this demo has the specific issue I want to address. You should clone the repository and follow the instructions <a href="https://github.com/elastic/opentelemetry-demo">here</a> to get this up and running. I recommend using Kubernetes and I will be doing this in my AWS EKS (Elastic Kubernetes Service) environment. </li>
</ul>
<h3 id="theopentelemetrydemo">The OpenTelemetry Demo</h3>
<p>The OpenTelemetry Demo is a comprehensive, microservices-based application designed to showcase the capabilities and best practices of OpenTelemetry instrumentation. It simulates an e-commerce platform, incorporating various services such as frontend, cart, checkout, and payment processing. This demo serves as an excellent learning tool and reference implementation for developers and organizations looking to adopt OpenTelemetry.</p>
<p>The demo application generates traces, metrics, and logs across its interconnected services, demonstrating how OpenTelemetry can provide deep visibility into complex, distributed systems. It's particularly useful for experimenting with different collection, processing, and visualization techniques, making it an ideal playground for exploring observability concepts and tools like the OpenTelemetry Collector.</p>
<p>By using real-world scenarios and common architectural patterns, the OpenTelemetry Demo helps users understand how to effectively implement observability in their own applications and how to leverage the data for performance optimization and troubleshooting.</p>
<p>Once you have an Elastic Cloud instance and you fire up the OpenTelemetry demo, you should see something like this on the Elastic Service Map page:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3fbb0ad570a0e54e/6a7f1b78bdcff0139dc432bb/image3.png" alt="" /></p>
<p>Navigating to the traces page will give you the following set up.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6e49f9f047e722b1/6a7f1b7b42a117ba8b95c337/image1.png" alt="" /></p>
<p>As you can see there are some very broad transaction names here like HTTP GET and the averages will not be very accurate for specific business functions within your services as shown. </p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb7a70a039a860e25/6a7f1b7efc63ab374564d092/image6.png" alt="" /></p>
<p>So let's fix that with the OpenTelemetry Collector. </p>
<h2 id="theopentelemetrycollector">The OpenTelemetry Collector</h2>
<p>The OpenTelemetry Collector is a vital component in the OpenTelemetry ecosystem, serving as a vendor-agnostic way to receive, process, and export telemetry data. It acts as a centralized observability pipeline that can collect traces, metrics, and logs from various sources, then transform and route this data to multiple backend systems. </p>
<p>The collector's flexible architecture allows for easy configuration and extension through a wide range of receivers, processors, and exporters which you can explore over <a href="https://github.com/open-telemetry/opentelemetry-collector-contrib">here</a>. I have personally found navigating the 'contrib' archive incredibly useful for finding techniques that I didn't know existed. This makes the OpenTelemetry Collector an invaluable tool for organizations looking to standardize their observability data pipeline, reduce overhead, and seamlessly integrate with different monitoring and analysis platforms.</p>
<p>Let's go back to our problem, how do we change the transaction names that Elastic is using to something more useful so that our HTTP GET translates to something like payment-service/login? The first thing we do is we take the full http url and consider which parts of it relate to our transaction.  Looking at the span details we see a url </p>
<pre><code>my-otel-demo-frontendproxy:8080/api/recommendations?productIds=&amp;sessionId=45a9f3a4-39d8-47ed-bf16-01e6e81c80bc&amp;currencyCode=
</code></pre>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte7a6dfc4f52f3743/6a7f1b8173d9bd3cb029df82/image4.png" alt="" /></p>
<p>Now obviously we wouldn't want to create transaction names that map to every single session id, that would lead to the cardinality explosion we talked about earlier, however, something like the first two parts of the url 'api/recommendations' looks like exactly the kind of thing we need.</p>
<h3 id="theattributesprocessor">The attributes processor</h3>
<p>The OpenTelemetry collector gives us a useful tool <a href="https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/processor/attributesprocessor">here</a>, the attributes processor can help us extract parts of the url to use later in our observability pipeline. To do this is very simple, we simply build a regex like this one below. Now I should mention that I did not generate this regex myself but I used an LLM to do this for me, never fear regex again!</p>
<pre><code>attributes:
  actions:
    - key: http.url
      action: extract
      pattern: '^(?P&lt;short_url&gt;https?://[^/]+(?:/[^/]+)*)(?:/(?P&lt;url_truncated_path&gt;[^/?]+/[^/?]+))(?:\?|/?$)'
</code></pre>
<p>This configuration is doing some heavy lifting for us, so let's break it down:</p>
<ul>
<li>We're using the attributes processor, which is perfect for manipulating span attributes.</li>
<li>We're targeting the http.url attribute of incoming spans.</li>
<li>The extract action tells the processor to pull out specific parts of the URL using our regex pattern.</li>
</ul>
<p>Now, about that regex - it's designed to extract two key pieces of information:</p>
<ol>
<li><code>short_url</code>: This captures the protocol, domain, and optionally the first path segment. For example, in "https://example.com/api/users/profile", it would grab "https://example.com/api".</li>
<li><code>url_truncated_path</code>: This snags the next two path segments (if they exist). In our example, it would extract "users/profile".</li>
</ol>
<p>Why is this useful? Well, it allows us to create more specific transaction names based on the URL structure, without including overly specific details that could lead to cardinality explosion. For instance, we avoid capturing unique IDs or query parameters that would create a new transaction name for every single request.</p>
<p>So, if we have a URL like "https://example.com/api/users/profile?id=123", our extracted <code>url_truncated_path</code> would be "users/profile". This gives us a nice balance - it's more specific than just "HTTP GET", but not so specific that we end up with thousands of unique transaction names.</p>
<p>Now it's worth mentioning here that if you don't have an attribute you want to use for naming your transactions it is worth looking at the options for your SDK or agent, as an example the Java automatic instrumentation Otel agent has the <a href="https://opentelemetry.io/docs/zero-code/java/agent/instrumentation/http/#capturing-http-request-and-response-headers">following options</a> for capturing request and response headers. You can then subsequently use this data to name your transactions if the url is insufficient! </p>
<p>In the next steps, we'll see how to use this extracted information to create more meaningful span names, providing better granularity in our observability data without overwhelming our system. Remember, the goal is to enhance our visibility, not to drown in a sea of overly specific metrics!</p>
<h3 id="thetransformprocessor">The transform processor</h3>
<p>Now that we've extracted the relevant parts of our URLs, it's time to put that information to good use. Enter the transform processor - our next powerful tool in the OpenTelemetry Collector pipeline.</p>
<p>The <a href="https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/processor/transformprocessor">transform processor</a> allows us to modify various aspects of our telemetry data, including span names. Here's the configuration we'll use:</p>
<pre><code>transform:
  trace_statements:
    - context: span
      statements:
        - set(name, attributes["url_truncated_path"])
</code></pre>
<p>Let's break this down:</p>
<ul>
<li>We're using the transform processor, which gives us fine-grained control over our spans.</li>
<li>We're focusing on <code>trace_statements</code>, as we want to modify our trace spans.</li>
<li>The <code>context: span</code> tells the processor to apply these changes to each individual span.</li>
<li>Our statement is where the magic happens: we're setting the span's name to the value of the <code>url_truncated_path</code> attribute we extracted earlier.</li>
</ul>
<p>What does this mean in practice? Remember our previous example URL "https://example.com/api/users/profile?id=123"? Instead of a generic span name like "HTTP GET", we'll now have a much more informative name: "users/profile".</p>
<p>This transformation brings several benefits:</p>
<ol>
<li>Improved Readability: At a glance, you can now see what part of your application is being accessed.</li>
<li>Better Aggregation: You can easily group and analyze similar requests, like all operations on user profiles.</li>
<li>Balanced Cardinality: We're specific enough to be useful, but not so specific that we create a new span name for every unique URL.</li>
</ol>
<p>By combining the attribute extraction we did earlier with this transformation, we've created a powerful system for generating meaningful span names. This approach gives us deep insight into our application's behavior without the risk of cardinality explosion. </p>
<h2 id="puttingitalltogether">Putting it All Together</h2>
<p>The resulting config for the OpenTelemetry collector is below remember this goes into the opentelemetry-demo/kubernetes/elastic-helm/configmap-deployment.yaml and is applied with kubectl apply -f configmap-deployment.yaml</p>
<pre><code>---
apiVersion: v1
kind: ConfigMap
metadata:
  name: elastic-otelcol-agent
  namespace: default
  labels:
    app.kubernetes.io/name: otelcol

data:
  relay: |
    connectors:
      spanmetrics: {}
    exporters:
      debug: {}
      otlp/elastic:
        endpoint: ${env:ELASTIC_APM_ENDPOINT}
        compression: none
        headers:
          Authorization: Bearer ${ELASTIC_APM_SECRET_TOKEN}
    extensions:
    processors:
      batch: {}
      resource:
        attributes:
          - key: deployment.environment
            value: "opentelemetry-demo"
            action: upsert
      attributes:
        actions:
          - key: http.url
            action: extract
            pattern: '^(?P&lt;short_url&gt;https?://[^/]+(?:/[^/]+)*)(?:/(?P&lt;url_truncated_path&gt;[^/?]+/[^/?]+))(?:\?|/?$)'
      transform:
        trace_statements:
          - context: span
            statements:
              - set(name, attributes["url_truncated_path"])
    receivers:
      httpcheck/frontendproxy:
        targets:
        - endpoint: http://example-frontendproxy:8080
      otlp:
        protocols:
          grpc:
            endpoint: ${env:MY_POD_IP}:4317
          http:
            cors:
              allowed_origins:
              - http://*
              - https://*
            endpoint: ${env:MY_POD_IP}:4318
    service:
      extensions:
      pipelines:
        logs:
          exporters:
          - debug
          - otlp/elastic
          processors:
          - batch
          - resource
          - attributes
          - transform
          receivers:
          - otlp
        metrics:
          exporters:
          - otlp/elastic
          - debug
          processors:
          - batch
          - resource
          receivers:
          - httpcheck/frontendproxy
          - otlp
          - spanmetrics
        traces:
          exporters:
          - otlp/elastic
          - debug
          - spanmetrics
          processors:
          - batch
          - resource
          - attributes
          - transform
          receivers:
          - otlp
      telemetry:
        metrics:
          address: ${env:MY_POD_IP}:8888
</code></pre>
<p>You'll notice that we tie everything together by adding our enrichment and transformations to the traces section in pipelines at the bottom of the collector config. This is the definition of our observability pipeline, bringing together all the pieces we've discussed to create more meaningful and actionable telemetry data.</p>
<p>By implementing this configuration, you're taking a significant step towards more insightful observability. You're not just collecting data; you're refining it to provide clear, actionable insights into your application's performance, check out the final result below!</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9b8c24e95c33f1a4/6a7f1b853cab1c804f0e4cb5/image2.png" alt="" /></p>
<h2 id="readytotakeyourobservabilitytothenextlevel">Ready to Take Your Observability to the Next Level?</h2>
<p>Implementing OpenTelemetry with Elastic Observability opens up a world of possibilities for understanding and optimizing your applications. But this is just the beginning! To further enhance your observability journey, check out these valuable resources:</p>
<ol>
<li><a href="https://www.elastic.co/observability-labs/blog/infrastructure-monitoring-with-opentelemetry-in-elastic-observability">Infrastructure Monitoring with OpenTelemetry in Elastic Observability</a></li>
<li><a href="https://www.elastic.co/observability-labs/blog/tag/opentelemetry">Explore More OpenTelemetry Content</a></li>
<li><a href="https://www.elastic.co/observability-labs/blog/using-the-otel-operator-for-injecting-java-agents">Using the OTel Operator for Injecting Java Agents</a></li>
<li><a href="https://www.elastic.co/what-is/opentelemetry">What is OpenTelemetry?</a></li>
</ol>
<p>We encourage you to dive deeper, experiment with these configurations, and see how they can transform your observability data. Remember, the key is to find the right balance between detail and manageability.</p>
<p>Have you implemented similar strategies in your observability pipeline? We'd love to hear about your experiences and insights. Share your thoughts in the comments below or reach out to us on our community forums.</p>
<p>Stay tuned for Part 2 of this series, where we will look at an advanced technique for collecting more data that can help you get even more granular by collecting Span names, baggage and data for metrics using a Java plugin all without code.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/tailoring-span-names-and-enriching-spans-without-changing-code-with-opentelemetry</link>
    <guid isPermaLink="false">tailoring-span-names-and-enriching-spans-without-changing-code-with-opentelemetry</guid>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[APM]]></category>
    <category><![CDATA[Infrastructure Monitoring]]></category>
    <dc:creator><![CDATA[David Hope]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt89485fbb57db9f4e/6a7f1b8796b5a6989c87b8b1/tailor.jpg" length="0" type="image/jpeg"/>
    <pubDate>Mon, 26 Aug 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Introducing Elastic Distributions of OpenTelemetry]]></title>
    <description><![CDATA[Elastic is proud to introduce Elastic Distributions of OpenTelemetry (EDOT), which contains Elastic’s versions of the OpenTelemetry Collector and several language SDKs like Python, Java, .NET, and NodeJS. These help provide enhanced features and enterprise-grade support for EDOT.]]></description>
    <content:encoded><![CDATA[<p>We are announcing the availability of Elastic Distributions of OpenTelemetry (EDOT). These Elastic distributions, currently in tech preview,  have been developed to enhance the capabilities of standard OpenTelemetry distributions and improve existing OpenTelemetry support from Elastic. </p>
<p>The Elastic Distributions of OpenTelemetry (EDOT) are composed of OpenTelemetry (OTel) project components, OTel Collector, and language SDKs,  which provide users with the necessary capabilities and out-of-the-box configurations, enabling quick and effortless infra and application monitoring.</p>
<p>While OTel components are feature-rich, enhancements through the community can take time. Additionally, support is left up to the community or individual users and organizations. Hence EDOT will bring the following to end users:</p>
<ul>
<li><p><strong>Deliver enhanced features earlier than OTel</strong>: By providing features unavailable in the “vanilla” OpenTelemetry components, we can quickly meet customers’ requirements while still providing an OpenTelemetry native and vendor-agnostic instrumentation for their applications. Elastic will continuously upstream these enhanced features.</p></li>
<li><p><strong>Enhanced OTel support</strong> - By maintaining Elastic distributions, we can better support customers with enhancements and fixes outside of the OTel release cycles. In addition, Elastic support can troubleshoot issues on the EDOT.</p></li>
</ul>
<p>EDOT currently includes the following tech preview components, which will  grow over time:</p>
<ul>
<li><p><a href="https://www.elastic.co/observability-labs/blog/elastic-distribution-opentelemetry-collector">Elastic Distribution of OpenTelemetry (EDOT) Collector</a></p></li>
<li><p><a href="https://www.elastic.co/observability-labs/blog/elastic-distribution-opentelemetry-java-agent">Elastic Distribution of OpenTelemetry (EDOT) Java</a>.</p></li>
<li><p><a href="https://www.elastic.co/observability-labs/blog/elastic-opentelemetry-distribution-python">Elastic Distribution of OpenTelemetry (EDOT) Python</a></p></li>
<li><p><a href="https://www.elastic.co/observability-labs/blog/elastic-opentelemetry-distribution-node-js">Elastic Distribution of OpenTelemetry (EDOT) NodeJS</a></p></li>
<li><p><a href="https://www.elastic.co/observability-labs/blog/elastic-opentelemetry-distribution-dotnet-applications">Elastic Distribution of OpenTelemetry (EDOT) .NET</a></p></li>
<li><p><a href="https://www.elastic.co/observability-labs/blog/apm-ios-android-native-apps">Elastic Distribution of OpenTelemetry (EDOT)  iOS and Android</a></p></li>
</ul>
<p>Details and documentation for all EDOT are available in our public <a href="https://github.com/elastic/opentelemetry">OpenTelemetry GitHub repository</a>. </p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt43eeb982d6e02dbd/6a9fb52927a5318002dcacc7/edot-components-dark.png" alt="EDOT Components" /></p>
<h2 id="elasticdistributionofopentelemetryedotcollectoraidelasticdistributionofopentelemetryedotcollectora">Elastic Distribution of OpenTelemetry (EDOT) Collector<a id="elastic-distribution-of-opentelemetry-edot-collector"></a></h2>
<p>The EDOT Collector, recently released with the 8.15 release of Elastic Observability enhances Elastic’s existing OTel capabilities. The EDOT Collector can, in addition to service monitoring, forward application logs, infrastructure logs, and metrics using standard OpenTelemetry Collector receivers like file logs and host metrics receivers.</p>
<p>Additionally, users of the Elastic Distribution of the OpenTelemetry Collector benefit from container logs automatically enriched with Kubernetes metadata by leveraging the powerful <a href="https://opentelemetry.io/blog/2024/otel-collector-container-log-parser/">container log parser</a> that Elastic recently contributed. This OpenTelemetry-based enrichment enhances the context and value of the collected logs, providing deeper insights and more effective troubleshooting capabilities.</p>
<p>This new collector distribution ensures that exported data is fully compatible with the Elastic Platform, enhancing the overall observability experience. Elastic also ensures that Elastic-curated UIs can seamlessly handle both the Elastic Common Schema (ECS) and OpenTelemetry formats.</p>
<h2 id="elasticdistributionsforlanguagesdksaidelasticdistributionsforlanguagesdksa">Elastic Distributions for Language SDKs<a id="elastic-distributions-for-language-sdks"></a></h2>
<p><a href="https://www.elastic.co/guide/en/apm/agent/index.html">Elastic's APM agents</a> have capabilities yet to be available in the OTel SDKs. EDOT brings these capabilities into the OTel language SDKs while maintaining seamless integration with Elastic Observability. Elastic will release OTel versions of all its APM agents, and continue to add additional language SDKs mirroring OTel.</p>
<h2 id="continuedsupportfornativeotelcomponentsaidcontinuedsupportfornativeotelcomponentsa">Continued support for Native OTel components<a id="continued-support-for-native-otel-components"></a></h2>
<p>EDOT does not preclude users from using native components. Users are still able to use:</p>
<ul>
<li><p><strong>OpenTelemetry Vanilla Language SDKs:</strong> use standard OpenTelemetry code instrumentation for many popular programming languages sending OTLP traces to Elastic via APM server.</p></li>
<li><p><strong>Upstream Distribution of OpenTelemetry Collector (Contrib or Custom):</strong> Send traces using the OpenTelemetry Collector with OTLP receiver and OTLP exporter to Elastic via APM server.</p></li>
</ul>
<p>Elastic is committed to contributing EDOT features or components upstream into the OpenTelemetry community, fostering a collaborative environment, and enhancing the overall OpenTelemetry ecosystem.</p>
<h2 id="extendingourcommitmenttovendoragnosticdatacollectionaidextendingourcommitmenttovendoragnosticdatacollectiona">Extending our commitment to vendor-agnostic data collection<a id="extending-our-commitment-to-vendor-agnostic-data-collection"></a></h2>
<p>Elastic remains committed to supporting OpenTelemetry by being OTel first and building a vendor-agnostic framework. As OpenTelemetry constantly grows its support of SDKs and components,  Elastic will continue to refine and mirror EDOT to OpenTelemetry and push enhancements upstream. </p>
<p>Over the past year, Elastic has been active in OTel through its <a href="https://opentelemetry.io/blog/2023/ecs-otel-semconv-convergence/">donation of Elastic Common Schema (ECS)</a>, contributions to the native <a href="https://www.elastic.co/observability-labs/blog/elastic-distribution-opentelemetry-collector">OpenTelemetry Collector</a> and language SDKs, and a recent <a href="https://www.elastic.co/observability-labs/blog/elastic-profiling-agent-acceptance-opentelemetry">donation of its Universal Profiling agent</a> to OpenTelemetry. </p>
<p>EDOT  builds on our decision to fully adopt and recommend OpenTelemetry as the preferred solution for observing applications. With EDOT, Elastic customers can future-proof their investments and adopt OpenTelemetry, giving them vendor-neutral instrumentation with Elastic enterprise-grade support.</p>
<p>Our vision is that Elastic will work with the OpenTelemetry community to donate features through the standardization processes and contribute the code to implement those in the native OpenTelemetry components. In time, as OTel capabilities advance, and many of the Elastic-exclusive features transition into OpenTelemetry, we look forward to no longer having Elastic Distributions for OpenTelemetry.. In the meantime, we can deliver those capabilities via our OpenTelemetry distributions.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/elastic-distributions-opentelemetry</link>
    <guid isPermaLink="false">elastic-distributions-opentelemetry</guid>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[APM]]></category>
    <category><![CDATA[Infrastructure Monitoring]]></category>
    <dc:creator><![CDATA[Alexander Wert,Miguel Luna,Bahubali Shetti]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1c945be5a78916b3/6a7f07a5b43770d70b4d6a91/edot-image.png" length="0" type="image/png"/>
    <pubDate>Thu, 15 Aug 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[Tracing LangChain apps with Elastic, OpenLLMetry, and OpenTelemetry]]></title>
    <description><![CDATA[LangChain applications are growing in use. The ability to build out RAG-based applications, simple AI Assistants, and more is becoming the norm. Observing these applications is even harder. Given the various options that are out there, this blog shows how to use OpenTelemetry instrumentation with OpenLLMetry and ingest it into Elastic Observability APM]]></description>
    <content:encoded><![CDATA[<p>LangChain has rapidly emerged as a crucial framework in the AI development landscape, particularly for building applications powered by large language models (LLMs). As its adoption has soared among developers, the need for effective debugging and performance optimization tools has become increasingly apparent. One such essential tool is the ability to obtain and analyze traces from LangChain applications. Tracing provides invaluable insights into the execution flow, helping developers understand and improve their AI-driven systems. </p>
<p>There are several options to trace for LangChain. One is Langsmith, ideal for detailed tracing and a complete breakdown of requests to large language models (LLMs). However, it is specific to Langchain. OpenTelemetry (OTel) is now broadly accepted as the industry standard for tracing. As one of the major Cloud Native Computing Foundation (CNCF) projects, with as many commits as Kubernetes, it is gaining support from major ISVs and cloud providers delivering support for the framework. </p>
<p>Hence, many LangChain-based applications will have multiple components beyond just LLM interactions. Using OpenTelemetry with LangChain is essential. OpenLLMetry is an available option for tracing Langchain apps in addition to Langsmith.</p>
<p>This blog will show how you can get LangChain tracing into Elastic using the OpenLLMetry library <code>opentelemetry-instrumentation-langchain</code>.</p>
<h2 id="prerequisitesaidprerequisitesa">Pre-requisites:<a id="pre-requisites"></a></h2>
<ul>
<li><p>An Elastic Cloud account — <a href="https://cloud.elastic.co/">sign up now</a>, and become familiar with <a href="https://www.elastic.co/guide/en/observability/current/apm-open-telemetry.html">Elastic’s OpenTelemetry configuration</a></p></li>
<li><p>Have a LangChain app to instrument</p></li>
<li><p>Be familiar with using <a href="https://opentelemetry.io/docs/languages/python/libraries/">OpenTelemetry’s Python SDK</a> </p></li>
<li><p>An account on your favorite LLM, with API keys</p></li>
</ul>
<h2 id="overview">Overview</h2>
<p>In highlighting tracing I created a simple LangChain app that does the following:</p>
<ol>
<li><p>Takes customer input on the command line. (Queries)</p></li>
<li><p>Sends these to the Azure OpenAI LLM via a LangChain.</p></li>
<li><p>Chain tools are set to use the search with Tavily </p></li>
<li><p>The LLM uses the output which returns the relevant information to the user.</p></li>
</ol>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta85f2ad823158d9d/6a7f08acead8ec024fbaa6ad/LangChainAppCLI.png" alt="Chat Interaction" /></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt55c861f606e4c4f0/6a7f08af42a1170c6a95bd06/LangChainAppInAPM.png" alt="LangChainChat App in Elastic APM" /></p>
<p>As you can see Elastic Observability’s APM recognizes the LangChain App, and also shows the full trace (done with manual instrumentation):</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt45bed91c1521dd6d/6a7f08b24c4bfb1a90ccd38b/LangChainAutoIntrument.png" alt="LangChainChat App in Elastic APM" /></p>
<p>As the above image shows:</p>
<ol>
<li>The user makes a query</li>
<li>Azure OpenAI is called, but it uses a tool (Tavily) to obtain some results</li>
<li>Azure OpenAI reviews and returns a summary to the end user</li>
</ol>
<p>The code was manually instrumented, but auto-instrument can also be used.</p>
<h2 id="opentelemetryconfigurationaidopentelemetryconfigurationa">OpenTelemetry Configuration<a id="opentelemetry-configuration"></a></h2>
<p>In using OpenTelemetry, we need to configure the SDK to generate traces and configure Elastic’s endpoint and authorization. Instructions can be found in <a href="https://opentelemetry.io/docs/zero-code/python/#setup">OpenTelemetry Auto-Instrumentation setup documentation</a>.</p>
<h3 id="opentelemetryenvironmentvariablesaidopentelemetryenvironmentvariablesa">OpenTelemetry Environment variables:<a id="opentelemetry-environment-variables"></a></h3>
<p>OpenTelemetry Environment variables for Elastic can be set as follows in linux (or in the code).</p>
<pre><code>OTEL_EXPORTER_OTLP_ENDPOINT=12345.apm.us-west-2.aws.cloud.es.io:443
OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer%20ZZZZZZZ"
OTEL_RESOURCE_ATTRIBUTES="service.name=langchainChat,service.version=1.0,deployment.environment=production"
</code></pre>
<p>As you can see <code>OTEL_EXPORTER_OTLP_ENDPOINT</code> is set to Elastic, and the corresponding authorization header is also provided. These can be easily obtained from Elastic’s APM configuration screen under OpenTelemetry</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2273479708a89677/6a7f08b5b43770a9da4d6af5/LangChainAppOTelAPMsetup.png" alt="LangChainChat App in Elastic APM" /></p>
<p><strong>Note: No agent is needed, we simply send the OTLP trace messages directly to Elastic’s APM server.</strong> </p>
<h2 id="openllmetrylibraryaidopenllmetrylibrarya">OpenLLMetry Library:<a id="openllmetry-library"></a></h2>
<p>OpenTelemetry's auto-instrumentation can be extended to trace other frameworks via instrumentation packages.</p>
<p>First, you must install the following package: </p>
<p><code>pip install opentelemetry-instrumentation-langchain</code></p>
<p>This library was developed by OpenLLMetry. </p>
<p>Then you will need to add the following to the code.</p>
<pre><code>from opentelemetry.instrumentation.langchain import LangchainInstrumentor
LangchainInstrumentor().instrument()
</code></pre>
<h2 id="instrumentationaidinstrumentationa">Instrumentation<a id="instrumentation"></a></h2>
<p>Once the libraries are added, and the environment variables are set, you can use auto-instrumentation With auto-instrumentation, the following:</p>
<pre><code>opentelemetry-instrument python tavilyAzureApp.py
</code></pre>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt45bed91c1521dd6d/6a7f08b24c4bfb1a90ccd38b/LangChainAutoIntrument.png" alt="LangChainChat App in Elastic APM" /></p>
<p>The OpenLLMetry library does pull out the flow correctly with minimal manual instrumentation except for adding the OpenLLMetry library.</p>
<ol>
<li><p>Takes customer input on the command line. (Queries)</p></li>
<li><p>Sends these to the Azure OpenAI LLM via a Lang chain.</p></li>
<li><p>Chain tools are set to use the search with Tavily </p></li>
<li><p>The LLM uses the output which returns the relevant information to the user.</p></li>
</ol>
<h3 id="manualinstrumentationaidmanualinstrumentationa">Manual-instrumentation<a id="manual-instrumentation"></a></h3>
<p>If you want to get more details out of the application, you will need to manually instrument. To get more traces follow my <a href="https://www.elastic.co/observability-labs/blog/manual-instrumentation-python-apps-opentelemetry">Python instrumentation guide</a>. This guide will walk you through setting up the necessary OpenTelemetry bits, Additionally, you can also look at the documentation in <a href="https://opentelemetry.io/docs/languages/python/instrumentation/">OTel for instrumenting in Python</a>.</p>
<p>Note that the env variables <code>OTEL_EXPORTER_OTLP_HEADERS</code> and <code>OTEL_EXPORTER_OTLP_ENDPOINT</code> are set as noted in the section above. You can also set up the <code>OTEL_RESOURCE_ATTRIBUTES</code>. </p>
<p>Once you follow the steps in either guide and initiate the tracer, you will have to essentially just add the span where you want to get more details. In the example below, only one line of code is added for span initialization. </p>
<p>Look at the placement of with <code>tracer.start_as_current_span("getting user query") as span:</code> below</p>
<pre><code># Creates a tracer from the global tracer provider
tracer = trace.get_tracer("newsQuery")

async def chat_interface():
    print("Welcome to the AI Chat Interface!")
    print("Type 'quit' to exit the chat.")

    with tracer.start_as_current_span("getting user query") as span:
        while True:
            user_input = input("\nYou: ").strip()

            if user_input.lower() == 'quit':
                print("Thank you for chatting. Goodbye!")
                break

            print("AI: Thinking...")
            try:
                result = await chain.ainvoke({"query": user_input})
                print(f"AI: {result.content}")
            except Exception as e:
                print(f"An error occurred: {str(e)}")


if __name__ == "__main__":
    asyncio.run(chat_interface())
</code></pre>
<p>As you can see, with manual instrumentation, we get the following trace:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt54624a5f8db253fa/6a7f08b82f00b2c4d8efe9f3/LangChainAppManualTrace.png" alt="LangChainChat App in Elastic APM" /></p>
<p>Which calls out when we enter our query function. <code>async def chat_interface()</code></p>
<h2 id="conclusionaidconclusiona">Conclusion<a id="conclusion"></a></h2>
<p>In this blog, we discussed the following:</p>
<ul>
<li><p>How to manually instrument LangChain with OpenTelemetry</p></li>
<li><p>How to properly initialize OpenTelemetry and add a custom span</p></li>
<li><p>How to easily set the OTLP ENDPOINT and OTLP HEADERS with Elastic without the need for a collector</p></li>
<li><p>See traces in Elastic Observability APM</p></li>
</ul>
<p>Hopefully, this provides an easy-to-understand walk-through of instrumenting LangChain with OpenTelemetry and how easy it is to send traces into Elastic.</p>
<p><strong>Additional resources for OpenTelemetry with Elastic:</strong></p>
<ul>
<li><p><a href="https://www.elastic.co/blog/opentelemetry-observability">Independence with OpenTelemetry on Elastic</a></p></li>
<li><p><a href="https://www.elastic.co/blog/implementing-kubernetes-observability-security-opentelemetry">Modern observability and security on Kubernetes with Elastic and OpenTelemetry</a></p></li>
<li><p><a href="https://www.elastic.co/blog/3-models-logging-opentelemetry-elastic">3 models for logging with OpenTelemetry and Elastic</a></p></li>
<li><p><a href="https://www.elastic.co/blog/adding-free-and-open-elastic-apm-as-part-of-your-elastic-observability-deployment">Adding free and open Elastic APM as part of your Elastic Observability deployment</a></p></li>
<li><p><a href="https://www.elastic.co/blog/monitor-openai-api-gpt-models-opentelemetry-elastic">Monitor OpenAI API and GPT models with OpenTelemetry and Elastic</a></p></li>
<li><p>Futureproof<a href="https://www.elastic.co/virtual-events/future-proof-your-observability-platform-with-opentelemetry-and-elastic"> your observability platform with OpenTelemetry and Elastic</a></p></li>
<li><p>Instrumentation resources:</p></li>
<li><p>Python: <a href="https://www.elastic.co/blog/auto-instrumentation-of-python-applications-opentelemetry">Auto-instrumentation</a>, <a href="https://www.elastic.co/blog/manual-instrumentation-of-python-applications-opentelemetry">Manual instrumentation</a></p></li>
<li><p>Java: <a href="https://www.elastic.co/blog/auto-instrumentation-of-java-applications-opentelemetry">Auto-instrumentation</a>, <a href="https://www.elastic.co/blog/manual-instrumentation-of-java-applications-opentelemetry">Manual instrumentation </a></p></li>
<li><p>Node.js: <a href="https://www.elastic.co/blog/auto-instrument-nodejs-applications-opentelemetry">Auto-instrumentation</a>, <a href="https://www.elastic.co/blog/manual-instrumentation-of-nodejs-applications-opentelemetry">Manual instrumentation</a></p></li>
<li><p>.NET: <a href="https://www.elastic.co/blog/auto-instrumentation-of-net-applications-opentelemetry">Auto-instrumentation</a>, <a href="https://www.elastic.co/blog/manual-instrumentation-of-net-applications-opentelemetry">Manual instrumentation</a></p></li>
</ul>
<p>Also log into <a href="https://cloud.elastic.co">cloud.elastic.co</a> to try out Elastic with a free trial.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/elastic-opentelemetry-langchain-tracing</link>
    <guid isPermaLink="false">elastic-opentelemetry-langchain-tracing</guid>
    <category><![CDATA[LLM Observability]]></category>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[APM]]></category>
    <category><![CDATA[Infrastructure Monitoring]]></category>
    <dc:creator><![CDATA[Bahubali Shetti]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blted3172bb9d8e783d/6a7f08bc9090b0b4ec84e853/LangChainBlogMainImage.png" length="0" type="image/png"/>
    <pubDate>Fri, 02 Aug 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Using a custom agent with the OpenTelemetry Operator for Kubernetes]]></title>
    <description><![CDATA[]]></description>
    <content:encoded><![CDATA[<p>This is the second part of a two part series. The first part is available at <a href="https://www.elastic.co/observability-labs/blog/using-the-otel-operator-for-injecting-java-agents">Zero config OpenTelemetry auto-instrumentation for Kubernetes Java applications</a>. In that first part I walk through setting up and installing the <a href="https://github.com/open-telemetry/opentelemetry-operator/">OpenTelemetry Operator for Kubernetes</a>, and configuring that for auto-instrumentation of a Java application using the <a href="https://github.com/open-telemetry/opentelemetry-java-instrumentation/">OpenTelemetry Java agent</a>. </p>
<p>In this second part, I show how to install <em>any</em> Java agent via the OpenTelemetry operator, using the Elastic Java agents as examples.</p>
<h2 id="installationandconfigurationrecap">Installation and configuration recap</h2>
<p>Part 1 of this series, <a href="https://www.elastic.co/observability-labs/blog/using-the-otel-operator-for-injecting-java-agents">Zero config OpenTelemetry auto-instrumentation for Kubernetes Java applications</a>, details the installation and configuration of the OpenTelemetry operator and an Instrumentation resource. Here is an outline of the steps as a reminder:</p>
<ol>
<li>Install cert-manager, eg <code>kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.14.4/cert-manager.yaml</code></li>
<li>Install the operator, eg <code>kubectl apply -f https://github.com/open-telemetry/opentelemetry-operator/releases/latest/download/opentelemetry-operator.yaml</code></li>
<li>Create an Instrumentation resource</li>
<li>Add an annotation to either the deployment or the namespace</li>
<li>Deploy the application as normal</li>
</ol>
<p>In that first part, steps 3, 4 &amp; 5 were implemented for the <a href="https://github.com/open-telemetry/opentelemetry-java-instrumentation/">OpenTelemetry Java agent</a>. In this blog I’ll implement them for other agents, using the Elastic APM agents as examples. I assume that steps 1 &amp; 2 outlined above have already been done, ie that the operator is now installed. I will continue using the <code>banana</code> namespace for the examples, so ensure that namespace exists (<code>kubectl create namespace banana</code>). As per part 1, if you use any of the example instrumentation definitions below, you’ll need to substitute <code>my.apm.server.url</code> and <code>my-apm-secret-token</code> with the values appropriate for your collector.</p>
<h2 id="usingtheelasticdistributionforopentelemetryjava">Using the Elastic Distribution for OpenTelemetry Java</h2>
<p>From version 0.4.0, the <a href="https://github.com/elastic/elastic-otel-java">Elastic Distribution for OpenTelemetry Java</a> includes the agent jar at the path <code>/javaagent.jar</code> in the docker image - which is essentially all that is needed for a docker image to be usable by the OpenTelemetry operator for auto-instrumentation. This means the Instrumentation resource is straightforward to define, and as it’s a distribution of the OpenTelemetry Java agent, all the OpenTelemetry environment can apply:</p>
<pre><code>apiVersion: opentelemetry.io/v1alpha1
kind: Instrumentation
metadata:
  name: elastic-otel
  namespace: banana
spec:
  exporter:
    endpoint: https://my.apm.server.url
  propagators:
    - tracecontext
    - baggage
    - b3
  sampler:
    type: parentbased_traceidratio
    argument: "1.0"
  java:
    image: docker.elastic.co/observability/elastic-otel-javaagent:1.10.0
    env:
      - name: OTEL_EXPORTER_OTLP_HEADERS
        value: "Authorization=Bearer my-apm-secret-token"
      - name: ELASTIC_OTEL_INFERRED_SPANS_ENABLED
        value: "true"
      - name: ELASTIC_OTEL_SPAN_STACK_TRACE_MIN_DURATION
        value: "50"
</code></pre>
<p>I’ve included environment for switching on several features in the agent, including</p>
<ol>
<li>ELASTIC_APM_PROFILING_INFERRED_SPANS_ENABLED to switch on the inferred spans implementation feature described in <a href="https://www.elastic.co/observability-labs/blog/tracing-data-inferred-spans-opentelemetry">this blog</a></li>
<li>Span stack traces are automatically captured if the span takes more than ELASTIC_OTEL_SPAN_STACK_TRACE_MIN_DURATION (default would be 5ms)</li>
</ol>
<p>Adding in the annotation …</p>
<pre><code>metadata:
  annotations:
    instrumentation.opentelemetry.io/inject-java: "elastic-otel"
</code></pre>
<p>… to the pod yaml gets the application traced, and displayed in the Elastic APM UI, including the inferred child spans and stack traces</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1cf450efadd3a797/6a7f1c0296b5a66b7787b8c1/elastic-apm-ui-with-stack-trace.png" alt="Elastic APM UI showing methodB traced with stack traces and inferred spans" /></p>
<p>The additions from the features mentioned above are circled in red - inferred spans (for methodC and methodD) bottom left, and the stack trace top right. (Note that the pod included the <code>OTEL_INSTRUMENTATION_METHODS_INCLUDE</code> environment variable set to <code>"test.Testing[methodB]"</code> so that traces from methodB are shown; for pod configuration see the "Trying it" section in <a href="https://www.elastic.co/observability-labs/blog/using-the-otel-operator-for-injecting-java-agents">part 1</a>)</p>
<h2 id="usingtheelasticapmjavaagent">Using the Elastic APM Java agent</h2>
<p>From version 1.50.0, the <a href="https://github.com/elastic/apm-agent-java">Elastic APM Java agent</a> includes the agent jar at the path /javaagent.jar in the docker image - which is essentially all that is needed for a docker image to be usable by the OpenTelemetry operator for auto-instrumentation. This means the Instrumentation resource is straightforward to define:</p>
<pre><code>apiVersion: opentelemetry.io/v1alpha1
kind: Instrumentation
metadata:
  name: elastic-apm
  namespace: banana
spec:
  java:
    image: docker.elastic.co/observability/apm-agent-java:1.55.4
    env:
      - name: ELASTIC_APM_SERVER_URL
        value: "https://my.apm.server.url"
      - name: ELASTIC_APM_SECRET_TOKEN
        value: "my-apm-secret-token"
      - name: ELASTIC_APM_LOG_LEVEL
        value: "INFO"
      - name: ELASTIC_APM_PROFILING_INFERRED_SPANS_ENABLED
        value: "true"
      - name: ELASTIC_APM_LOG_SENDING
        value: "true"
</code></pre>
<p>I’ve included environment for switching on several features in the agent, including</p>
<ul>
<li>ELASTIC_APM_LOG_LEVEL set to the default value (INFO) which could easily be switched to DEBUG</li>
<li>ELASTIC_APM_PROFILING_INFERRED_SPANS_ENABLED to switch on the inferred spans implementation equivalent to the feature described in <a href="https://www.elastic.co/observability-labs/blog/tracing-data-inferred-spans-opentelemetry">this blog</a></li>
<li>ELASTIC_APM_LOG_SENDING which switches on sending logs to the APM UI, the logs are automatically correlated with transactions (for all common logging frameworks)</li>
</ul>
<p>Adding in the annotation …</p>
<pre><code>metadata:
  annotations:
     instrumentation.opentelemetry.io/inject-java: "elastic-apm"
</code></pre>
<p>… to the pod yaml gets the application traced, and displayed in the Elastic APM UI, including the inferred child spans</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt425d331c27b53881/6a7f1c0596b5a6621887b8c5/elastic-apm-ui-with-inferred-spans.png" alt="Elastic APM UI showing methodB traced with inferred spans" /></p>
<p>(Note that the pod included the <code>ELASTIC_APM_TRACE_METHODS</code> environment variable set to <code>"test.Testing#methodB"</code> so that traces from methodB are shown; for pod configuration see the "Trying it" section in <a href="https://www.elastic.co/observability-labs/blog/using-the-otel-operator-for-injecting-java-agents">part 1</a>)</p>
<h2 id="usinganextensionwiththeopentelemetryjavaagent">Using an extension with the OpenTelemetry Java agent</h2>
<p>Setting up an Instrumentation resource for the OpenTelemetry Java agent is straightforward and was done in <a href="https://www.elastic.co/observability-labs/blog/using-the-otel-operator-for-injecting-java-agents">part 1</a> of this two part series - and you can see from the above examples it’s just a matter of deciding on the docker image URL you want to use. However if you want to include an <em>extension</em> in your deployment, this is a little more complex, but also supported by the operator. Basically the extensions you want to include with the agent need to be in docker images - or you have to build an image which includes the extensions that are not already in images. Then you declare the images and the directories the extensions are in, in the Instrumentation resource. As an example, I’ll show an Instrumentation which uses version 2.5.0 of the <a href="https://github.com/open-telemetry/opentelemetry-java-instrumentation/">OpenTelemetry Java agent</a> together with the <a href="https://github.com/elastic/elastic-otel-java/tree/main/inferred-spans">inferred spans extension</a> from the <a href="https://github.com/elastic/elastic-otel-java">Elastic OpenTelemetry Java distribution</a>. The distro image includes the extension at path <code>/extensions/elastic-otel-agentextension.jar</code>. The Instrumentation resource allows either directories or file paths to be specified, here I’ll list the directory:</p>
<pre><code>apiVersion: opentelemetry.io/v1alpha1
kind: Instrumentation
metadata:
  name: otel-plus-extension-instrumentation
  namespace: banana
spec:
  exporter:
    endpoint: https://my.apm.server.url
  propagators:
    - tracecontext
    - baggage
    - b3
  sampler:
    type: parentbased_traceidratio
    argument: "1.0"
  java:
    image: ghcr.io/open-telemetry/opentelemetry-operator/autoinstrumentation-java:2.5.0
    extensions:
      - image: "docker.elastic.co/observability/elastic-otel-javaagent:1.10.0"
        dir: "/extensions"
    env:
      - name: OTEL_EXPORTER_OTLP_HEADERS
        value: "Authorization=Bearer my-apm-secret-token"
      - name: ELASTIC_OTEL_INFERRED_SPANS_ENABLED
        value: "true"
</code></pre>
<p>Note that you can have multiple <code>image … dir</code> pairs, ie include multiple extensions from different images. Note also if you are testing this specific configuration that the inferred spans extension included here will be contributed to the OpenTelemetry contrib repo at some point after this blog is published, after which the extension may no longer be present in a later version of the referred image (since it will be available from the <a href="https://github.com/open-telemetry/opentelemetry-java-contrib/">contrib repo</a> instead).</p>
<h2 id="nextsteps">Next steps</h2>
<p>Here I’ve shown how to use any agent with the <a href="https://github.com/open-telemetry/opentelemetry-operator/">OpenTelemetry Operator for Kubernetes</a>, and configure that for your system. In particular the examples have showcased how to use the Elastic Java agents to auto-instrument Java applications running in your Kubernetes clusters, along with how to enable features, using Instrumentation resources. And you can set it up for either zero config for deployments, or for just one annotation which is generally a more flexible mechanism (you can have multiple Instrumentation resource definitions, and the deployment can select the appropriate one for its application).</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/using-the-otel-operator-for-injecting-elastic-agents</link>
    <guid isPermaLink="false">using-the-otel-operator-for-injecting-elastic-agents</guid>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[APM]]></category>
    <category><![CDATA[Kubernetes]]></category>
    <category><![CDATA[Infrastructure Monitoring]]></category>
    <dc:creator><![CDATA[Jack Shirazi]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt55dfb115f9341105/6a7f1c08ea068d4de1f0a2f9/blog-header-720x420.jpg" length="0" type="image/jpeg"/>
    <pubDate>Tue, 16 Jul 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Zero config OpenTelemetry auto-instrumentation for Kubernetes Java applications]]></title>
    <description><![CDATA[Walking through how to install and enable the OpenTelemetry Operator for Kubernetes to auto-instrument Java applications, with no configuration changes needed for deployments]]></description>
    <content:encoded><![CDATA[<p>The <a href="https://github.com/open-telemetry/opentelemetry-java-instrumentation/">OpenTelemetry Java agent</a> has a number of <a href="https://opentelemetry.io/docs/languages/java/automatic/#setup">ways to install</a> the agent into a Java application. If you are running your Java applications in Kubernetes pods, there is a separate mechanism (which under the hood uses JAVA_TOOL_OPTIONS and other environment variables) to auto-instrument Java applications. This auto-instrumentation can be achieved with zero configuration of the applications and pods!</p>
<p>The mechanism to achieve zero-config auto-instrumentation of Java applications in Kubernetes is via the <a href="https://github.com/open-telemetry/opentelemetry-operator/">OpenTelemetry Operator for Kubernetes</a>. This operator has many capabilities and the full documentation (and of course source) is available in the project itself. In this blog, I'll walk through installing, setting up and running zero-config auto-instrumentation of Java applications in Kubernetes using the OpenTelemetry Operator.</p>
<h2 id="installingtheopentelemetryoperatoraidinstallingtheopentelemetryoperatora">Installing the OpenTelemetry Operator<a id="installing-the-opentelemetry-operator"></a></h2>
<p>At the time of writing this blog, the OpenTelemetry Operator needs the certification manager to be installed, after which the operator can be installed. Installing from the web is straightforward. First install the <code>cert-manager</code> (the version to be installed will be specified in the <a href="https://github.com/open-telemetry/opentelemetry-operator/">OpenTelemetry Operator for Kubernetes</a> documentation):</p>
<pre><code>kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.14.4/cert-manager.yaml
</code></pre>
<p>Then when the cert managers are ready (<code>kubectl get pods -n cert-manager</code>)  …</p>
<pre><code>NAMESPACE&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; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; READY
cert-manager &amp;nbsp; cert-manager-67c98b89c8-rnr5s&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 1/1
cert-manager &amp;nbsp; cert-manager-cainjector-5c5695d979-q9hxz &amp;nbsp; &amp;nbsp; 1/1
cert-manager &amp;nbsp; cert-manager-webhook-7f9f8648b9-8gxgs&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 1/1
</code></pre>
<p>… you can install the OpenTelemetry Operator:</p>
<pre><code>kubectl apply -f https://github.com/open-telemetry/opentelemetry-operator/releases/latest/download/opentelemetry-operator.yaml
</code></pre>
<p>You can, of course, use a specific version of the operator instead of the <code>latest</code>. But here I’ve used the <code>latest</code> version.</p>
<h2 id="aninstrumentationresourceaidaninstrumentationresourcea">An Instrumentation resource<a id="an-instrumentation-resource"></a></h2>
<p>Now you need to add just one further Kubernetes resource to enable auto-instrumentation: an <code>Instrumentation</code> resource. I am going to use the <code>banana</code> namespace for my examples, so I have first created that namespace (<code>kubectl create namespace banana</code>). The auto-instrumentation is specified and configured by these Instrumentation resources. Here is a basic one which will allow every Java pod in the <code>banana</code> namespace to be auto-instrumented with version 2.5.0 of the <a href="https://github.com/open-telemetry/opentelemetry-java-instrumentation/">OpenTelemetry Java agent</a>:</p>
<pre><code>apiVersion: opentelemetry.io/v1alpha1
kind: Instrumentation
metadata:
  name: banana-instr
  namespace: banana
spec:
  exporter:
    endpoint: "https://my.endpoint"
  propagators:
    - tracecontext
    - baggage
    - b3
  sampler:
    type: parentbased_traceidratio
    argument: "1.0"
  java:
    image: ghcr.io/open-telemetry/opentelemetry-operator/autoinstrumentation-java:2.5.0
    env:
      - name: OTEL_EXPORTER_OTLP_HEADERS
        value: "Authorization=Bearer MyAuth"
</code></pre>
<p>Creating this resource (eg with <code>kubectl apply -f banana-instr.yaml</code>, assuming the above yaml was saved in file <code>banana-instr.yaml</code>) makes the <code>banana-instr</code> Instrumentation resource available for use. (Note you will need to change <code>my.endpoint</code> and <code>MyAuth</code> to values appropriate for your collector.) You can use this instrumentation immediately by adding an annotation to any deployment in the <code>banana</code> namespace:</p>
<pre><code>metadata:
&amp;nbsp;&amp;nbsp;annotations:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;instrumentation.opentelemetry.io/inject-java: "true"
</code></pre>
<p>The <code>banana-instr</code> Instrumentation resource is not yet set to be applied by <em>default</em> to all pods in the banana namespace. Currently it's zero-config as far as the <em>application</em> is concerned, but it requires an annotation added to a <em>pod or deployment</em>. To make it fully zero-config for <em>all pods</em> in the <code>banana</code> namespace, we need to add that annotation to the namespace itself, ie editing the namespace (<code>kubectl edit namespace banana</code>) so it would then have contents similar to</p>
<pre><code>apiVersion: v1
kind: Namespace
metadata:
&amp;nbsp;&amp;nbsp;name: banana
&amp;nbsp;&amp;nbsp;annotations:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;instrumentation.opentelemetry.io/inject-java: "banana-instr"
...
</code></pre>
<p>Now we have a namespace that is going to auto-instrument <em>every</em> Java application deployed in the <code>banana</code> namespace with the 2.5.0 <a href="https://github.com/open-telemetry/opentelemetry-java-instrumentation/">OpenTelemetry Java agent</a>!</p>
<h2 id="tryingitaidtryingita">Trying it<a id="trying-it"></a></h2>
<p>There is a simple example Java application at <a href="http://docker.elastic.co/demos/apm/k8s-webhook-test">docker.elastic.co/demos/apm/k8s-webhook-test</a> which just repeatedly calls the chain <code>main-&gt;methodA-&gt;methodB-&gt;methodC-&gt;methodD</code> with some sleeps in the calls. Running this (<code>kubectl apply -f banana-app.yaml</code>) using a very basic pod definition:</p>
<pre><code>apiVersion: v1
kind: Pod
metadata:
  name: banana-app
  namespace: banana
  labels:
    app: banana-app
spec:
  containers:
    - image: docker.elastic.co/demos/apm/k8s-webhook-test
      imagePullPolicy: Always
      name: banana-app
      env: 
      - name: OTEL_INSTRUMENTATION_METHODS_INCLUDE
        value: "test.Testing[methodB]"
</code></pre>
<p>results in the app being auto-instrumented with no configuration changes! The resulting app shows up in any APM UI, such as Elastic APM</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5339364ee45875ef/6a7f1c0d3ce8e2a2e5cf57d0/elastic-apm-ui-transaction.png" alt="Elastic APM UI showing methodB traced" /></p>
<p>As you can see, for this example I also added this env var to the pod yaml, <code>OTEL_INSTRUMENTATION_METHODS_INCLUDE="test.Testing[methodB]"</code> so that there were traces showing from methodB.</p>
<h2 id="thetechnologybehindtheautoinstrumentationaidthetechnologybehindtheautoinstrumentationa">The technology behind the auto-instrumentation<a id="the-technology-behind-the-auto-instrumentation"></a></h2>
<p>To use the auto-instrumentation there is no specific need to understand the underlying mechanisms, but for those of you interested, here’s a quick outline. </p>
<ol>
<li>The <a href="https://github.com/open-telemetry/opentelemetry-operator/">OpenTelemetry Operator for Kubernetes</a> installs a <a href="https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/">mutating webhook</a>, a standard Kubernetes component.</li>
<li>When deploying, Kubernetes first sends all definitions to the mutating webhook.</li>
<li>If the mutating webhook sees that the conditions for auto-instrumentation should be applied (ie </li>
<li>there is an Instrumentation resource for that namespace and</li>
<li>the correct annotation for that Instrumentation is applied to the definition in some way, either from the definition itself or from the namespace),</li>
<li>then the mutating webhook “mutates” the definition to include the environment defined by the Instrumentation resource.</li>
<li>The environment includes the explicit values defined in the env, as well as some implicit OpenTelemetry values (see the <a href="https://github.com/open-telemetry/opentelemetry-operator/">OpenTelemetry Operator for Kubernetes</a> documentation for full details).</li>
<li>And most importantly, the operator</li>
<li>pulls the image defined in the Instrumentation resource,</li>
<li>extracts the file at the path <code>/javaagent.jar</code> from that image (using shell command <code>cp</code>)</li>
<li>inserts it into the pod at path <code>/otel-auto-instrumentation-java/javaagent.jar</code></li>
<li>and adds the environment variable <code>JAVA_TOOL_OPTIONS=-javaagent:/otel-auto-instrumentation-java/javaagent.jar</code>.</li>
<li>The JVM automatically picks up that JAVA_TOOL_OPTIONS environment variable on startup and applies it to the JVM command-line.</li>
</ol>
<h2 id="nextstepsaidnextstepsa">Next steps<a id="next-steps"></a></h2>
<p>This walkthrough can be repeated in any Kubernetes cluster to demonstrate and experiment with auto-instrumentation (you will need to create the banana namespace first). In part 2 of this two part series, <a href="https://www.elastic.co/observability-labs/blog/using-the-otel-operator-for-injecting-elastic-agents">Using a custom agent with the OpenTelemetry Operator for Kubernetes</a>, I show how to install any Java agent via the OpenTelemetry operator, using the Elastic Java agents as examples.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/using-the-otel-operator-for-injecting-java-agents</link>
    <guid isPermaLink="false">using-the-otel-operator-for-injecting-java-agents</guid>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[APM]]></category>
    <category><![CDATA[Kubernetes]]></category>
    <category><![CDATA[Infrastructure Monitoring]]></category>
    <dc:creator><![CDATA[Jack Shirazi]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt53c963d5c03388fb/6a7f1c101967ea3597330ba4/blog-header.png" length="0" type="image/png"/>
    <pubDate>Thu, 11 Jul 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Introducing Elastic Distribution for OpenTelemetry Python]]></title>
    <description><![CDATA[Announcing the first alpha release of the Elastic Distribution for OpenTelemetry Python. See how easy it is to instrument your Python applications with OpenTelemetry in this blog post.]]></description>
    <content:encoded><![CDATA[<p>We are delighted to announce the alpha release of the <a href="https://github.com/elastic/elastic-otel-python#readme">Elastic Distribution for OpenTelemetry Python</a>. This project is a customized OpenTelemetry distribution that allows us to configure better defaults for using OpenTelemetry with the Elastic cloud offering.</p>
<h2 id="background">Background</h2>
<p>Elastic is standardizing on OpenTelemetry (OTel) for observability and security data collection. As part of that effort, we are <a href="https://www.elastic.co/blog/elastic-opentelemetry-sdk-distributions">providing distributions of the OpenTelemetry Language SDKs</a>. We have recently released alpha distributions for <a href="https://github.com/elastic/elastic-otel-java#readme">Java</a>, <a href="https://github.com/elastic/elastic-otel-dotnet#readme">.NET</a> and <a href="https://github.com/elastic/elastic-otel-node#readme">Node.js</a>. Our <a href="https://github.com/elastic/apm-agent-android#readme">Android</a> and <a href="https://github.com/elastic/apm-agent-ios#readme">iOS</a> SDKs have been OpenTelemetry-based from the start. The Elastic Distribution for OpenTelemetry Python is the latest addition.</p>
<h2 id="designchoices">Design choices</h2>
<p>We have chosen to provide a lean distribution that does not install all the instrumentations by default but that instead provides tools
to do so. We leverage the <code>opentelemetry-bootstrap</code> tool provided by OpenTelemetry Python project to scan the packages installed in your
environment and recognizes libraries we are able to instrument.  This tool can just report the instrumentations available and optionally
is able to install them as well.
This allows you to avoid installing packages you are not going to need or instrument libraries you are not interested in tracing.</p>
<h2 id="gettingstarted">Getting started</h2>
<p>To get started with Elastic Distribution for OpenTelemetry Python you need to install  the package <code>elastic-opentelemetry</code> in your project
environment. We'll use <code>pip</code> in our examples but you are free to use any python package and environment manager of your choice.</p>
<pre><code>pip install elastic-opentelemetry
</code></pre>
<p>Once you have installed our distro you'll have also the <code>opentelemetry-bootstrap</code> command available. Running it:</p>
<pre><code>opentelemetry-bootstrap
</code></pre>
<p>will list all available packages for your instrumentation, e.g. you can expect something like the following:</p>
<pre><code>opentelemetry-instrumentation-asyncio==0.46b0
opentelemetry-instrumentation-dbapi==0.46b0
opentelemetry-instrumentation-logging==0.46b0
opentelemetry-instrumentation-sqlite3==0.46b0
opentelemetry-instrumentation-threading==0.46b0
opentelemetry-instrumentation-urllib==0.46b0
opentelemetry-instrumentation-wsgi==0.46b0
opentelemetry-instrumentation-grpc==0.46b0
opentelemetry-instrumentation-requests==0.46b0
opentelemetry-instrumentation-system-metrics==0.46b0
opentelemetry-instrumentation-urllib3==0.46b0
</code></pre>
<p>It also provides a command option to install the packages automatically</p>
<pre><code>opentelemetry-bootstrap --action=install
</code></pre>
<p>It is advised to run this command every time you release a new version of your application so that you can install or just revise any
instrumentation packages for your code.</p>
<p>Some environment variables are needed to provide the needed configuration for instrumenting your services. These mostly
concern the destination of your traces but also for easily identifying your service.
A <em>service name</em> is required to have your service distinguishable from the others. Then you need to provide
the <em>authorization</em> headers for authentication with Elastic Observability cloud and the Elastic cloud endpoint where the data is sent.</p>
<p>The API Key you get from your Elastic cloud serverless project must be <em>URL-encoded</em>, you can do that with the following Python snippet:</p>
<pre><code>from urllib.parse import quote
quote("ApiKey &lt;your api key&gt;)
</code></pre>
<p>Once you have all your configuration values you can export via environment variables as below:</p>
<pre><code>export OTEL_RESOURCE_ATTRIBUTES=service.name=&lt;service-name&gt;
export OTEL_EXPORTER_OTLP_HEADERS="Authorization=&lt;url encoded apikey header value&gt;"
export OTEL_EXPORTER_OTLP_ENDPOINT=&lt;your elastic cloud url&gt;
</code></pre>
<p>We are done with the configuration and the last piece of the puzzle is wrapping your service invocation with
<code>opentelemetry-instrument</code>, the wrapper that provides <em>zero-code instrumentation</em>. <em>Zero-code</em> (or Automatic) instrumentation means
that the distribution will set up the OpenTelemetry SDK and enable all the previously installed instrumentations for you.
Unfortunately <em>Zero-code</em> instrumentation does not cover all libraries and some — web frameworks in particular — will require minimal manual
configuration.</p>
<p>For a web service running with gunicorn it may look like:</p>
<pre><code>opentelemetry-instrument gunicorn main:app
</code></pre>
<p>The result is an observable application using the industry-standard <a href="https://opentelemetry.io/">OpenTelemetry</a> — offering high-quality instrumentation of many popular Python libraries, a portable API to avoid vendor lock-in and an active community.</p>
<p>Using Elastic Observability, some out-of-the-box benefits you can expect are: rich trace viewing, Service maps, integrated metrics and log analysis, and more.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb4d06a40da2943f4/6a85cc2027c5cd6fab5f741a/traces-original.png" alt="trace sample screenshot" /></p>
<h2 id="whatsnext">What's next?</h2>
<p>Elastic is committed to helping OpenTelemetry succeed and to helping our customers use OpenTelemetry effectively in their systems. Last year, we <a href="https://opentelemetry.io/blog/2023/ecs-otel-semconv-convergence/">donated ECS</a> and continue to work on integrating it with OpenTelemetry Semantic Conventions. More recently, we are working on <a href="https://www.elastic.co/observability-labs/blog/elastic-profiling-agent-acceptance-opentelemetry">donating our eBPF-based profiler</a> to OpenTelemetry. We contribute to many of the language SDKs and other OpenTelemetry projects.</p>
<p>In the Python ecosystem we are active reviewers and contributors of both the <a href="https://github.com/open-telemetry/opentelemetry-python/">opentelemetry-python</a> and <a href="https://github.com/open-telemetry/opentelemetry-python-contrib/">opentelemetry-python-contrib</a> repositories.</p>
<p>The Elastic Distribution for OpenTelemetry Python is currently an alpha. Please <a href="https://github.com/elastic/elastic-otel-python/">try it out</a> and let us know if it might work for you. Watch for the <a href="https://github.com/elastic/elastic-otel-python/releases">latest releases here</a>. You can engage with us on <a href="https://github.com/elastic/elastic-otel-python/issues">the project issue tracker</a>.</p>
<p>We are eager to know your use cases to help you succeed in your Observability journey.</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>
<h2 id="resources">Resources</h2>
<ul>
<li>https://www.elastic.co/blog/elastic-opentelemetry-sdk-distributions</li>
<li>https://www.elastic.co/observability-labs/blog/elastic-distribution-opentelemetry-java-agent</li>
<li>https://www.elastic.co/observability-labs/blog/elastic-opentelemetry-distribution-dotnet-applications</li>
<li>https://www.elastic.co/observability-labs/blog/elastic-opentelemetry-distribution-node-js</li>
<li>https://www.elastic.co/observability-labs/blog/manual-instrumentation-python-apps-opentelemetry</li>
<li>https://www.elastic.co/observability-labs/blog/auto-instrumentation-python-applications-opentelemetry</li>
<li>https://www.elastic.co/observability-labs/blog/opentelemetry-observability</li>
</ul>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/elastic-opentelemetry-distribution-python</link>
    <guid isPermaLink="false">elastic-opentelemetry-distribution-python</guid>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[APM]]></category>
    <category><![CDATA[Infrastructure Monitoring]]></category>
    <dc:creator><![CDATA[Riccardo Magliocchetti]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt498330cde71bc9cd/6a85cc23331d7afa63c317bb/python.jpg" length="0" type="image/jpeg"/>
    <pubDate>Sun, 07 Jul 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Automatic cloud resource attributes with OpenTelemetry Java]]></title>
    <description><![CDATA[Capturing cloud resource attributes allow to describe application cloud deployment details. In this article we describe three distinct ways to enable them for Java applications using OpenTelemetry]]></description>
    <content:encoded><![CDATA[<p>With OpenTelemetry, the observed entities (application, services, processes, …) are described through resource attributes. The definitions and the values of those attributes are defined in the <a href="https://opentelemetry.io/docs/concepts/semantic-conventions/">semantic conventions</a>.\
In practice, for a typical java application running in a cloud environment like Google Cloud Platform (GCP), Amazon Web Services (AWS) or Azure, it means capturing the name of the cloud provider, the cloud service name or availability zone in addition to per-provider attributes. Those attributes are then used to describe and qualify the observability signals (logs, traces, metrics), defined by semantic conventions in the <a href="https://opentelemetry.io/docs/specs/semconv/resource/cloud/">cloud resource attributes</a> section.</p>
<p>When using the <a href="https://github.com/open-telemetry/opentelemetry-java">OpenTelemetry Java SDK</a> or the <a href="https://github.com/open-telemetry/opentelemetry-java-instrumentation">OpenTelemetry instrumentation agent</a>, those attributes are not automatically captured by default. In this article we will show you first how to enable them with the SDK, then using the instrumentation agent and then we will show you how using the <a href="https://github.com/elastic/elastic-otel-java/">Elastic OpenTelemetry Distribution</a> makes it even easier.</p>
<h2 id="opentelemetryjavasdk">OpenTelemetry Java SDK</h2>
<p>The OpenTelemetry Java SDK does not capture any cloud resource attributes, however it provides a pluggable service provider interface to register resource attributes providers and application developers have to provide the implementations.</p>
<p>Implementations for <a href="https://github.com/open-telemetry/opentelemetry-java-contrib/tree/main/gcp-resources">GCP</a> and <a href="https://github.com/open-telemetry/opentelemetry-java-contrib/tree/main/aws-resources">AWS</a> are already included in the <a href="https://github.com/open-telemetry/opentelemetry-java-contrib/">OpenTelemetry Java Contrib</a> repo, so if you are using one of those cloud providers then it's mostly a matter of adding those providers to your application dependencies. Thanks to autoconfiguration those should be automatically included and enabled once they are added to the application classpath. The <a href="https://github.com/open-telemetry/opentelemetry-java/tree/main/sdk-extensions/autoconfigure#resource-provider-spi">SDK documentation</a> provides all the details to add and configure those in your application.</p>
<p>If you are using a cloud provider for which no such implementation is available, then you still have the option to provide your own which is a straightforward implementation of the <a href="https://github.com/open-telemetry/opentelemetry-java/blob/main/sdk-extensions/autoconfigure/README.md#resource-provider-spi">ResourceProvider</a> SPI (Service Provider Interface). In order to keep things consistent, you will have to rely on the existing <a href="https://opentelemetry.io/docs/specs/semconv/resource/cloud/">cloud semantic conventions</a>.</p>
<p>For example here is an example of a simple cloud resource attributes provider for a fictitious cloud provider named "potatoes".</p>
<pre><code>package potatoes;

import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import io.opentelemetry.sdk.autoconfigure.spi.ResourceProvider;
import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.semconv.incubating.CloudIncubatingAttributes;

public class PotatoesResourceProvider implements ResourceProvider {

@Override
public Resource createResource(ConfigProperties configProperties) {
   return Resource.create(Attributes.of(
           CloudIncubatingAttributes.CLOUD_PROVIDER, "potatoes",
           CloudIncubatingAttributes.CLOUD_PLATFORM, "french-fries",
           CloudIncubatingAttributes.CLOUD_REGION, "garden"
           ));
  }
}
</code></pre>
<h2 id="opentelemetryjavainstrumentation">OpenTelemetry Java instrumentation</h2>
<p>The <a href="https://github.com/open-telemetry/opentelemetry-java-instrumentation">OpenTelemetry Java Instrumentation</a> provides a java agent that instruments the application at runtime automatically for an extensive set of frameworks and libraries (see <a href="https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/main/docs/supported-libraries.md">supported technologies</a>).</p>
<p>Using instrumentation means that the application bytecode and the embedded libraries are modified automatically to make them behave as if explicit modifications were made in their source code to call the OpenTelemetry SDK in order to create traces, spans and metrics.</p>
<p>When an application is deployed with the OpenTelemetry instrumentation agent, the cloud resource attributes for GCP and AWS are included but not enabled by default since version 2.2.0. You can enable them <a href="https://opentelemetry.io/docs/languages/java/automatic/configuration/#enable-resource-providers-that-are-disabled-by-default">through configuration</a> by setting the following properties:</p>
<ul>
<li><p>For AWS: <code>otel.resource.providers.aws.enabled=true</code></p></li>
<li><p>For GCP: <code>otel.resource.providers.gcp.enabled=true</code></p></li>
</ul>
<h2 id="elasticopentelemetryjavadistribution">Elastic OpenTelemetry Java Distribution</h2>
<p>The Elastic OpenTelemetry Java distribution relies on the OpenTelemetry Java instrumentation which we often refer to as the Vanilla OpenTelemetry, and it thus inherits all of its features.</p>
<p>One major difference though is that the resource attributes providers for GCP and AWS are included and enabled by default to provide a better onboarding experience without extra configuration.</p>
<p>The minor cost to this is that it might make the application startup slightly slower due to having to call an HTTP(S) endpoint. This overhead is usually negligible compared to application startup but can become significant for some setups.</p>
<p>In order to reduce the startup overhead, or when the cloud provider is known in advance, you can selectively disable unused provider implementations through configuration:</p>
<ul>
<li><p>For AWS: <code>otel.resource.providers.aws.enabled=false</code></p></li>
<li><p>For GCP: <code>otel.resource.providers.gcp.enabled=false</code></p></li>
</ul>
<h2 id="conclusion">Conclusion</h2>
<p>With this blogpost we have introduced what OpenTelemetry cloud resource attributes are and how they can be used and configured into application deployments using either OpenTelemetry SDK/API and Instrumentation agents.</p>
<p>When using the Elastic OpenTelemetry Java distribution, those resource providers are automatically provided and enabled for an easy and simple onboarding experience.</p>
<p>Another very interesting aspect of the cloud resource attribute providers available in the <a href="https://github.com/open-telemetry/opentelemetry-java-contrib">opentelemetry-java-contrib</a> repository is that they are maintained by their respective vendors (Google and Amazon). For the end-user it means those implementations should be quite well tested and be robust to changes in the underlying infrastructure. For solution vendors like Elastic, it means we don't have to re-implement and reverse-engineer the infrastructure details of every cloud provider, hence proving that investing in those common components is a net win for the broader OpenTelemetry community.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/opentelemetry-java-automatic-cloud-resource-attributes</link>
    <guid isPermaLink="false">opentelemetry-java-automatic-cloud-resource-attributes</guid>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[Infrastructure Monitoring]]></category>
    <dc:creator><![CDATA[Sylvain Juge]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd11cf83d31a10bb5/6a7f192e9090b0891b84ee03/flexible-implementation-1680X980.png" length="0" type="image/png"/>
    <pubDate>Thu, 27 Jun 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Combining Elastic Universal Profiling with Java APM Services and Traces]]></title>
    <description><![CDATA[Learn how to combine the power of Elastic universal profiling with APM data from Java services to easily pinpoint CPU bottlenecks. Compatible with both OpenTelemetry and the classic Elastic APM Agent.]]></description>
    <content:encoded><![CDATA[<p>In <a href="https://www.elastic.co/observability-labs/blog/continuous-profiling-distributed-tracing-correlation">a previous blog post</a>, we introduced the technical details of how we managed to correlate eBPF profiling data with APM traces.
This time, we'll show you how to get this feature up and running to pinpoint CPU bottlenecks in your Java services! The correlation is supported for both OpenTelemetry and the classic Elastic APM Agent. We'll show you how to enable it for both.</p>
<h2 id="demoapplication">Demo Application</h2>
<p>For this blog post, we’ll be using the <a href="https://github.com/JonasKunz/cpu-burner">cpu-burner demo application</a> to showcase the correlation capabilities of APM, tracing, and profiling in Elastic. This application was built to continuously execute several CPU-intensive tasks:</p>
<ul>
<li>It computes Fibonacci numbers using the naive, recursive algorithm.</li>
<li>It hashes random data with the SHA-2 and SHA-3 hashing algorithms.</li>
<li>It performs numerous large background allocations to stress the garbage collector.</li>
</ul>
<p>The computations of the Fibonacci numbers and the hashing will each be visible as transactions in Elastic: They have been manually instrumented using the OpenTelemetry API.</p>
<h2 id="settingupprofilingandapm">Setting up Profiling and APM</h2>
<p>First, we’ll need to set up the universal profiling host agent on the host where the demo application will run. Starting from version 8.14.0, correlation with APM data is supported and enabled out of the box for the profiler. There is no special configuration needed; we can just follow the <a href="https://www.elastic.co/guide/en/observability/current/profiling-get-started.html">standard setup guide</a>.
Note that at the time of writing, universal profiling only supports Linux.
On Windows, you'll have to use a VM to try the demo.
On macOS, you can use <a href="https://github.com/abiosoft/colima">colima</a> as docker engine and run the profiling host agent and the demo app in container images.</p>
<p>In addition, we’ll need to instrument our demo application with an APM agent. We can either use the <a href="https://github.com/elastic/apm-agent-java">classic Elastic APM agent</a> or the <a href="https://github.com/elastic/elastic-otel-java">Elastic OpenTelemetry Distribution</a>.</p>
<h3 id="usingtheclassicelasticapmagent">Using the Classic Elastic APM Agent</h3>
<p>Starting with version 1.50.0, the classic Elastic APM agent ships with the capability to correlate the traces it captures with the profiling data from universal profiling. We’ll just need to enable it explicitly via the <strong>universal_profiling_integration_enabled</strong> config option. Here is the standard command line for running the demo application with the setting enabled:</p>
<pre><code>curl -o 'elastic-apm-agent.jar' -L 'https://oss.sonatype.org/service/local/artifact/maven/redirect?r=releases&amp;g=co.elastic.apm&amp;a=elastic-apm-agent&amp;v=LATEST'
java -javaagent:elastic-apm-agent.jar \
-Delastic.apm.service_name=cpu-burner-elastic \
-Delastic.apm.secret_token=XXXXX \
-Delastic.apm.server_url=&lt;elastic-apm-server-endpoint&gt; \
-Delastic.apm.application_packages=co.elastic.demo \
-Delastic.apm.universal_profiling_integration_enabled=true \
-jar ./target/cpu-burner.jar
</code></pre>
<h3 id="usingopentelemetry">Using OpenTelemetry</h3>
<p>The feature is also available as an OpenTelemetry SDK extension.
This means you can use it as a plugin for the vanilla OpenTelemetry agent or add it to your OpenTelemetry SDK if you are not using an agent.
In addition, the feature ships by default with the Elastic OpenTelemetry Distribution for Java and can be used via any of the <a href="https://www.elastic.co/observability-labs/blog/elastic-distribution-opentelemetry-java-agent">possible usage methods</a>.
While the extension is currently Elastic-specific, we are already working with the various OpenTelemetry SIGs on standardizing the correlation mechanism, especially now after the <a href="https://www.elastic.co/observability-labs/blog/elastic-profiling-agent-acceptance-opentelemetry">eBPF profiling agent has been contributed</a>.</p>
<p>For this demo, we’ll be using the Elastic OpenTelemetry Distro Java agent to run the extension:</p>
<pre><code>curl -o 'elastic-otel-javaagent.jar' -L 'https://oss.sonatype.org/service/local/artifact/maven/redirect?r=releases&amp;g=co.elastic.otel&amp;a=elastic-otel-javaagent&amp;v=LATEST'
java -javaagent:./elastic-otel-javaagent.jar \
-Dotel.exporter.otlp.endpoint=&lt;elastic-cloud-OTLP-endpoint&gt; \
"-Dotel.exporter.otlp.headers=Authorization=Bearer XXXX" \
-Dotel.service.name=cpu-burner-otel \
-Delastic.otel.universal.profiling.integration.enabled=true \
-jar ./target/cpu-burner.jar
</code></pre>
<p>Here, we explicitly enabled the profiling integration feature via the <strong>elastic.otel.universal.profiling.integration.enabled</strong> property. Note that with an upcoming release of the universal profiling feature, this won’t be necessary anymore! The OpenTelemetry extension will then automatically detect the presence of the profiler and enable the correlation feature based on that.</p>
<p>The demo repository also comes with a Dockerfile, so you can alternatively build and run the app in docker:</p>
<pre><code>docker build -t cpu-burner .
docker run --rm -e OTEL_EXPORTER_OTLP_ENDPOINT=&lt;elastic-cloud-OTLP-endpoint&gt; -e OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer XXXX" cpu-burner
</code></pre>
<p>And that’s it for setup; we are now ready to inspect the correlated profiling data!</p>
<h2 id="analyzingservicecpuusage">Analyzing Service CPU Usage</h2>
<p>The first thing we can do now is head to the “Flamegraph” view in Universal Profiling and inspect flamegraphs filtered on APM services. Without the APM correlation, universal profiling is limited to filtering on infrastructure concepts, such as hosts, containers, and processes.
Below is a screencast showing a flamegraph filtered on the service name of our demo application:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7fc9f1694923ff35/6a7f1be296b5a666c387b8bd/service-profiling.gif" alt="Universal Profiling Flamegraph filtered on the service name of our demo application" /></p>
<p>With this filter applied, we get a flamegraph aggregated over all instances of our service. If that is not desired, we could narrow down the filter, e.g. based on the host or container names. Note that the same service-level flamegraph view is also available on the “Universal Profiling” tab in the APM service UI.</p>
<p>The flamegraphs show exactly how the demo application is spending its CPU time, independently of whether it is covered by instrumentation or not. From left to right, we can first see the time spent in application tasks: We can identify the background allocations not covered by APM transactions as well as the SHA-computation and Fibonacci transactions.
Interestingly, this application logic only covers roughly 60% of the total CPU time! The remaining time is spent mostly in the G1 garbage collector due to the high allocation rate of our application. The flamegraph shows all G1-related activities and the timing of the individual phases of concurrent tasks. We can easily identify those based on the native function names. This is made possible by universal profiling being capable of profiling and symbolizing the JVM’s C++ code in addition to the Java code.</p>
<h2 id="pinpointingtransactionbottlenecks">Pinpointing Transaction Bottlenecks</h2>
<p>While the service-level flamegraph already gives good insights on where our transactions consume the most CPU, this is mainly due to the simplicity of the demo application. In real-world applications, it can be much harder to pinpoint that certain stack frames come mostly from certain transactions. For this reason, the APM agent also correlates CPU profiling data from universal profiling on the transaction level.</p>
<p>We can navigate to the “Universal Profiling” tab on the transaction details page to get per-transaction flamegraphs:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte8b420da4c4d739e/6a7f1be633fa8ae8a1202ba0/navigate-to-transaction-profiles.gif" alt="Navigation to per-transaction profiling flamegraphs" /></p>
<p>For example, let’s have a look at the flamegraph of our transaction computing SHA-2 and SHA-3 hashes of randomly generated data:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2887385ca24655a5/6a7f1be96c6eac770cf145c5/tx-unfiltered.png" alt="Flamegraph for the hashing transaction" /></p>
<p>Interestingly, the flamegraph uncovers some unexpected results: The transactions spend more time computing the random bytes to be hashed rather than on the hashing itself! So if this were a real-world application, a possible optimization could be to use a more performant random number generator.</p>
<p>In addition, we can see that the MessageDigest.update call for computing the hash values fans out into two different code paths: One is a call into the <a href="https://www.bouncycastle.org/">BouncyCastle cryptography library</a>, the other one is a JVM stub routine, meaning that the JIT compiler has inserted special assembly code for a function.</p>
<p>The flamegraph shown in the screenshot displays the aggregated data for all “shaShenanigans” transactions in the given time filter. We can further filter this down using the transaction filter bar at the top. To make the best use of this, the demo application annotates the transactions with the hashing algorithm used via OpenTelemetry attributes:</p>
<pre><code>public static void shaShenanigans(MessageDigest digest) {
    Span span = tracer.spanBuilder("shaShenanigans")
        .setAttribute("algorithm", digest.getAlgorithm())
        .startSpan();
    ...
    span.end()
}
</code></pre>
<p>So, let’s filter our flamegraph based on the used hashing algorithm:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2886c86a7b533c09/6a7f1bec1967ea42e1330b9e/tx-filter-bar.png" alt="Transaction Filter Bar" /></p>
<p>Note that “SHA-256” is the name of the JVM built-in SHA-2 256-bit implementation. This now gives the following flamegraph:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1d5ae1b2fb5017f7/6a7f1bee3ce8e25d9ecf57c8/tx-sha-256.png" alt="Transaction Filter Bar" /></p>
<p>We can see that the BouncyCastle stack frames are gone and MessageDigest.update spends all its time in the JVM stub routines. Therefore, the stub routine is likely hand-crafted assembly from the JVM maintainers for the SHA2 algorithm.</p>
<p>If we instead filter on “SHA3-256”, we get the following result:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb3d9edb17ea1ad0a/6a7f1bf1bd219839697584d5/tx-sha3.png" alt="Transaction Filter Bar" /></p>
<p>Now, as expected, MessageDigest.update spends all its time in the BouncyCastle library for the SHA3 implementation. Note that the hashing here takes up more time in relation to the random data generation, showing that the SHA2 JVM stub routine is significantly faster than the BouncyCastle Java SHA3 implementation.</p>
<p>This filtering is not limited to custom attributes like those shown in this demo. You can filter on any transaction attributes, including latency, HTTP headers, and so on. For example, for typical HTTP applications, it allows analyzing the efficiency of the used JSON serializer based on the payload size.
Note that while it is possible to filter on single transaction instances (e.g. based on trace.id), this is not recommended: To allow continuous profiling in production systems, the profiler by default runs with a low sampling rate of 20hz. This means that for typical real-world applications, this will not yield enough data when looking at a single transaction execution. Instead, we gain insights by monitoring multiple executions of a group of transactions over time and aggregating their samples, for example in a flamegraph.</p>
<h2 id="summary">Summary</h2>
<p>A common reason for applications to degrade is overly high CPU usage. In this blog post, we showed how to combine universal profiling with APM to find the actual root cause in such cases: We explained how to analyze the CPU time using profiling flamegraphs on service and transaction levels.
In addition, we further drilled down into data using custom filters.
We used a simple demo application for this purpose, so go ahead and try it yourself with your own, real-world applications to uncover the actual power of the feature!</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/universal-profiling-with-java-apm-services-traces</link>
    <guid isPermaLink="false">universal-profiling-with-java-apm-services-traces</guid>
    <category><![CDATA[APM]]></category>
    <category><![CDATA[OpenTelemetry]]></category>
    <dc:creator><![CDATA[Jonas Kunz]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc825e711e84ad7a0/6a7f1bf3bdcff00c0ec432d9/blog-header.jpg" length="0" type="image/jpeg"/>
    <pubDate>Thu, 20 Jun 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Elastic contributes its Universal Profiling agent to OpenTelemetry]]></title>
    <description><![CDATA[Elastic is advancing the adoption of OpenTelemetry with the contribution of its universal profiling agent. Elastic is committed to ensuring a vendor-agnostic ingestion and collection of observability and security telemetry through OpenTelemetry.]]></description>
    <content:encoded><![CDATA[<p>Following great collaboration between Elastic and OpenTelemetry's profiling community, which included a thorough review process, the OpenTelemetry community has accepted Elastic's donation of our continuous profiling agent. This marks a significant milestone in helping establish profiling as the fourth telemetry signal in OpenTelemetry. Elastic’s eBPF-based continuous profiling agent observes code across different programming languages and runtimes, third-party libraries, kernel operations, and system resources with low CPU and memory overhead in production. SREs can now benefit from these capabilities: quickly identifying performance bottlenecks, maximizing resource utilization, reducing carbon footprint, and optimizing cloud spend.
Over the past year, we have been instrumental in <a href="https://opentelemetry.io/blog/2023/ecs-otel-semconv-convergence/">enhancing OpenTelemetry's Semantic Conventions</a> with the donation of Elastic Common Schema (ECS), contributing to the OpenTelemetry Collector and language SDKs, and have been working with OpenTelemetry’s Profiling Special Interest Group (SIG) to lay the foundation necessary to make profiling stable.</p>
<p>With today’s acceptance, we are officially contributing our continuous profiler technology to OpenTelemetry. We will also dedicate a team of profiling domain experts to co-maintain and advance the profiling capabilities within OTel.</p>
<p>We want to thank the OpenTelemetry community for the great and constructive cooperation on the donation proposal. We look forward to jointly establishing continuous profiling as an integral part of OpenTelemetry. </p>
<h2 id="whatiscontinuousprofiling">What is continuous profiling?</h2>
<p>Profiling is a technique used to understand the behavior of a software application by collecting information about its execution. This includes tracking the duration of function calls, memory usage, CPU usage, and other system resources. </p>
<p>However, traditional profiling solutions have significant drawbacks limiting adoption in production environments:</p>
<ul>
<li>Significant cost and performance overhead due to code instrumentation</li>
<li>Disruptive service restarts</li>
<li>Inability to get visibility into third-party libraries</li>
</ul>
<p>Unlike traditional profiling, which is often done only in a specific development phase or under controlled test conditions, continuous profiling runs in the background with minimal overhead. This provides real-time, actionable insights without replicating issues in separate environments. SREs, DevOps, and developers can see how code affects performance and cost, making code and infrastructure improvements easier.</p>
<h2 id="contributionofproductiongradefeatures">Contribution of production-grade features</h2>
<p>Elastic Universal Profiling is a whole-system, always-on, continuous profiling solution that eliminates the need for code instrumentation, recompilation, on-host debug symbols or service restarts. Leveraging eBPF, Elastic Universal Profiling profiles every line of code running on a machine, including application code, kernel, and third-party libraries. The solution measures code efficiency in three dimensions, CPU utilization, CO2, and cloud cost, to help organizations manage efficient services by minimizing computational waste.</p>
<p>The Elastic profiling agent facilitates identifying non-optimal code paths, uncovering "unknown unknowns", and provides comprehensive visibility into the runtime behavior of all applications. Elastic’s continuous profiling agent supports various runtimes and languages, such as C/C++, Rust, Zig, Go, Java, Python, Ruby, PHP, Node.js, V8, Perl, and .NET.</p>
<p>Additionally, organizations can meet sustainability objectives by minimizing computational wastage, ensuring seamless alignment with their strategic <a href="https://en.wikipedia.org/wiki/Environmental,_social,_and_corporate_governance">ESG</a> goals.</p>
<h2 id="benefitstoopentelemetry">Benefits to OpenTelemetry</h2>
<p>This contribution not only boosts the standardization of continuous profiling for observability but also accelerates the practical adoption of profiling as the fourth key signal in OTel. Customers get a vendor-agnostic way of collecting profiling data and enabling correlation with existing signals, like tracing, metrics, and logs, opening <a href="https://www.elastic.co/blog/continuous-profiling-distributed-tracing-correlation">new potential for observability insights and a more efficient troubleshooting experience</a>. </p>
<p>OTel-based continuous profiling unlocks the following possibilities for users:</p>
<ul>
<li><p>Improved customer experience: delivering consistent service quality and performance through continuous profiling ensures customers have an application that performs optimally, remains responsive, and is reliable.</p></li>
<li><p>Maximize gross margins: Businesses can optimize their cloud spend and improve profitability by reducing the computational resources needed to run applications. Whole system continuous profiling identifies the most expensive functions (down to the lines of code) across diverse environments that may span multiple cloud providers. In the cloud context, every CPU cycle saved translates to money saved. </p></li>
<li><p>Minimize environmental impact: energy consumption associated with computing is a growing concern (source: <a href="https://energy.mit.edu/news/energy-efficient-computing/">MIT Energy Initiative</a> ). More efficient code translates to lower energy consumption, reducing carbon (CO2) footprint. </p></li>
<li><p>Accelerate engineering workflows: continuous profiling provides detailed insights to help troubleshoot complex issues faster, guide development, and improve overall code quality.</p></li>
<li><p>Improved vendor neutrality and increased efficiency: an OTel eBPF-based profiling agent removes the need to use proprietary APM agents and offers a more efficient way to collect profiling telemetry.</p></li>
</ul>
<p>With these benefits, customers can now manage the overall application’s efficiency on the cloud while ensuring their engineering teams optimize it.</p>
<h2 id="whatcomesnext">What comes next?</h2>
<p>While the acceptance of Elastic’s donation of the profiling agent marks a significant milestone in the evolution of OTel’s eBPF-based continuous profiling capabilities, it represents the beginning of a broader journey. Moving forward, we will continue collaborating closely with the OTel Profiling and Collector SIGs to ensure seamless integration of the profiling agent within the broader OTel ecosystem. During this phase, users can test early preview versions of the OTel profiling integration by following the directions in the <a href="https://github.com/elastic/otel-profiling-agent/">otel-profiling-agent</a> repository.</p>
<p>Elastic remains deeply committed to OTel’s vision of enabling cross-signal correlation. We plan to further contribute to the community by sharing our innovative research and implementations, specifically those facilitating the correlation between profiling data and distributed traces, across several OTel language SDKs and the profiling agent.</p>
<p>We are excited about our <a href="https://opentelemetry.io/blog/2023/ecs-otel-semconv-convergence/">growing relationship with OTel</a> and the opportunity to donate our profiling agent in a way that benefits both the Elastic community and the broader OTel community. Learn more about <a href="https://www.elastic.co/observability/opentelemetry">Elastic’s OpenTelemetry support</a> and learn how to contribute to the ongoing profiling work in the community.</p>
<h2 id="additionalresources">Additional Resources</h2>
<p>Additional details on Elastic’s Universal Profiling can be found in the <a href="https://www.elastic.co/observability-labs/blog/elastic-profiling-agent-acceptance-opentelemetry-faq">FAQ</a>. </p>
<p>For insights into observability, visit Observability labs where OTel specific articles are also available.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/elastic-profiling-agent-acceptance-opentelemetry</link>
    <guid isPermaLink="false">elastic-profiling-agent-acceptance-opentelemetry</guid>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[APM]]></category>
    <dc:creator><![CDATA[Christos Kalkanis,Alexander Wert,Abhishek Singh]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd8cc7c3e97bcb500/6a8408894423e173149fc5f1/profiling-acceptance.png" length="0" type="image/png"/>
    <pubDate>Thu, 06 Jun 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[FAQ - Elastic contributes its Universal Profiling agent to OpenTelemetry]]></title>
    <description><![CDATA[Elastic is advancing the adoption of OpenTelemetry with the contribution of its universal profiling agent. Elastic is committed to ensuring a vendor-agnostic ingestion and collection of observability and security telemetry through OpenTelemetry.]]></description>
    <content:encoded><![CDATA[<h2 id="whatisbeingannounced">What is being announced?</h2>
<p>Elastic’s <a href="https://github.com/open-telemetry/community/issues/1918">donation proposal</a> for contributing its Universal Profiling™ agent has now been accepted by the OpenTelemetry community. Elastic’s Universal Profiling agent, the industry’s most comprehensive fleet-wide Universal Profiling solution, empowers users to quickly identify performance bottlenecks, reduce cloud spend, and minimize their carbon footprint. With the contribution of the Elastic Universal Profiling Agent to OpenTelemetry, all customers will benefit from its features and capabilities.</p>
<h2 id="whatdoelasticusersneedtoknow">What do Elastic users need to know?</h2>
<p>Elastic’s contribution of the continuous profiling agent will not change the existing set of Elastic’s continuous profiling features or how we ingest and store profiling data. </p>
<p>Elastic will participate and closely collaborate with the OTel community to manage not only the addition of the continuous profiling agent to OTel but also work with and drive the OTel community’s Profiling Special Interest Group (SIG) in shaping OTel’s continuous profiling evolution. </p>
<p>Elastic has facilitated the definition of the OTel <a href="https://github.com/open-telemetry/oteps/blob/main/text/profiles/0239-profiles-data-model.md">Profiling Data Model</a>, a crucial step toward standardizing profiling data. Moreover, the recent merge of the <a href="https://github.com/open-telemetry/oteps/pull/239">OpenTelemetry Enhancement Proposal (OTEP) introducing profiling support to the OpenTelemetry Protocol (OTLP)</a> marked an additional milestone. </p>
<h2 id="whyiselasticcontributingitsprofilingagenttootel">Why is Elastic contributing its Profiling Agent to OTel?</h2>
<p>This contribution not only accelerates the standardization of continuous profiling but also makes continuous profiling the 4th key signal in observability. This empowers everyone in the observability community to continuously profile with a standardized agent. The addition of Elastic’s continuous profiling agent will:</p>
<ul>
<li><p>Align efforts around a single standard poised for broad adoption by users.</p></li>
<li><p>Drive better visibility and improvement of resource usage and cost management for operations.</p></li>
<li><p>Enable vendors and the community to focus on richer features versus dealing with data transformation tasks.</p></li>
<li><p>Enable continuous profiling to become the 4th key signal in Observability.</p></li>
<li><p>Increase continuous profiling adoption and the continued evolution and convergence of observability and security domains.</p></li>
</ul>
<h2 id="whyiscontinuousprofilingneededbyorganizations">Why is continuous profiling needed by organizations?</h2>
<p>The contribution of Elastic’s continuous profiling agent now helps customers realize the following benefits of continuous profiling:</p>
<ul>
<li><p>Maximize gross margins: By reducing the computational resources needed to run applications, businesses can optimize their cloud spend and improve profitability. Whole-system continuous profiling is one way of identifying the most expensive applications (down to the lines of code) across diverse environments that may span multiple cloud providers. This principle aligns with the familiar adage, "A penny saved is a penny earned." In the cloud context, every CPU cycle saved translates to money saved. </p></li>
<li><p>Minimize environmental impact: Energy consumption associated with computing is a growing concern (source: <a href="https://energy.mit.edu/news/energy-efficient-computing/">MIT Energy Initiative</a>). More efficient code translates to lower energy consumption, contributing to a reduction in carbon (CO2) footprint. </p></li>
<li><p>Accelerate engineering workflows: Continuous profiling provides detailed insights to help debug complex issues faster, guide development, and improve overall code quality.</p></li>
</ul>
<p>With these benefits, customers can now not only manage the overall application’s efficiency on the cloud, but also ensure the application is optimally developed.</p>
<h2 id="whatiscontinuousprofiling">What is continuous profiling?</h2>
<p>Elastic’s continuous profiling agent is a whole-system, always-on, continuous profiling solution that eliminates the need for run-time/bytecode instrumentation, recompilation, on-host debug symbols or service restarts.   </p>
<p>Profiling helps organizations run efficient services by minimizing computational wastage, thereby reducing operational costs. Leveraging <a href="https://ebpf.io/">eBPF</a>, the Elastic profiling agent provides unprecedented visibility into the runtime behavior of all applications: it builds stack traces that go from the kernel, through userspace native code, all the way into code running in higher level runtimes, enabling you to identify performance regressions, reduce wasteful computations, and debug complex issues faster. </p>
<p>To this end, it measures code efficiency in three dimensions: CPU utilization, CO2, and cloud cost. This approach resonates with the sustainability objectives of our customers –– ensuring that Elastic continuous profiling aligns seamlessly with their strategic <a href="https://en.wikipedia.org/wiki/Environmental,_social,_and_corporate_governance">ESG</a> goals</p>
<h2 id="doeselasticsupportopentelemetrytoday">Does Elastic support OpenTelemetry today?</h2>
<p><a href="https://www.elastic.co/observability/opentelemetry">Elastic supports OTel natively</a>. Elastic users can send OTel data directly from applications or through the OTel collector into Elastic APM, which processes both OTel SemConv and ECS. With this native OTel support, all <a href="https://www.elastic.co/observability/application-performance-monitoring">Elastic APM capabilities</a> are available with OTel. <a href="https://www.elastic.co/guide/en/apm/guide/current/open-telemetry.html">See Elastic documentation to learn more about OTel integration</a>.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt30a50ab674e5130c/6a7f0906c2cc09d2202493c8/blog-elastic-otel-2.png" alt="Native OpenTelemetry Support in Elastic" /></p>
<h2 id="wherecanilearnmoreaboutelasticsuniversalprofiling">Where can I learn more about Elastic’s Universal Profiling?</h2>
<p>Elastic’s resources help you understand continuous profiling and how to use it in different scenarios:</p>
<hr />
<ul>
<li><p><a href="https://www.elastic.co/observability/universal-profiling">Elastic Universal Profiling home page</a></p></li>
<li><p><a href="https://www.elastic.co/blog/elastic-universal-profiling-agent-open-source">Elastic Universal Profiling agent going open source under Apache 2</a></p></li>
<li><p><a href="https://www.elastic.co/blog/continuous-profiling-distributed-tracing-correlation">Pinpointing performance issues with profiling</a></p></li>
<li><p><a href="https://www.elastic.co/blog/continuous-profiling-is-generally-available">Elastic releases Universal Profiling</a></p></li>
<li><p><a href="https://www.elastic.co/blog/whole-system-visibility-elastic-universal-profiling">Whole system profiling with Universal Profiling</a></p></li>
<li><p><a href="https://www.elastic.co/blog/continuous-profiling-efficient-cost-effective-applications">Cost-effective applications with Universal Profiling</a></p></li>
<li><p><a href="https://www.elastic.co/guide/en/observability/current/universal-profiling.html">Elastic documentation on Universal Profiling</a></p></li>
</ul>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/elastic-profiling-agent-acceptance-opentelemetry-faq</link>
    <guid isPermaLink="false">elastic-profiling-agent-acceptance-opentelemetry-faq</guid>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[APM]]></category>
    <dc:creator><![CDATA[Elastic Observability Team]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6fd9c621644659fd/6a7f090be3a219742d99f2fd/profiling-acceptance-faq.png" length="0" type="image/png"/>
    <pubDate>Thu, 06 Jun 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Revealing unknowns in your tracing data with inferred spans in OpenTelemetry]]></title>
    <description><![CDATA[Distributed tracing is essential in understanding complex systems, but it can miss latency issue details. By combining profiling techniques with distributed tracing, Elastic provides the inferred spans feature as an extension for the OTel Java SDK.]]></description>
    <content:encoded><![CDATA[<p>In the complex world of microservices and distributed systems, achieving transparency and understanding the intricacies and inefficiencies of service interactions and request flows has become a paramount challenge. Distributed tracing is essential in understanding distributed systems. But distributed tracing, whether manually applied or auto-instrumented, is usually rather coarse-grained. Hence, distributed tracing covers only a limited fraction of the system and can easily miss parts of the system that are the most useful to trace.</p>
<p>Addressing this gap, Elastic developed the concept of inferred spans as a powerful enhancement to traditional instrumentation-based tracing as an extension for the OpenTelemetry Java SDK/Agent. We are in the process of contributing this back to OpenTelemetry, until then our <a href="https://github.com/elastic/elastic-otel-java/tree/main/inferred-spans">extension</a> can be seamlessly used with the existing OpenTelelemetry Java SDK (as described below).</p>
<p>Inferred spans are designed to augment the visibility provided by instrumentation-based traces, shedding light on latency sources within the application or libraries that were previously uninstrumented. This feature significantly expands the utility of distributed tracing, allowing for a more comprehensive understanding of system behavior and facilitating a deeper dive into performance optimization.</p>
<h2 id="whatisinferredspans">What is inferred spans?</h2>
<p>Inferred spans is an observability technique that combines distributed tracing with profiling techniques to illuminate the darker, unobserved corners of your application — areas where standard instrumentation techniques fall short. The inferred spans feature interweaves information derived from profiling stacktraces with instrumentation-based tracing data, allowing for the generation of new spans based on the insights drawn from profiling data.</p>
<p>This feature proves invaluable when dealing with custom code or third-party libraries that significantly contribute to the request latency but lack built-in or external instrumentation support. Often, identifying or crafting specific instrumentation for these segments can range from challenging to outright unfeasible. Moreover, certain scenarios exist where implementing instrumentation is impractical due to the potential for substantial performance overhead. For instance, instrumenting application locking mechanisms, despite their critical role, is not viable because of their ubiquitous nature and the significant latency overhead the instrumentation can introduce to application requests. Still, ideally, such latency issues would be visible within your distributed traces.</p>
<p>Inferred spans ensures a deeper visibility into your application’s performance dynamics including the above-mentioned scenarios.</p>
<h2 id="inferredspansinaction">Inferred spans in action</h2>
<p>To demonstrate the inferred spans feature we will use the Java implementation of the <a href="https://github.com/elastic/observability-examples/tree/main/Elastiflix/java-favorite">Elastiflix demo application</a>. Elasticflix has an endpoint called favorites that does some Redis calls and also includes an artificial delay. First, we use the plain OpenTelemetry Java Agent to instrument our application:</p>
<pre><code>java -javaagent:/path/to/otel-javaagent-&lt;version&gt;.jar \
-Dotel.service.name=my-service-name \
-Dotel.exporter.otlp.endpoint=https://&lt;our-elastic-apm-endpoint&gt; \
"-Dotel.exporter.otlp.headers=Authorization=Bearer SECRETTOKENHERE" \
-jar my-service-name.jar
</code></pre>
<p>With the OpenTelemetry Java Agent we get out-of-the-box instrumentation for HTTP entry points and calls to Redis for our Elastiflix application. The resulting traces contain spans for the POST /favorites entrypoint, as well as a few short spans for the calls to Redis.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt68a2ac377960ee63/6a7f1b9deab5be0d5220ab16/image2.png" alt="POST /favorites entrypoint" /></p>
<p>As you can see in the trace above, it’s not clear where most of the time is spent within the POST /favorites request.</p>
<p>Let’s see how inferred spans can shed light into these areas. You can use the inferred spans feature either manually with your OpenTelemetry SDK (see section below), package it as a drop-in extension for the upstream OpenTelemetry Java agent, or just use <a href="https://github.com/elastic/elastic-otel-java/tree/main">Elastic’s distribution of the OpenTelemetry Java agent</a> that comes with the inferred spans feature.</p>
<p>For convenience, we just download the <a href="https://mvnrepository.com/artifact/co.elastic.otel/elastic-otel-javaagent/0.0.1">agent jar</a> of the Elastic distribution and extend the configuration to enable the inferred spans feature:</p>
<pre><code>java -javaagent:/path/to/elastic-otel-javaagent-&lt;version&gt;.jar \
-Dotel.service.name=my-service-name \
-Dotel.exporter.otlp.endpoint=https://XX.apm.europe-west3.gcp.cloud.es.io:443 \
"-Dotel.exporter.otlp.headers=Authorization=Bearer SECRETTOKENHERE" \
-Delastic.otel.inferred.spans.enabled=true \
-jar my-service-name.jar
</code></pre>
<p>The only non-standard option here is elastic.otel.inferred.spans.enabled: The inferred spans Feature is currently opt-in and therefore needs to be enabled explicitly. Running the same application with the inferred spans feature enabled yields more comprehensive traces:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1288446f29c1e24e/6a7f1ba096b5a6727c87b8b5/image1.png" alt="more comprehensive traces" /></p>
<p>The inferred-spans (colored blue in the above screenshot) follow the naming pattern Class#method. With that, the inferred spans feature helps us pinpoint the exact methods that contribute the most to the overall latency of the request. Note that the parent-child relationship between the HTTP entry span, the Redis spans, and the inferred spans is reconstructed correctly, resulting in a fully functional trace structure.</p>
<p>Examining the handleDelay method within the Elastiflix application reveals the use of a straightforward sleep statement. Although the sleep method is not CPU-bound, the full duration of this delay is captured as inferred spans. This stems from employing the async-profiler's wall clock time profiling, as opposed to solely relying on CPU profiling. The ability of the inferred spans feature to reflect actual latency, including for I/O operations and other non-CPU-bound tasks, represents a significant advancement. It allows for diagnosing and resolving performance issues that extend beyond CPU limitations, offering a more nuanced view of system behavior.</p>
<h2 id="usinginferredspanswithyourownopentelemetrysdk">Using inferred spans with your own OpenTelemetry SDK</h2>
<p>OpenTelemetry is a highly extensible framework: Elastic embraces this extensibility by also publishing most extensions shipped with our OpenTelemetry Java Distro as standalone-extensions to the <a href="https://github.com/open-telemetry/opentelemetry-java">OpenTelemetry Java SDK</a>.</p>
<p>As a result, if you do not want to use our distro (e.g., because you don’t need or want bytecode instrumentation in your project), you can still use our extensions, such as the extension for the inferred spans feature. All you need to do is set up the <a href="https://opentelemetry.io/docs/languages/java/instrumentation/#initialize-the-sdk">OpenTelemetry SDK in your code</a> and add the inferred spans extension as a dependency:</p>
<pre><code>&lt;dependency&gt;
    &lt;groupId&gt;co.elastic.otel&lt;/groupId&gt;
    &lt;artifactId&gt;inferred-spans&lt;/artifactId&gt;
    &lt;version&gt;{latest version}&lt;/version&gt;
&lt;/dependency&gt;
</code></pre>
<p>During your SDK setup, you’ll have to initialize and register the extension:</p>
<pre><code>InferredSpansProcessor inferredSpans = InferredSpansProcessor.builder()
  .samplingInterval(Duration.ofMillis(10)) //the builder offers all config options
  .build();
SdkTracerProvider tracerProvider = SdkTracerProvider.builder()
  .addSpanProcessor(inferredSpans)
.addSpanProcessor(BatchSpanProcessor.builder(OtlpGrpcSpanExporter.builder()
    .setEndpoint("https://&lt;your-elastic-apm-endpoint&gt;")
    .addHeader("Authorization", "Bearer &lt;secrettoken&gt;")
    .build()).build())
  .build();
inferredSpans.setTracerProvider(tracerProvider);
</code></pre>
<p>The inferred spans extension seamlessly integrates with the <a href="https://opentelemetry.io/docs/languages/java/instrumentation/#automatic-configuration">OpenTelemetry SDK Autoconfiguration mechanism</a>. By incorporating the OpenTelemetry SDK and its extensions as dependencies within your application code — rather than through an external agent — you gain the flexibility to configure them using the same environment variables or JVM properties. Once the inferred spans extension is included in your classpath, activating it for autoconfigured SDKs becomes straightforward. Simply enable it using the elastic.otel.inferred.spans.enabled property, as previously described, to leverage the full capabilities of this feature with minimal setup.</p>
<h2 id="howdoesinferredspanswork">How does inferred spans work?</h2>
<p>The inferred spans feature leverages the capabilities of collecting wall clock time profiling data of the widely-used <a href="https://github.com/async-profiler/async-profiler">async-profiler</a>, a low-overhead, popular production-time profiler in the Java ecosystem. It then transforms the profiling data into actionable spans as part of the distributed traces. But what mechanism allows for this transformation?</p>
<p>Essentially, the inferred spans extension engages with the lifecycle of span events, specifically when a span is either activated or deactivated across any thread via the <a href="https://opentelemetry.io/docs/specs/otel/context/">OpenTelemetry context</a>. Upon the activation of the initial span within a transaction, the extension commences a session of wall-clock profiling via the async-profiler, set to a predetermined duration. Concurrently, it logs the details of all span activations and deactivations, capturing their respective timestamps and the threads on which they occurred.</p>
<p>Following the completion of the profiling session, the extension processes the profiling data alongside the log of span events. By correlating the data, it reconstructs the inferred spans. It's important to note that, in certain complex scenarios, the correlation may assign an incorrect name to a span. To mitigate this and aid in accurate identification, the extension enriches the inferred spans with stacktrace segments under the code.stacktrace attribute, offering users clarity and insight into the precise methods implicated.</p>
<h2 id="inferredspansvscorrelationoftraceswithprofilingdata">Inferred spans vs. correlation of traces with profiling data</h2>
<p>In the wake of OpenTelemetry's recent <a href="https://opentelemetry.io/blog/2024/profiling/">announcement of the profiling signal</a>, coupled with <a href="https://www.elastic.co/blog/elastic-donation-proposal-to-contribute-profiling-agent-to-opentelemetry">Elastic's commitment to donating the Universal Profiling Agent</a> to OpenTelemetry, you might be wondering about how the inferred spans feature differentiates from merely correlating profiling data with distributed traces using span IDs and trace IDs. Rather than viewing these as competing functionalities, it's more accurate to consider them complementary.</p>
<p>The inferred spans feature and the correlation of tracing with profiling data both employ similar methodologies — melding tracing information with profiling data. However, they each shine in distinct areas. Inferred spans excels at identifying long-running methods that could escape notice with traditional CPU profiling, which is more adept at pinpointing CPU bottlenecks. A unique advantage of inferred spans is its ability to account for I/O time, capturing delays caused by operations like disk access that wouldn't typically be visible in CPU profiling flamegraphs.</p>
<p>However, the inferred spans feature has its limitations, notably in detecting latency issues arising from "death by a thousand cuts" — where a method, although not time-consuming per invocation, significantly impacts total latency due to being called numerous times across a request. While individual calls might not be captured as inferred spans due to their brevity, CPU-bound methods contributing to latency are unveiled through CPU profiling, as flamegraphs display the aggregate CPU time consumed by these methods.</p>
<p>An additional strength of the inferred spans feature lies in its data structure, offering a simplified tracing model that outlines typical parent-child relationships, execution order, and good latency estimates. This structure is achieved by integrating tracing data with span activation/deactivation events and profiling data, facilitating straightforward navigation and troubleshooting of latency issues within individual traces.</p>
<p>Correlating distributed tracing data with profiling data comes with a different set of advantages. Learn more about it in our related blog post, <a href="https://www.elastic.co/blog/continuous-profiling-distributed-tracing-correlation">Beyond the trace: Pinpointing performance culprits with continuous profiling and distributed tracing correlation</a>.</p>
<h2 id="whatabouttheperformanceoverhead">What about the performance overhead?</h2>
<p>As mentioned before, the inferred spans functionality is based on the widely used async-profiler, known for its minimal impact on performance. However, the efficiency of profiling operations is not without its caveats, largely influenced by the specific configurations employed. A pivotal factor in this balancing act is the sampling interval — the longer the interval between samples, the lower the incurred overhead, albeit at the expense of potentially overlooking shorter methods that could be critical to the inferred spans feature discovery process.</p>
<p>Adjusting the probability-based trace sampling presents another way for optimization, directly influencing the overhead. For instance, setting trace sampling to 50% effectively halves the profiling load, making the inferred spans feature even more resource-efficient on average per request. This nuanced approach to tuning ensures that the inferred spans feature can be leveraged in real-world, production environments with a manageable performance footprint. When properly configured, this feature offers a potent, low-overhead solution for enhancing observability and diagnostic capabilities within production applications.</p>
<h2 id="whatsnextforinferredspansandopentelemetry">What’s next for inferred spans and OpenTelemetry?</h2>
<p>This blog post outlined and introduced the inferred spans feature available as an extension for the OpenTelemetry Java SDK and built into the newly introduced Elastic OpenTelemetry Java Distro. Inferred spans allows users to troubleshoot latency issues in areas of code that are not explicitly instrumented while utilizing traditional tracing data.</p>
<p>The feature is currently merely a port of the existing feature from the proprietary Elastic APM Agent. With Elastic embracing OpenTelemetry, we plan on contributing this extension to the upstream OpenTelemetry project. For that, we also plan on migrating the extension to the latest async-profiler 3.x release. <a href="https://github.com/elastic/elastic-otel-java/tree/main/inferred-spans">Try out inferred spans for yourself</a> and see how it can help you diagnose performance problems in your applications.</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/tracing-data-inferred-spans-opentelemetry</link>
    <guid isPermaLink="false">tracing-data-inferred-spans-opentelemetry</guid>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[APM]]></category>
    <dc:creator><![CDATA[Jonas Kunz,Alexander Wert]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt97993d2b178280f9/6a7f1ba2bdcff03963c432c7/148360-Blog-header-image--Revealing-Unknowns-in-your-Tracing-Data-with-Inferred-Spans-in-OpenTelemetry_V1.jpg" length="0" type="image/jpeg"/>
    <pubDate>Mon, 22 Apr 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Elastic Universal Profiling agent, a continuous profiling solution, is now open source]]></title>
    <description><![CDATA[At Elastic, open source isn't just philosophy, it's our DNA. Dive into the future with our open-sourced Universal Profiling agent, revolutionizing software efficiency and sustainability.]]></description>
    <content:encoded><![CDATA[<p>Elastic Universal Profiling™ agent is now open source! The industry’s most advanced fleetwide continuous profiling solution empowers users to identify performance bottlenecks, reduce cloud spend, and minimize their carbon footprint. This post explores the history of the agent, its move to open source, and its future integration with OpenTelemetry.</p>
<h2 id="elasticuniversalprofilingagentgoesopensourceunderapache2">Elastic Universal Profiling™ Agent goes open source under Apache 2</h2>
<p>At Elastic, open source is more than just a philosophy — it's our DNA. We believe the benefits of whole-system continuous profiling extend far beyond performance optimization. It's a win for businesses and the planet alike. For instance, since launching Elastic Universal Profiling in general availability (GA), we've observed a wide variety of use cases from customers.</p>
<p>These range from customers relying fully on Universal Profiling's <a href="https://www.elastic.co/guide/en/observability/current/universal-profiling.html#profiling-differential-views-intro">differential flame graphs and topN functions</a> for insights during release management to utilizing AI assistants for quickly optimizing expensive functions. This includes using profiling data to identify the optimal energy-efficient cloud region to run certain workloads. Additionally, customers are using insights that Universal Profiling provides to build evidence to challenge cloud provider bills. As it turns out, cloud providers' in-VM agents can consume a significant portion of the CPU time, which customers are billed for.</p>
<p>In a move that will empower the community to take advantage of continuous profiling's benefits, <strong>we're thrilled to announce that the Elastic Universal Profiling agent</strong> , a pioneering eBPF-based continuous profiling agent, <strong>is now open source under the Apache 2 license!</strong></p>
<p>This move democratizes <strong>hyper-scaler efficiency for everyone</strong> , opening exciting new possibilities for the future of continuous profiling, as well as its role in observability and <strong>OpenTelemetry</strong>.</p>
<h2 id="implementationoftheopentelemetryotelprofilingprotocol">Implementation of the OpenTelemetry (OTel) Profiling protocol</h2>
<p>Our commitment to open source goes beyond just the agent itself. We recently <a href="https://www.elastic.co/blog/elastic-donation-proposal-to-contribute-profiling-agent-to-opentelemetry">announced our intent to donate</a> the agent to OpenTelemetry and have further solidified this goal by implementing the experimental <a href="https://github.com/open-telemetry/oteps/blob/main/text/profiles/0239-profiles-data-model.md">OTel Profiling data model</a>. This allows the open-sourced eBPF-based continuous profiling agent to communicate seamlessly with OpenTelemetry backends.</p>
<p>But that's not all! We've also launched an innovative feature that <a href="https://www.elastic.co/blog/continuous-profiling-distributed-tracing-correlation">correlates profiling data with OpenTelemetry distributed traces</a>. This powerful capability offers a deeper level of insight into application performance, enabling the identification of bottlenecks with greater precision. Upon donating the Profiling agent to OTel, Elastic will also contribute critical components that enable distributed trace correlation within the <a href="https://github.com/elastic/elastic-otel-java">Elastic distribution of the OTel Java agent</a> to the upstream OTel Java SDK. This underscores Elastic Observability's commitment to both open source and the support of open standards like OpenTelemetry while pushing the boundaries of what is possible in observability.</p>
<h2 id="whatdoesthismeanforelasticuniversalprofilingcustomers">What does this mean for Elastic Universal Profiling customers?</h2>
<p>We'd like to express our <strong>immense gratitude to all our customers</strong> who have been part of this journey, from the early stages of private beta to GA. Your feedback has been invaluable in shaping Universal Profiling into the powerful product it is today.</p>
<p>By open-sourcing the Universal Profiling agent and contributing it to OpenTelemetry, we're fostering a win-win situation for both you and the broader community. This move opens doors for innovation and collaboration, ultimately leading to a more robust and versatile whole-system continuous profiling solution for everyone.</p>
<p>Furthermore, we're actively working on exciting novel ways to integrate Universal Profiling seamlessly within Elastic Observability. Expect further announcements soon, outlining how you can unlock even greater value from your profiling data within a unified observability experience in a way that has never been done before.</p>
<p>The open-sourced agent is using the recently released (experimental) OTel Profiling <a href="https://github.com/open-telemetry/opentelemetry-proto/pull/534">signal</a>. As a precaution, we recommend not using it in production environments.</p>
<p>Please continue using the official Elastic distribution of the Universal Profiling agent until the agent is formally accepted by OTel and the protocol reaches a stable phase. There's no need to take any action at this time, and we will ensure to have a smooth transition plan in place for you.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5ac093f76868020d/6a7f099e9090b082d884e897/image1.png" alt="1 - Elastic Universal Profiling" /></p>
<h2 id="whatdoesthismeanfortheopentelemetrycommunity">What does this mean for the OpenTelemetry community?</h2>
<p>OpenTelemetry is adopting continuous profiling as a key signal. By open-sourcing the eBPF-based profiling agent and working towards donating it to OTel, Elastic is making it possible to accelerate the standardization of continuous profiling within OpenTelemetry. This move has a massive impact on the observability community, empowering everyone to continuously profile their systems with a standardized protocol.</p>
<p>This is particularly timely as <a href="https://www.bbc.co.uk/news/technology-32335003">Moore's Law</a> slows down and cloud computing takes hold, making computational efficiency critical for businesses.</p>
<p>Here's how whole-system continuous profiling benefits you:</p>
<ul>
<li><p><strong>Maximize gross margins:</strong> By reducing the computational resources needed to run applications, businesses can optimize their cloud spend and improve profitability. Whole-system continuous profiling is one way of identifying the most expensive applications (down to the lines of code) across diverse environments that may span multiple cloud providers. This principle aligns with the familiar adage, <em>"a penny saved is a penny earned."</em> In the cloud context, every CPU cycle saved translates to money saved. </p></li>
<li><p><strong>Minimize environmental impact:</strong> Energy consumption associated with computing is a growing concern (source: <a href="https://energy.mit.edu/news/energy-efficient-computing/">MIT Energy Initiative</a>). More efficient code translates to lower energy consumption, contributing to a reduction in carbon footprint. </p></li>
<li><p><strong>Accelerate engineering workflows:</strong> Continuous profiling provides detailed insights to help debug complex issues faster, guide development, and improve overall code quality.</p></li>
</ul>
<p>This is where Elastic Universal Profiling comes in — designed to help organizations run efficient services by minimizing computational wastage. To this end, it measures code efficiency in three dimensions: <strong>CPU utilization</strong> , <strong>CO</strong>** 2 <strong>, and</strong> cloud cost**.</p>
<p>Elastic's journey with continuous profiling began by joining forces with <a href="https://www.elastic.co/about/press/elastic-and-optimyze-join-forces-to-deliver-continuous-profiling-of-infrastructure-applications-and-services">optimyze.cloud</a> –– this became the foundation for <a href="https://www.elastic.co/observability/universal-profiling">Elastic Universal Profiling</a>. We are excited to see this product evolve into its next growth phase in the open-source world.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt87756cae4d4b2b7e/6a7f09a2e3a219861599f330/image2.png" alt="2 - car manufacturers" /></p>
<h2 id="readytogiveitaspin">Ready to give it a spin?</h2>
<p>As Elastic Universal Profiling transitions into this new open source era, the potential for transformative impact on performance optimization, cost efficiency, and environmental sustainability is immense. Elastic's approach — balancing innovation with responsibility — paves the way for a future where technology not only powers our world but does so in a way that is sustainable and accessible to all.</p>
<p>Get started with the open source Elastic Universal Profiling agent today! <a href="https://github.com/elastic/otel-profiling-agent/">Download it directly from GitHub</a> and follow the instructions in the repository.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7da98a238725d2ae/6a7f09a53cab1cb4cb0e4714/image3.png" alt="3 - dripping graph and 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>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/elastic-universal-profiling-agent-open-source</link>
    <guid isPermaLink="false">elastic-universal-profiling-agent-open-source</guid>
    <category><![CDATA[APM]]></category>
    <category><![CDATA[OpenTelemetry]]></category>
    <dc:creator><![CDATA[Israel Ogbole,Christos Kalkanis]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2f7aadb6cb2ddfa4/6a840b9d1eb9e5964b2c2b9e/tree_tunnel.jpg" length="0" type="image/jpeg"/>
    <pubDate>Mon, 15 Apr 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Beyond the trace: Pinpointing performance culprits with continuous profiling and distributed tracing correlation]]></title>
    <description><![CDATA[Frustrated by slow traces but unsure where the code bottleneck lies? Elastic Universal Profiling correlates profiling stacktraces with OpenTelemetry (OTel) traces, helping you identify and pinpoint the exact lines of code causing performance issues.]]></description>
    <content:encoded><![CDATA[<p>Observability goes beyond monitoring; it's about truly understanding your system. To achieve this comprehensive view, practitioners need a unified observability solution that natively combines insights from metrics, logs, traces, and crucially, <strong>continuous profiling</strong>. While metrics, logs, and traces offer valuable insights, they can't answer the all-important "why." Continuous profiling signals act as a magnifying glass, providing granular code visibility into the system's hidden complexities. They fill the gap left by other data sources, enabling you to answer critical questions –– why is this trace slow? Where exactly in the code is the bottleneck residing?</p>
<p>Traces provide the "what" and "where" — what happened and where in your system. Continuous profiling refines this understanding by pinpointing the "why" and validating your hypotheses about the "what." Just like a full-body MRI scan, Elastic's whole-system continuous profiling (powered by eBPF) uncovers unknown-unknowns in your system. This includes not just your code, but also third-party libraries and kernel activity triggered by your application transactions. This comprehensive visibility improves your mean-time-to-detection (MTTD) and mean-time-to-recovery (MTTR) KPIs.</p>
<p><em>[Related article:</em> <a href="https://www.elastic.co/blog/observability-profiling-metrics-logs-traces"><em>Why metrics, logs, and traces aren’t enough</em></a><em>]</em></p>
<h2 id="bridgingthedisconnectbetweencontinuousprofilingandoteltraces">Bridging the disconnect between continuous profiling and OTel traces</h2>
<p>Historically, continuous profiling signals have been largely disconnected from OpenTelemetry (OTel) traces. Here's the exciting news: we're bridging this gap! We're introducing native correlation between continuous profiling signals and OTel traces, starting with Java.</p>
<p>Imagine this: You're troubleshooting a performance issue and identify a slow trace. Whole-system continuous profiling steps in, acting like an MRI scan for your entire codebase and system. It narrows down the culprit to the specific lines of code hogging CPU time within the context of your distributed trace. This empowers you to answer the "why" question with minimal effort and confidence, all within the same troubleshooting context.</p>
<p>Furthermore, by correlating continuous profiling with distributed tracing, Elastic Observability customers can measure the cloud cost and CO<sub>2</sub> impact of every code change at the service and transaction level.</p>
<p>This milestone is significant, especially considering the recent developments in the OTel community. With <a href="https://www.cncf.io/blog/2024/03/19/opentelemetry-announces-support-for-profiling/">OTel adopting profiling</a> and Elastic <a href="https://www.elastic.co/blog/elastic-donation-proposal-to-contribute-profiling-agent-to-opentelemetry">donating the industry’s most advanced eBPF-based continuous profiling agent to OTel</a>, we're set for a game-changer in observability — empowering OTel end users with a correlated system visibility that goes from a trace span in the userspace down to the kernel.</p>
<p>Furthermore, achieving this goal, especially with Java, presented significant challenges and demanded serious engineering R&amp;D. This blog post will delve into these challenges, explore the approaches we considered in our proof-of-concepts, and explain how we arrived at a solution that can be easily extended to other OTel language agents. Most importantly, this solution correlates traces with profiling signals at the agent, not in the backend — to ensure optimal query performance and minimal reliance on vendor backend storage architectures.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltbeb5f5909292eadb/6a7f0465e02fac7d7d5d61fd/trace.png" alt="Profiling flamegraph for a specific trace.id" /></p>
<h2 id="figuringouttheactiveoteltraceandspan">Figuring out the active OTel trace and span</h2>
<p>The primary technical challenge in this endeavor is essentially the following: whenever the profiler interrupts an OTel instrumented process to capture a stacktrace, we need to be able to efficiently determine the active span and trace ID (per-thread) and the service name (per-process).</p>
<p>For the purpose of this blog, we'll focus on the recently released <a href="https://github.com/elastic/elastic-otel-java">Elastic distribution of the OTel Java instrumentation</a>, but the approach that we ended up with generalizes to any language that can load and call into a native library. So, how do we get our hands on those IDs?</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0b4ed35c8633ca78/6a7f0469e88c656dd800b2a6/service-popout.png" alt="Profiling correlated with service.name, showing  CO2 and cloud cost impact by line of code." /></p>
<p>The OTel Java agent itself keeps track of the active span by storing a stack of spans in the <a href="https://opentelemetry.io/docs/concepts/context-propagation/#context">OpenTelemetryContext</a>, which itself is stored in a <a href="https://docs.oracle.com/javase/8/docs/api/java/lang/ThreadLocal.html">ThreadLocal</a> variable. We originally considered reading these Java structures directly from BPF, but we eventually decided against that approach. There is no documented specification on how ThreadLocals are implemented, and reliably reading and following the JVM's internal data-structures would incur a high maintenance burden. Any minor update to the JVM could change details of the structure layouts. To add to this, we would also have to reverse engineer how each JVM version lays out Java class fields in memory, as well as how all the high-level Java types used in the context objects are actually implemented under the hood. This approach further wouldn't generalize to any non-JVM language and needs to be repeated for any language that we wish to support.</p>
<p>After we had convinced ourselves that reading Java ThreadLocal directly is not the answer, we decided to look for more portable alternatives instead. The option that we ultimately settled with is to load and call into a C++ library that is responsible for making the required information available via a known and defined interface whenever the span changes.</p>
<p>Other than with Java's ThreadLocals, the details on how a native shared library should expose per-process and per-thread data are well-defined in the System V ABI specification and the architecture specific ELF ABI documents.</p>
<h2 id="exposingperprocessinformation">Exposing per-process information</h2>
<p>Exposing per-process data is easy: we simply declare a global variable . . .</p>
<pre><code>void* elastic_tracecorr_process_storage_v1 = nullptr;
</code></pre>
<p>. . . and expose it via ELF symbols. When the user initializes the OTel library to set the service name, we allocate a buffer and populate it with data in a <a href="https://github.com/elastic/apm/blob/149cd3e39a77a58002344270ed2ad35357bdd02d/specs/agents/universal-profiling-integration.md#process-storage-layout">protocol that we defined for this purpose</a>. Once the buffer is fully populated, we update the global pointer to point to the buffer.</p>
<p>On the profiling agent side, we already have code in place that detects libraries and executables loaded into any process's address space. We normally use this mechanism to detect and analyze high-level language interpreters (e.g., libpython, libjvm) when they are loaded, but it also turned out to be a perfect fit to detect the OTel trace correlation library. When the library is detected in a process, we scan the exports, resolve the symbol, and read the per-process information directly from the instrumented process’ memory.</p>
<h2 id="exposingperthreadinformation">Exposing per-thread information</h2>
<p>With the easy part out of the way, let's get to the nitty-gritty portion: exposing per-thread information via thread-local storage (TLS). So, what exactly is TLS, and how does it work? At the most basic level, the idea is to have <strong>one instance of a variable for every thread</strong>. Semantically you can think of it like having a global Map\&lt;ThreadID, T&gt;, although that is not how it is implemented.</p>
<p>On Linux, there are two major options for thread locals: TSD and TLS.</p>
<h2 id="threadspecificdatatsd">Thread-specific data (TSD)</h2>
<p>TSD is the older and probably more commonly known variant. It works by explicitly allocating a key via pthread_key_create — usually during process startup — and passing it to all threads that require access to the thread-local variable. The threads can then pass that key to the pthread_getspecific and pthread_setspecific functions to read and update the variable for the currently running thread.</p>
<p>TSD is simple, but for our purposes it has a range of drawbacks:</p>
<ul>
<li><p>The pthread_key_t structure is opaque and doesn't have a defined layout. Similar to the Java ThreadLocals, the underlying data-structures aren't defined by the ABI documents and different libc implementations (glibc, musl) will handle them differently.</p></li>
<li><p>We cannot call a function like pthread_getspecific from BPF, so we'd have to reverse engineer and reimplement the logic. Logic may change between libc versions, and we’d have to detect the version and support all variants that may come up in the wild.</p></li>
<li><p>TSD performance is not predictable and varies depending on how many thread local variables have been allocated in the process previously. This may not be a huge concern for Java specifically since spans are typically not swapped super rapidly, but it’d likely be quite noticeable for user-mode scheduling languages where the context might need to be swapped at every await point/coroutine yield.</p></li>
</ul>
<p>None of this is strictly prohibitive, but a lot of this is annoying at the very least. Let’s see if we can do better!</p>
<h2 id="threadlocalstoragetls">Thread-local storage (TLS)</h2>
<p>Starting with C11 and C++11, both languages support thread local variables directly via the _Thread_local and thread_local storage specifiers, respectively. Declaring a variable as per-thread is now a matter of simply adding the keyword:</p>
<pre><code>thread_local void* elastic_tracecorr_tls_v1 = nullptr;
</code></pre>
<p>You might assume that the compiler simply inserts calls to the corresponding pthread function calls when variables declared with this are accessed, but this is not actually the case. The reality is surprisingly complicated, and it turns out that there are four different models of TLS that the compiler can choose to generate. For some of those models, there are further multiple dialects that can be used to implement them. The different models and dialects come with various portability versus performance trade-offs. If you are interested in the details, I suggest reading this <a href="https://maskray.me/blog/2021-02-14-all-about-thread-local-storage">blog article</a> that does a great job at explaining them.</p>
<p>The TLS model and dialect are usually chosen by the compiler based on a somewhat opaque and complicated set of architecture-specific rules. Fortunately for us, both gcc and clang allow users to pick a particular one using the -ftls-model and -mtls-dialect arguments. The variant that we ended up picking for our purposes is -ftls-model=global-dynamic and -mtls-dialect=gnu2 (and desc on aarch64).</p>
<p>Let's take a look at the assembly that is being generated when accessing a thread_local variable under these settings. Our function:</p>
<pre><code>void setThreadProfilingCorrelationBuffer(JNIEnv* jniEnv, jobject bytebuffer) {
  if (bytebuffer == nullptr) {
    elastic_tracecorr_tls_v1 = nullptr;
  } else {
    elastic_tracecorr_tls_v1 = jniEnv-&gt;GetDirectBufferAddress(bytebuffer);
  }
}
</code></pre>
<p>Is compiled to the following assembly code:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltbe785dc1354e597b/6a7f046b73d9bd7e5929d784/assembly.png" alt="assembly" /></p>
<p>Both possible branches assign a value to our thread-local variable. Let’s focus at the right branch corresponding to the nullptr case to get rid of the noise from the GetDirectBufferAddress function call:</p>
<pre><code>lea   rax, elastic_tracecorr_tls_v1_tlsdesc  ;; Load some pointer into rax.
call  qword ptr [rax]                        ;; Read &amp; call function pointer at rax.
mov   qword ptr fs:[rax], 0                  ;; Assign 0 to the pointer returned by
                                             ;; the function that we just called.
</code></pre>
<p>The fs: portion of the mov instruction is the actual magic bit that makes the memory read per-thread. We’ll get to that later; let’s first look at the mysterious elastic_tracecorr_tls_v1_tlsdesc variable that the compiler emitted here. It’s an instance of the tlsdesc structure that is located somewhere in the .got.plt ELF section. The structure looks like this:</p>
<pre><code>struct tlsdesc {
  // Function pointer used to retrieve the offset
  uint64_t (*resolver)(tlsdesc*);

  // TLS offset -- more on that later.
  uint64_t tp_offset;
}
</code></pre>
<p>The resolver field is initialized with nullptr and tp_offset with a per-executable offset. The first thread-local variable in an executable will usually have offset 0, the next one sizeof(first_var), and so on. At first glance this may appear to be similar to how TSD works, with the call to pthread_getspecific to resolve the actual offset, but there is a crucial difference. When the library is loaded, the resolver field is filled in with the address of __tls_get_addr by the loader (ld.so). __tls_get_addr is a relatively heavy function that allocates a TLS offset that is globally unique between all shared libraries in the process. It then proceeds by updating the tlsdesc structure itself, inserting the global offset and replacing the resolver function with a trivial one:</p>
<pre><code>void* second_stage_resolver(tlsdesc* desc) {
  return tlsdesc-&gt;tp_offset;
}
</code></pre>
<p>In essence, this means that the first access to a tlsdesc based thread-local variable is rather expensive, but all subsequent ones are cheap. We further know that by the time that our C++ library starts publishing per-thread data, it must have gone through the initial resolving process already. Consequently, all that we need to do is to read the final offset from the process's memory and memorize it. We also refresh the offset every now and then to ensure that we really have the final offset, combating the unlikely but possible race condition that we read the offset before it was initialized. We can detect this case by comparing the resolver address against the address of the __tls_get_addr function exported by ld.so.</p>
<h2 id="determiningthetlsoffsetfromanexternalprocess">Determining the TLS offset from an external process</h2>
<p>With that out of the way, the next question that arises is how to actually find the tlsdesc in memory so that we can read the offset. Intuitively one might expect that the dynamic symbol exported on the ELF file points to that descriptor, but that is not actually the case.</p>
<pre><code>$ readelf --wide --dyn-syms elastic-jvmti-linux-x64.so | grep elastic_tracecorr_tls_v1
328: 0000000000000000     8 TLS   GLOBAL DEFAULT   19 elastic_tracecorr_tls_v1
</code></pre>
<p>The dynamic symbol instead contains an offset relative to the start of the .tls ELF section and points to the initial value that libc initializes the TLS value with when it is allocated. So how does ld.so find the tlsdesc to fill in the initial resolver? In addition to the dynamic symbol, the compiler also emits a relocation record for our symbol, and that one actually points to the descriptor structure that we are looking for.</p>
<pre><code>$ readelf --relocs --wide elastic-jvmti-linux-x64.so | grep R_X86_64_TLSDESC
00000000000426e8  0000014800000024 R_X86_64_TLSDESC       0000000000000000
elastic_tracecorr_tls_v1 + 0
</code></pre>
<p>To read the final TLS offset, we thus simply have to:</p>
<ul>
<li><p>Wait for the event notifying us about a new shared library being loaded into a process</p></li>
<li><p>Do some cheap heuristics to detect our C++ library, avoiding the more expensive analysis below from being executed for every unrelated library on the system</p></li>
<li><p>Analyze the library on disk and scan ELF relocations for our per-thread variable to extract the tlsdesc address</p></li>
<li><p>Rebase that address to match where our library was loaded in that particular process</p></li>
<li><p>Read the offset from tlsdesc+8</p></li>
</ul>
<h2 id="determiningthetlsbase">Determining the TLS base</h2>
<p>Now that we have the offset, how do we use that to actually read the data that the library puts there for us? This brings us back to the magic fs: portion of the mov instruction that we discussed earlier. In X86, most memory operands can optionally be supplied with a segment register that influences the address translation.</p>
<p>Segments are an archaic construct from the early days of 16-bit X86 where they were used to extend the address space. Essentially the architecture provides a range of segment registers that can be configured with different base addresses, thus allowing more than 16-bits worth of memory to be accessed. In times of 64-bit processors, this is hardly a concern anymore. In fact, X86-64 aka AMD64 got rid of all but two of those segment registers: fs and gs.</p>
<p>So why keep two of them? It turns out that they are quite useful for the use-case of thread-local data. Since every thread can be configured to have its own base address in these segment registers, we can use it to point to a block of data for this specific thread. That is precisely what libc implementations on Linux are doing with the fs segment. The offset that we snatched from the processes memory earlier is used as an address with the fs segment register, and the CPU automatically adds it to the per-thread base address.</p>
<p>To retrieve the base address pointed to by the fs segment register in the kernel, we need to read its destination from the kernel’s task_struct for the thread that we happened to interrupt with our profiling timer event. Getting the task struct is easy because we are blessed with the bpf_get_current_task BPF helper functions. BPF helpers are pretty much syscalls for BPF programs: we can just ask the Linux kernel to hand us the pointer.</p>
<p>Armed with the task pointer, we now have to read the thread.fsbase (X86-64) or thread.uw.tp_value (aarch64) field to get our desired base address that the user-mode process accesses via fs. This is where things get complicated one last time, at least if we wish to support older kernels without <a href="https://www.kernel.org/doc/html/latest/bpf/btf.html">BTF support</a> (we do!). The <a href="https://github.com/torvalds/linux/blob/259f7d5e2baf87fcbb4fabc46526c9c47fed1914/include/linux/sched.h#L748">task_struct is huge</a> and there are hundreds of fields that can be present or not depending on how the kernel is configured. Being a core primitive of the scheduler, it is also constantly subject to changes between different kernel versions. On modern Linux distributions, the kernel is typically nice enough to tell us the offset via BTF. On older ones, the situation is more complicated. Since hardcoding the offset is clearly not an option if we hope the code to be portable, we instead have to figure out the offset by ourselves.</p>
<p>We do this by consulting /proc/kallsyms, a file with mappings between kernel functions and their addresses, and then using BPF to dump the compiled code of a kernel function that rarely changes and uses the desired offset. We dynamically disassemble and analyze the function and extract the offset directly from the assembly. For X86-64 specifically, we dump the <a href="https://elixir.bootlin.com/linux/v5.9.16/source/arch/x86/kernel/hw_breakpoint.c#L452">aout_dump_debugregs</a> function that accesses thread-&gt;ptrace_bps, which has consistently been 16 bytes away from the fsbase field that we are interested in for all kernels that we have ever looked at.</p>
<h2 id="readingtlsdatafromkernel">Reading TLS data from kernel</h2>
<p>With all the required offsets at our hands, we can now finally do what we set out to do in the first place: use them to enrich our stack traces with the OTel trace and span IDs that our C++ library prepared for us!</p>
<pre><code>void maybe_add_otel_info(Trace* trace) {
  // Did user-mode insert a TLS offset for this process? Read it.
  TraceCorrProcInfo* proc = bpf_map_lookup_elem(&amp;tracecorr_procs, &amp;trace-&gt;pid);

  // No entry -&gt; process doesn't have the C++ library loaded.
  if (!proc) return;

  // Load the fsbase offset from our global configuration map.
  u32 key = 0;
  SystemConfig* syscfg = bpf_map_lookup_elem(&amp;system_config, &amp;key);

  // Read the fsbase offset from the kernel's task struct.
  u8* fsbase;
  u8* task = (u8*)bpf_get_current_task();
  bpf_probe_read_kernel(&amp;fsbase, sizeof(fsbase), task + syscfg-&gt;fsbase_offset);

  // Use the TLS offset to read the **pointer** to our TLS buffer.
  void* corr_buf_ptr;
  bpf_probe_read_user(
    &amp;corr_buf_ptr,
    sizeof(corr_buf_ptr),
    fsbase + proc-&gt;tls_offset
  );

  // Read the information that our library prepared for us.
  TraceCorrelationBuf corr_buf;
  bpf_probe_read_user(&amp;corr_buf, sizeof(corr_buf), corr_buf_ptr);

  // If the library reports that we are currently in a trace, store it into
  // the stack trace that will be reported to our user-land process.
  if (corr_buf.trace_present &amp;&amp; corr_buf.valid) {
    trace-&gt;otel_trace_id.as_int.hi = corr_buf.trace_id.as_int.hi;
    trace-&gt;otel_trace_id.as_int.lo = corr_buf.trace_id.as_int.lo;
    trace-&gt;otel_span_id.as_int = corr_buf.span_id.as_int;
  }
}
</code></pre>
<h2 id="sendingoutthemappings">Sending out the mappings</h2>
<p>From this point on, everything further is pretty simple. The C++ library sets up a unix datagram socket during startup and communicates the socket path to the profiler via the per-process data block. The stacktraces annotated with the OTel trace and span IDs are sent from BPF to our user-mode profiler process via perf event buffers, which in turn sends the mappings between OTel span and trace and stack trace hashes to the C++ library. Our extensions to the OTel instrumentation framework then read those mappings and insert the stack trace hashes into the OTel trace.</p>
<p>This approach has a few major upsides compared to the perhaps more obvious alternative of sending out the OTel span and trace ID with the profiler’s stacktrace records. We want the stacktrace associations to be stored in the trace indices to allow filtering and aggregating stacktraces by the plethora of fields available on OTel traces. If we were to send out the trace IDs via the profiler's gRPC connection instead, we’d have to search for and update the corresponding OTel trace records in the profiling collector to insert the stack trace hashes.</p>
<p>This is not trivial: stacktraces are sent out rather frequently (every 5 seconds, as of writing) and the corresponding OTel trace might not have been sent and stored by the time the corresponding stack traces arrive in our cluster. We’d have to build a kind of delay queue and periodically retry updating the OTel trace documents, introducing avoidable database work and complexity in the collectors. With the approach of sending stacktrace mappings to the OTel instrumented process instead, the need for server-side merging vanishes entirely.</p>
<h2 id="tracecorrelationinaction">Trace correlation in action</h2>
<p>With all the hard work out of the way, let’s take a look at what trace correlation looks like in action!</p>
<div>
    
</div>
<h2 id="futureworksupportingotherlanguages">Future work: Supporting other languages</h2>
<p>We have demonstrated that trace correlation can work nicely for Java, but we have no intention of stopping there. The general approach that we discussed previously should work for any language that can efficiently load and call into our C++ library and doesn’t do user-mode scheduling with coroutines. The problem with user-mode scheduling is that the logical thread can change at any await/yield point, requiring us to update the trace IDs in TLS. Many such coroutine environments like Rust’s Tokio provide the ability to register a callback for whenever the active task is swapped, so they can be supported easily. Other languages, however, do not provide that option.</p>
<p>One prominent example in that category is Go: goroutines are built on user-mode scheduling, but to our knowledge there’s no way to instrument the scheduler. Such languages will need solutions that don’t go via the generic TLS path. For Go specifically, we have already built a prototype that uses pprof labels that are associated with a specific Goroutine, having Go’s scheduler update them for us automatically.</p>
<h2 id="gettingstarted">Getting started</h2>
<p>We hope this blog post has given you an overview of correlating profiling signals to distributed tracing, and its benefits for end-users.</p>
<p>To get started, download the <a href="https://github.com/elastic/elastic-otel-java">Elastic distribution of the OTel agent</a>, which contains the new trace correlation library. Additionally, you will need the latest version of Universal Profiling agent, bundled with <a href="https://www.elastic.co/blog/whats-new-elastic-8-13-0">Elastic Stack version 8.13</a>.</p>
<h2 id="acknowledgment">Acknowledgment</h2>
<p>We appreciate <a href="https://github.com/trask">Trask Stalnaker</a>, maintainer of the OTel Java agent, for his feedback on our approach and for reviewing the early draft of this blog post.</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/continuous-profiling-distributed-tracing-correlation</link>
    <guid isPermaLink="false">continuous-profiling-distributed-tracing-correlation</guid>
    <category><![CDATA[APM]]></category>
    <category><![CDATA[OpenTelemetry]]></category>
    <dc:creator><![CDATA[Joel Höner,Israel Ogbole,Jonas Kunz]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta319a1a25f2344e3/6a7f046e96b5a6604187b0b3/Under_highway_bridge.jpg" length="0" type="image/jpeg"/>
    <pubDate>Thu, 28 Mar 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Analyzing OpenTelemetry apps with Elastic AI Assistant and APM]]></title>
    <description><![CDATA[Elastic Observability provides native OpenTelemetry support, but analyzing applications logs, metrics, and traces can be daunting. Elastic Observability not only provides AIOps features but also an AI Assistant (co-pilot) to help get to MTTR faster.]]></description>
    <content:encoded><![CDATA[<p>OpenTelemetry is rapidly becoming the most expansive project within the Cloud Native Computing Foundation (CNCF), boasting as many commits as Kubernetes and garnering widespread support from customers. Numerous companies are adopting OpenTelemetry and integrating it into their applications. Elastic® offers detailed <a href="https://www.elastic.co/blog/getting-started-opentelemetry-instrumentation-sample-app">guides</a> on implementing OpenTelemetry for applications. However, like many applications, pinpointing and resolving issues can be time-consuming.</p>
<p>The <a href="https://www.elastic.co/blog/context-aware-insights-elastic-ai-assistant-observability">Elastic AI Assistant</a> significantly enhances the process, not only in identifying but also in resolving issues. This is further enhanced by Elastic’s new Service Level Objective (SLO) capability, allowing you to streamline your entire site reliability engineering (SRE) process from detecting potential issues to enhancing the overall customer experience.</p>
<p>In this blog, we will demonstrate how you, as an SRE, can detect issues in a service equipped with OpenTelemetry. We will explore problem identification using Elastic APM, Elastic’s AIOps capabilities, and the Elastic AI Assistant.</p>
<p>We will illustrate this using the <a href="https://github.com/elastic/opentelemetry-demo">OpenTelemetry demo</a>, with a <a href="https://opentelemetry.io/docs/demo/feature-flags/">feature flag (cartService)</a> that is activated.</p>
<p>Our walkthrough will encompass two scenarios:</p>
<ol>
<li><p>When the SLO for cart service becomes noncompliant, we will analyze the error through Elastic APM. The Elastic AI Assistant will assist by providing a runbook and a GitHub issue to facilitate issue analysis.</p></li>
<li><p>Should the SLO for the cart service be noncompliant, we will examine the trace that indicates a high failure rate. We will employ AIOps for failure correlation and the AI Assistant to analyze logs and Kubernetes metrics directly from the Assistant.</p></li>
</ol>
<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 the configuration:</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>We used the OpenTelemetry Demo. Directions for using Elastic with OpenTelemetry Demo are <a href="https://github.com/elastic/opentelemetry-demo">here</a>.</p></li>
<li><p>Additionally you will need to connect your AI Assistant to your favorite LLM. We used Azure OpenAI GPT-4.</p></li>
<li><p>We also ran the OpenTelemetry Demo on Kubernetes, specifically on GKE.</p></li>
</ul>
<h2 id="slononcompliance">SLO noncompliance</h2>
<p>Elastic APM recently released the SLO (Service Level Objectives) feature 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>We set up two SLOs for cart service:</p>
<ul>
<li><p><strong>Availability SLO</strong> , which monitors its availability by ensuring that transactions succeed. We set up the feature flag in the OpenTelemetry application, which generates an error for EmptyCart transactions 10% of the time.</p></li>
<li><p><strong>Latency SLO</strong> to ensure transactions are not going below a specific latency, which will reduce customer experiences.</p></li>
</ul>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf3eaf5cb49ab688a/6a7f0223e02fac902d5d60ff/image1.png" alt="1 - SLOs" /></p>
<p>Because of the OTel cartservice feature flag, the availability SLO is triggered, and within the SLO details, we see that over a seven-day period the availability is well below our target of 99.9, at 95.5. Additionally all the error budget that was available is also exhausted.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt26f1432b0e332202/6a7f0226b43770cf024d6825/image2.png" alt="2 - cart service otel" /></p>
<p>With SLO, you can easily identify when issues with customer experience occur, or when potential issues with services arise before they become potentially worse.</p>
<h2 id="scenario1analyzingapmtraceandlogswithaiassistant">Scenario 1: Analyzing APM trace and logs with AI Assistant</h2>
<p>Once the SLO is found as non-compliant, we can dive into cart service to investigate in Elastic APM. The following walks through the set of steps you can take in Elastic APM and how to use the AI Assistant to analyze the issue:</p>
<div>
    
</div>
<p>From the video, we can see that once in APM, we took the following steps.</p>
<ol>
<li><p>Investigated the trace EmptyCart, which was experiencing larger than normal failure rates.</p></li>
<li><p>The trace showed a significant number of failures, which also resulted in slightly larger latency.</p></li>
<li><p>We used AIOps failure correlation to identify the potential component causing the failure, which correlated to a field value of FailedPrecondition.</p></li>
<li><p>While filtering on that value and reviewing the logs, we still couldn’t understand what this meant.</p></li>
<li><p>This is where you can use Elastic’s AI Assistant to further your understanding of the issue.</p></li>
</ol>
<p>AI Assistant helped us analyze the following:</p>
<ol>
<li><p>It helped us understand what the log message meant and that it was related to the Redis connection failure issue.</p></li>
<li><p>Because we couldn’t connect to Redis, we asked the AI Assistant to give us the metrics for the Redis Kubernetes pods.</p></li>
<li><p>We learned there were two pods for Redis from the logs over the last two hours.</p></li>
<li><p>However, we also learned that the memory of one seems to be increasing.</p></li>
<li><p>It seems that Redis restarted (hence the second pod), and with this information we could dive deeper into what happened to Redis.</p></li>
</ol>
<p>You can see how quickly we could correlate a significant amount of information, logs, metrics, and traces through the AI Assistant and Elastic’s APM capabilities. We didn’t have to go through multiple screens to hunt down information.</p>
<h2 id="scenario2analyzingapmerrorwithaiassistant">Scenario 2: Analyzing APM error with AI Assistant</h2>
<p>Once the SLO is found as noncompliant, we can dive into cart service to investigate in Elastic APM. The following walks through the set of steps you can take in Elastic APM and use the AI Assistant to analyze the issue:</p>
<div>
    
</div>
<p>From the video, we can see that once in APM, we took the following steps:</p>
<ol>
<li><p>We noticed a specific error for the APM service.</p></li>
<li><p>We investigated this in the error tab, and while we see it’s an issue with connection to Redis, we still need more information.</p></li>
<li><p>The AI Assistant helps us understand the stacktrace and provides some potential causes for the error and ways to diagnose and resolve it.</p></li>
<li><p>We also asked it for a runbook, created by our SRE team, which gives us steps to work through this particular issue.</p></li>
</ol>
<p>But as you can see, AI Assistant provides us not only with information about the error message but also how to diagnose it and potentially resolve it with an internal runbook.</p>
<h2 id="achievingoperationalexcellenceoptimalperformanceandreliability">Achieving operational excellence, optimal performance, and reliability</h2>
<p>We’ve shown how an OpenTelemetry instrumented application (OTel demo) can be analyzed using Elastic’s features, especially the AI Assistant coupled with Elastic APM, AIOps, and the latest SLO features. Elastic significantly streamlines the process of identifying and resolving issues within your applications.</p>
<p>Through our detailed walkthrough of two distinct scenarios, we have seen how Elastic APM and the AI Assistant can efficiently analyze and address noncompliance with SLOs in a cart service. The ability to quickly correlate information, logs, metrics, and traces through these tools not only saves time but also enhances the overall effectiveness of the troubleshooting process.</p>
<p>The use of Elastic's AI Assistant in these scenarios underscores the value of integrating advanced AI capabilities into operational workflows. It goes beyond simple error analysis, offering insights into potential causes and providing actionable solutions, sometimes even with customized runbooks. This integration of technology fundamentally changes how SREs approach problem-solving, making the process more efficient and less reliant on manual investigation.</p>
<p>Overall, the advancements in Elastic’s APM, AIOps capabilities, and the AI Assistant, particularly in handling OpenTelemetry data, represent a significant step forward in operational excellence. These tools enable SREs to not only react swiftly to emerging issues but also proactively manage and optimize the performance and reliability of their services, thereby ensuring an enhanced customer experience.</p>
<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 cloud? <a href="https://www.elastic.co/cloud/cloud-trial-overview">Start a free trial</a>.</p>
<blockquote>
  <ul>
  <li><a href="https://www.elastic.co/blog/service-level-objectives-slos-logs-metrics">Build better Service Level Objectives (SLOs) from logs and metrics</a></li>
  <li><a href="https://www.elastic.co/blog/whats-new-elastic-observability-8-12-0">Elastic Observability 8.12: GA for AI Assistant, SLO, and Mobile APM support</a></li>
  <li><a href="https://www.elastic.co/blog/native-opentelemetry-support-in-elastic-observability">Native Observability support in Elastic Observability</a></li>
  <li><a href="https://www.elastic.co/blog/context-aware-insights-elastic-ai-assistant-observability">Context-aware insights using the Elastic AI Assistant for Observability</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>
<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/analyzing-opentelemetry-apps-elastic-ai-assistant-apm</link>
    <guid isPermaLink="false">analyzing-opentelemetry-apps-elastic-ai-assistant-apm</guid>
    <category><![CDATA[APM]]></category>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[AI]]></category>
    <dc:creator><![CDATA[Bahubali Shetti]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb9decfe96627ccd0/6a7f022977b034eedd3ff0a1/ecs-otel-announcement-3.jpeg" length="0" type="image/jpeg"/>
    <pubDate>Tue, 12 Mar 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Adding free and open Elastic APM as part of your Elastic Observability deployment]]></title>
    <description><![CDATA[Learn how to gather application trace data and store it alongside the logs and metrics from your applications and infrastructure with Elastic Observability and Elastic APM.]]></description>
    <content:encoded><![CDATA[<p>In a recent post, we showed you <a href="https://www.elastic.co/blog/getting-started-with-free-and-open-elastic-observability">how to get started with the free and open tier of Elastic Observability</a>. Below, we'll walk through what you need to do to expand your deployment so you can start gathering metrics from application performance monitoring (APM) or "tracing" data in your observability cluster, for free.</p>
<h2 id="whatisapm">What is APM?</h2>
<p>Application performance monitoring lets you see where your applications spend their time, what they are doing, what other applications or services they are calling, and what errors or exceptions they are encountering.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt75d1516cb178adb6/6a85c74c501a8561c6fbb28a/screenshot-serverless-distributed-trace.png" alt="" /></p>
<p>In addition, APM also lets you see history and trends for key performance indicators, such as latency and throughput, as well as transaction and dependency information:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt886c0c626ef5bb89/6a85c74f80984c6d8e668f32/ruby-overview.png" alt="" /></p>
<p>Whether you're setting up alerts for SLA breaches, trying to gauge the impact of your latest release, or deciding where to make the next improvement, APM can help with your root-cause analysis to help improve your users' experience and drive your mean time to resolution (MTTR) toward zero.</p>
<h2 id="logicalarchitecture">Logical architecture</h2>
<p>Elastic APM relies on the APM Integration inside Elastic Agent, which forwards application trace and metric data from applications instrumented with APM agents to an Elastic Observability cluster. Elastic APM supports multiple agent flavors:</p>
<ul>
<li>Native Elastic APM Agents, available for <a href="https://www.elastic.co/guide/en/apm/agent/index.html">multiple languages</a>, including Java, .NET, Go, Ruby, Python, Node.js, PHP, and client-side JavaScript</li>
<li>Code instrumented with <a href="https://www.elastic.co/guide/en/apm/get-started/current/open-telemetry-elastic.html">OpenTelemetry</a></li>
<li>Code instrumented with <a href="https://www.elastic.co/guide/en/apm/get-started/current/opentracing.html">OpenTracing</a></li>
<li>Code instrumented with <a href="https://www.elastic.co/guide/en/apm/server/current/jaeger.html">Jaeger</a></li>
</ul>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0218d3e17c29a3b2/6a85c7515c27902af0f59a71/blog-elastic-observability-instrumented-services.png" alt="" /></p>
<p>In this blog, we'll provide a quick example of how to instrument code with the native Elastic APM Python agent, but the overall steps are similar for other languages.</p>
<p>Please note that there is a strong distinction between the <strong>Elastic APM Agent</strong> and the <strong>Elastic Agent</strong>. These are very different components, as you can see in the diagram above, so it's important not to confuse them.</p>
<h2 id="installtheelasticagent">Install the Elastic Agent</h2>
<p>The first step is to install the Elastic Agent. You either need Fleet <a href="https://www.elastic.co/guide/en/fleet/current/add-a-fleet-server.html">installed first</a>, or you can install the Elastic Agent standalone. Install the Elastic Agent somewhere by <a href="https://www.elastic.co/guide/en/fleet/master/elastic-agent-installation.html">following this guide</a>. This will give you an APM Integration endpoint you can hit. Note that this step is not necessary in Elastic Cloud, as we host the APM Integration for you. Check Elastic Agent is up by running:</p>
<pre><code>curl &lt;ELASTIC_AGENT_HOSTNAME&gt;:8200
</code></pre>
<h2 id="instrumentingsamplecodewithanelasticapmagent">Instrumenting sample code with an Elastic APM agent</h2>
<p>The instructions for the various language agents differ based on the programming language, but at a high level they have a similar flow. First, you add the dependency for the agent in the language's native spec, then you configure the agent to let it know how to find the APM Integration.</p>
<p>You can try out any flavor you'd like, but I am going to walk through the Python instructions using this Python example that <a href="https://github.com/davidgeorgehope/PythonElasticAPMExample">I created</a>.</p>
<h3 id="getthesamplecodeoruseyourown">Get the sample code (or use your own)</h3>
<p>To get started, I clone the GitHub repository then change to the directory:</p>
<pre><code>git clone https://github.com/davidgeorgehope/PythonElasticAPMExample
cd PythonElasticAPMExample
</code></pre>
<h3 id="howtoaddthedependency">How to add the dependency</h3>
<p>Adding the Elastic APM Dependency is simple — check the app.py file from <a href="https://github.com/davidgeorgehope/PythonElasticAPMExample/blob/main/app.py">the github repo</a> and you will notice the following lines of code.</p>
<pre><code>import elasticapm
from elasticapm import Client

app = Flask(__name__)
app.config["ELASTIC_APM"] = {    "SERVICE_NAME": os.environ.get("APM_SERVICE_NAME", "flask-app"),    "SECRET_TOKEN": os.environ.get("APM_SECRET_TOKEN", ""),    "SERVER_URL": os.environ.get("APM_SERVER_URL", "http://localhost:8200"),}
elasticapm.instrumentation.control.instrument()
client = Client(app.config["ELASTIC_APM"])
</code></pre>
<p>The Python library for Flask is capable of auto detecting transactions, but you can also start transactions in code as per the following, as we have done in this example:</p>
<pre><code>@app.route("/")
def hello():
    client.begin_transaction('demo-transaction')
    client.end_transaction('demo-transaction', 'success')
</code></pre>
<h3 id="configuretheagent">Configure the agent</h3>
<p>The agents need to send application trace data to the APM Integration, and to do this it has to be reachable. I configured the Elastic Agent to listen on my local host's IP, so anything in my subnet can send data to it. As you can see from the code below, we use docker-compose.yml to pass in the config via environment variables. Please edit these variables for your own Elastic installation.</p>
<pre><code># docker-compose.yml
version: "3.9"
services:
  flask_app:
    build: .
    ports:
      - "5001:5001"
    environment:
      - PORT=5001
      - APM_SERVICE_NAME=flask-app
      - APM_SECRET_TOKEN=your_secret_token
      - APM_SERVER_URL=http://host.docker.internal:8200
</code></pre>
<p>Some commentary on the above:</p>
<ul>
<li><strong>service_name:</strong> If you leave this out it will just default to the application's name, but you can override that here.</li>
<li><strong>secret_token:</strong> <a href="https://www.elastic.co/guide/en/apm/server/current/secret-token.html">Secret tokens</a> allow you to authorize requests to the APM Server, but they require that the APM Server is set up with SSL/TLS and that a secret token has been set up. We're not using HTTPS between the agents and the APM Server, so we'll comment this one out.</li>
<li><strong>server_url:</strong> This is how the agent can reach the APM Integration inside Elastic Agent. Replace this with the name or IP of your host running Elastic Agent.</li>
</ul>
<p>Now that the Elastic APM side of the configuration is done, we simply follow the steps from the <a href="https://github.com/davidgeorgehope/PythonElasticAPMExample/blob/main/README.md">README</a> to start up.</p>
<pre><code>docker-compose up --build -d
</code></pre>
<p>The build step will take several minutes.</p>
<p>You can navigate to the running sample application by visiting http://localhost:5001. There's not a lot to the sample, but it does generate some APM data. To generate a bit of a load, you can reload them a few times or run a quick little script:</p>
<pre><code>#!/bin/bash
# load_test.sh
url="http://localhost:5001"
for i in {1..1000}
do
  curl -s -o /dev/null $url
  sleep 1
done
</code></pre>
<p>This will just reload the pages every second.</p>
<p>Back in Kibana, navigate back to the APM app (hamburger icon, then select <strong>APM</strong> ) and you should see our new flask-app service (I let mine run so it shows a bit more history):</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc0b22219b36a9336/6a85c7549d2b718e27f938c4/blog-elastic-observability-services.png" alt="" /></p>
<p>The Service Overview page provides an at-a-glance summary of the health of a service in one place. If you're a developer or an SRE, this is the page that will help you answer questions like:</p>
<ul>
<li>How did a new deployment impact performance?</li>
<li>What are the top impacted transactions?</li>
<li>How does performance correlate with underlying infrastructure?</li>
</ul>
<p>This view provides a list of all of the applications that have sent application trace data to Elastic APM in the specified period of time (in this case, the last 15 minutes). There are also sparklines showing mini graphs of latency, throughput, and error rate. Clicking on <strong>flask-app</strong> takes us to the <strong>service overview</strong> page, which shows the various transactions within the service (recall that my script is hitting the / endpoint, as seen in the <strong>Transactions</strong> section). We get bigger graphs for <strong>Latency</strong> , <strong>Throughput</strong> , <strong>Errors</strong> , and <strong>Error Rates</strong>.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4d432bbc41e408db/6a85c75768266682891eab66/blog-elastic-observability-flask-app.png" alt="" /></p>
<p>When you're instrumenting real applications, under real load, you'll see a lot more connectivity (and errors!)</p>
<p>Clicking on a transaction in the transaction view, in this case, our sample app's demo-transaction transaction, we can see exactly what operations were called:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb4f05eae471a702c/6a85c75a342d69fd7c21b03f/blog-elastic-observability-flask-app-demo-transaction.png" alt="" /></p>
<p>This includes detailed information about calls to external services, such as database queries:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt897018765d4cb3af/6a85c75d342d69678e21b043/blog-elastic-observability-span-details.png" alt="" /></p>
<h2 id="whatsnext">What's next?</h2>
<p>Now that you've got your Elastic Observability cluster up and running and collecting out-of-the-box application trace data, explore the public APIs for the languages that your applications are using, which allow you to take your APM data to the next level. The APIs allow you to add custom metadata, define business transactions, create custom spans, and more. You can find the public API specs for the various APM agents (such as <a href="https://www.elastic.co/guide/en/apm/agent/java/current/public-api.html">Java</a>, <a href="https://www.elastic.co/guide/en/apm/agent/ruby/current/api.html">Ruby</a>, <a href="https://www.elastic.co/guide/en/apm/agent/python/current/index.html">Python</a>, and more) on the APM agent <a href="https://www.elastic.co/guide/en/apm/agent/index.html">documentation pages</a>.</p>
<p>If you'd like to learn more about Elastic APM, check out <a href="https://www.elastic.co/webinars/introduction-to-elastic-apm-in-the-shift-to-cloud-native">our webinar on Elastic APM in the shift to cloud native</a> to see other ways that Elastic APM can help you in your ecosystem.</p>
<p>If you decide that you'd rather have us host your observability cluster, you can sign up for a free trial of the <a href="https://www.elastic.co/cloud/">Elasticsearch Service on Elastic Cloud</a> and change your agents to point to your new cluster.</p>
<p><em>Originally published May 5, 2021; updated April 6, 2023.</em></p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/free-open-elastic-apm-observability-deployment</link>
    <guid isPermaLink="false">free-open-elastic-apm-observability-deployment</guid>
    <category><![CDATA[APM]]></category>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[Metrics]]></category>
    <dc:creator><![CDATA[David Hope]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb8a4183daa602b2a/6a85c760bc5bb342fdf81a2d/blog-thumb-release-apm.png" length="0" type="image/png"/>
    <pubDate>Wed, 28 Feb 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[Elastic APM for iOS and Android Native apps]]></title>
    <description><![CDATA[This blog provides an overview of the key capabilities included in the Elastic APM solution for iOS and Android native apps, as well as a walkthrough of the configuration details and troubleshooting workflow for a few error scenarios.]]></description>
    <content:encoded><![CDATA[<blockquote>
  <p><strong>WARNING</strong>: This article shows information about the Android agent that is no longer accurate for versions <code>1.x</code>. Please refer to <a href="https://www.elastic.co/docs/reference/apm/agents/android">its documentation</a> to learn about its new APIs.</p>
</blockquote>
<p>Elastic® APM for iOS and Android native apps is generally available in the stack release v8.12. The Elastic <a href="https://github.com/elastic/apm-agent-ios">iOS</a> and <a href="https://github.com/elastic/apm-agent-android">Android</a> APM agents are open-source and have been developed on-top, i.e., as a distribution of the OpenTelemetry Swift and Android SDK/API, respectively.</p>
<h2 id="overviewofthemobileapmsolution">Overview of the Mobile APM solution</h2>
<p>The OpenTelemetry SDK/API for iOS and Android supports capabilities such as auto-instrumentation of HTTP requests, API for manual instrumentation, data model based on the OpenTelemetry semantic conventions, and buffering support. Additionally, the Elastic APM agent distributions also support an easier initialization process and novel features such as remote config and user session based sampling. The Elastic <a href="https://github.com/elastic/apm-agent-ios">iOS</a> and <a href="https://github.com/elastic/apm-agent-android">Android</a> APM agents being <em>distributions</em> are maintained per Elastic’s standard support T&amp;Cs.</p>
<p>There are curated or pre-built dashboards provided in Kibana® for monitoring, data analysis, and for troubleshooting purposes. The <strong>Service Overview</strong> view shown below provides relevant frontend KPIs such as crash rate, http requests, average app load time, and more, including the comparison view.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt91be7bfb456b1441/6a85c94aabdc296bf31224c4/1.png" alt="1 - comparison view" /></p>
<p>Further, the geographic distribution of user traffic is available on a map at a country and regional level. The service overview dashboard also shows trends of metrics such as throughput, latency, failed transaction rate, and distribution of traffic by device make-model, network connection type, and app version.</p>
<p>The <strong>Transactions</strong> view shown below highlights the performance of the different transaction groups, including the distributed trace end-to-end of individual transactions with links to associated spans, errors and crashes. Further, users can see at a glance the distribution of traffic by device make and model, app version, and OS version.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb174c5d176ef23e6/6a85c94e11893c00d7a7ab60/2.png" alt="2- opbeans android" /></p>
<p>Tabular views such as the one highlighted below located at the bottom of <strong>Transactions</strong> tab makes it relatively easy to see how the device make and model, App version, etc., impacts latency and crash rate.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5f83e08c0af3c9b9/6a85c950eaf24581b8a49f27/3.png" alt="3 - latency and crash rate" /></p>
<p>The <strong>Errors &amp; Crashes</strong> view shown below can be used to analyze the different error and crash groups. The unsymbolicated (iOS) or obfuscated (Android) stacktrace of the individual error or crash instance is also available in this view.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7a48d2990a65a1ea/6a85c95318249c466b18f793/4.png" alt="4 - opbeans swift" /></p>
<p>The <strong>Service Map</strong> view shown below provides a visualization of the end-to-end service interdependencies, including any third-party APIs, proxy servers, and databases.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta639d74a283c954d/6a85c956e2447a1d488b13d0/5.png" alt="5 - flowchart" /></p>
<p>The comprehensive pre-built dashboards for observing the mobile frontend in Kibana provide visibility into the sources of errors, crashes, and bottlenecks to ease troubleshooting of issues in the production environment. The underlying Elasticsearch® Platform also supports the ability to query raw data, build custom metrics and custom dashboards, alerting, SLOs, and anomaly detection. Altogether the platform provides a comprehensive set of tools to expedite root cause analysis and remediation, thereby facilitating a high velocity of innovation.</p>
<h2 id="walkthroughofthedebuggingworkflowforsomeerrorscenarios">Walkthrough of the debugging workflow for some error scenarios</h2>
<p>Next, we will provide a walkthrough of the configuration details and the troubleshooting workflow for a couple of error scenarios in iOS and Android native apps.</p>
<h3 id="scenario1">Scenario 1</h3>
<p>In this example, we will debug a crash in an asynchronous method using Apple’s crash report <strong>symbolication</strong> as well as <strong>breadcrumbs</strong> to deduce the cause of the crash.</p>
<p><strong>Symbolication</strong><br />
In this scenario, users notice a spike in the crash occurrences of a particular crash group in the Errors &amp; Crashes tab and decide to investigate further. A new crash comes in on the Crashes tab, and the developer follows these steps to symbolicate the crash report locally.</p>
<ol>
<li>Copy the crash via the UI and paste it into a file with the following name format \&lt;AppBinaryName&gt;_\&lt;DateTime&gt;. For example, “opbeans-swift_2024-01-18-114211.ips`.</li>
</ol>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltbec22d51521f45c7/6a85c9584710c6e8ead3cb19/6.png" alt="6 - Symbolication" /></p>
<ol>
<li>Apple provides <a href="https://developer.apple.com/documentation/xcode/adding-identifiable-symbol-names-to-a-crash-report">detailed instructions</a> on how to symbolicate this file locally either automatically through Xcode or manually using the command line.</li>
</ol>
<p><strong>Breadcrumbs</strong><br />
The second frame of the first thread shows that the crash is occuring in a Worker instance.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt12e7dbf052ee6068/6a85c95c331d7aeb58c3175d/7.png" alt="7 - Breadcrumbs" /></p>
<p>This instance is actually used in many places, and due to the asynchronous nature of this function, it’s not possible to determine immediately where this call is coming from. Nevertheless, we can utilize features of the Open Telemetry SDK to add more context to these crashes and then put the pieces together to find the site of the crash.</p>
<p>By adding “breadcrumbs” around this Worker instance, it is possible to track down which calls to the Worker are actually associated with this crash.</p>
<p><strong>Example:</strong><br />
Create a logger provider in the Worker class as a public variable for ease of access, as shown below:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt260c153e71243aed/6a85c95fe2447acd2e8b13d6/8.png" alt="8 - example code" /></p>
<p>Create breadcrumbs everywhere the Worker.doWork() function is called:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta3fc688f6eec5690/6a85c9622d64d51b7d081d04/9.png" alt="9 - Create breadcrumbs everywhere the Worker.doWork() function" /></p>
<p>Each of these breadcrumbs will use the same event <strong>name</strong> “worker_breadcrumb” so they can be consistently queried, and the differentiation will be done using the “ <strong>source</strong> ” attribute.</p>
<p>In this example, the Worker.doWork() function is being called from a CustomerRow struct (a table row which does work ‘onTapGesture’). If you were to call this method from multiple places in a CustomerRow struct, you may also add additional differentiations to the “ <strong>source</strong> ” attribute value, such as the associated function (e.g., “CustomerRow#onTapGesture”).</p>
<p>Now that the app is reporting these breadcrumbs, we can use Discover to <strong>query</strong> for them, as shown below:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta4792d8540ea20e4/6a85c96511893c2390a7ab64/10.png" alt="10 - Discover to query" /></p>
<p> <strong>Note:</strong>  <em>Event</em>  <strong>names</strong>  <em>sent by the agent are translated to event</em>  <strong>action</strong>  <em>in Elastic Common Schema (ECS), so ensure the query uses this field.</em></p>
<ol>
<li><p>You can add a filter: <code>event.action: “worker_breadcrumb”</code> and it shows all events generated from this new breadcrumb.</p></li>
<li><p>You can also see the various sources: ProductRow, CustomerRow, CartRow, etc.</p></li>
<li><p>If you add <strong>error.type : crash</strong> to the query, you can see crashes alongside the breadcrumbs:</p></li>
</ol>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0d5e3d3c91d562ae/6a85c96818249cc4bd18f797/11.png" alt="11 - crashes along side the breadcrumbs" /></p>
<p>A crash and a breadcrumb next to each other in the timeline may come from completely different devices, so we need another differentiator. For each crash, we have metadata that contains the <strong>session.id</strong> associated with the crash, viewable from the Metadata tab. We can query using this <strong>session.id</strong> to ensure that the only data we are looking at in Discover is from a single user session (i.e., a single device) that resulted in the crash.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9a61245760515fb0/6a85c96beaf2457b92a49f2b/12.png" alt="12. - session.id" /></p>
<p>In Discover, we can now see the session event flow, on a single device, concerning the crash via the breadcrumbs, as shown below:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8f7fabb6c2755bbb/6a85c96e2d64d51251081d08/13.png" alt="13 - session event flow" /></p>
<p>It looks like the last breadcrumb before the crash was from the “CustomerRow” breadcrumb. Now this gives the app developer a good place to start their root cause analysis or investigation.</p>
<h3 id="scenario2">Scenario 2</h3>
<p> <strong>Note:</strong>  <em>This scenario requires the Elastic Android agent version “0.14.0” or higher.</em></p>
<p>An Android sample app has a form composed of two screens that are created using two fragments (<code>FirstPage</code> and <code>SecondPage</code>). In the first screen, the app makes a backend API call to get a key that identifies the form submission. This key is stored in memory in the app and must be available on the last screen where the form is sent; the key must be sent along with the form's data.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd041ab09986f741c/6a85c970d7b2e70de0fe84b6/14.jpg" alt="14 - form submission" /></p>
<p><strong>The problem</strong><br />
We start to see a spike in crash occurrences in Kibana (null pointer exception) in the Errors &amp; Crashes tab that always seem to happen on the last screen of the form, when the users click on the "FINISH" button. Nevertheless, <strong>this is not always reproducible</strong> , so the root cause isn't clear just by looking at the crash’s stacktrace alone. Here’s what it looks like:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5a3320046b2fa019/6a85c97493ffb9589eb9140b/15.png" alt="15 - stack trace" /></p>
<p>When we take a look at the code referenced in the stacktrace, this is what we can see:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt468d6da2973f89a0/6a85c9779bf9944f060a054b/16.png" alt="16 - When we take a look at the code referenced in the stacktrace, this is what we can see:" /></p>
<p>This is the line where the crash happens, so it seems like the variable “formId” (which is a static String located in “FirstPage”) was null by the time this code was executed, causing a null pointer exception to be raised. This variable is set within the “FirstPage” fragment after the backend request is done to retrieve the id. The only way to get to the “SecondPage” is by passing through the “FirstPage.” So, the stacktrace alone doesn’t help much as the pages have to be opened in order, and the first one will always set the “formId” variable. Therefore, it doesn’t seem likely that the formId could be null in “SecondPage.”</p>
<p><strong>Finding the root cause</strong><br />
Apart from taking a look at the crash’s stacktrace, it could also be useful to take a look at complementary data that would help put the pieces together and get a broader picture of what other things happened while our app was running when the crash happened. For this case, we know that the form ID must come from our backend service, so we could start by ruling out that there was an error with the backend call. We do this by checking the traces from the creation of our FirstPage fragment where the form ID request is executed, in the Transaction details view:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt38678706cbce8da0/6a85c97a1aa1e1ef42ff8d37/17.png" alt="17 - trace sample" /></p>
<p>The “Created” spans represent the time it took to create the first fragment. The topmost one shows the Activity creation, followed by the NavHostFragment, followed by “FirstScreen.” Not long after its creation, we see that a GET HTTP request to our backend is made to retrieve our form ID and, according to the traces, the GET request was successful. We can therefore rule out that there is an issue with the backend communication for this problem.</p>
<p>Another option could be looking at the logs sent throughout the <a href="https://opentelemetry.io/docs/specs/semconv/general/session/">session</a> in our app where the crash occurred (we could also take a look at all the logs coming from our app but they would be too many to analyze this one issue). To do so, we first copy one of the spans’ “session.id” values (any span would work since the same session ID will be available in all the data that was sent from our app during the time that the crash occurred) available in the span details flyout.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltaae166d8999ccb7b/6a85c97df9373db3c896f56e/18.png" alt="18 - red box highlighted" /></p>
<p> <strong>Note:</strong>  <em>The same session ID can also be found in the crash metadata.</em></p>
<p>Now that we have identified our session, we can open up the Logs Explorer view and take a look at all of our app’s logs within that same session, as shown below:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltcd9ae193f9bc4b72/6a85c980331d7ac6f4c31767/19.png" alt="19 - app's logs" /></p>
<p>By looking at the logs, and adding a few fields to show the app’s lifecycle status and the error types, we see the log events that are <a href="https://github.com/elastic/apm/blob/main/specs/agents/mobile/events.md">automatically collected</a> from our app. We can see the crash event at the top of the list as the latest one. We can also see our app’s lifecycle events, and if we keep scrolling through, we’ll get to some lifecycle events that are going to help find our root cause:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt166a2855c5721aa7/6a85c983f5f1a02e202ec8c9/20.png" alt="20 - root cause" /></p>
<p>We can see there are a couple of lifecycle events that tell us that the app was restarted during the session. This is an important hint because it means that the Android OS killed our app at some point, which is common when an app stays in the background for a while. With this information, we could try to reproduce the issue by forcing the OS to kill our app in the background and then see how it behaves when reopened from the recently opened apps menu.</p>
<p>After giving it a try, we could reproduce the issue and we found that the static “formId” variable was lost when the app was restarted, causing it to be null when the SecondPage fragment requested it. We can now research best practices of passing arguments to Fragments so we can change our code to prevent relying on static fields and instead store and share values between screens, thus preventing this crash from happening again.</p>
<p><strong>Bonus:</strong> For this scenario, it was enough for us to rely on the events that are sent automatically by the APM Agent; however, if those aren’t enough for other cases, we can always send custom events in the places where we want to track the state changes of our app via the OpenTelemetry event API, as shown in the the code snippet below:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt73c7313eb46de1e3/6a85c98611893cd033a7ab68/21.png" alt="21 - black code box" /></p>
<h2 id="makethemostofyourelasticapmexperience">Make the most of your Elastic APM Experience</h2>
<p>In this post, we reviewed Elastic’s new Mobile APM solution available in 8.12. The new solution uses Elastic’s new <a href="https://github.com/elastic/apm-agent-ios">iOS</a> and <a href="https://github.com/elastic/apm-agent-android">Android</a> APM agents that are open-source and have been developed on-top, i.e., as a distribution of the OpenTelemetry Swift and Android SDK/API, respectively.</p>
<p>We also reviewed configuration details and the troubleshooting workflow for two error scenarios in iOS and Android native apps.</p>
<ul>
<li><p><strong>iOS scenario:</strong> Debug a crash in an asynchronous method using Apple’s crash report <strong>symbolication</strong> as well as <strong>breadcrumbs</strong> to deduce the cause of the crash.</p></li>
<li><p><strong>Android scenario:</strong> Analyze why users get a null pointer exception on the last screen when they click on the “FINISH” button of a form. Analyzing this is not always clear by looking at the crash’s stack trace and isn’t easily reproducible.</p></li>
</ul>
<p>In both instances, we found the root cause of the crash using distributed traces from the mobile device as well as correlated logs. Hopefully this blog provided a review of how Elastic can help manage and monitor Mobile native apps.</p>
<p>Elastic invites SREs and developers to experience our Mobile APM solution 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>.</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/apm-ios-android-native-apps</link>
    <guid isPermaLink="false">apm-ios-android-native-apps</guid>
    <category><![CDATA[APM]]></category>
    <category><![CDATA[OpenTelemetry]]></category>
    <dc:creator><![CDATA[Akhilesh Pokhariyal,Cesar Munoz,Bryce Buchanan]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3184b0ef29376383/6a85c9884710c64fbad3cb21/141949-elastic-blogheaderimage.png" length="0" type="image/png"/>
    <pubDate>Thu, 08 Feb 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Enhancing SRE troubleshooting with the AI Assistant for Observability and your organization's runbooks]]></title>
    <description><![CDATA[Empower your SRE team with this guide to enriching Elastic's AI Assistant Knowledge Base with your organization's internal observability information for enhanced alert remediation and incident management.]]></description>
    <content:encoded><![CDATA[<p>The <a href="https://www.elastic.co/blog/context-aware-insights-elastic-ai-assistant-observability">Observability AI Assistant</a> helps users explore and analyze observability data using a natural language interface, by leveraging automatic function calling to request, analyze, and visualize your data to transform it into actionable observability. The Assistant can also set up a Knowledge Base, powered by <a href="https://www.elastic.co/guide/en/machine-learning/current/ml-nlp-elser.html">Elastic Learned Sparse EncodeR</a> (ELSER) to provide additional context and recommendations from private data, alongside the large language models (LLMs) using RAG (Retrieval Augmented Generation). Elastic’s Stack — as a vector database with out-of-the-box semantic search and connectors to LLM integrations and the Observability solution — is the perfect toolkit to extract the maximum value of combining your company's unique observability knowledge with generative AI.</p>
<h2 id="enhancedtroubleshootingforsres">Enhanced troubleshooting for SREs</h2>
<p>Site reliability engineers (SRE) in large organizations often face challenges in locating necessary information for troubleshooting alerts, monitoring systems, or deriving insights due to scattered and potentially outdated resources. This issue is particularly significant for less experienced SREs who may require assistance even with the presence of a runbook. Recurring incidents pose another problem, as the on-call individual may lack knowledge about previous resolutions and subsequent steps. Mature SRE teams often invest considerable time in system improvements to minimize "fire-fighting," utilizing extensive automation and documentation to support on-call personnel.</p>
<p>Elastic® addresses these challenges by combining generative AI models with relevant search results from your internal data using RAG. The <a href="https://www.elastic.co/guide/en/observability/current/obs-ai-assistant.html">Observability AI Assistant's internal Knowledge Base</a>, powered by our semantic search retrieval model <a href="https://www.elastic.co/guide/en/machine-learning/current/ml-nlp-elser.html">ELSER</a>, can recall information at any point during a conversation, providing RAG responses based on internal knowledge.</p>
<p>This Knowledge Base can be enriched with your organization's information, such as runbooks, GitHub issues, internal documentation, and Slack messages, allowing the AI Assistant to provide specific assistance. The Assistant can also document and store specific information from an ongoing conversation with an SRE while troubleshooting issues, effectively creating runbooks for future reference. Furthermore, the Assistant can generate summaries of incidents, system status, runbooks, post-mortems, or public announcements.</p>
<p>This ability to retrieve, summarize, and present contextually relevant information is a game-changer for SRE teams, transforming the work from chasing documents and data to an intuitive, contextually sensitive user experience.The Knowledge Base (see <a href="https://www.elastic.co/guide/en/observability/current/obs-ai-assistant.html#obs-ai-requirements">requirements</a>) serves as a central repository of Observability knowledge, breaking documentation silos and integrating tribal knowledge, making this information accessible to SREs enhanced with the power of LLMs.</p>
<p>Your LLM provider may collect query telemetry when using the AI Assistant. If your data is confidential or has sensitive details, we recommend you verify the data treatment policy of the LLM connector you provided to the AI Assistant.</p>
<p>In this blog post, we will cover different ways to enrich your Knowledge Base (KB) with internal information. We will focus on a specific alert, indicating that there was an increase in logs with “502 Bad Gateway” errors that has surpassed the alert’s threshold.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt432418d2871ff281/6a7f1b0873d9bd18b729df69/elastic-blog-1.png" alt="1 - threshold breached" /></p>
<h2 id="howtotroubleshootanalertwiththeknowledgebase">How to troubleshoot an alert with the Knowledge Base</h2>
<p>Before the KB has been enriched with internal information, when the SRE asks the AI Assistant about how to troubleshoot an alert, the response from the LLM will be based on the data it learned during training; however, the LLM is not able to answer questions related to private, recent, or emerging knowledge. In this case, when asking for the steps to troubleshoot the alert, the response will be based on generic information.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf2577ea1ce6b71b1/6a7f1b0b05b7b519c318bd51/elastic-blog-2.png" alt="2 - troubleshooting steps" /></p>
<p>However, once the KB has been enriched with your runbooks, when your team receives a new alert on “502 Bad Gateway” Errors, they can use AI Assistant to access the internal knowledge to troubleshoot it, using semantic search to find the appropriate runbook in the Knowledge Base.</p>
<p>In this blog, we will cover different ways to add internal information on how to troubleshoot an alert to the Knowledge Base:</p>
<ol>
<li><p>Ask the assistant to remember the content of an existing runbook.</p></li>
<li><p>Ask the Assistant to summarize and store in the Knowledge Base the steps taken during a conversation and store it as a runbook.</p></li>
<li><p>Import your runbooks from GitHub or another external source to the Knowledge Base using our Connector and APIs.</p></li>
</ol>
<p>After the runbooks have been added to the KB, the AI Assistant is now able to recall the internal and specific information in the runbooks. By leveraging the retrieved information, the LLM could provide more accurate and relevant recommendations for troubleshooting the alert. This could include suggesting potential causes for the alert, steps to resolve the issue, preventative measures for future incidents, or asking the assistant to help execute the steps mentioned in the runbook using functions. With more accurate and relevant information at hand, the SRE could potentially resolve the alert more quickly, reducing downtime and improving service reliability.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte9f381e6f96debcb/6a7f1b0e73d9bd5ba529df6d/Screenshot_2023-11-10_at_9.52.38_AM.png" alt="3 - troubleshooting 502 Bad gateway" /></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb7975cbd842b6cd8/6a7f1b11c2e91480da016ffa/elastic-blog-4.png" alt="4 - (5) test the backend directly" /></p>
<p>Your Knowledge Base documents will be stored in the indices <em>.kibana-observability-ai-assistant-kb-</em>*. Have in mind that LLMs have restrictions on the amount of information the model can read and write at once, called token limit. Imagine you're reading a book, but you can only remember a certain number of words at a time. Once you've reached that limit, you start to forget the earlier words you've read. That's similar to how a token limit works in an LLM.</p>
<p>To keep runbooks within the token limit for Retrieval Augmented Generation (RAG) models, ensure the information is concise and relevant. Use bullet points for clarity, avoid repetition, and use links for additional information. Regularly review and update the runbooks to remove outdated or irrelevant information. The goal is to provide clear, concise, and effective troubleshooting information without compromising the quality due to token limit constraints. LLMs are great for summarization, so you could ask the AI Assistant to help you make the runbooks more concise.</p>
<h2 id="asktheassistanttorememberthecontentofanexistingrunbook">Ask the assistant to remember the content of an existing runbook</h2>
<p>The easiest way to store a runbook into the Knowledge Base is to just ask the AI Assistant to do it! Open a new conversation and ask “Can you store this runbook in the KB for future reference?” followed by pasting the content of the runbook in plain text.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt88e751fe6aabf649/6a7f1b146c6eac1f20f145b5/elastic-blog-5.png" alt="5 - new conversation - let's work on this together" /></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltcbdbc2440f80ddda/6a7f1b1696b5a6f0e687b89f/elastic-blog-6.png" alt="6 - new converastion" /></p>
<p>The AI Assistant will then store it in the Knowledge Base for you automatically, as simple as that.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt678daca55cfe0ff7/6a7f1b19fc63ab131a64d08e/elastic-blog-7.png" alt="7 - storing a runbook" /></p>
<h2 id="asktheassistanttosummarizeandstorethestepstakenduringaconversationintheknowledgebase">Ask the Assistant to summarize and store the steps taken during a conversation in the Knowledge Base</h2>
<p>You can also ask the AI Assistant to remember something while having a conversation — for example, after you have troubleshooted an alert using the AI Assistant, you could ask to "remember how to troubleshoot this alert for next time." The AI Assistant will create a summary of the steps taken to troubleshoot the alert and add it to the Knowledge Base, effectively creating runbooks for future reference. Next time you are faced with a similar situation, the AI Assistant will recall this information and use it to assist you.</p>
<p>In the following demo, the user asks the Assistant to remember the steps that have been followed to troubleshoot the root cause of an alert, and also to ping the Slack channel when this happens again. In a later conversation with the Assistant, the user asks what can be done about a similar problem, and the AI Assistant is able to remember the steps and also reminds the user to ping the Slack channel.</p>
<p>After receiving the alert, you can open the AI Assistant chat and test troubleshooting the alert. After investigating an alert, ask the AI Assistant to summarize the analysis and the steps taken to root cause. To remember them for the next time, we have a similar alert and add extra instruction like to warn the Slack channel.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt59ebedea4c01cbd8/6a7f1b1dbd21980b3c7584bf/elastic-blog-8.png" alt="8. -teal box" /></p>
<p>The Assistant will use the built-in functions to summarize the steps and store them into your Knowledge Base, so they can be recalled in future conversations.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3ab3c30c33361206/6a7f1b20c2cc0977992499ca/Screenshot_2023-11-08_at_11.34.08_AM.png" alt="9 - Elastic assistant chat (CROP)" /></p>
<p>Open a new conversation, and ask what are the steps to take when troubleshooting a similar alert to the one we just investigated. The Assistant will be able to recall the information stored in the KB that is related to the specific alert, using semantic search based on <a href="https://www.elastic.co/guide/en/machine-learning/current/ml-nlp-elser.html">ELSER</a>, and provide a summary of the steps taken to troubleshoot it, including the last indication of informing the Slack channel.</p>
<div>
    
</div>
<h2 id="importyourrunbooksstoredingithubtotheknowledgebaseusingapisorourgithubconnector">Import your runbooks stored in GitHub to the Knowledge Base using APIs or our GitHub Connector</h2>
<p>You can also add proprietary data into the Knowledge Base programmatically by ingesting it (e.g., GitHub Issues, Markdown files, Jira tickets, text files) into Elastic.</p>
<p>If your organization has created runbooks that are stored in Markdown documents in GitHub, follow the steps in the next section of this blog post to index the runbook documents into your Knowledge Base.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt55e94e2bbca1d4c5/6a7f1b23ead8ec8c11baac5e/elastic-blog-10.png" alt="10 - github handling 502" /></p>
<p>The steps to ingest documents into the Knowledge Base are the following:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte50ccc306ed43ff6/6a7f1b26227b1cac36598a09/elastic-blog-11.png" alt="11 - using internal knowledge" /></p>
<h3 id="ingestyourorganizationsknowledgeintoelasticsearch">Ingest your organization’s knowledge into Elasticsearch</h3>
<p><strong>Option 1:</strong> <strong>Use the</strong> <a href="https://www.elastic.co/guide/en/enterprise-search/current/crawler.html"><strong>Elastic web crawler</strong></a> <strong>.</strong> Use the web crawler to programmatically discover, extract, and index searchable content from websites and knowledge bases. When you ingest data with the web crawler, a search-optimized <a href="https://www.elastic.co/blog/what-is-an-elasticsearch-index">Elasticsearch® index</a> is created to hold and sync webpage content.</p>
<p><strong>Option 2: Use Elasticsearch's</strong> <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-index_.html"><strong>Index API</strong></a> <strong>.</strong> <a href="https://www.elastic.co/guide/en/cloud/current/ec-ingest-guides.html">Watch tutorials</a> that demonstrate how you can use the Elasticsearch language clients to ingest data from an application.</p>
<p><strong>Option 3: Build your own connector.</strong> Follow the steps described in this blog: <a href="https://www.elastic.co/search-labs/how-to-create-customized-connectors-for-elasticsearch">How to create customized connectors for Elasticsearch</a>.</p>
<p><strong>Option 4: Use Elasticsearch</strong> <a href="https://www.elastic.co/guide/en/workplace-search/current/workplace-search-content-sources.html"><strong>Workplace Search connectors</strong></a> <strong>.</strong> For example, the <a href="https://www.elastic.co/guide/en/workplace-search/current/workplace-search-github-connector.html">GitHub connector</a> can automatically capture, sync, and index issues, Markdown files, pull requests, and repos.</p>
<ul>
<li>Follow the steps to <a href="https://www.elastic.co/guide/en/workplace-search/current/workplace-search-github-connector.html#github-configuration">configure the GitHub Connector in GitHub</a> to create an OAuth App from the GitHub platform.</li>
</ul>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt934623424f99c138/6a7f1b29bd21985ea47584c3/elastic-blog-12.png" alt="12 - elastic workplace search" /></p>
<ul>
<li>Now you can connect a GitHub instance to your organization. Head to your organization’s <strong>Search &gt; Workplace Search</strong> administrative dashboard, and locate the Sources tab.</li>
</ul>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt562317ca772f6b3d/6a7f1b2ceab5be27ce20ab08/Screenshot_2023-11-08_at_10.19.19_AM.png" alt="13 - screenshot" /></p>
<ul>
<li>Select <strong>GitHub</strong> (or GitHub Enterprise) in the Configured Sources list, and follow the GitHub authentication flow as presented. Upon the successful authentication flow, you will be redirected to Workplace Search and will be prompted to select the Organization you would like to synchronize.</li>
</ul>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6c465cdefe93ce4d/6a7f1b2fde231504f2fd80af/elastic-blog-14.png" alt="14 - configure and connect" /></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt54d1b7727cb79ab9/6a7f1b32eab5be3b6220ab0c/elastic-blog-15.png" alt="15 - how to add github" /></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf86ab1779e93317c/6a7f1b35bdcff09009c432af/elastic-blog-16.png" alt="16 - github" /></p>
<ul>
<li>After configuring the connector and selecting the organization, the content should be synchronized and you will be able to see it in Sources. If you don’t need to index all the available content, you can specify the indexing rules via the API. This will help shorten indexing times and limit the size of the index. See <a href="https://www.elastic.co/guide/en/workplace-search/current/workplace-search-customizing-indexing-rules.html">Customizing indexing</a>.</li>
</ul>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltded840b7b410baf3/6a7f1b37eab5be779e20ab10/elastic-blog-17.png" alt="17 - source overview" /></p>
<ul>
<li>The source has created an index in Elastic with the content (Issues, Markdown Files…) from your organization. You can find the index name by navigating to <strong>Stack Management &gt; Index Management</strong> , activating the <strong>Include hidden Indices</strong> button on the right, and searching for “GitHub.”</li>
</ul>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt22ee12a22538f12f/6a7f1b3b05b7b517f518bd55/elastic-blog-18.png" alt="18 - index mgmt" /></p>
<ul>
<li>You can explore the documents you have indexed by creating a Data View and exploring it in Discover. Go to <strong>Stack Management &gt; Kibana &gt; Data Views &gt; Create data view</strong> and introduce the data view Name, Index pattern (make sure you activate “Allow hidden and system indices” in advanced options), and Timestamp field:</li>
</ul>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt10bb270db2987d53/6a7f1b3eb437702e514d711e/elastic-blog-19.png" alt="19 - create data view" /></p>
<ul>
<li>You can now explore the documents in Discover using the data view:</li>
</ul>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0626e95bf462405f/6a7f1b4142a11770e795c32b/elastic-blog-20.png" alt="20 - data view" /></p>
<h3 id="reindexyourinternalrunbooksintotheaiassistantsknowledgebaseindexusingitssemanticsearchpipeline">Reindex your internal runbooks into the AI Assistant’s Knowledge Base Index, using it's semantic search pipeline</h3>
<p>Your Knowledge Base documents are stored in the indices <em>.kibana-observability-ai-assistant-kb-*</em>. To add your internal runbooks imported from GitHub to the KB, you just need to reindex the documents from the index you created in the previous step to the KB’s index. To add the semantic search capabilities to the documents in the KB, the reindex should also use the ELSER pipeline preconfigured for the KB, <em>.kibana-observability-ai-assistant-kb-ingest-pipeline</em>.</p>
<p>By creating a Data View with the KB index, you can explore the content in Discover.</p>
<p>You execute the query below in <strong>Management &gt; Dev Tools</strong> , making sure to replace the following, both on “_source” and “inline”:</p>
<ul>
<li>InternalDocsIndex : name of the index where your internal docs are stored</li>
<li>text_field : name of the field with the text of your internal docs</li>
<li>timestamp : name of the field of the timestamp in your internal docs</li>
<li>public : (true or false) if true, makes a document available to all users in the defined <a href="https://www.elastic.co/guide/en/kibana/current/xpack-spaces.html">Kibana Space</a> (if is defined) or in all spaces (if is not defined); if false, document will be restricted to the user indicated in</li>
<li>(optional) space : if defined, restricts the internal document to be available in a specific <a href="https://www.elastic.co/guide/en/kibana/current/xpack-spaces.html">Kibana Space</a></li>
<li>(optional) user.name : if defined, restricts the internal document to be available for a specific user</li>
<li>(optional) "query" filter to index only certain docs (see below)</li>
</ul>
<pre><code>POST _reindex
{
    "source": {
        "index": "&lt;InternalDocsIndex&gt;",
        "_source": [
            "&lt;text_field&gt;",
            "&lt;timestamp&gt;",
            "namespace",
            "is_correction",
            "public",
            "confidence"
        ]
    },
    "dest": {
        "index": ".kibana-observability-ai-assistant-kb-000001",
        "pipeline": ".kibana-observability-ai-assistant-kb-ingest-pipeline"
    },
    "script": {
        "inline": "ctx._source.text=ctx._source.remove(\"&lt;text_field&gt;\");ctx._source.namespace=\"&lt;space&gt;\";ctx._source.is_correction=false;ctx._source.public=&lt;public&gt;;ctx._source.confidence=\"high\";ctx._source['@timestamp']=ctx._source.remove(\"&lt;timestamp&gt;\");ctx._source['user.name'] = \"&lt;user.name&gt;\""
    }
}
</code></pre>
<p>You may want to specify the type of documents that you reindex in the KB — for example, you may only want to reindex Markdown documents (like Runbooks). You can add a “query” filter to the documents in the source. In the case of GitHub, runbooks are identified with the “type” field containing the string “file,” and you could add that to the reindex query like indicated below. To add also GitHub Issues, you can also include in the query “type” field containing the string “issues”:</p>
<pre><code>"source": {
        "index": "&lt;InternalDocsIndex&gt;",
        "_source": [
            "&lt;text_field&gt;",
            "&lt;timestamp&gt;",
            "namespace",
            "is_correction",
            "public",
            "confidence"
        ],
    "query": {
      "terms": {
        "type": ["file"]
      }
    }
</code></pre>
<p>Great! Now that the data is stored in your Knowledge Base, you can ask the Observability AI Assistant any questions about it:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta473e0043f5dbf04/6a7f1b442f00b25aabefef31/elastic-blog-21.png" alt="21 - new conversation" /></p>
<div>
    
</div>
<div>
    
</div>
<h2 id="conclusion">Conclusion</h2>
<p>In conclusion, leveraging internal Observability knowledge and adding it to the Elastic Knowledge Base can greatly enhance the capabilities of the AI Assistant. By manually inputting information or programmatically ingesting documents, SREs can create a central repository of knowledge accessible through the power of Elastic and LLMs. The AI Assistant can recall this information, assist with incidents, and provide tailored observability to specific contexts using Retrieval Augmented Generation. By following the steps outlined in this article, organizations can unlock the full potential of their Elastic AI Assistant.</p>
<p><a href="https://www.elastic.co/generative-ai/ai-assistant">Start enriching your Knowledge Base with the Elastic AI Assistant today</a> and empower your SRE team with the tools they need to excel. Follow the steps outlined in this article and take your incident management and alert remediation processes to the next level. Your journey toward a more efficient and effective SRE operation begins now.</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/sre-troubleshooting-ai-assistant-observability-runbooks</link>
    <guid isPermaLink="false">sre-troubleshooting-ai-assistant-observability-runbooks</guid>
    <category><![CDATA[Agentic Observability]]></category>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[Kubernetes]]></category>
    <category><![CDATA[AI]]></category>
    <category><![CDATA[LLM Observability]]></category>
    <dc:creator><![CDATA[Almudena Sanz Olivé,Katrin Freihofner,Tom Grabowski]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2d0f6fc2d38fa05b/6a7f1b47bd21987d717584c9/11-hand.jpg" length="0" type="image/jpeg"/>
    <pubDate>Wed, 08 Nov 2023 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[Elastic's contribution: Invokedynamic in the OpenTelemetry Java agent]]></title>
    <description><![CDATA[The instrumentation approach in OpenTelemetry's Java Agent comes with some limitations with respect to maintenance and testability. Elastic contributes an invokedynamic-based instrumentation approach that helps overcoming these limitations.]]></description>
    <content:encoded><![CDATA[<p>As the second largest and active Cloud Native Computing Foundation (CNCF) project, <a href="https://opentelemetry.io/">OpenTelemetry</a> is well on its way to becoming the ubiquitous, unified standard and framework for observability. OpenTelemetry owes this success to its comprehensive and feature-rich toolset that allows users to retrieve valuable observability data from their applications with low effort. The OpenTelemetry Java agent is one of the most mature and feature-rich components in OpenTelemetry’s ecosystem. It provides automatic instrumentation for JVM-based applications and comes with a broad coverage of auto-instrumentation modules for popular Java-frameworks and libraries.</p>
<p>The original instrumentation approach used in the OpenTelemetry Java agent left the maintenance and development of auto-instrumentation modules subject to some restrictions. As part of <a href="https://www.elastic.co/blog/transforming-observability-ai-assistant-otel-standardization-continuous-profiling-log-analytics">our reinforced commitment to OpenTelemetry</a>, Elastic® helps evolve and improve OpenTelemetry projects and components. <a href="https://www.elastic.co/blog/ecs-elastic-common-schema-otel-opentelemetry-announcement">Elastic’s contribution of the Elastic Common Schema</a> to OpenTelemetry was an important step for the open-source community. As another step in our commitment to OpenTelemetry, Elastic started contributing to the OpenTelemetry Java agent.</p>
<h2 id="elasticsinvokedynamicbasedinstrumentationapproach">Elastic’s invokedynamic-based instrumentation approach</h2>
<p>To overcome the above-mentioned limitations in developing and maintaining auto-instrumentation modules in the OpenTelemetry Java agent, Elastic started contributing its <a href="https://www.elastic.co/blog/embracing-invokedynamic-to-tame-class-loaders-in-java-agents"><strong>invokedynamic</strong></a><a href="https://www.elastic.co/blog/embracing-invokedynamic-to-tame-class-loaders-in-java-agents">-based instrumentation approach</a> to the OpenTelemetry Java agent in July 2023.</p>
<p>To explain the improvement, you should know that in Java, a common approach to do auto-instrumentation of applications is through utilizing Java agents that do bytecode instrumentation at runtime. <a href="https://bytebuddy.net/#/">Byte Buddy</a> is a popular and widespread utility that helps with bytecode instrumentation without the need to deal with Java’s bytecode directly. Instrumentation logic that collects observability data from the target application’s code lives in so-called <em>advice methods</em>. Byte Buddy provides different ways of hooking these advice methods into the target application’s methods:</p>
<ul>
<li><em>Advice inlining:</em> The advice method’s code is being copied into the instrumented target method.</li>
<li><em>Static advice dispatching:</em> The instrumented target method invokes static advice methods that need to be visible by the instrumented code.</li>
<li><em>Advice dispatching with</em>  <strong>invokedynamic</strong> __:_ The instrumented target method uses the JVM’s <strong>invokedynamic</strong> bytecode instruction to call advice methods that are isolated from the instrumented code.</li>
</ul>
<p>These different approaches are described in great detail in our related blog post on <a href="https://www.elastic.co/blog/embracing-invokedynamic-to-tame-class-loaders-in-java-agents">Elastic’s Java APM agent using invokedynamic</a>. In a nutshell, both approaches, <em>advice inlining</em> and <em>dispatching to static advice methods</em> come with some limitations with respect to writing and maintaining the advice code. So far, the OpenTelemetry Java agent has used <em>advice inlining</em> for its bytecode instrumentation. The resulting limitations on developing instrumentations are <a href="https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/v1.30.0/docs/contributing/writing-instrumentation-module.md#use-advice-classes-to-write-code-that-will-get-injected-to-the-instrumented-library-classes">documented in corresponding developer guidelines</a>. Among other things, the limitation of not being able to debug advice code is a painful restriction when developing and maintaining instrumentation code.</p>
<p>Elastic’s APM Java agent has been using the <strong>invokedynamic</strong> approach with its benefits for years — field-proven by thousands of customers. To help improve the OpenTelemetry Java agent, Elastic started contributing the <strong>invokedynamic</strong> approach with the goal to simplify and improve the development and maintainability of auto-instrumentation modules. The contribution proposal and the implementation outline is documented in more detail in <a href="https://github.com/open-telemetry/opentelemetry-java-instrumentation/issues/8999">this GitHub issue</a>.</p>
<p>With the new approach in place, Elastic will help migrate existing instrumentations so the OTel Java community can benefit from the <strong>invokedynamic</strong> -based instrumentation approach.</p>
<blockquote>
  <p>Elastic supports OTel natively, and has numerous capabilities to help you analyze your application with OTel. </p>
  <ul>
  <li><a href="https://www.elastic.co/blog/opentelemetry-observability">Native OpenTelemetry support in Elastic Observability</a></li>
  <li><a href="https://www.elastic.co/blog/best-practices-instrumenting-opentelemetry">Best Practices for instrumenting OpenTelemetry</a></li>
  <li><a href="https://www.elastic.co/blog/opentelemetry-observability">Independence with OpenTelemetry on Elastic</a></li>
  </ul>
  <p>Instrumenting with OpenTelemetry:</p>
  <ul>
  <li><a href="https://www.elastic.co/blog/getting-started-opentelemetry-instrumentation-sample-app">Elastiflix application</a>, a guide to instrument different languages with OpenTelemetry (this is the application the team built to highlight <em>all</em> the languages below)</li>
  <li>Python: <a href="https://www.elastic.co/blog/auto-instrumentation-of-python-applications-opentelemetry">Auto-instrumentation</a>, <a href="https://www.elastic.co/blog/manual-instrumentation-of-python-applications-opentelemetry">Manual instrumentation</a></li>
  <li>Java: <a href="https://www.elastic.co/blog/auto-instrumentation-of-java-applications-opentelemetry">Auto-instrumentation</a>, <a href="https://www.elastic.co/blog/manual-instrumentation-of-java-applications-opentelemetry">Manual instrumentation </a></li>
  <li>Node.js: <a href="https://www.elastic.co/blog/auto-instrument-nodejs-applications-opentelemetry">Auto-instrumentation</a>, <a href="https://www.elastic.co/blog/manual-instrumentation-of-nodejs-applications-opentelemetry">Manual instrumentation</a></li>
  <li>.NET: <a href="https://www.elastic.co/blog/auto-instrumentation-of-net-applications-opentelemetry">Auto-instrumentation</a>, <a href="https://www.elastic.co/blog/manual-instrumentation-of-net-applications-opentelemetry">Manual instrumentation</a><br />
  Go: <a href="https://elastic.co/blog/manual-instrumentation-of-go-applications-opentelemetry">Manual instrumentation</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/invokedynamic-opentelemetry-java-agent</link>
    <guid isPermaLink="false">invokedynamic-opentelemetry-java-agent</guid>
    <category><![CDATA[OpenTelemetry]]></category>
    <dc:creator><![CDATA[Alexander Wert,Jack Shirazi,Jonas Kunz,Sylvain Juge]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4923c41bb46647d7/6a85c99b18249cfe1d18f79f/24-crystals.jpeg" length="0" type="image/jpeg"/>
    <pubDate>Thu, 19 Oct 2023 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Native OpenTelemetry support in Elastic Observability]]></title>
    <description><![CDATA[Elastic offers native support for OpenTelemetry by allowing for direct ingest of OpenTelemetry traces, metrics, and logs without conversion, and applying any Elastic feature against OTel data without degradation in capabilities.]]></description>
    <content:encoded><![CDATA[<p>NOTE: Since writing this blog, new OTel data ingest configurations are now available in Elastic. See recent <a href="https://www.elastic.co/observability-labs/blog/elastic-opentelemetry-otel-operator">blog</a></p>
<p>OpenTelemetry is more than just becoming the open ingestion standard for observability. As one of the major Cloud Native Computing Foundation (CNCF) projects, with as many commits as Kubernetes, it is gaining support from major ISVs and cloud providers delivering support for the framework. Many global companies from finance, insurance, tech, and other industries are starting to standardize on OpenTelemetry. With OpenTelemetry, DevOps teams have a consistent approach to collecting and ingesting telemetry data providing a de-facto standard for observability.</p>
<p>Elastic<sup>®</sup> is strategically standardizing on OpenTelemetry for the main data collection architecture for observability and security. Additionally, Elastic is making a commitment to help OpenTelemetry become the best de facto data collection infrastructure for the observability ecosystem. Elastic is deepening its relationship with OpenTelemetry beyond the recent contribution of Elastic Common Schema (ECS) to OpenTelemetry (OTel).</p>
<p>Today, Elastic supports OpenTelemetry natively, since Elastic 7.14, by being able to directly ingest OpenTelemetry protocol (OTLP) based traces, metrics, and logs.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt68f6108956523f81/6a7f0e5ffc63ab7fae64cd0f/elastic-blog-1-otel-config-options.png" alt="otel configuration options" /></p>
<p>In this blog, we’ll review the current OpenTelemetry support provided by Elastic, which includes the following:</p>
<ul>
<li><a href="https://www.elastic.co/observability-labs/blog/native-opentelemetry-support-in-elastic-observability#ingesting-opentelemetry-into-elastic"><strong>Easy ingest of distributed tracing and metrics</strong></a> for applications configured with OpenTelemetry agents for Python, NodeJS, Java, Go, and .NET</li>
<li><a href="https://www.elastic.co/observability-labs/blog/native-opentelemetry-support-in-elastic-observability#opentelemetry-logs-in-elastic"><strong>OpenTelemetry logs instrumentation and ingest</strong></a> using various configurations</li>
<li><a href="https://www.elastic.co/observability-labs/blog/native-opentelemetry-support-in-elastic-observability#opentelemetry-is-elastics-preferred-schema"><strong>Open semantic conventions</strong></a> for logs and more through ECS, which is not part of OpenTelemetry</li>
<li><a href="https://www.elastic.co/observability-labs/blog/native-opentelemetry-support-in-elastic-observability#elastic-observability-apm-and-machine-learning-capabilities"><strong>Machine learning based AIOps capabilities</strong></a>, such as latency correlations, failure correlations, anomaly detection, log spike analysis, predictive pattern analysis, Elastic AI Assistant support, and more, all apply to native OTLP telemetry.</li>
<li><a href="https://www.elastic.co/observability-labs/blog/native-opentelemetry-support-in-elastic-observability#elastic-allows-you-to-migrate-to-otel-on-your-schedule"><strong>Migrate applications to OpenTelemetry at your own speed</strong></a>. Elastic’s APM capabilities all work seamlessly even with a mix of services using OpenTelemetry and/or Elastic APM agents. You can even combine OpenTelemetry instrumentation with Elastic Agent.</li>
<li><a href="https://www.elastic.co/observability-labs/blog/native-opentelemetry-support-in-elastic-observability#integrated-kubernetes-and-opentelemetry-views-in-elastic"><strong>Integrated views and analysis with Kubernetes clusters</strong></a>, which most OpenTelemetry applications are running on. Elastic can highlight specific pods and containers related to each service when analyzing issues for applications based on OpenTelemetry.</li>
</ul>
<h2 id="ingestingopentelemetryintoelastic">Ingesting OpenTelemetry into Elastic</h2>
<p>If you’re interested in seeing how simple it is to ingest OpenTelemetry traces and metrics into Elastic, follow the steps outlined in this blog.</p>
<p>Let’s outline what Elastic provides for ingesting OpenTelemetry data. Here are all your options:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta805e805b620c7c6/6a7f0e61c2cc0960a8249626/elastic-blog-2-flowchart.png" alt="flowchart" /></p>
<h3 id="usingtheopentelemetrycollector">Using the OpenTelemetry Collector</h3>
<p>When using the OpenTelemetry Collector, which is the most common configuration option, you simply have to add two key variables.</p>
<p>The instructions utilize a specific opentelemetry-collector configuration for Elastic. Essentially, the Elastic <a href="https://github.com/elastic/opentelemetry-demo/blob/main/kubernetes/elastic-helm/values.yaml">values.yaml</a> file specified in the elastic/opentelemetry-demo configure the opentelemetry-collector to point to the Elastic APM Server using two main values:</p>
<p>OTEL_EXPORTER_OTLP_ENDPOINT is Elastic’s APM Server<br />
OTEL_EXPORTER_OTLP_HEADERS Elastic Authorization</p>
<p>These two values can be found in the OpenTelemetry setup instructions under the APM integration instructions (Integrations-&gt;APM) in your Elastic Cloud.</p>
<h3 id="nativeopentelemetryagentsembeddedincode">Native OpenTelemetry agents embedded in code</h3>
<p>If you are thinking of using OpenTelemetry libraries in your code, you can simply point the service to Elastic’s APM server, because it supports native OLTP protocol. No special Elastic conversion is needed.</p>
<p>To demonstrate this effectively and provide some education on how to use OpenTelemetry, we have two applications you can use to learn from:</p>
<ul>
<li><a href="https://github.com/elastic/opentelemetry-demo">Elastic’s version of OpenTelemetry demo</a>: As with all the other observability vendors, we have our own forked version of the OpenTelemetry demo.</li>
<li><a href="https://github.com/elastic/workshops-instruqt/tree/main/Elastiflix">Elastiflix:</a> This demo application is an example to help you learn how to instrument on various languages and telemetry signals.</li>
</ul>
<p>Check out our blogs on using the Elastiflix application and instrumenting with OpenTelemetry:</p>
<ul>
<li><a href="https://www.elastic.co/blog/getting-started-opentelemetry-instrumentation-sample-app">Elastiflix application</a>, a guide to instrument different languages with OpenTelemetry</li>
<li>Python: <a href="https://www.elastic.co/blog/auto-instrumentation-of-python-applications-opentelemetry">Auto-instrumentation</a>, <a href="https://www.elastic.co/blog/manual-instrumentation-of-python-applications-opentelemetry">Manual-instrumentation</a></li>
<li>Java: <a href="https://www.elastic.co/blog/auto-instrumentation-of-java-applications-opentelemetry">Auto-instrumentation</a>, <a href="https://www.elastic.co/blog/manual-instrumentation-of-java-applications-opentelemetry">Manual-instrumentation</a></li>
<li>Node.js: <a href="https://www.elastic.co/blog/auto-instrument-nodejs-applications-opentelemetry">Auto-instrumentation</a>, <a href="https://www.elastic.co/blog/manual-instrumentation-of-nodejs-applications-opentelemetry">Manual-instrumentation</a></li>
<li>.NET: <a href="https://www.elastic.co/blog/auto-instrumentation-of-net-applications-opentelemetry">Auto-instrumentation</a>, <a href="https://www.elastic.co/blog/manual-instrumentation-of-net-applications-opentelemetry">Manual-instrumentation</a></li>
</ul>
<p>We have created YouTube videos on these topics as well:</p>
<ul>
<li><a href="https://youtu.be/wMXMRsjFg-8?feature=shared">How to Manually Instrument Java with OpenTelemetry (Part 1)</a></li>
<li><a href="https://youtu.be/PX7s6RRLGaU?feature=shared">How to Manually Instrument Java with OpenTelemetry (Part 2)</a></li>
<li><a href="https://youtu.be/hXTlV_RnELc?feature=shared">Custom Java Instrumentation with OpenTelemetry</a></li>
<li><a href="https://youtu.be/E8g9u_uOFO4?feature=shared">Elastic APM - Automatic .NET Instrumentation with OpenTelemetry</a></li>
<li><a href="https://youtu.be/7J9M2JsHwRE?feature=shared">How to Manually Instrument .NET Applications with OpenTelemetry</a></li>
</ul>
<p>Given Elastic and OpenTelemetry’s vast user base, these provide a rich source of education for anyone trying to learn the intricacies of instrumenting with OpenTelemetry.</p>
<h3 id="elasticagentssupportingopentelemetry">Elastic Agents supporting OpenTelemetry</h3>
<p>If you’ve already implemented OpenTelemetry, you can still use them with OpenTelemetry. <a href="https://www.elastic.co/blog/opentelemetry-instrumentation-elastic-apm-agent-features">Elastic APM agents today are able to ship OpenTelemetry</a> spans as part of a trace. This means that if you have any component in your application that emits an OpenTelemetry span, it’ll be part of the trace the Elastic APM agent captures.</p>
<h2 id="opentelemetrylogsinelastic">OpenTelemetry logs in Elastic</h2>
<p>If you look at OpenTelemetry documentation, you will see that a lot of language libraries are still in experimental or not implemented yet state. Java is in stable state, per the documentation. Depending on your service’s language, 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>In a previous blog, we discussed <a href="https://www.elastic.co/blog/3-models-logging-opentelemetry-elastic">3 different configurations to properly get logging data into Elastic for Java</a>. The blog explores the current state of the art of OpenTelemetry logging and provides 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 slf4j key-value pairs (“structured logging”)</li>
<li>Automatic attachment of metadata carried between services via OTel baggage</li>
<li>Use of an Elastic Observability backend</li>
<li>Consistent data fidelity in Elastic regardless of the approach taken</li>
</ul>
<p>Three models, which are covered in the blog, currently exist for getting your application or service logs to Elastic with correlation to OTel tracing and baggage:</p>
<ul>
<li>Output logs from your service (alongside traces and metrics) using an embedded OpenTelemetry Instrumentation library to Elastic via the OTLP protocol</li>
<li>Write logs from your service to a file scrapped by the OpenTelemetry Collector, which then forwards to Elastic via the OTLP protocol</li>
<li>Write logs from your service to a file scrapped by Elastic Agent (or Filebeat), which then forwards to Elastic via an Elastic-defined protocol</li>
</ul>
<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="opentelemetryiselasticspreferredschema">OpenTelemetry is Elastic’s preferred schema</h2>
<p>Elastic recently contributed the <a href="https://opentelemetry.io/blog/2023/ecs-otel-semconv-convergence/">Elastic Common Schema (ECS) to the OpenTelemetry (OTel)</a> project, enabling a unified data specification for security and observability data within the OTel Semantic Conventions framework.</p>
<p>ECS, an open source specification, was developed with support from the Elastic user community to define a common set of fields to be used when storing event data in Elasticsearch<sup>®</sup>. ECS helps reduce management and storage costs stemming from data duplication, improving operational efficiency.</p>
<p>Similarly, OTel’s Semantic Conventions (SemConv) also specify common names for various kinds of operations and data. The benefit of using OTel SemConv is in following a common naming scheme that can be standardized across a codebase, libraries, and platforms for OTel users.</p>
<p>The merging of ECS and OTel SemConv will help advance OTel’s adoption and the continued evolution and convergence of observability and security domains.</p>
<h2 id="elasticobservabilityapmandmachinelearningcapabilities">Elastic Observability APM and machine learning capabilities</h2>
<p>All of Elastic Observability’s APM capabilities are available with OTel data (read more on this in our blog, <a href="https://www.elastic.co/blog/opentelemetry-observability">Independence with OpenTelemetry</a>):</p>
<ul>
<li>Service maps</li>
<li>Service details (latency, throughput, failed transactions)</li>
<li>Dependencies between services</li>
<li>Transactions (traces)</li>
<li>ML correlations (specifically for latency)</li>
<li>Service logs</li>
</ul>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt64b1360c6f4f835b/6a7f0e652f00b28c7befebf4/elastic-blog-3-services.png" alt="services" /></p>
<p>In addition to Elastic’s APM and unified view of the telemetry data, you will now be able to use Elastic’s powerful machine learning capabilities to reduce the analysis, and alerting to help reduce MTTR. Here are some of the ML based AIOps capabilities we have:</p>
<ul>
<li><a href="https://www.elastic.co/blog/reduce-mttd-ml-machine-learning-observability"><strong>Anomaly detection:</strong></a> 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 OpenTelemetry data — learning trends, periodicity, and more.</li>
<li><a href="https://www.elastic.co/blog/reduce-mttd-ml-machine-learning-observability"><strong>Log categorization:</strong></a> Elastic also identifies patterns in your OpenTelemetry log events quickly, so that you can take action quicker.</li>
<li><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.</li>
<li><a href="https://www.elastic.co/blog/observability-logs-machine-learning-aiops"><strong>Log spike detector</strong></a> helps identify reasons for increases in OpenTelemetry log rates. It makes it easy to find and investigate causes of unusual spikes by using the analysis workflow view.</li>
<li><a href="https://www.elastic.co/blog/observability-logs-machine-learning-aiops"><strong>Log pattern analysis</strong></a> helps you find patterns in unstructured log messages and makes it easier to examine your data.</li>
</ul>
<h2 id="elasticallowsyoutomigratetootelonyourschedule">Elastic allows you to migrate to OTel on your schedule</h2>
<p>Although OpenTelemetry supports many programming languages, the <a href="https://opentelemetry.io/docs/instrumentation/">status of its major functional components</a> — metrics, traces, and logs — are still at various stages. Thus migrating applications written in Java, Python, and JavaScript are good choices to start with as their metrics, traces, and logs (for Java) are stable.</p>
<p>For the other languages that are not yet supported, you can easily instrument those using Elastic Agents, therefore running your <a href="https://www.elastic.co/observability">full stack observability platform</a> in mixed mode (Elastic agents with OpenTelemetry agents).</p>
<p>Here is a simple example:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltbff34e303f9e3330/6a7f0e67ea068d2474f09f1c/elastic-blog-4-services2.png" alt="services 2" /></p>
<p>The above shows a simple variation of our standard Elastic Agent application with one service flipped to OTel — the newsletter-otel service. But we can easily and as needed convert each of these services to OTel as development resources allow.</p>
<p>Hence you can migrate what you need to OpenTelemetry with Elastic as specific languages reach a stable state, and you can then continue your migration to OpenTelemetry agents.</p>
<h2 id="integratedkubernetesandopentelemetryviewsinelastic">Integrated Kubernetes and OpenTelemetry views in Elastic</h2>
<p>Elastic manages your Kubernetes cluster using the Elastic Agent, and you can use it on your Kubernetes cluster where your OpenTelemetry application is running. Hence you can not only use OpenTelemetry for your application, but Elastic can also monitor the corresponding Kubernetes cluster.</p>
<p>There are two configurations for Kubernetes:</p>
<p><strong>1. Simply deploying the Elastic Agent daemon set on the kubernetes cluster.</strong> We outline this out in the article entitled <a href="https://www.elastic.co/blog/kubernetes-cluster-metrics-logs-monitoring">Managing your Kubernetes cluster with Elastic Observability</a>. This would also push just the Kubernetes metrics and logs to Elastic.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0f91de133133262e/6a7f0e6a3ce8e2abc1cf540f/elastic-blog-5-cloud-nodes.png" alt="elastic cloud nodes" /></p>
<p><strong>2. Deploying the Elastic Agent with not only the Kubernetes Daemon set, but also Elastic’s APM integration, the Defend (Security) integration, and Network Packet capture integration</strong> to provide more comprehensive Kubernetes cluster observability. We outline this configuration in the following article <a href="https://www.elastic.co/blog/implementing-kubernetes-observability-security-opentelemetry">Modern observability and security on Kubernetes with Elastic and OpenTelemetry</a>.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd34ef1f6e71a7446/6a7f0e6dea068d609ff09f20/elastic-blog-6-flowhcart.png" alt="flowchart" /></p>
<p>Both <a href="https://www.elastic.co/observability/opentelemetry">OpenTelemetry visualization</a> examples use the OpenTelemetry demo, and in Elastic, we tie the Kubernetes information with the application to provide you an ability to see Kubernetes information from your traces in APM. This provides a more integrated approach when troubleshooting.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0b4b8776d336437e/6a7f0e706c6eac80c7f141a9/elastic-blog-7-pod-deets.png" alt="pod details" /></p>
<h2 id="summary">Summary</h2>
<p>In essence, Elastic's commitment goes beyond mere support for OpenTelemetry. We are dedicated to ensuring our customers not only adopt OpenTelemetry but thrive with it. Through our solutions, expertise, and resources, we aim to elevate the observability journey for every business, turning data into actionable insights that drive growth and innovation.</p>
<blockquote>
  <p>Developer resources:</p>
  <ul>
  <li><a href="https://www.elastic.co/blog/getting-started-opentelemetry-instrumentation-sample-app">Elastiflix application</a>, a guide to instrument different languages with OpenTelemetry</li>
  <li>Python: <a href="https://www.elastic.co/blog/auto-instrumentation-of-python-applications-opentelemetry">Auto-instrumentation</a>, <a href="https://www.elastic.co/blog/manual-instrumentation-of-python-applications-opentelemetry">Manual-instrumentation</a></li>
  <li>Java: <a href="https://www.elastic.co/blog/auto-instrumentation-of-java-applications-opentelemetry">Auto-instrumentation</a>, <a href="https://www.elastic.co/blog/manual-instrumentation-of-java-applications-opentelemetry">Manual-instrumentation</a></li>
  <li>Node.js: <a href="https://www.elastic.co/blog/auto-instrument-nodejs-applications-opentelemetry">Auto-instrumentation</a>, <a href="https://www.elastic.co/blog/manual-instrumentation-of-nodejs-applications-opentelemetry">Manual-instrumentation</a></li>
  <li>.NET: <a href="https://www.elastic.co/blog/auto-instrumentation-of-net-applications-opentelemetry">Auto-instrumentation</a>, <a href="https://www.elastic.co/blog/manual-instrumentation-of-net-applications-opentelemetry">Manual-instrumentation</a></li>
  <li>Go: <a href="https://elastic.co/blog/manual-instrumentation-of-go-applications-opentelemetry">Manual-instrumentation</a></li>
  <li><a href="https://www.elastic.co/blog/best-practices-instrumenting-opentelemetry">Best practices for OpenTelemetry</a></li>
  </ul>
  <p>General configuration and use case resources:</p>
  <ul>
  <li><a href="https://www.elastic.co/blog/opentelemetry-observability">Independence with OpenTelemetry on Elastic</a></li>
  <li><a href="https://www.elastic.co/blog/implementing-kubernetes-observability-security-opentelemetry">Modern observability and security on Kubernetes with Elastic and OpenTelemetry</a></li>
  <li><a href="https://www.elastic.co/blog/3-models-logging-opentelemetry-elastic">3 models for logging with OpenTelemetry and Elastic</a></li>
  <li><a href="https://www.elastic.co/blog/adding-free-and-open-elastic-apm-as-part-of-your-elastic-observability-deployment">Adding free and open Elastic APM as part of your Elastic Observability deployment</a></li>
  <li><a href="https://www.elastic.co/blog/custom-metrics-app-code-java-agent-plugin">Capturing custom metrics through OpenTelemetry API in code with Elastic</a></li>
  <li><a href="https://www.elastic.co/virtual-events/future-proof-your-observability-platform-with-opentelemetry-and-elastic">Future-proof your observability platform with OpenTelemetry and Elastic</a></li>
  <li><a href="https://www.elastic.co/blog/kubernetes-k8s-observability-elasticsearch-cncf">Elastic Observability: Built for open technologies like Kubernetes, OpenTelemetry, Prometheus, Istio, and more</a></li>
  </ul>
</blockquote>
<p>Don’t have an Elastic Cloud account yet? <a href="https://cloud.elastic.co/registration">Sign up for Elastic Cloud</a> and try out the instrumentation capabilities that I discussed above. I would be interested in getting your feedback about your experience in gaining visibility into your application stack 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/native-opentelemetry-support-in-elastic-observability</link>
    <guid isPermaLink="false">native-opentelemetry-support-in-elastic-observability</guid>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[Metrics]]></category>
    <category><![CDATA[APM]]></category>
    <category><![CDATA[Kubernetes]]></category>
    <dc:creator><![CDATA[Bahubali Shetti]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2700a8e353c3fb55/6a7f0e7342a117e08695bf4c/ecs-otel-announcement-2.jpg" length="0" type="image/jpeg"/>
    <pubDate>Wed, 13 Sep 2023 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Manual instrumentation of Go applications with OpenTelemetry]]></title>
    <description><![CDATA[In this blog post, we will show you how to manually instrument Go applications using OpenTelemetry. We will explore how to use the proper OpenTelemetry Go packages and, in particular, work on instrumenting tracing in a Go application.]]></description>
    <content:encoded><![CDATA[<p>DevOps and SRE teams are transforming the process of software development. While DevOps engineers focus on efficient software applications and service delivery, SRE teams are key to ensuring reliability, scalability, and performance. These teams must rely on a full-stack observability solution that allows them to manage and monitor systems and ensure issues are resolved before they impact the business.</p>
<p>Observability across the entire stack of modern distributed applications requires data collection, processing, and correlation often in the form of dashboards. Ingesting all system data requires installing agents across stacks, frameworks, and providers — a process that can be challenging and time-consuming for teams who have to deal with version changes, compatibility issues, and proprietary code that doesn't scale as systems change.</p>
<p>Thanks to <a href="http://opentelemetry.io">OpenTelemetry</a> (OTel), DevOps and SRE teams now have a standard way to collect and send data that doesn't rely on proprietary code and have a large support community reducing vendor lock-in.</p>
<p>In this blog post, we will show you how to manually instrument Go applications using OpenTelemetry. This approach is slightly more complex than using auto-instrumentation</p>
<p>In a <a href="https://www.elastic.co/blog/opentelemetry-observability">previous blog</a>, we also reviewed how to use the OpenTelemetry demo and connect it to Elastic<sup>®</sup>, as well as some of Elastic’s capabilities with OpenTelemetry. In this blog, we will use <a href="https://github.com/elastic/observability-examples">an alternative demo application</a>, which helps highlight manual instrumentation in a simple way.</p>
<p>Finally, we will discuss how Elastic supports mixed-mode applications, which run with Elastic and OpenTelemetry agents. The beauty of this is that there is <strong>no need for the otel-collector</strong>! This setup enables you to slowly and easily migrate an application to OTel with Elastic according to a timeline that best fits your business.</p>
<h2 id="applicationprerequisitesandconfig">Application, prerequisites, and config</h2>
<p>The application that we use for this blog is called <a href="https://github.com/elastic/observability-examples">Elastiflix</a>, a movie streaming application. It consists of several micro-services written in .NET, NodeJS, Go, and Python.</p>
<p>Before we instrument our sample application, we will first need to understand how Elastic can receive the telemetry data.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt13a3ccd8116bdc07/6a85ccb1f5f1a0cd052ec93b/GO-flowhcart.png" alt="Elastic configuration options for OpenTelemetry" /></p>
<p>All of Elastic Observability’s APM capabilities are available with OTel data. Some of these include:</p>
<ul>
<li>Service maps</li>
<li>Service details (latency, throughput, failed transactions)</li>
<li>Dependencies between services, distributed tracing</li>
<li>Transactions (traces)</li>
<li>Machine learning (ML) correlations</li>
<li>Log correlation</li>
</ul>
<p>In addition to Elastic’s APM and a unified view of the telemetry data, you will also be able to use Elastic’s powerful machine learning capabilities to reduce the analysis, and alerting to help reduce MTTR.</p>
<h2 id="prerequisites">Prerequisites</h2>
<ul>
<li>An Elastic Cloud account — <a href="https://cloud.elastic.co/">sign up now</a></li>
<li>A clone of the <a href="https://github.com/elastic/observability-examples">Elastiflix demo application</a>, or your own Go application</li>
<li>Basic understanding of Docker — potentially install <a href="https://www.docker.com/products/docker-desktop/">Docker Desktop</a></li>
<li>Basic understanding of Go</li>
</ul>
<h2 id="viewtheexamplesourcecode">View the example source code</h2>
<p>The full source code including the Dockerfile used in this blog can be found on <a href="https://github.com/elastic/observability-examples/tree/main/Elastiflix/go-favorite-otel-manual">GitHub</a>. The repository also contains the <a href="https://github.com/elastic/observability-examples/tree/main/Elastiflix/go-favorite">same application without instrumentation</a>. This allows you to compare each file and see the differences.</p>
<p>Before we begin, let’s look at the non-instrumented code first.</p>
<p>This is our simple go application that can receive a GET request. Note that the code shown here is a slightly abbreviated version.</p>
<pre><code>package main

import (
    "log"
    "net/http"
    "os"
    "time"

    "github.com/go-redis/redis/v8"

    "github.com/sirupsen/logrus"

    "github.com/gin-gonic/gin"
    "strconv"
    "math/rand"
)

var logger = &amp;logrus.Logger{
    Out:   os.Stderr,
    Hooks: make(logrus.LevelHooks),
    Level: logrus.InfoLevel,
    Formatter: &amp;logrus.JSONFormatter{
        FieldMap: logrus.FieldMap{
            logrus.FieldKeyTime:  "@timestamp",
            logrus.FieldKeyLevel: "log.level",
            logrus.FieldKeyMsg:   "message",
            logrus.FieldKeyFunc:  "function.name", // non-ECS
        },
        TimestampFormat: time.RFC3339Nano,
    },
}

func main() {
    delayTime,  := strconv.Atoi(os.Getenv("TOGGLE_SERVICE_DELAY"))

    redisHost := os.Getenv("REDIS_HOST")
    if redisHost == "" {
        redisHost = "localhost"
    }

    redisPort := os.Getenv("REDIS_PORT")
    if redisPort == "" {
        redisPort = "6379"
    }

    applicationPort := os.Getenv("APPLICATION_PORT")
    if applicationPort == "" {
        applicationPort = "5000"
    }

    // Initialize Redis client
    rdb := redis.NewClient(&amp;redis.Options{
        Addr:     redisHost + ":" + redisPort,
        Password: "",
        DB:       0,
    })

    // Initialize router
    r := gin.New()
    r.Use(logrusMiddleware)

    r.GET("/favorites", func(c *gin.Context) {
        // artificial sleep for delayTime
        time.Sleep(time.Duration(delayTime) * time.Millisecond)

        userID := c.Query("user_id")

        contextLogger(c).Infof("Getting favorites for user %q", userID)

        favorites, err := rdb.SMembers(c.Request.Context(), userID).Result()
        if err != nil {
            contextLogger(c).Error("Failed to get favorites for user %q", userID)
            c.String(http.StatusInternalServerError, "Failed to get favorites")
            return
        }

        contextLogger(c).Infof("User %q has favorites %q", userID, favorites)

        c.JSON(http.StatusOK, gin.H{
            "favorites": favorites,
        })
    })

    // Start server
    logger.Infof("App startup")
    log.Fatal(http.ListenAndServe(":"+applicationPort, r))
    logger.Infof("App stopped")
}
</code></pre>
<h2 id="stepbystepguide">Step-by-step guide</h2>
<h3 id="step0logintoyourelasticcloudaccount">Step 0. Log in to your Elastic Cloud account</h3>
<p>This blog assumes you have an Elastic Cloud account — if not, follow the <a href="https://cloud.elastic.co/registration?elektra=en-cloud-page">instructions to get started on Elastic Cloud</a>.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdec739045c430140/6a85ccb527c5cd10885f742e/elastic-blog-4-free-trial.png" alt="free trial" /></p>
<h3 id="step1installandinitializeopentelemetry">Step 1. Install and initialize OpenTelemetry</h3>
<p>As a first step, we’ll need to add some additional packages to our application.</p>
<pre><code>import (
      "github.com/go-redis/redis/extra/redisotel/v8"
      "go.opentelemetry.io/otel"
      "go.opentelemetry.io/otel/attribute"
      "go.opentelemetry.io/otel/exporters/otlp/otlptrace"
    "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"

    "go.opentelemetry.io/otel/propagation"

    "google.golang.org/grpc/credentials"
    "crypto/tls"

      sdktrace "go.opentelemetry.io/otel/sdk/trace"

    "go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin"

    "go.opentelemetry.io/otel/trace"
    "go.opentelemetry.io/otel/codes"
)
</code></pre>
<p>This code imports necessary OpenTelemetry packages, including those for tracing, exporting, and instrumenting specific libraries like Redis.</p>
<p>Next we read the "OTEL_EXPORTER_OTLP_ENDPOINT" variable and initialize the exporter.</p>
<pre><code>var (
    collectorURL = os.Getenv("OTEL_EXPORTER_OTLP_ENDPOINT")
)
var tracer trace.Tracer


func initTracer() func(context.Context) error {
    tracer = otel.Tracer("go-favorite-otel-manual")

    // remove https:// from the collector URL if it exists
    collectorURL = strings.Replace(collectorURL, "https://", "", 1)
    secretToken := os.Getenv("ELASTIC_APM_SECRET_TOKEN")
    if secretToken == "" {
        log.Fatal("ELASTIC_APM_SECRET_TOKEN is required")
    }

    secureOption := otlptracegrpc.WithInsecure()
    exporter, err := otlptrace.New(
        context.Background(),
        otlptracegrpc.NewClient(
            secureOption,
            otlptracegrpc.WithEndpoint(collectorURL),
            otlptracegrpc.WithHeaders(map[string]string{
                "Authorization": "Bearer " + secretToken,
            }),
            otlptracegrpc.WithTLSCredentials(credentials.NewTLS(&amp;tls.Config{})),
        ),
    )

    if err != nil {
        log.Fatal(err)
    }

    otel.SetTracerProvider(
        sdktrace.NewTracerProvider(
            sdktrace.WithSampler(sdktrace.AlwaysSample()),
            sdktrace.WithBatcher(exporter),
        ),
    )
    otel.SetTextMapPropagator(
        propagation.NewCompositeTextMapPropagator(
            propagation.Baggage{},
            propagation.TraceContext{},
        ),
    )
    return exporter.Shutdown
}
</code></pre>
<p>For instrumenting connections to Redis, we will add a tracing hook to it, and in order to instrument Gin, we will add the OTel middleware. This will automatically capture all interactions with our application, since Gin will be fully instrumented. In addition, all outgoing connections to Redis will also be instrumented.</p>
<pre><code>// Initialize Redis client
    rdb := redis.NewClient(&amp;redis.Options{
        Addr:     redisHost + ":" + redisPort,
        Password: "",
        DB:       0,
    })
    rdb.AddHook(redisotel.NewTracingHook())
    // Initialize router
    r := gin.New()
    r.Use(logrusMiddleware)
    r.Use(otelgin.Middleware("go-favorite-otel-manual"))
</code></pre>
<p><strong>Adding custom spans</strong><br />
Now that we have everything added and initialized, we can add custom spans.</p>
<p>If we want to have additional instrumentation for a part of our app, we simply start a custom span and then defer ending the span.</p>
<pre><code>// start otel span
ctx := c.Request.Context()
ctx, span := tracer.Start(ctx, "add_favorite_movies")
defer span.End()
</code></pre>
<p>For comparison, this is the instrumented code of our sample application. You can find the full source code in <a href="https://github.com/elastic/observability-examples/tree/main/Elastiflix/go-favorite-otel-manual">GitHub</a>.</p>
<pre><code>package main

import (
    "log"
    "net/http"
    "os"
    "time"
    "context"

    "github.com/go-redis/redis/v8"
    "github.com/go-redis/redis/extra/redisotel/v8"


    "github.com/sirupsen/logrus"

    "github.com/gin-gonic/gin"

  "go.opentelemetry.io/otel"
  "go.opentelemetry.io/otel/attribute"
  "go.opentelemetry.io/otel/exporters/otlp/otlptrace"
  "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"

    "go.opentelemetry.io/otel/propagation"

    "google.golang.org/grpc/credentials"
    "crypto/tls"

  sdktrace "go.opentelemetry.io/otel/sdk/trace"

    "go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin"

    "go.opentelemetry.io/otel/trace"

    "strings"
    "strconv"
    "math/rand"
    "go.opentelemetry.io/otel/codes"

)

var tracer trace.Tracer

func initTracer() func(context.Context) error {
    tracer = otel.Tracer("go-favorite-otel-manual")

    collectorURL = strings.Replace(collectorURL, "https://", "", 1)

    secureOption := otlptracegrpc.WithInsecure()

    // split otlpHeaders by comma and convert to map
    headers := make(map[string]string)
    for _, header := range strings.Split(otlpHeaders, ",") {
        headerParts := strings.Split(header, "=")

        if len(headerParts) == 2 {
            headers[headerParts[0]] = headerParts[1]
        }
    }

    exporter, err := otlptrace.New(
        context.Background(),
        otlptracegrpc.NewClient(
            secureOption,
            otlptracegrpc.WithEndpoint(collectorURL),
            otlptracegrpc.WithHeaders(headers),
            otlptracegrpc.WithTLSCredentials(credentials.NewTLS(&amp;tls.Config{})),
        ),
    )

    if err != nil {
        log.Fatal(err)
    }

    otel.SetTracerProvider(
        sdktrace.NewTracerProvider(
            sdktrace.WithSampler(sdktrace.AlwaysSample()),
            sdktrace.WithBatcher(exporter),
            //sdktrace.WithResource(resources),
        ),
    )
    otel.SetTextMapPropagator(
        propagation.NewCompositeTextMapPropagator(
            propagation.Baggage{},
            propagation.TraceContext{},
        ),
    )
    return exporter.Shutdown
}

var (
  collectorURL = os.Getenv("OTEL_EXPORTER_OTLP_ENDPOINT")
    otlpHeaders = os.Getenv("OTEL_EXPORTER_OTLP_HEADERS")
)


var logger = &amp;logrus.Logger{
    Out:   os.Stderr,
    Hooks: make(logrus.LevelHooks),
    Level: logrus.InfoLevel,
    Formatter: &amp;logrus.JSONFormatter{
        FieldMap: logrus.FieldMap{
            logrus.FieldKeyTime:  "@timestamp",
            logrus.FieldKeyLevel: "log.level",
            logrus.FieldKeyMsg:   "message",
            logrus.FieldKeyFunc:  "function.name", // non-ECS
        },
        TimestampFormat: time.RFC3339Nano,
    },
}

func main() {
    cleanup := initTracer()
  defer cleanup(context.Background())

    redisHost := os.Getenv("REDIS_HOST")
    if redisHost == "" {
        redisHost = "localhost"
    }

    redisPort := os.Getenv("REDIS_PORT")
    if redisPort == "" {
        redisPort = "6379"
    }

    applicationPort := os.Getenv("APPLICATION_PORT")
    if applicationPort == "" {
        applicationPort = "5000"
    }

    // Initialize Redis client
    rdb := redis.NewClient(&amp;redis.Options{
        Addr:     redisHost + ":" + redisPort,
        Password: "",
        DB:       0,
    })
    rdb.AddHook(redisotel.NewTracingHook())


    // Initialize router
    r := gin.New()
    r.Use(logrusMiddleware)
    r.Use(otelgin.Middleware("go-favorite-otel-manual"))


    // Define routes
    r.GET("/", func(c *gin.Context) {
        contextLogger(c).Infof("Main request successful")
        c.String(http.StatusOK, "Hello World!")
    })

    r.GET("/favorites", func(c *gin.Context) {
        // artificial sleep for delayTime
        time.Sleep(time.Duration(delayTime) * time.Millisecond)

        userID := c.Query("user_id")

        contextLogger(c).Infof("Getting favorites for user %q", userID)

        favorites, err := rdb.SMembers(c.Request.Context(), userID).Result()
        if err != nil {
            contextLogger(c).Error("Failed to get favorites for user %q", userID)
            c.String(http.StatusInternalServerError, "Failed to get favorites")
            return
        }

        contextLogger(c).Infof("User %q has favorites %q", userID, favorites)

        c.JSON(http.StatusOK, gin.H{
            "favorites": favorites,
        })
    })

    // Start server
    logger.Infof("App startup")
    log.Fatal(http.ListenAndServe(":"+applicationPort, r))
    logger.Infof("App stopped")
}
</code></pre>
<h3 id="step2runningthedockerimagewithenvironmentvariables">Step 2. Running the Docker image with environment variables</h3>
<p>As specified in the <a href="https://opentelemetry.io/docs/specs/otel/configuration/sdk-environment-variables/">OTEL documentation</a>, we will use environment variables and pass in the configuration values that are found in your APM Agent’s configuration section.</p>
<p>Because Elastic accepts OTLP natively, we just need to provide the Endpoint and authentication where the OTEL Exporter needs to send the data, as well as some other environment variables.</p>
<p><strong>Where to get these variables in Elastic Cloud and Kibana</strong> <sup>®</sup><br />
You can copy the endpoints and token from Kibana under the path /app/home#/tutorial/apm.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltebfc9096105dc59e/6a85ccb89d2b7100f0f939ca/elastic-blog-GO-apm-agents.png" alt="GO apm agents" /></p>
<p>You will need to copy the OTEL_EXPORTER_OTLP_ENDPOINT as well as the OTEL_EXPORTER_OTLP_HEADERS.</p>
<p><strong>Build the image</strong></p>
<pre><code>docker build -t  go-otel-manual-image .
</code></pre>
<h2 id="runtheimage">Run the image</h2>
<pre><code>docker run \
       -e OTEL_EXPORTER_OTLP_ENDPOINT="&lt;REPLACE WITH OTEL_EXPORTER_OTLP_ENDPOINT&gt;" \
       -e OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer &lt;REPLACE WITH TOKEN&gt;" \
       -e OTEL_RESOURCE_ATTRIBUTES="service.version=1.0,deployment.environment=production,service.name=go-favorite-otel-manual" \
       -p 5000:5000 \
       go-otel-manual-image
</code></pre>
<p>You can now issue a few requests in order to generate trace data. Note that these requests are expected to return an error, as this service relies on a connection to Redis that you don’t currently have running. As mentioned before, you can find a more complete example using Docker compose <a href="https://github.com/elastic/observability-examples/tree/main/Elastiflix">here</a>.</p>
<pre><code>curl localhost:500/favorites
# or alternatively issue a request every second

while true; do curl "localhost:5000/favorites"; sleep 1; done;
</code></pre>
<h2 id="howdothetracesshowupinelastic">How do the traces show up in Elastic?</h2>
<p>Now that the service is instrumented, you should see the following output in Elastic APM when looking at the transactions section of your Node.js service:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta64d1c7fc7f17c32/6a85ccbb2d64d5249d081d72/GO-trace-samples.png" alt="trace samples" /></p>
<h2 id="conclusion">Conclusion</h2>
<p>In this blog, we discussed the following:</p>
<ul>
<li>How to manually instrument Go with OpenTelemetry</li>
<li>How to properly initialize OpenTelemetry and add a custom span</li>
<li>How to easily set the OTLP ENDPOINT and OTLP HEADERS with Elastic without the need for a collector</li>
</ul>
<p>Hopefully, this provides an easy-to-understand walk-through of instrumenting Go with OpenTelemetry and how easy it is to send traces into Elastic.</p>
<blockquote>
  <p>Developer resources:</p>
  <ul>
  <li><a href="https://www.elastic.co/blog/getting-started-opentelemetry-instrumentation-sample-app">Elastiflix application</a>, a guide to instrument different languages with OpenTelemetry</li>
  <li>Python: <a href="https://www.elastic.co/blog/auto-instrumentation-of-python-applications-opentelemetry">Auto-instrumentation</a>, <a href="https://www.elastic.co/blog/manual-instrumentation-of-python-applications-opentelemetry">Manual-instrumentation</a></li>
  <li>Java: <a href="https://www.elastic.co/blog/auto-instrumentation-of-java-applications-opentelemetry">Auto-instrumentation</a>, <a href="https://www.elastic.co/blog/manual-instrumentation-of-java-applications-opentelemetry">Manual-instrumentation</a></li>
  <li>Node.js: <a href="https://www.elastic.co/blog/auto-instrument-nodejs-applications-opentelemetry">Auto-instrumentation</a>, <a href="https://www.elastic.co/blog/manual-instrumentation-of-nodejs-applications-opentelemetry">Manual-instrumentation</a></li>
  <li>.NET: <a href="https://www.elastic.co/blog/auto-instrumentation-of-net-applications-opentelemetry">Auto-instrumentation</a>, <a href="https://www.elastic.co/blog/manual-instrumentation-of-net-applications-opentelemetry">Manual-instrumentation</a></li>
  <li>Go: <a href="https://elastic.co/blog/manual-instrumentation-apps-opentelemetry">Manual-instrumentation</a></li>
  <li><a href="https://www.elastic.co/blog/best-practices-instrumenting-opentelemetry">Best practices for instrumenting OpenTelemetry</a></li>
  </ul>
  <p>General configuration and use case resources:</p>
  <ul>
  <li><a href="https://www.elastic.co/blog/opentelemetry-observability">Independence with OpenTelemetry on Elastic</a></li>
  <li><a href="https://www.elastic.co/blog/implementing-kubernetes-observability-security-opentelemetry">Modern observability and security on Kubernetes with Elastic and OpenTelemetry</a></li>
  <li><a href="https://www.elastic.co/blog/3-models-logging-opentelemetry-elastic">3 models for logging with OpenTelemetry and Elastic</a></li>
  <li><a href="https://www.elastic.co/blog/adding-free-and-open-elastic-apm-as-part-of-your-elastic-observability-deployment">Adding free and open Elastic APM as part of your Elastic Observability deployment</a></li>
  <li><a href="https://www.elastic.co/blog/custom-metrics-app-code-java-agent-plugin">Capturing custom metrics through OpenTelemetry API in code with Elastic</a></li>
  <li><a href="https://www.elastic.co/virtual-events/future-proof-your-observability-platform-with-opentelemetry-and-elastic">Future-proof your observability platform with OpenTelemetry and Elastic</a></li>
  <li><a href="https://www.elastic.co/blog/kubernetes-k8s-observability-elasticsearch-cncf">Elastic Observability: Built for open technologies like Kubernetes, OpenTelemetry, Prometheus, Istio, and more</a></li>
  </ul>
</blockquote>
<p>Don’t have an Elastic Cloud account yet? <a href="https://cloud.elastic.co/registration">Sign up for Elastic Cloud</a> and try out the auto-instrumentation capabilities that I discussed above. I would be interested in getting your feedback about your experience in gaining visibility into your application stack 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/manual-instrumentation-apps-opentelemetry</link>
    <guid isPermaLink="false">manual-instrumentation-apps-opentelemetry</guid>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[APM]]></category>
    <category><![CDATA[Infrastructure Monitoring]]></category>
    <dc:creator><![CDATA[Luca Wintergerst]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt985f77895b54aaab/6a85ccbe342d69d08921b121/observability-launch-series-5-go-manual.jpg" length="0" type="image/jpeg"/>
    <pubDate>Tue, 12 Sep 2023 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Manual instrumentation of .NET applications with OpenTelemetry]]></title>
    <description><![CDATA[In this blog, we will look at how to manually instrument your .NET applications using OpenTelemetry, which provides a set of APIs, libraries, and agents to capture distributed traces and metrics from your application. You can analyze them in Elastic.]]></description>
    <content:encoded><![CDATA[<p>In the fast-paced universe of software development, especially in the cloud-native realm, DevOps and SRE teams are increasingly emerging as essential partners in application stability and growth.</p>
<p>DevOps engineers continuously optimize software delivery, while SRE teams act as the stewards of application reliability, scalability, and top-tier performance. The challenge? These teams require a cutting-edge observability solution, one that encompasses full-stack insights, empowering them to rapidly manage, monitor, and rectify potential disruptions before they culminate into operational challenges.</p>
<p>Observability in our modern distributed software ecosystem goes beyond mere monitoring — it demands limitless data collection, precision in processing, and the correlation of this data into actionable insights. However, the road to achieving this holistic view is paved with obstacles, from navigating version incompatibilities to wrestling with restrictive proprietary code.</p>
<p>Enter <a href="https://opentelemetry.io/">OpenTelemetry (OTel)</a>, with the following benefits for those who adopt it:</p>
<ul>
<li>Escape vendor constraints with OTel, freeing yourself from vendor lock-in and ensuring top-notch observability.</li>
<li>See the harmony of unified logs, metrics, and traces come together to provide a complete system view.</li>
<li>Improve your application oversight through richer and enhanced instrumentations.</li>
<li>Embrace the benefits of backward compatibility to protect your prior instrumentation investments.</li>
<li>Embark on the OpenTelemetry journey with an easy learning curve, simplifying onboarding and scalability.</li>
<li>Rely on a proven, future-ready standard to boost your confidence in every investment.</li>
<li>Explore manual instrumentation, enabling customized data collection to fit your unique needs.</li>
<li>Ensure monitoring consistency across layers with a standardized observability data framework.</li>
<li>Decouple development from operations, driving peak efficiency for both.</li>
</ul>
<p>In this post, we will dive into the methodology to instrument a .NET application manually using Docker.</p>
<h2 id="whatscovered">What's covered?</h2>
<ul>
<li>Instrumenting the .NET application manually</li>
<li>Creating a Docker image for a .NET application with the OpenTelemetry instrumentation baked in</li>
<li>Installing and running the OpenTelemetry .NET Profiler for automatic instrumentation</li>
</ul>
<h2 id="prerequisites">Prerequisites</h2>
<ul>
<li>An understanding of Docker and .NET</li>
<li>Elastic Cloud</li>
<li>Docker installed on your machine (we recommend docker desktop)</li>
</ul>
<h2 id="viewtheexamplesourcecode">View the example source code</h2>
<p>The full source code, including the Dockerfile used in this blog, can be found on <a href="https://github.com/elastic/observability-examples/tree/main/Elastiflix/dotnet-login-otel-manual">GitHub</a>. The repository also contains the <a href="https://github.com/elastic/observability-examples/tree/main/Elastiflix/dotnet-login">same application without instrumentation</a>. This allows you to compare each file and see the differences.</p>
<p>The following steps will show you how to instrument this application and run it on the command line or in Docker. If you are interested in a more complete OTel example, take a look at the docker-compose file <a href="https://github.com/elastic/observability-examples/tree/main#start-the-app">here</a>, which will bring up the full project.</p>
<h2 id="stepbystepguide">Step-by-step guide</h2>
<p>This blog assumes you have an Elastic Cloud account — if not, follow the <a href="https://cloud.elastic.co/registration?elektra=en-cloud-page">instructions to get started on Elastic Cloud</a>.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb8ef49de791ae8ea/6a85ccde9d2b7104e3f939ce/elastic-blog-2-free-trial.png" alt="" /></p>
<h2 id="step1gettingstarted">Step 1. Getting started</h2>
<p>In our demonstration, we will manually instrument a .NET Core application - Login. This application simulates a simple user login service. In this example, we are only looking at Tracing since the OpenTelemetry logging instrumentation is currently at mixed maturity, as mentioned <a href="https://opentelemetry.io/docs/instrumentation/">here</a>.</p>
<p>The application has the following files:</p>
<ol>
<li><p>Program.cs</p></li>
<li><p>Startup.cs</p></li>
<li><p>Telemetry.cs</p></li>
<li><p>LoginController.cs</p></li>
</ol>
<h2 id="step2instrumentingtheapplication">Step 2. Instrumenting the application</h2>
<p>When it comes to OpenTelemetry, the .NET ecosystem presents some unique aspects. While OpenTelemetry offers its API, .NET leverages its native <strong>System</strong>.Diagnostics API to implement OpenTelemetry's Tracing API. The pre-existing constructs such as <strong>ActivitySource</strong> and <strong>Activity</strong> are aptly repurposed to comply with OpenTelemetry.</p>
<p>That said, understanding the OpenTelemetry API and its terminology remains crucial for .NET developers. It's pivotal in gaining full command over instrumenting your applications, and as we've seen, it also extends to understanding elements of the <strong>System</strong>.Diagnostics API.</p>
<p>For those who might lean toward using the original OpenTelemetry APIs over the <strong>System</strong>.Diagnostics ones, there is also a way. OpenTelemetry provides an API shim for tracing that you can use. It enables developers to switch to OpenTelemetry APIs, and you can find more details about it in the OpenTelemetry API Shim documentation.</p>
<p>By integrating such practices into your .NET application, you can take full advantage of the powerful features OpenTelemetry provides, irrespective of whether you're using OpenTelemetry's API or the <strong>System</strong>.Diagnostics API.</p>
<p>In this blog, we are sticking to the default method and using the Activity convention which the <strong>System</strong>.Diagnostics API dictates.</p>
<p>To manually instrument a .NET application, you need to make changes in each of these files. Let's take a look at these changes one by one.</p>
<h3 id="programcs">Program.cs</h3>
<p>This is the entry point for our application. Here, we create an instance of IHostBuilder with default configurations. Notice how we set up a console logger with Serilog.</p>
<pre><code>public static void Main(string[] args)
{
    Log.Logger = new LoggerConfiguration().WriteTo.Console().CreateLogger();
    CreateHostBuilder(args).Build().Run();
}
</code></pre>
<h3 id="startupcs">Startup.cs</h3>
<p>In the <strong>Startup</strong>.cs file, we use the <strong>ConfigureServices</strong> method to add the OpenTelemetry Tracing.</p>
<pre><code>public void ConfigureServices(IServiceCollection services)
{
    services.AddOpenTelemetry().WithTracing(builder =&gt; builder.AddOtlpExporter()
        .AddSource("Login")
        .AddAspNetCoreInstrumentation()
        .AddOtlpExporter()
        .ConfigureResource(resource =&gt;
            resource.AddService(
                serviceName: "Login"))
    );
    services.AddControllers();
}
</code></pre>
<p>The WithTracing method enables tracing in OpenTelemetry. We add the OTLP (OpenTelemetry Protocol) exporter, which is a general-purpose telemetry data delivery protocol. We also add the AspNetCoreInstrumentation, which will automatically collect traces from our application. This is a critically important step that is not mentioned in the OpenTelemetry docs. Without adding this method, the instrumentation was not working for me for the Login application.</p>
<h3 id="telemetrycs">Telemetry.cs</h3>
<p>This file contains the definition of our ActivitySource. The ActivitySource represents the source of the telemetry activities. It is named after the service name for your application, and this name can come from a configuration file, constants file, etc. We can use this ActivitySource to start activities.</p>
<pre><code>using System.Diagnostics;

public static class Telemetry
{
    //...

    // Name it after the service name for your app.
    // It can come from a config file, constants file, etc.
    public static readonly ActivitySource LoginActivitySource = new("Login");

    //...
}
</code></pre>
<p>In our case, we've created an <strong>ActivitySource</strong> named <strong>Login</strong>. In our <strong>LoginController</strong>.cs, we use this <strong>LoginActivitySource</strong> to start a new activity when we begin our operations.</p>
<pre><code>using (Activity activity = Telemetry.LoginActivitySource.StartActivity("SomeWork"))
{
    // Perform operations here
}
</code></pre>
<p>This piece of code starts a new activity named <strong>SomeWork</strong> , performs some operations (in this case, generating a random user and logging them in), and then ends the activity. These activities are traced and can be analyzed later to understand the performance of the operations.</p>
<p>This <strong>ActivitySource</strong> is fundamental to OpenTelemetry's manual instrumentation. It represents the source of the activities and provides a way to start and stop activities.</p>
<h3 id="logincontrollercs">LoginController.cs</h3>
<p>In the <strong>LoginController</strong>.cs file, we are tracing the operations performed by the GET and POST methods. We start a new activity, <strong>SomeWork</strong> , before we begin our operations and dispose of it once we're done.</p>
<pre><code>using (Activity activity = Telemetry.LoginActivitySource.StartActivity("SomeWork"))
{
    var user = GenerateRandomUserResponse();
    Log.Information("User logged in: {UserName}", user);
    return user;
}
</code></pre>
<p>This will track the time taken by these operations and send this data to any configured telemetry backend via the OTLP exporter.</p>
<h2 id="step3baseimagesetup">Step 3. Base image setup</h2>
<p>Now that we have our application source code created and instrumented, it’s time to create a Dockerfile to build and run our .NET Login service.</p>
<p>Start with the .NET runtime image for the base layer of our Dockerfile:</p>
<pre><code>FROM ${ARCH}mcr.microsoft.com/dotnet/aspnet:7.0. AS base
WORKDIR /app
EXPOSE 8000
</code></pre>
<p>Here, we're setting up the application's runtime environment.</p>
<h2 id="step4buildingthenetapplication">Step 4. Building the .NET application</h2>
<p>This feature of Docker is just the best. Here, we compile our .NET application. We'll use the SDK image. In the bad old days, we used to build on a different platform and then put the compiled code into the Docker container. This way, we are much more confident our build will replicate from a developers desktop and into production by using Docker all the way through.</p>
<pre><code>FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:8.0-preview AS build
ARG TARGETPLATFORM

WORKDIR /src
COPY ["login.csproj", "./"]
RUN dotnet restore "./login.csproj"
COPY . .
WORKDIR "/src/."
RUN dotnet build "login.csproj" -c Release -o /app/build
</code></pre>
<p>This section ensures that our .NET code is properly restored and compiled.</p>
<h2 id="step5publishingtheapplication">Step 5. Publishing the application</h2>
<p>Once built, we'll publish the app:</p>
<pre><code>FROM build AS publish
RUN dotnet publish "login.csproj" -c Release -o /app/publish
</code></pre>
<h2 id="step6preparingthefinalimage">Step 6. Preparing the final image</h2>
<p>Now, let's set up the final runtime image:</p>
<pre><code>FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
</code></pre>
<h2 id="step7entrypointsetup">Step 7. Entry point setup</h2>
<p>Lastly, set the Docker image's entry point to both source the OpenTelemetry instrumentation, which sets up the Environment variables required to bootstrap the .NET Profiler, and then we start our .NET application:</p>
<pre><code>ENTRYPOINT ["/bin/bash", "-c", "dotnet login.dll"]
</code></pre>
<h2 id="step8runningthedockerimagewithenvironmentvariables">Step 8. Running the Docker image with environment variables</h2>
<p>To build and run the Docker image, you'd typically follow these steps:</p>
<h3 id="buildthedockerimage">Build the Docker image</h3>
<p>First, you'd want to build the Docker image from your Dockerfile. Let's assume the Dockerfile is in the current directory, and you'd like to name/tag your image dotnet-login-otel-image.</p>
<pre><code>docker build -t dotnet-login-otel-image .
</code></pre>
<h3 id="runthedockerimage">Run the Docker image</h3>
<p>After building the image, you'd run it with the specified environment variables. For this, the docker <strong>run</strong> command is used with the -e flag for each environment variable.</p>
<pre><code>docker run \
       -e OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer ${ELASTIC_APM_SECRET_TOKEN}" \
       -e OTEL_EXPORTER_OTLP_ENDPOINT="${ELASTIC_APM_SERVER_URL}" \
       -e OTEL_METRICS_EXPORTER="otlp" \
       -e OTEL_RESOURCE_ATTRIBUTES="service.version=1.0,deployment.environment=production" \
       -e OTEL_SERVICE_NAME="dotnet-login-otel-manual" \
       -e OTEL_TRACES_EXPORTER="otlp" \
       dotnet-login-otel-image
</code></pre>
<p>Make sure that <code>${ELASTIC_APM_SECRET_TOKEN}</code> and <code>${ELASTIC_APM_SERVER_URL}</code> are set in your shell environment, replace them with their actual values from the cloud as shown below.</p>
<p><strong>Getting Elastic Cloud variables</strong><br />
You can copy the endpoints and token from Kibana under the path <code>/app/home#/tutorial/apm</code>.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb324309b1a97b34f/6a85cce1e2447a221d8b1436/elastic-blog-3-apm-agents.png" alt="apm agents" /></p>
<p>You can also use an environment file with docker run --env-file to make the command less verbose if you have multiple environment variables.</p>
<p>Once you have this up and running, you can ping the endpoint for your instrumented service (in our case, this is /login), and you should see the app appear in Elastic APM, as shown below:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt78e1bff9ac568fa5/6a85cce35c27903789f59b47/services-2.png" alt="services" /></p>
<p>It will begin by tracking throughput and latency critical metrics for SREs to pay attention to.</p>
<p>Digging in, we can see an overview of all our Transactions.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5c8d9a1ff381b15b/6a85cce6331d7a0d87c317e7/manual-net-login.png" alt="login" /></p>
<p>And look at specific transactions, including the “SomeWork” activity/span we created in the code above:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt02ad8d91efaccb83/6a85cce9bc5bb3452cf81b39/latency_distribution_graph.png" alt="latency distribution graph" /></p>
<p>There is clearly an outlier here, where one transaction took over 20ms. This is likely to be due to the CLR warming up.</p>
<h2 id="wrappingup">Wrapping up</h2>
<p>With the code here instrumented and the Dockerfile bootstrapping the application, you've transformed your simple .NET application into one that's instrumented with OpenTelemetry. This will aid greatly in understanding application performance, tracing errors, and gaining insights into how users interact with your software.</p>
<p>Remember, observability is a crucial aspect of modern application development, especially in distributed systems. With tools like OpenTelemetry, understanding complex systems becomes a tad bit easier.</p>
<p>In this blog, we discussed the following:</p>
<ul>
<li>How to manually instrument .NET with OpenTelemetry.</li>
<li>Using standard commands in a Docker file, our instrumented application was built and started.</li>
<li>Using OpenTelemetry and its support for multiple languages, DevOps and SRE teams can instrument their applications with ease, gaining immediate insights into the health of the entire application stack and reducing mean time to resolution (MTTR).</li>
</ul>
<p>Since Elastic can support a mix of methods for ingesting data whether it be using auto-instrumentation of open-source OpenTelemetry or manual instrumentation with its native APM agents, you can plan your migration to OTel by focusing on a few applications first and then using OpenTelemety across your applications later on in a manner that best fits your business needs.</p>
<blockquote>
  <p>Developer resources:</p>
  <ul>
  <li><a href="https://www.elastic.co/blog/getting-started-opentelemetry-instrumentation-sample-app">Elastiflix application</a>, a guide to instrument different languages with OpenTelemetry</li>
  <li>Python: <a href="https://www.elastic.co/blog/auto-instrumentation-of-python-applications-opentelemetry">Auto-instrumentation</a>, <a href="https://www.elastic.co/blog/manual-instrumentation-of-python-applications-opentelemetry">Manual-instrumentation</a></li>
  <li>Java: <a href="https://www.elastic.co/blog/auto-instrumentation-of-java-applications-opentelemetry">Auto-instrumentation</a>, <a href="https://www.elastic.co/blog/manual-instrumentation-of-java-applications-opentelemetry">Manual-instrumentation</a></li>
  <li>Node.js: <a href="https://www.elastic.co/blog/auto-instrument-nodejs-applications-opentelemetry">Auto-instrumentation</a>, <a href="https://www.elastic.co/blog/manual-instrumentation-of-nodejs-applications-opentelemetry">Manual-instrumentation</a></li>
  <li>.NET: <a href="https://www.elastic.co/blog/auto-instrumentation-of-net-applications-opentelemetry">Auto-instrumentation</a>, <a href="https://www.elastic.co/observability-labs/blog/manual-instrumentation-net-apps-opentelemetry">Manual-instrumentation</a></li>
  <li>Go: <a href="https://elastic.co/blog/manual-instrumentation-of-go-applications-opentelemetry">Manual-instrumentation</a></li>
  <li><a href="https://www.elastic.co/blog/best-practices-instrumenting-opentelemetry">Best practices for instrumenting OpenTelemetry</a></li>
  </ul>
  <p>General configuration and use case resources:</p>
  <ul>
  <li><a href="https://www.elastic.co/blog/opentelemetry-observability">Independence with OpenTelemetry on Elastic</a></li>
  <li><a href="https://www.elastic.co/blog/implementing-kubernetes-observability-security-opentelemetry">Modern observability and security on Kubernetes with Elastic and OpenTelemetry</a></li>
  <li><a href="https://www.elastic.co/blog/3-models-logging-opentelemetry-elastic">3 models for logging with OpenTelemetry and Elastic</a></li>
  <li><a href="https://www.elastic.co/blog/adding-free-and-open-elastic-apm-as-part-of-your-elastic-observability-deployment">Adding free and open Elastic APM as part of your Elastic Observability deployment</a></li>
  <li><a href="https://www.elastic.co/blog/custom-metrics-app-code-java-agent-plugin">Capturing custom metrics through OpenTelemetry API in code with Elastic</a></li>
  <li><a href="https://www.elastic.co/virtual-events/future-proof-your-observability-platform-with-opentelemetry-and-elastic">Future-proof your observability platform with OpenTelemetry and Elastic</a></li>
  <li><a href="https://www.elastic.co/blog/kubernetes-k8s-observability-elasticsearch-cncf">Elastic Observability: Built for open technologies like Kubernetes, OpenTelemetry, Prometheus, Istio, and more</a></li>
  </ul>
</blockquote>
<p>Don’t have an Elastic Cloud account yet? <a href="https://cloud.elastic.co/registration">Sign up for Elastic Cloud</a> and try out the instrumentation capabilities that I discussed above. I would be interested in getting your feedback about your experience in gaining visibility into your application stack 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/manual-instrumentation-net-apps-opentelemetry</link>
    <guid isPermaLink="false">manual-instrumentation-net-apps-opentelemetry</guid>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[APM]]></category>
    <category><![CDATA[Infrastructure Monitoring]]></category>
    <dc:creator><![CDATA[David Hope]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd3bbe921bdcfc897/6a85ccebabdc29dbcb122538/observability-launch-series-4-net-manual.jpg" length="0" type="image/jpeg"/>
    <pubDate>Fri, 01 Sep 2023 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Auto-instrumentation of .NET applications with OpenTelemetry]]></title>
    <description><![CDATA[OpenTelemetry provides an observability framework for cloud-native software, allowing us to trace, monitor, and debug applications seamlessly. In this post, we'll explore how to automatically instrument a .NET application using OpenTelemetry.]]></description>
    <content:encoded><![CDATA[<p>In the fast-paced universe of software development, especially in the cloud-native realm, DevOps and SRE teams are increasingly emerging as essential partners in application stability and growth.</p>
<p>DevOps engineers continuously optimize software delivery, while SRE teams act as the stewards of application reliability, scalability, and top-tier performance. The challenge? These teams require a cutting-edge observability solution, one that encompasses full-stack insights, empowering them to rapidly manage, monitor, and rectify potential disruptions before they culminate into operational challenges.</p>
<p>Observability in our modern distributed software ecosystem goes beyond mere monitoring — it demands limitless data collection, precision in processing, and the correlation of this data into actionable insights. However, the road to achieving this holistic view is paved with obstacles, from navigating version incompatibilities to wrestling with restrictive proprietary code.</p>
<p>Enter <a href="https://opentelemetry.io/">OpenTelemetry (OTel)</a>, with the following benefits for those who adopt it:</p>
<ul>
<li>Escape vendor constraints with OTel, freeing yourself from vendor lock-in and ensuring top-notch observability.</li>
<li>See the harmony of unified logs, metrics, and traces come together to provide a complete system view.</li>
<li>Improve your application oversight through richer and enhanced instrumentations.</li>
<li>Embrace the benefits of backward compatibility to protect your prior instrumentation investments.</li>
<li>Embark on the OpenTelemetry journey with an easy learning curve, simplifying onboarding and scalability.</li>
<li>Rely on a proven, future-ready standard to boost your confidence in every investment.</li>
<li>Explore manual instrumentation, enabling customized data collection to fit your unique needs.</li>
<li>Ensure monitoring consistency across layers with a standardized observability data framework.</li>
<li>Decouple development from operations, driving peak efficiency for both.</li>
</ul>
<p>Given this context, OpenTelemetry emerges as an unmatched observability solution for cloud-native software, seamlessly enabling tracing, monitoring, and debugging. One of its strengths is the ability to auto-instrument applications, allowing developers the luxury of collecting invaluable telemetry without delving into code modifications.</p>
<p>In this post, we will dive into the methodology to instrument a .NET application using Docker, blending the best of both worlds: powerful observability without the code hassles.</p>
<h2 id="whatscovered">What's covered?</h2>
<ul>
<li>How APM works with .NET using CLR Profiler functionality</li>
<li>Creating a Docker image for a .NET application with the OpenTelemetry instrumentation baked in</li>
<li>Installing and running the OpenTelemetry .NET Profiler for automatic instrumentation</li>
</ul>
<h2 id="howapmworkswithnetusingclrprofilerfunctionality">How APM works with .NET using CLR Profiler functionality</h2>
<p>Before we delve into the details, let's clear up some confusion around .NET Profilers and CPU Profilers like Elastic<sup>®</sup>’s Universal Profiling tool — we don’t want to get these two things mixed up, as they have very different purposes.</p>
<p>When discussing profiling tools, especially in the context of .NET, it's not uncommon to encounter confusion between a ".NET profiler" and a "CPU profiler." Though both are used to diagnose and optimize applications, they serve different primary purposes and operate at different levels. Let's clarify the distinction:</p>
<h3 id="netprofiler">.NET Profiler</h3>
<ol>
<li><p><strong>Scope:</strong> Specifically targets .NET applications. It is designed to work with the .NET runtime (i.e., the Common Language Runtime (CLR)).</p></li>
<li><p><strong>Functionality:</strong></p></li>
<li><p><strong>Use cases:</strong></p></li>
</ol>
<h3 id="cpuprofiler">CPU Profiler</h3>
<ol>
<li><p><strong>Scope:</strong> More general than a .NET profiler. It can profile any application, irrespective of the language or runtime, as long as it runs on the CPU being profiled.</p></li>
<li><p><strong>Functionality:</strong></p></li>
<li><p><strong>Use cases:</strong></p></li>
</ol>
<p>While both .NET profilers and CPU profilers aid in optimizing and diagnosing application performance, their approach and depth differ. A .NET profiler offers deep insights specifically into the .NET ecosystem, allowing for fine-grained analysis and instrumentation. In contrast, a CPU profiler provides a broader view, focusing on CPU usage patterns across any application, regardless of its development platform.</p>
<p>It's worth noting that for comprehensive profiling of a .NET application, you might use both: the .NET profiler to understand code-level behaviors specific to .NET and the CPU profiler to get an overview of CPU resource utilization.</p>
<p>Now that we've cleared that up, let's focus on the .NET Profiler, which we are discussing in this blog for automatic instrumentation of .NET applications. First, let's familiarize ourselves with some foundational concepts and terminologies relevant to a .NET Profiler:</p>
<ul>
<li><strong>CLR (Common Language Runtime):</strong> CLR is a core component of the .NET framework, acting as the execution engine for .NET apps. It provides key services like memory management, exception handling, and type safety.</li>
<li><strong>Profiler API:</strong>.NET provides a set of APIs for profiling applications. These APIs let tools and developers monitor or manipulate .NET applications during runtime.</li>
<li><strong>IL (Intermediate Language):</strong> After compiling, .NET source code turns into IL, a low-level, platform-agnostic representation. This IL code is then compiled just-in-time (JIT) into machine code by the CLR during application execution.</li>
<li><strong>JIT compilation:</strong> JIT stands for just-in-time. In .NET, the CLR compiles IL to native code just before its execution.</li>
</ul>
<p>Now, let's explore how automatic instrumentation works using CLR Profiler.</p>
<p>Automatic instrumentation in .NET, much like Java's bytecode instrumentation, revolves around modifying the behavior of your application's methods during runtime, without changing the actual source code.</p>
<p>Here’s a step-by-step breakdown:</p>
<ol>
<li><p><strong>Attach the profiler:</strong> When launching your .NET application, you'll have to specify to load the profiler. The CLR checks for the presence of a profiler by reading environment variables. If it finds one, the CLR initializes the profiler before any user code is executed.</p></li>
<li><p><strong>Use Profiler API to monitor events:</strong> The Profiler API allows a profiler to monitor various events. For instance, method JIT compilation events can be tracked. When a method is about to be JIT compiled, the profiler gets notified.</p></li>
<li><p><strong>Manipulate IL code:</strong> Upon getting notified of a JIT compilation, the profiler can manipulate the IL code of the method. Using the Profiler API, the profiler can insert, delete, or replace IL instructions. This is analogous to how Java agents modify bytecode. For example, if you want to measure a method's execution time, you'd modify the IL to insert calls to start and stop a timer at the beginning and end of the method, respectively.</p></li>
<li><p><strong>Execution of transformed code:</strong> Once the IL has been modified, the JIT compiler will translate it into machine code. The application will then execute this machine code, which includes the additions made by the profiler.</p></li>
<li><p><strong>Gather and report data:</strong> The added instrumentation can collect various data, such as method execution times or call counts. This data can then be relayed to an application performance management (APM) tool, which can provide insights, visualizations, and alerts based on the data.</p></li>
</ol>
<p>In essence, automatic instrumentation with CLR Profiler is about modifying the behavior of your .NET methods at runtime. This is invaluable for monitoring, diagnosing, and fine-tuning the performance of .NET applications without intruding on the application's actual source code.</p>
<h2 id="prerequisites">Prerequisites</h2>
<ul>
<li>A basic understanding of Docker and .NET</li>
<li>Elastic Cloud</li>
<li>Docker installed on your machine (we recommend docker desktop)</li>
</ul>
<h2 id="viewtheexamplesourcecode">View the example source code</h2>
<p>The full source code, including the Dockerfile used in this blog, can be found on <a href="https://github.com/elastic/observability-examples/tree/main/Elastiflix/dotnet-login-otel-manual">GitHub</a>. The repository also contains the <a href="https://github.com/elastic/observability-examples/tree/main/Elastiflix/dotnet-login">same application without instrumentation</a>. This allows you to compare each file and see the differences.</p>
<p>The following steps will show you how to instrument this application and run it on the command line or in Docker. If you are interested in a more complete OTel example, take a look at the docker-compose file <a href="https://github.com/elastic/observability-examples/tree/main#start-the-app">here</a>, which will bring up the full project.</p>
<h2 id="stepbystepguide">Step-by-step guide</h2>
<p>This blog assumes you have an Elastic Cloud account — if not, follow the <a href="https://cloud.elastic.co/registration?elektra=en-cloud-page">instructions to get started on Elastic Cloud</a>.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltee94942c207c6253/6a85c7ec1aa1e1b6b1ff8ce7/elastic-blog-2-free-trial.png" alt="" /></p>
<h2 id="step1baseimagesetup">Step 1. Base image setup</h2>
<p>Start with the .NET runtime image for the base layer of our Dockerfile:</p>
<pre><code>FROM ${ARCH}mcr.microsoft.com/dotnet/aspnet:7.0. AS base
WORKDIR /app
EXPOSE 8000
</code></pre>
<p>Here, we're setting up the application's runtime environment.</p>
<h2 id="step2buildingthenetapplication">Step 2. Building the .NET application</h2>
<p>This feature of Docker is just the best. Here, we compile our .NET application using the SDK image. In the bad old days, we used to build on a different platform and then put the compiled code into the Docker container. This way, we are much more confident our build will replicate from a developer’s desktop and into production by using Docker all the way through.</p>
<pre><code>FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:8.0-preview AS build
ARG TARGETPLATFORM

WORKDIR /src
COPY ["login.csproj", "./"]
RUN dotnet restore "./login.csproj"
COPY . .
WORKDIR "/src/."
RUN dotnet build "login.csproj" -c Release -o /app/build
</code></pre>
<p>This section ensures that our .NET code is properly restored and compiled.</p>
<h2 id="step3publishingtheapplication">Step 3. Publishing the application</h2>
<p>Once built, we'll publish the app:</p>
<pre><code>FROM build AS publish
RUN dotnet publish "login.csproj" -c Release -o /app/publish
</code></pre>
<h2 id="step4preparingthefinalimage">Step 4. Preparing the final image</h2>
<p>Now, let's set up the final runtime image:</p>
<pre><code>FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish
</code></pre>
<h2 id="step5installingopentelemetry">Step 5. Installing OpenTelemetry</h2>
<p>We'll install dependencies and download the OpenTelemetry auto-instrumentation script:</p>
<pre><code>RUN apt-get update &amp;&amp; apt-get install -y zip curl
RUN mkdir /otel
RUN curl -L -o /otel/otel-dotnet-install.sh https://github.com/open-telemetry/opentelemetry-dotnet-instrumentation/releases/download/v0.7.0/otel-dotnet-auto-install.sh
RUN chmod +x /otel/otel-dotnet-install.sh
</code></pre>
<h2 id="step6configureopentelemetry">Step 6. Configure OpenTelemetry</h2>
<p>Designate where OpenTelemetry should reside and execute the installation script. Note that the ENV OTEL_DOTNET_AUTO_HOME is required as the script looks for it:</p>
<pre><code>ENV OTEL_DOTNET_AUTO_HOME=/otel
RUN /bin/bash /otel/otel-dotnet-install.sh
</code></pre>
<h2 id="step7additionalconfiguration">Step 7. Additional configuration</h2>
<p>Make sure the auto-instrumentation and platform detection scripts are executable and run the platform detection script.</p>
<pre><code>COPY platform-detection.sh /otel/
RUN chmod +x /otel/instrument.sh
RUN chmod +x /otel/platform-detection.sh &amp;&amp; /otel/platform-detection.sh
</code></pre>
<p>This platform detection script will check if the Docker build is for ARM64 and implement a workaround to get the OpenTelemetry instrumentation to work on MacOS. If you happen to be running locally on MacOS M1 or M2 processors, you will be grateful for this script.</p>
<h2 id="step8entrypointsetup">Step 8. Entry point setup</h2>
<p>Lastly, set the Docker image's entry point to both source the OpenTelemetry instrumentation, which sets up the environment variables required to bootstrap the .NET Profiler, and then we start our .NET application:</p>
<pre><code>ENTRYPOINT ["/bin/bash", "-c", "source /otel/instrument.sh &amp;&amp; dotnet login.dll"]
</code></pre>
<h2 id="step9runningthedockerimagewithenvironmentvariables">Step 9. Running the Docker image with environment variables</h2>
<p>To build and run the Docker image, you'd typically follow these steps:</p>
<h3 id="buildthedockerimage">Build the Docker image</h3>
<p>First, you'd want to build the Docker image from your Dockerfile. Let's assume the Dockerfile is in the current directory, and you'd like to name/tag your image dotnet-login-otel-image.</p>
<pre><code>docker build -t dotnet-login-otel-image .
</code></pre>
<h3 id="runthedockerimage">Run the Docker image</h3>
<p>After building the image, you'd run it with the specified environment variables. For this, the docker <strong>run</strong> command is used with the -e flag for each environment variable.</p>
<pre><code>docker run \
       -e OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer ${ELASTIC_APM_SECRET_TOKEN}" \
       -e OTEL_EXPORTER_OTLP_ENDPOINT="${ELASTIC_APM_SERVER_URL}" \
       -e OTEL_METRICS_EXPORTER="otlp" \
       -e OTEL_RESOURCE_ATTRIBUTES="service.version=1.0,deployment.environment=production" \
       -e OTEL_SERVICE_NAME="dotnet-login-otel-auto" \
       -e OTEL_TRACES_EXPORTER="otlp" \
       dotnet-login-otel-image
</code></pre>
<p>Make sure that <code>${ELASTIC_APM_SECRET_TOKEN}</code> and <code>${ELASTIC_APM_SERVER_URL}</code> are set in your shell environment, and replace them with their actual values from the cloud as shown below.<br />
Getting Elastic Cloud variables</p>
<p>You can copy the endpoints and token from Kibana<sup>®</sup> under the path <code>/app/home#/tutorial/apm</code>.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6e92903598e3f7b2/6a85c7ef1aa1e13db4ff8ceb/elastic-blog-3-apm-agents.png" alt="apm agents" /></p>
<p>You can also use an environment file with docker run --env-file to make the command less verbose if you have multiple environment variables.</p>
<p>Once you have this up and running, you can ping the endpoint for your instrumented service (in our case, this is /login), and you should see the app appear in Elastic APM, as shown below:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt953edf94dcada272/6a85c7f2331d7a8430c316fd/services-3.png" alt="services" /></p>
<p>It will begin by tracking throughput and latency critical metrics for SREs to pay attention to.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt564046f1b5aca688/6a85c7f633f2441fd249f478/dotnet-login-otel-auto-1.png" alt="dotnet-login-otel-auto-1" /></p>
<p>Digging in, we can see an overview of all our Transactions.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8288064e8ce34deb/6a85c7f9ba7accdfb99920e6/dotnet-login-otel-auto-2.png" alt="dotnet-login-otel-auto-2" /></p>
<p>And look at specific transactions:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltae4f2575265ca818/6a85c7fb078290ac2a321700/specific_transactions.png" alt="specific transactions" /></p>
<p>There is clearly an outlier here, where one transaction took over 200ms. This is likely to be due to the .NET CLR warming up. Click on <strong>Logs</strong> , and we see that logs are also brought over. The OTel Agent will automatically bring in logs and correlate them with traces for you:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd46cc91508749702/6a85c7fe8c29444f1cb88fc6/otel_agent.png" alt="otel agent" /></p>
<h2 id="wrappingup">Wrapping up</h2>
<p>With this Dockerfile, you've transformed your simple .NET application into one that's automatically instrumented with OpenTelemetry. This will aid greatly in understanding application performance, tracing errors, and gaining insights into how users interact with your software.</p>
<p>Remember, observability is a crucial aspect of modern application development, especially in distributed systems. With tools like OpenTelemetry, understanding complex systems becomes a tad bit easier.</p>
<p>In this blog, we discussed the following:</p>
<ul>
<li>How to auto-instrument .NET with OpenTelemetry.</li>
<li>Using standard commands in a Docker file, auto-instrumentation was done efficiently and without adding code in multiple places enabling manageability.</li>
<li>Using OpenTelemetry and its support for multiple languages, DevOps and SRE teams can auto-instrument their applications with ease gaining immediate insights into the health of the entire application stack and reduce mean time to resolution (MTTR).</li>
</ul>
<p>Since Elastic can support a mix of methods for ingesting data, whether it be using auto-instrumentation of open-source OpenTelemetry or manual instrumentation with its native APM agents, you can plan your migration to OTel by focusing on a few applications first and then using OpenTelemety across your applications later on in a manner that best fits your business needs.</p>
<blockquote>
  <p>Developer resources:</p>
  <ul>
  <li><a href="https://www.elastic.co/blog/getting-started-opentelemetry-instrumentation-sample-app">Elastiflix application</a>, a guide to instrument different languages with OpenTelemetry</li>
  <li>Python: <a href="https://www.elastic.co/blog/auto-instrumentation-of-python-applications-opentelemetry">Auto-instrumentation</a>, <a href="https://www.elastic.co/blog/manual-instrumentation-of-python-applications-opentelemetry">Manual-instrumentation</a></li>
  <li>Java: <a href="https://www.elastic.co/blog/auto-instrumentation-of-java-applications-opentelemetry">Auto-instrumentation</a>, <a href="https://www.elastic.co/blog/manual-instrumentation-of-java-applications-opentelemetry">Manual-instrumentation</a></li>
  <li>Node.js: <a href="https://www.elastic.co/blog/auto-instrument-nodejs-applications-opentelemetry">Auto-instrumentation</a>, <a href="https://www.elastic.co/blog/manual-instrumentation-of-nodejs-applications-opentelemetry">Manual-instrumentation</a></li>
  <li>.NET: <a href="https://www.elastic.co/blog/auto-instrumentation-net-applications-opentelemetry">Auto-instrumentation</a>, <a href="https://www.elastic.co/blog/manual-instrumentation-of-net-applications-opentelemetry">Manual-instrumentation</a></li>
  <li>Go: <a href="https://elastic.co/blog/manual-instrumentation-of-go-applications-opentelemetry">Manual-instrumentation</a></li>
  <li><a href="https://www.elastic.co/blog/best-practices-instrumenting-opentelemetry">Best practices for instrumenting OpenTelemetry</a></li>
  </ul>
  <p>General configuration and use case resources:</p>
  <ul>
  <li><a href="https://www.elastic.co/blog/opentelemetry-observability">Independence with OpenTelemetry on Elastic</a></li>
  <li><a href="https://www.elastic.co/blog/implementing-kubernetes-observability-security-opentelemetry">Modern observability and security on Kubernetes with Elastic and OpenTelemetry</a></li>
  <li><a href="https://www.elastic.co/blog/3-models-logging-opentelemetry-elastic">3 models for logging with OpenTelemetry and Elastic</a></li>
  <li><a href="https://www.elastic.co/blog/adding-free-and-open-elastic-apm-as-part-of-your-elastic-observability-deployment">Adding free and open Elastic APM as part of your Elastic Observability deployment</a></li>
  <li><a href="https://www.elastic.co/blog/custom-metrics-app-code-java-agent-plugin">Capturing custom metrics through OpenTelemetry API in code with Elastic</a></li>
  <li><a href="https://www.elastic.co/virtual-events/future-proof-your-observability-platform-with-opentelemetry-and-elastic">Future-proof your observability platform with OpenTelemetry and Elastic</a></li>
  <li><a href="https://www.elastic.co/blog/kubernetes-k8s-observability-elasticsearch-cncf">Elastic Observability: Built for open technologies like Kubernetes, OpenTelemetry, Prometheus, Istio, and more</a></li>
  </ul>
</blockquote>
<p>Don’t have an Elastic Cloud account yet? <a href="https://cloud.elastic.co/registration">Sign up for Elastic Cloud</a> and try out the auto-instrumentation capabilities that I discussed above. I would be interested in getting your feedback about your experience in gaining visibility into your application stack 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/auto-instrumentation-net-applications-opentelemetry</link>
    <guid isPermaLink="false">auto-instrumentation-net-applications-opentelemetry</guid>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[APM]]></category>
    <category><![CDATA[Infrastructure Monitoring]]></category>
    <dc:creator><![CDATA[David Hope]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb1add1b117d08e30/6a85c801eaf2451645a49eef/observability-launch-series-4-net-auto.jpg" length="0" type="image/jpeg"/>
    <pubDate>Fri, 01 Sep 2023 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Manual instrumentation with OpenTelemetry for Python applications]]></title>
    <description><![CDATA[In this blog post, we will show you how to manually instrument Python applications using OpenTelemetry. We will explore how to use the proper OpenTelemetry Python libraries and in particular work on instrumenting tracing in a Python application.]]></description>
    <content:encoded><![CDATA[<p>DevOps and SRE teams are transforming the process of software development. While DevOps engineers focus on efficient software applications and service delivery, SRE teams are key to ensuring reliability, scalability, and performance. These teams must rely on a full-stack observability solution that allows them to manage and monitor systems and ensure issues are resolved before they impact the business.</p>
<p>Observability across the entire stack of modern distributed applications requires data collection, processing, and correlation often in the form of dashboards. Ingesting all system data requires installing agents across stacks, frameworks, and providers — a process that can be challenging and time-consuming for teams who have to deal with version changes, compatibility issues, and proprietary code that doesn't scale as systems change.</p>
<p>Thanks to <a href="http://opentelemetry.io">OpenTelemetry</a> (OTel), DevOps and SRE teams now have a standard way to collect and send data that doesn't rely on proprietary code and have a large support community reducing vendor lock-in.</p>
<p>In a <a href="https://www.elastic.co/blog/opentelemetry-observability">previous blog</a>, we also reviewed how to use the <a href="https://github.com/elastic/opentelemetry-demo">OpenTelemetry demo</a> and connect it to Elastic<sup>®</sup>, as well as some of Elastic’s capabilities with OpenTelemetry and Kubernetes.</p>
<p>In this blog, we will show how to use <a href="https://opentelemetry.io/docs/instrumentation/python/manual/">manual instrumentation for OpenTelemetry</a> with the Python service of our <a href="https://github.com/elastic/observability-examples">application called Elastiflix</a>. This approach is slightly more complex than using <a href="https://www.elastic.co/blog/auto-instrumentation-of-python-applications-opentelemetry">automatic instrumentation</a>.</p>
<p>The beauty of this is that there is <strong>no need for the otel-collector</strong>! This setup enables you to slowly and easily migrate an application to OTel with Elastic according to a timeline that best fits your business.</p>
<h2 id="applicationprerequisitesandconfig">Application, prerequisites, and config</h2>
<p>The application that we use for this blog is called <a href="https://github.com/elastic/observability-examples">Elastiflix</a>, a movie streaming application. It consists of several micro-services written in .NET, NodeJS, Go, and Python.</p>
<p>Before we instrument our sample application, we will first need to understand how Elastic can receive the telemetry data.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9872326d55e43cf5/6a85cd008c2944e11fb8907f/elastic-blog-1-config.png" alt="configuration" /></p>
<p>All of Elastic Observability’s APM capabilities are available with OTel data. Some of these include:</p>
<ul>
<li>Service maps</li>
<li>Service details (latency, throughput, failed transactions)</li>
<li>Dependencies between services, distributed tracing</li>
<li>Transactions (traces)</li>
<li>Machine learning (ML) correlations</li>
<li>Log correlation</li>
</ul>
<p>In addition to Elastic’s APM and a unified view of the telemetry data, you will also be able to use Elastic’s powerful machine learning capabilities to reduce the analysis, and alerting to help reduce MTTR.</p>
<h2 id="prerequisites">Prerequisites</h2>
<ul>
<li>An Elastic Cloud account — <a href="https://cloud.elastic.co/">sign up now</a></li>
<li>A clone of the <a href="https://github.com/elastic/observability-examples">Elastiflix demo application</a>, or your own Python application</li>
<li>Basic understanding of Docker — potentially install <a href="https://www.docker.com/products/docker-desktop/">Docker Desktop</a></li>
<li>Basic understanding of Python</li>
</ul>
<h2 id="viewtheexamplesourcecode">View the example source code</h2>
<p>The full source code, including the Dockerfile used in this blog, can be found on <a href="https://github.com/elastic/observability-examples/tree/main/Elastiflix/python-favorite-otel-auto">GitHub</a>. The repository also contains the <a href="https://github.com/elastic/observability-examples/tree/main/Elastiflix/python-favorite">same application without instrumentation</a>. This allows you to compare each file and see the differences.</p>
<p>The following steps will show you how to instrument this application and run it on the command line or in Docker. If you are interested in a more complete OTel example, take a look at the docker-compose file <a href="https://github.com/elastic/observability-examples/tree/main#start-the-app">here</a>, which will bring up the full project.</p>
<p>Before we begin, let’s look at the non-instrumented code first.</p>
<p>This is our simple Python Flask application that can receive a GET request. (This is a portion of the full <a href="https://github.com/elastic/observability-examples/blob/main/Elastiflix/python-favorite/main.py">main.py</a> file.)</p>
<pre><code>from flask import Flask, request
import sys

import logging
import redis
import os
import ecs_logging
import datetime
import random
import time

redis_host = os.environ.get('REDIS_HOST') or 'localhost'
redis_port = os.environ.get('REDIS_PORT') or 6379

application_port = os.environ.get('APPLICATION_PORT') or 5000

app = Flask(__name__)

# Get the Logger
logger = logging.getLogger("app")
logger.setLevel(logging.DEBUG)

# Add an ECS formatter to the Handler
handler = logging.StreamHandler()
handler.setFormatter(ecs_logging.StdlibFormatter())
logger.addHandler(handler)
logging.getLogger('werkzeug').setLevel(logging.ERROR)
logging.getLogger('werkzeug').addHandler(handler)

r = redis.Redis(host=redis_host, port=redis_port, decode_responses=True)

@app.route('/favorites', methods=['GET'])
def get_favorite_movies():
    user_id = str(request.args.get('user_id'))

    logger.info('Getting favorites for user ' + user_id, extra={
        "event.dataset": "favorite.log",
        "user.id": request.args.get('user_id')
    })

    favorites = r.smembers(user_id)

    # convert to list
    favorites = list(favorites)
    logger.info('User ' + user_id + ' has favorites: ' + str(favorites), extra={
        "event.dataset": "favorite.log",
        "user.id": user_id
    })
    return { "favorites": favorites}

logger.info('App startup')
app.run(host='0.0.0.0', port=application_port)
logger.info('App Stopped')
</code></pre>
<h2 id="stepbystepguide">Step-by-step guide</h2>
<h3 id="step0logintoyourelasticcloudaccount">Step 0. Log in to your Elastic Cloud account</h3>
<p>This blog assumes you have an Elastic Cloud account — if not, follow the <a href="https://cloud.elastic.co/registration?elektra=en-cloud-page">instructions to get started on Elastic Cloud</a>.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt48e32c5f69261876/6a85cd039d2b71c977f939e0/elastic-blog-2-trial.png" alt="trial" /></p>
<h3 id="step1installandinitializeopentelemetry">Step 1. Install and initialize OpenTelemetry</h3>
<p>As a first step, we’ll need to add some additional libraries to our application.</p>
<pre><code>from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor

from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
from opentelemetry.instrumentation.flask import FlaskInstrumentor
from opentelemetry.instrumentation.requests import RequestsInstrumentor
from opentelemetry.instrumentation.redis import RedisInstrumentor
from opentelemetry.sdk.resources import Resource
</code></pre>
<p>This code imports necessary OpenTelemetry libraries, including those for tracing, exporting, and instrumenting specific libraries like Flask, Requests, and Redis.</p>
<p>Next we read the variables:</p>
<pre><code>OTEL_EXPORTER_OTLP_HEADERS
OTEL_EXPORTER_OTLP_ENDPOINT
</code></pre>
<p>And then initialize the exporter.</p>
<pre><code>otel_exporter_otlp_headers = os.environ.get('OTEL_EXPORTER_OTLP_HEADERS')

otel_exporter_otlp_endpoint = os.environ.get('OTEL_EXPORTER_OTLP_ENDPOINT')

exporter = OTLPSpanExporter(endpoint=otel_exporter_otlp_endpoint, headers=otel_exporter_otlp_headers)
</code></pre>
<p>In order to pass additional parameters to OpenTelemetry, we will read the OTEL_RESOURCE_ATTRIBUTES variable and convert it into an object.</p>
<pre><code>resource_attributes = os.environ.get('OTEL_RESOURCE_ATTRIBUTES') or 'service.version=1.0,deployment.environment=production'
key_value_pairs = resource_attributes.split(',')
result_dict = {}

for pair in key_value_pairs:
    key, value = pair.split('=')
    result_dict[key] = value
</code></pre>
<p>Next, we will then use these parameters to populate the resources configuration.</p>
<pre><code>resourceAttributes = {
     "service.name": otel_service_name,
     "service.version": result_dict['service.version'],
     "deployment.environment": result_dict['deployment.environment']
}

resource = Resource.create(resourceAttributes)
</code></pre>
<p>We then set up the trace provider using the previously created resource. The trace provider will allow us to create spans later after getting a tracer instance from it.</p>
<p>Additionally, we specify the use of BatchSPanProcessor. The Span processor is an interface that allows hooks for span start and end method invocations.</p>
<p>In OpenTelemetry, different Span processors are offered. The BatchSPanProcessor batches span and sends them in bulk. Multiple Span processors can be configured to be active at the same time using the MultiSpanProcessor. <a href="https://opentelemetry.io/docs/instrumentation/java/manual/#span-processor">See OpenTelemetry documentation</a>.</p>
<p>Additionally, we added the resource module. This allows us to specify attributes such as service.name, version, and more. See <a href="https://opentelemetry.io/docs/specs/otel/resource/semantic_conventions/#semantic-attributes-with-sdk-provided-default-value">OpenTelemetry semantic conventions documentation</a> for more details.</p>
<pre><code>provider = TracerProvider(resource=resource)
processor = BatchSpanProcessor(exporter)
provider.add_span_processor(processor)

# Sets the global default tracer provider
trace.set_tracer_provider(provider)

# Creates a tracer from the global tracer provider
tracer = trace.get_tracer(otel_service_name)
</code></pre>
<p>Finally, because we are using Flask and Redis, we also add the following, which allows us to automatically instrument both Flask and Redis.</p>
<p>Technically you could consider this “cheating.” We are using some parts of the Python auto-instrumentation. However, it’s generally a good approach to resort to using some of the auto-instrumentation modules. This saves you a lot of time, and in addition, it ensures that functionality like distributed tracing will work automatically for any requests you receive or send.</p>
<pre><code>FlaskInstrumentor().instrument_app(app)
RequestsInstrumentor().instrument()
RedisInstrumentor().instrument()
</code></pre>
<h3 id="step2addingcustomspans">Step 2. Adding Custom Spans</h3>
<p>Now that we have everything added and initialized, we can add custom spans.</p>
<p>If we want to have additional instrumentation for a part of our app, we simply wrap the /favoritesGET function code using Python with:</p>
<pre><code>with tracer.start_as_current_span("add_favorite_movies", set_status_on_exception=True) as span:
        ...
</code></pre>
<p>The wrapped code is as follows:</p>
<pre><code>@app.route('/favorites', methods=['GET'])
def get_favorite_movies():
    # add artificial delay if enabled
    if delay_time &gt; 0:
        time.sleep(max(0, random.gauss(delay_time/1000, delay_time/1000/10)))

    with tracer.start_as_current_span("get_favorite_movies") as span:
        user_id = str(request.args.get('user_id'))

        logger.info('Getting favorites for user ' + user_id, extra={
            "event.dataset": "favorite.log",
            "user.id": request.args.get('user_id')
        })

        favorites = r.smembers(user_id)

        # convert to list
        favorites = list(favorites)
        logger.info('User ' + user_id + ' has favorites: ' + str(favorites), extra={
            "event.dataset": "favorite.log",
            "user.id": user_id
        })
</code></pre>
<p><strong>Additional code</strong></p>
<p>In addition to modules and span instrumentation, the sample application also checks some environment variables at startup. When sending data to Elastic without an OTel collector, the OTEL_EXPORTER_OTLP_HEADERS variable is required as it contains the authentication. The same is true for OTEL_EXPORTER_OTLP_ENDPOINT, the host where we’ll send the telemetry data.</p>
<pre><code>otel_exporter_otlp_headers = os.environ.get('OTEL_EXPORTER_OTLP_HEADERS')
# fail if secret token not set
if otel_exporter_otlp_headers is None:
    raise Exception('OTEL_EXPORTER_OTLP_HEADERS environment variable not set')


otel_exporter_otlp_endpoint = os.environ.get('OTEL_EXPORTER_OTLP_ENDPOINT')
# fail if server url not set
if otel_exporter_otlp_endpoint is None:
    raise Exception('OTEL_EXPORTER_OTLP_ENDPOINT environment variable not set')
else:
    exporter = OTLPSpanExporter(endpoint=otel_exporter_otlp_endpoint, headers=otel_exporter_otlp_headers)
</code></pre>
<p><strong>Final code</strong><br />
For comparison, this is the instrumented code of our sample application. You can find the full source code in <a href="https://github.com/elastic/observability-examples/tree/main/Elastiflix/python-favorite-otel-manual">GitHub</a>.</p>
<pre><code>from flask import Flask, request
import sys

import logging
import redis
import os
import ecs_logging
import datetime
import random
import time

from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor

#Using grpc exporter since per the instructions in OTel docs this is needed for any endpoint receiving OTLP.

from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
from opentelemetry.instrumentation.flask import FlaskInstrumentor
from opentelemetry.instrumentation.requests import RequestsInstrumentor
from opentelemetry.instrumentation.redis import RedisInstrumentor
#from opentelemetry.instrumentation.wsgi import OpenTelemetryMiddleware
from opentelemetry.sdk.resources import Resource

redis_host = os.environ.get('REDIS_HOST') or 'localhost'
redis_port = os.environ.get('REDIS_PORT') or 6379
otel_traces_exporter = os.environ.get('OTEL_TRACES_EXPORTER') or 'otlp'
otel_metrics_exporter = os.environ.get('OTEL_TRACES_EXPORTER') or 'otlp'
environment = os.environ.get('ENVIRONMENT') or 'dev'
otel_service_version = os.environ.get('OTEL_SERVICE_VERSION') or '1.0.0'
resource_attributes = os.environ.get('OTEL_RESOURCE_ATTRIBUTES') or 'service.version=1.0,deployment.environment=production'

otel_exporter_otlp_headers = os.environ.get('OTEL_EXPORTER_OTLP_HEADERS')
# fail if secret token not set
if otel_exporter_otlp_headers is None:
    raise Exception('OTEL_EXPORTER_OTLP_HEADERS environment variable not set')
#else:
#    otel_exporter_otlp_fheaders= f"Authorization=Bearer%20{secret_token}"

otel_exporter_otlp_endpoint = os.environ.get('OTEL_EXPORTER_OTLP_ENDPOINT')
# fail if server url not set
if otel_exporter_otlp_endpoint is None:
    raise Exception('OTEL_EXPORTER_OTLP_ENDPOINT environment variable not set')
else:
    exporter = OTLPSpanExporter(endpoint=otel_exporter_otlp_endpoint, headers=otel_exporter_otlp_headers)


key_value_pairs = resource_attributes.split(',')
result_dict = {}

for pair in key_value_pairs:
    key, value = pair.split('=')
    result_dict[key] = value

resourceAttributes = {
     "service.name": result_dict['service.name'],
     "service.version": result_dict['service.version'],
     "deployment.environment": result_dict['deployment.environment']
#     # Add more attributes as needed
}

resource = Resource.create(resourceAttributes)


provider = TracerProvider(resource=resource)
processor = BatchSpanProcessor(exporter)
provider.add_span_processor(processor)

# Sets the global default tracer provider
trace.set_tracer_provider(provider)

# Creates a tracer from the global tracer provider
tracer = trace.get_tracer("favorite")


application_port = os.environ.get('APPLICATION_PORT') or 5000

app = Flask(__name__)


FlaskInstrumentor().instrument_app(app)
#OpenTelemetryMiddleware().instrument()
RequestsInstrumentor().instrument()
RedisInstrumentor().instrument()

#app.wsgi_app = OpenTelemetryMiddleware(app.wsgi_app)

# Get the Logger
logger = logging.getLogger("app")
logger.setLevel(logging.DEBUG)

# Add an ECS formatter to the Handler
handler = logging.StreamHandler()
handler.setFormatter(ecs_logging.StdlibFormatter())
logger.addHandler(handler)
logging.getLogger('werkzeug').setLevel(logging.ERROR)
logging.getLogger('werkzeug').addHandler(handler)

r = redis.Redis(host=redis_host, port=redis_port, decode_responses=True)

@app.route('/favorites', methods=['GET'])
def get_favorite_movies():
    with tracer.start_as_current_span("get_favorite_movies") as span:
        user_id = str(request.args.get('user_id'))

        logger.info('Getting favorites for user ' + user_id, extra={
            "event.dataset": "favorite.log",
            "user.id": request.args.get('user_id')
        })

        favorites = r.smembers(user_id)

        # convert to list
        favorites = list(favorites)
        logger.info('User ' + user_id + ' has favorites: ' + str(favorites), extra={
            "event.dataset": "favorite.log",
            "user.id": user_id
        })
        return { "favorites": favorites}

logger.info('App startup')
app.run(host='0.0.0.0', port=application_port)
logger.info('App Stopped')
</code></pre>
<h3 id="step3runningthedockerimagewithenvironmentvariables">Step 3. Running the Docker image with environment variables</h3>
<p>As specified in the <a href="https://opentelemetry.io/docs/instrumentation/python/automatic/#configuring-the-agent">OTEL documentation</a>, we will use environment variables and pass in the configuration values to enable it to connect with <a href="https://www.elastic.co/guide/en/observability/current/apm-open-telemetry.html">Elastic Observability’s APM server</a>.</p>
<p>Because Elastic accepts OTLP natively, we just need to provide the Endpoint and authentication where the OTEL Exporter needs to send the data, as well as some other environment variables.</p>
<p><strong>Getting Elastic Cloud variables</strong><br />
You can copy the endpoints and token from Kibana<sup>®</sup> under the path <code>/app/home#/tutorial/apm</code>.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte89b58dfd2d7d759/6a85cd05f9373d290b96f5ca/elastic-blog-3-apm.png" alt="apm agents" /></p>
<p>You will need to copy the following environment variables:</p>
<pre><code>OTEL_EXPORTER_OTLP_ENDPOINT
OTEL_EXPORTER_OTLP_HEADERS
</code></pre>
<p><strong>Build the image</strong></p>
<pre><code>docker build -t  python-otel-manual-image .
</code></pre>
<p><strong>Run the image</strong></p>
<pre><code>docker run \
       -e OTEL_EXPORTER_OTLP_ENDPOINT="&lt;REPLACE WITH OTEL_EXPORTER_OTLP_ENDPOINT&gt;" \
       -e OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer &lt;REPLACE WITH TOKEN&gt;" \
       -e OTEL_RESOURCE_ATTRIBUTES="service.version=1.0,deployment.environment=production,service.name=python-favorite-otel-manual" \
       -p 3001:3001 \
       python-otel-manual-image
</code></pre>
<p>You can now issue a few requests in order to generate trace data. Note that these requests are expected to return an error, as this service relies on a connection to Redis that you don’t currently have running. As mentioned before, you can find a more complete example using docker-compose <a href="https://github.com/elastic/observability-examples/tree/main/Elastiflix">here</a>.</p>
<pre><code>curl localhost:500/favorites
# or alternatively issue a request every second

while true; do curl "localhost:5000/favorites"; sleep 1; done;
</code></pre>
<h3 id="step4exploretracesmetricsandlogsinelasticapm">Step 4. Explore traces, metrics, and logs in Elastic APM</h3>
<p>Now that the service is instrumented, you should see the following output in Elastic APM when looking at the transactions section of your Python service:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2771387c853ec52c/6a85cd0827c5cd50915f7436/elastic-blog-4-graph1.png" alt="graph-1" /></p>
<p>Notice how this is slightly different from the auto-instrumented version, as we now also have our custom span in this view.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltfd6ed8ba3e4fecb9/6a85cd0bf5f1a01dae2ec94d/elastic-blog-5-graph2.png" alt="graph-2" /></p>
<h2 id="isitworthit">Is it worth it?</h2>
<p>This is the million-dollar question. Depending on what level of detail you need, it's potentially necessary to manually instrument. Manual instrumentation lets you add custom spans, custom labels, and metrics where you want or need them. It allows you to get a level of detail that otherwise would not be possible and is oftentimes important for tracking business-specific KPIs.</p>
<p>Your operations, and whether you need to troubleshoot or analyze the performance of specific parts of the code, will dictate when and what to instrument. But it’s helpful to know that you have the option to manually instrument.</p>
<p>If you noticed we didn’t yet instrument metrics, that is another blog. We discussed logs in a <a href="https://www.elastic.co/blog/3-models-logging-opentelemetry-elastic">previous blog</a>.</p>
<h2 id="conclusion">Conclusion</h2>
<p>In this blog, we discussed the following:</p>
<ul>
<li>How to manually instrument Python with OpenTelemetry</li>
<li>How to properly initialize OpenTelemetry and add a custom span</li>
<li>How to easily set the OTLP ENDPOINT and OTLP HEADERS with Elastic without the need for a collector</li>
</ul>
<p>Hopefully, this provides an easy-to-understand walk-through of instrumenting Python with OpenTelemetry and how easy it is to send traces into Elastic.</p>
<blockquote>
  <p>Developer resources:</p>
  <ul>
  <li><a href="https://www.elastic.co/blog/getting-started-opentelemetry-instrumentation-sample-app">Elastiflix application</a>, a guide to instrument different languages with OpenTelemetry</li>
  <li>Python: <a href="https://www.elastic.co/blog/auto-instrumentation-of-python-applications-opentelemetry">Auto-instrumentation</a>, <a href="https://www.elastic.co/observability-labs/blog/manual-instrumentation-python-apps-opentelemetry">Manual-instrumentation</a></li>
  <li>Java: <a href="https://www.elastic.co/blog/auto-instrumentation-of-java-applications-opentelemetry">Auto-instrumentation</a>, <a href="https://www.elastic.co/blog/manual-instrumentation-of-java-applications-opentelemetry">Manual-instrumentation</a></li>
  <li>Node.js: <a href="https://www.elastic.co/blog/auto-instrument-nodejs-applications-opentelemetry">Auto-instrumentation</a>, <a href="https://www.elastic.co/blog/manual-instrumentation-of-nodejs-applications-opentelemetry">Manual-instrumentation</a></li>
  <li>.NET: <a href="https://www.elastic.co/blog/auto-instrumentation-of-net-applications-opentelemetry">Auto-instrumentation</a>, <a href="https://www.elastic.co/blog/manual-instrumentation-of-net-applications-opentelemetry">Manual-instrumentation</a></li>
  <li>Go: <a href="https://elastic.co/blog/manual-instrumentation-of-go-applications-opentelemetry">Manual-instrumentation</a></li>
  <li><a href="https://www.elastic.co/blog/best-practices-instrumenting-opentelemetry">Best practices for instrumenting OpenTelemetry</a></li>
  </ul>
  <p>General configuration and use case resources:</p>
  <ul>
  <li><a href="https://www.elastic.co/blog/opentelemetry-observability">Independence with OpenTelemetry on Elastic</a></li>
  <li><a href="https://www.elastic.co/blog/implementing-kubernetes-observability-security-opentelemetry">Modern observability and security on Kubernetes with Elastic and OpenTelemetry</a></li>
  <li><a href="https://www.elastic.co/blog/3-models-logging-opentelemetry-elastic">3 models for logging with OpenTelemetry and Elastic</a></li>
  <li><a href="https://www.elastic.co/blog/adding-free-and-open-elastic-apm-as-part-of-your-elastic-observability-deployment">Adding free and open Elastic APM as part of your Elastic Observability deployment</a></li>
  <li><a href="https://www.elastic.co/blog/custom-metrics-app-code-java-agent-plugin">Capturing custom metrics through OpenTelemetry API in code with Elastic</a></li>
  <li><a href="https://www.elastic.co/virtual-events/future-proof-your-observability-platform-with-opentelemetry-and-elastic">Future-proof your observability platform with OpenTelemetry and Elastic</a></li>
  <li><a href="https://www.elastic.co/blog/kubernetes-k8s-observability-elasticsearch-cncf">Elastic Observability: Built for open technologies like Kubernetes, OpenTelemetry, Prometheus, Istio, and more</a></li>
  </ul>
</blockquote>
<p>Don’t have an Elastic Cloud account yet? <a href="https://cloud.elastic.co/registration">Sign up for Elastic Cloud</a> and try out the auto-instrumentation capabilities that I discussed above. I would be interested in getting your feedback about your experience in gaining visibility into your application stack 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/manual-instrumentation-python-apps-opentelemetry</link>
    <guid isPermaLink="false">manual-instrumentation-python-apps-opentelemetry</guid>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[APM]]></category>
    <category><![CDATA[Infrastructure Monitoring]]></category>
    <dc:creator><![CDATA[Bahubali Shetti]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc68eb6cebdc0eb2e/6a85cd0e342d69087121b12d/observability-launch-series-2-python-manual_(1).jpg" length="0" type="image/jpeg"/>
    <pubDate>Thu, 31 Aug 2023 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Manual instrumentation with OpenTelemetry for Node.js applications]]></title>
    <description><![CDATA[In this blog post, we will show you how to manually instrument Node.js applications using OpenTelemetry. We will explore how to use the proper OpenTelemetry Node.js libraries and in particular work on instrumenting tracing in a Node.js application.]]></description>
    <content:encoded><![CDATA[<p>DevOps and SRE teams are transforming the process of software development. While DevOps engineers focus on efficient software applications and service delivery, SRE teams are key to ensuring reliability, scalability, and performance. These teams must rely on a full-stack observability solution that allows them to manage and monitor systems and ensure issues are resolved before they impact the business.</p>
<p>Observability across the entire stack of modern distributed applications requires data collection, processing, and correlation often in the form of dashboards. Ingesting all system data requires installing agents across stacks, frameworks, and providers — a process that can be challenging and time-consuming for teams who have to deal with version changes, compatibility issues, and proprietary code that doesn't scale as systems change.</p>
<p>Thanks to <a href="http://opentelemetry.io">OpenTelemetry</a> (OTel), DevOps and SRE teams now have a standard way to collect and send data that doesn't rely on proprietary code and have a large support community reducing vendor lock-in.</p>
<p>In a <a href="https://www.elastic.co/blog/opentelemetry-observability">previous blog</a>, we also reviewed how to use the <a href="https://github.com/elastic/opentelemetry-demo">OpenTelemetry demo</a> and connect it to Elastic<sup>®</sup>, as well as some of Elastic’s capabilities with OpenTelemetry and Kubernetes.</p>
<p>In this blog, we will show how to use <a href="https://opentelemetry.io/docs/instrumentation/java/manual/">manual instrumentation for OpenTelemetry</a> with the Node.js service of our <a href="https://github.com/elastic/observability-examples">application called Elastiflix</a>. This approach is slightly more complex than using <a href="https://www.elastic.co/blog/auto-instrument-nodejs-applications-opentelemetry">auto-instrumentation</a>.</p>
<p>The beauty of this is that there is <strong>no need for the otel-collector</strong>! This setup enables you to slowly and easily migrate an application to OTel with Elastic according to a timeline that best fits your business.</p>
<h2 id="applicationprerequisitesandconfig">Application, prerequisites, and config</h2>
<p>The application that we use for this blog is called <a href="https://github.com/elastic/observability-examples">Elastiflix</a>, a movie streaming application. It consists of several micro-services written in .NET, NodeJS, Go, and Python.</p>
<p>Before we instrument our sample application, we will first need to understand how Elastic can receive the telemetry data.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3fe9d71f697e142b/6a85ccef9829261daa58392e/elastic-blog-1-config.png" alt="Configuration" /></p>
<p>All of Elastic Observability’s APM capabilities are available with OTel data. Some of these include:</p>
<ul>
<li>Service maps</li>
<li>Service details (latency, throughput, failed transactions)</li>
<li>Dependencies between services, distributed tracing</li>
<li>Transactions (traces)</li>
<li>Machine learning (ML) correlations</li>
<li>Log correlation</li>
</ul>
<p>In addition to Elastic’s APM and a unified view of the telemetry data, you will also be able to use Elastic’s powerful machine learning capabilities to reduce the analysis, and alerting to help reduce MTTR.</p>
<h2 id="prerequisites">Prerequisites</h2>
<ul>
<li>An Elastic Cloud account — <a href="https://cloud.elastic.co/">sign up now</a></li>
<li>A clone of the <a href="https://github.com/elastic/observability-examples">Elastiflix demo application</a>, or your own Node.js application</li>
<li>Basic understanding of Docker — potentially install <a href="https://www.docker.com/products/docker-desktop/">Docker Desktop</a></li>
<li>Basic understanding of Node.js</li>
</ul>
<h2 id="viewtheexamplesourcecode">View the example source code</h2>
<p>The full source code, including the Dockerfile used in this blog, can be found on <a href="https://github.com/elastic/observability-examples/tree/main/Elastiflix/node-server-otel-manual">GitHub</a>. The repository also contains the <a href="https://github.com/elastic/observability-examples/tree/main/Elastiflix/node-server">same application without instrumentation</a>. This allows you to compare each file and see the differences.</p>
<p>Before we begin, let’s look at the non-instrumented code first.</p>
<p>This is our simple index.js file that can receive a POST request. See the full code <a href="https://github.com/elastic/observability-examples/blob/main/Elastiflix/node-server-otel-manual/index.js">here</a>.</p>
<pre><code>const pino = require("pino");
const ecsFormat = require("@elastic/ecs-pino-format"); //
const log = pino({ ...ecsFormat({ convertReqRes: true }) });
const expressPino = require("express-pino-logger")({ logger: log });

var API_ENDPOINT_FAVORITES =
  process.env.API_ENDPOINT_FAVORITES || "127.0.0.1:5000";
API_ENDPOINT_FAVORITES = API_ENDPOINT_FAVORITES.split(",");

const express = require("express");
const cors = require("cors")({ origin: true });
const cookieParser = require("cookie-parser");
const { json } = require("body-parser");

const PORT = process.env.PORT || 3001;

const app = express().use(cookieParser(), cors, json(), expressPino);

const axios = require("axios");

app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use((err, req, res, next) =&gt; {
  log.error(err.stack);
  res.status(500).json({ error: err.message, code: err.code });
});

var favorites = {};

app.post("/api/favorites", (req, res) =&gt; {
  var randomIndex = Math.floor(Math.random() * API_ENDPOINT_FAVORITES.length);
  if (process.env.THROW_NOT_A_FUNCTION_ERROR == "true" &amp;&amp; Math.random() &lt; 0.5) {
    // randomly choose one of the endpoints
    axios
      .post(
        "http://" +
          API_ENDPOINT_FAVORITES[randomIndex] +
          "/favorites?user_id=1",
        req.body
      )
      .then(function (response) {
        favorites = response.data;
        // quiz solution: "42"
        res.jsonn({ favorites: favorites });
      })
      .catch(function (error) {
        res.json({ error: error, favorites: [] });
      });
  } else {
    axios
      .post(
        "http://" +
          API_ENDPOINT_FAVORITES[randomIndex] +
          "/favorites?user_id=1",
        req.body
      )
      .then(function (response) {
        favorites = response.data;
        res.json({ favorites: favorites });
      })
      .catch(function (error) {
        res.json({ error: error, favorites: [] });
      });
  }
});

app.listen(PORT, () =&gt; {
  console.log(`Server listening on ${PORT}`);
});
</code></pre>
<h2 id="stepbystepguide">Step-by-step guide</h2>
<h3 id="step0logintoyourelasticcloudaccount">Step 0. Log in to your Elastic Cloud account</h3>
<p>This blog assumes you have an Elastic Cloud account — if not, follow the <a href="https://cloud.elastic.co/registration?elektra=en-cloud-page">instructions to get started on Elastic Cloud</a>.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt211a6288bba0d038/6a85ccf2d7b2e7ea7dfe8522/elastic-blog-2-trial.png" alt="trial" /></p>
<h3 id="step1installandinitializeopentelemetry">Step 1. Install and initialize OpenTelemetry</h3>
<p>As a first step, we’ll need to add some additional modules to our application.</p>
<pre><code>const opentelemetry = require("@opentelemetry/api");
const { NodeTracerProvider } = require("@opentelemetry/sdk-trace-node");
const { BatchSpanProcessor } = require("@opentelemetry/sdk-trace-base");
const { Resource } = require("@opentelemetry/resources");
const {
  SemanticResourceAttributes,
} = require("@opentelemetry/semantic-conventions");

const { registerInstrumentations } = require("@opentelemetry/instrumentation");
const { HttpInstrumentation } = require("@opentelemetry/instrumentation-http");
const {
  ExpressInstrumentation,
} = require("@opentelemetry/instrumentation-express");
</code></pre>
<p>We start by creating a collectorOptions object with parameters such as the url and headers for connecting to the Elastic APM Server or OpenTelemetry collector.</p>
<pre><code>const collectorOptions = {
  url: OTEL_EXPORTER_OTLP_ENDPOINT,
  headers: OTEL_EXPORTER_OTLP_HEADERS,
};
</code></pre>
<p>In order to pass additional parameters to OpenTelemetry, we will read the OTEL_RESOURCE_ATTRIBUTES variable and convert it into an object.</p>
<pre><code>const envAttributes = process.env.OTEL_RESOURCE_ATTRIBUTES || "";

// Parse the environment variable string into an object
const attributes = envAttributes.split(",").reduce((acc, curr) =&gt; {
  const [key, value] = curr.split("=");
  if (key &amp;&amp; value) {
    acc[key.trim()] = value.trim();
  }
  return acc;
}, {});
</code></pre>
<p>Next we will then use these parameters to populate the resources configuration.</p>
<pre><code>const resource = new Resource({
  [SemanticResourceAttributes.SERVICE_NAME]:
    attributes["service.name"] || "node-server-otel-manual",
  [SemanticResourceAttributes.SERVICE_VERSION]:
    attributes["service.version"] || "1.0.0",
  [SemanticResourceAttributes.DEPLOYMENT_ENVIRONMENT]:
    attributes["deployment.environment"] || "production",
});
</code></pre>
<p>We then set up the trace provider using the previously created resource, followed by the exporter which takes the collectorOptions from before. The trace provider will allow us to create spans later.</p>
<p>Additionally, we specify the use of BatchSPanProcessor. The Span processor is an interface that allows hooks for span start and end method invocations.</p>
<p>In OpenTelemetry, different Span processors are offered. The BatchSPanProcessor batches span and sends them in bulk. Multiple Span processors can be configured to be active at the same time using the MultiSpanProcessor. <a href="https://opentelemetry.io/docs/instrumentation/java/manual/#span-processor">See OpenTelemetry documentation</a>.</p>
<p>Additionally, we added the resource module. This allows us to specify attributes such as service.name, version, and more. See <a href="https://opentelemetry.io/docs/specs/otel/resource/semantic_conventions/#semantic-attributes-with-sdk-provided-default-value">OpenTelemetry semantic conventions documentation</a> for more details.</p>
<pre><code>const tracerProvider = new NodeTracerProvider({
  resource: resource,
});

const exporter = new OTLPTraceExporter(collectorOptions);
tracerProvider.addSpanProcessor(new BatchSpanProcessor(exporter));
tracerProvider.register();
</code></pre>
<p>Next, we are going to register some instrumentations. This will automatically instrument Express and HTTP for us. While it’s possible to do this step fully manually as well, it would be complex and a waste of time. This way we can ensure that any incoming and outgoing request is captured properly and that functionality such as distributed tracing works without any additional work.</p>
<pre><code>registerInstrumentations({
  instrumentations: [new HttpInstrumentation(), new ExpressInstrumentation()],
  tracerProvider: tracerProvider,
});
</code></pre>
<p>As a last step, we will now get an instance of the tracer that we can use to create custom spans.</p>
<pre><code>const tracer = opentelemetry.trace.getTracer();
</code></pre>
<h3 id="step2addingcustomspans">Step 2. Adding custom spans</h3>
<p>Now that we have the modules added and initialized, we can add custom spans.</p>
<p>Our sample application has a POST request which calls a downstream service. If we want to have additional instrumentation for this part of our app, we simply wrap the function code with:</p>
<pre><code>tracer.startActiveSpan('favorites',   tracer.startActiveSpan('favorites', (span) =&gt; {...
</code></pre>
<p>The wrapped code is as follows:</p>
<pre><code>app.post("/api/favorites", (req, res, next) =&gt; {
  tracer.startActiveSpan("favorites", (span) =&gt; {
    axios
      .post(
        "http://" + API_ENDPOINT_FAVORITES + "/favorites?user_id=1",
        req.body
      )
      .then(function (response) {
        favorites = response.data;
        span.end();
        res.jsonn({ favorites: favorites });
      })
      .catch(next);
  });
});
</code></pre>
<p><strong>Automatic error handling</strong><br />
For automatic error handling, we are adding a function that we use in Express which captures the exception for any error that happens during runtime.</p>
<pre><code>app.use((err, req, res, next) =&gt; {
  log.error(err.stack);
  span = opentelemetry.trace.getActiveSpan();
  span.recordException(error);
  span.end();
  res.status(500).json({ error: err.message, code: err.code });
});
</code></pre>
<p><strong>Additional code</strong><br />
n addition to modules and span instrumentation, the sample application also checks some environment variables at startup. When sending data to Elastic without an OTel collector, the OTEL_EXPORTER_OTLP_HEADERS variable is required as it contains the authentication. The same is true for OTEL_EXPORTER_OTLP_ENDPOINT, the host where we’ll send the telemetry data.</p>
<pre><code>const OTEL_EXPORTER_OTLP_HEADERS = process.env.OTEL_EXPORTER_OTLP_HEADERS;
// error if secret token is not set
if (!OTEL_EXPORTER_OTLP_HEADERS) {
  throw new Error("OTEL_EXPORTER_OTLP_HEADERS environment variable is not set");
}

const OTEL_EXPORTER_OTLP_ENDPOINT = process.env.OTEL_EXPORTER_OTLP_ENDPOINT;
// error if server url is not set
if (!OTEL_EXPORTER_OTLP_ENDPOINT) {
  throw new Error(
    "OTEL_EXPORTER_OTLP_ENDPOINT environment variable is not set"
  );
}
</code></pre>
<p><strong>Final code</strong><br />
For comparison, this is the instrumented code of our sample application. You can find the full source code in <a href="https://github.com/elastic/observability-examples/tree/main/Elastiflix/node-server-otel-manual">GitHub</a>.</p>
<pre><code>const pino = require("pino");
const ecsFormat = require("@elastic/ecs-pino-format"); //
const log = pino({ ...ecsFormat({ convertReqRes: true }) });
const expressPino = require("express-pino-logger")({ logger: log });

// Add OpenTelemetry packages
const opentelemetry = require("@opentelemetry/api");
const { NodeTracerProvider } = require("@opentelemetry/sdk-trace-node");
const { BatchSpanProcessor } = require("@opentelemetry/sdk-trace-base");
const {
  OTLPTraceExporter,
} = require("@opentelemetry/exporter-trace-otlp-grpc");
const { Resource } = require("@opentelemetry/resources");
const {
  SemanticResourceAttributes,
} = require("@opentelemetry/semantic-conventions");

const { registerInstrumentations } = require("@opentelemetry/instrumentation");

// Import OpenTelemetry instrumentations
const { HttpInstrumentation } = require("@opentelemetry/instrumentation-http");
const {
  ExpressInstrumentation,
} = require("@opentelemetry/instrumentation-express");

var API_ENDPOINT_FAVORITES =
  process.env.API_ENDPOINT_FAVORITES || "127.0.0.1:5000";
API_ENDPOINT_FAVORITES = API_ENDPOINT_FAVORITES.split(",");

const OTEL_EXPORTER_OTLP_HEADERS = process.env.OTEL_EXPORTER_OTLP_HEADERS;
// error if secret token is not set
if (!OTEL_EXPORTER_OTLP_HEADERS) {
  throw new Error("OTEL_EXPORTER_OTLP_HEADERS environment variable is not set");
}

const OTEL_EXPORTER_OTLP_ENDPOINT = process.env.OTEL_EXPORTER_OTLP_ENDPOINT;
// error if server url is not set
if (!OTEL_EXPORTER_OTLP_ENDPOINT) {
  throw new Error(
    "OTEL_EXPORTER_OTLP_ENDPOINT environment variable is not set"
  );
}

const collectorOptions = {
  // url is optional and can be omitted - default is http://localhost:4317
  // Unix domain sockets are also supported: 'unix:///path/to/socket.sock'
  url: OTEL_EXPORTER_OTLP_ENDPOINT,
  headers: OTEL_EXPORTER_OTLP_HEADERS,
};

const envAttributes = process.env.OTEL_RESOURCE_ATTRIBUTES || "";

// Parse the environment variable string into an object
const attributes = envAttributes.split(",").reduce((acc, curr) =&gt; {
  const [key, value] = curr.split("=");
  if (key &amp;&amp; value) {
    acc[key.trim()] = value.trim();
  }
  return acc;
}, {});

// Create and configure the resource object
const resource = new Resource({
  [SemanticResourceAttributes.SERVICE_NAME]:
    attributes["service.name"] || "node-server-otel-manual",
  [SemanticResourceAttributes.SERVICE_VERSION]:
    attributes["service.version"] || "1.0.0",
  [SemanticResourceAttributes.DEPLOYMENT_ENVIRONMENT]:
    attributes["deployment.environment"] || "production",
});

// Create and configure the tracer provider
const tracerProvider = new NodeTracerProvider({
  resource: resource,
});
const exporter = new OTLPTraceExporter(collectorOptions);
tracerProvider.addSpanProcessor(new BatchSpanProcessor(exporter));
tracerProvider.register();

//Register instrumentations
registerInstrumentations({
  instrumentations: [new HttpInstrumentation(), new ExpressInstrumentation()],
  tracerProvider: tracerProvider,
});

const express = require("express");
const cors = require("cors")({ origin: true });
const cookieParser = require("cookie-parser");
const { json } = require("body-parser");

const PORT = process.env.PORT || 3001;

const app = express().use(cookieParser(), cors, json(), expressPino);

const axios = require("axios");

app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use((err, req, res, next) =&gt; {
  log.error(err.stack);
  span = opentelemetry.trace.getActiveSpan();
  span.recordException(error);
  span.end();
  res.status(500).json({ error: err.message, code: err.code });
});

const tracer = opentelemetry.trace.getTracer();

var favorites = {};

app.post("/api/favorites", (req, res, next) =&gt; {
  tracer.startActiveSpan("favorites", (span) =&gt; {
    var randomIndex = Math.floor(Math.random() * API_ENDPOINT_FAVORITES.length);

    if (
      process.env.THROW_NOT_A_FUNCTION_ERROR == "true" &amp;&amp;
      Math.random() &lt; 0.5
    ) {
      // randomly choose one of the endpoints
      axios
        .post(
          "http://" +
            API_ENDPOINT_FAVORITES[randomIndex] +
            "/favorites?user_id=1",
          req.body
        )
        .then(function (response) {
          favorites = response.data;
          // quiz solution: "42"
          span.end();
          res.jsonn({ favorites: favorites });
        })
        .catch(next);
    } else {
      axios
        .post(
          "http://" +
            API_ENDPOINT_FAVORITES[randomIndex] +
            "/favorites?user_id=1",
          req.body
        )
        .then(function (response) {
          favorites = response.data;
          span.end();
          res.json({ favorites: favorites });
        })
        .catch(next);
    }
  });
});

app.listen(PORT, () =&gt; {
  log.info(`Server listening on ${PORT}`);
});
</code></pre>
<h3 id="step3runningthedockerimagewithenvironmentvariables">Step 3. Running the Docker image with environment variables</h3>
<p>We will use environment variables and pass in the configuration values to enable it to connect with <a href="https://www.elastic.co/guide/en/observability/current/apm-open-telemetry.html">Elastic Observability’s APM server</a>.</p>
<p>Because Elastic accepts OTLP natively, we just need to provide the Endpoint and authentication where the OTEL Exporter needs to send the data, as well as some other environment variables.</p>
<p><strong>Getting Elastic Cloud variables</strong><br />
You can copy the endpoints and token from Kibana<sup>®</sup> under the path <code>/app/home#/tutorial/apm</code>.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt118d2bf7fe9c04d2/6a85ccf580984cd3c2669010/elastic-blog-3-apm.png" alt="apm" /></p>
<p>You will need to copy the following environment variables:</p>
<pre><code>OTEL_EXPORTER_OTLP_ENDPOINT
OTEL_EXPORTER_OTLP_HEADERS
</code></pre>
<p><strong>Build the image</strong></p>
<pre><code>docker build -t  node-otel-manual-image .
</code></pre>
<p><strong>Run the image</strong></p>
<pre><code>docker run \
       -e OTEL_EXPORTER_OTLP_ENDPOINT="&lt;REPLACE WITH OTEL_EXPORTER_OTLP_ENDPOINT&gt;" \
       -e OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer &lt;REPLACE WITH TOKEN&gt;" \
       -e OTEL_RESOURCE_ATTRIBUTES="service.version=1.0,deployment.environment=production,service.name=node-server-otel-manual" \
       -p 3001:3001 \
       node-otel-manual-image
</code></pre>
<p>You can now issue a few requests in order to generate trace data. Note that these requests are expected to return an error, as this service relies on some downstream services that you may not have running on your machine.</p>
<pre><code>curl localhost:3001/api/login
curl localhost:3001/api/favorites

# or alternatively issue a request every second

while true; do curl "localhost:3001/api/favorites"; sleep 1; done;
</code></pre>
<h3 id="step4exploreinelasticapm">Step 4. Explore in Elastic APM</h3>
<p>Now that the service is instrumented, you should see the following output in Elastic APM when looking at the transactions section of your Node.js service:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9df327136c5c23fe/6a85ccf89a32f10f50a7e022/elastic-blog-4-graphs.png" alt="graphs" /></p>
<p>Notice how this mirrors the auto-instrumented version.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9df327136c5c23fe/6a85ccf89a32f10f50a7e022/elastic-blog-4-graphs.png" alt="graphs-2" /></p>
<h2 id="isitworthit">Is it worth it?</h2>
<p>This is the million-dollar question. Depending on what level of detail you need, it's potentially necessary to manually instrument. Manual instrumentation lets you add custom spans, custom labels, and metrics where you want or need them. It allows you to get a level of detail that otherwise would not be possible and is oftentimes important for tracking business-specific KPIs.</p>
<p>Your operations, and whether you need to troubleshoot or analyze the performance of specific parts of the code, will dictate when and what to instrument. But it’s helpful to know that you have the option to manually instrument.</p>
<p>If you noticed we didn’t yet instrument metrics, that is another blog. We discussed logs in a <a href="https://www.elastic.co/blog/3-models-logging-opentelemetry-elastic">previous blog</a>.</p>
<h2 id="conclusion">Conclusion</h2>
<p>In this blog, we discussed the following:</p>
<ul>
<li>How to manually instrument Node.js with OpenTelemetry</li>
<li>The different modules needed when using Express</li>
<li>How to properly initialize and instrument span</li>
<li>How to easily set the OTLP ENDPOINT and OTLP HEADERS from Elastic without the need for a collector</li>
</ul>
<p>Hopefully, this provides an easy-to-understand walk-through of instrumenting Node.js with OpenTelemetry and how easy it is to send traces into Elastic.</p>
<blockquote>
  <p>Developer resources:</p>
  <ul>
  <li><a href="https://www.elastic.co/blog/getting-started-opentelemetry-instrumentation-sample-app">Elastiflix application</a>, a guide to instrument different languages with OpenTelemetry</li>
  <li>Python: <a href="https://www.elastic.co/blog/auto-instrumentation-of-python-applications-opentelemetry">Auto-instrumentation</a>, <a href="https://www.elastic.co/blog/manual-instrumentation-of-python-applications-opentelemetry">Manual-instrumentation</a></li>
  <li>Java: <a href="https://www.elastic.co/blog/auto-instrumentation-of-java-applications-opentelemetry">Auto-instrumentation</a>, <a href="https://www.elastic.co/blog/manual-instrumentation-of-java-applications-opentelemetry">Manual-instrumentation</a></li>
  <li>Node.js: <a href="https://www.elastic.co/blog/auto-instrument-nodejs-applications-opentelemetry">Auto-instrumentation</a>, <a href="https://www.elastic.co/observability-labs/blog/manual-instrumentation-nodejs-apps-opentelemetry">Manual-instrumentation</a></li>
  <li>.NET: <a href="https://www.elastic.co/blog/auto-instrumentation-of-net-applications-opentelemetry">Auto-instrumentation</a>, <a href="https://www.elastic.co/blog/manual-instrumentation-of-net-applications-opentelemetry">Manual-instrumentation</a></li>
  <li>Go: <a href="https://elastic.co/blog/manual-instrumentation-of-go-applications-opentelemetry">Manual-instrumentation</a></li>
  <li><a href="https://www.elastic.co/blog/best-practices-instrumenting-opentelemetry">Best practices for instrumenting OpenTelemetry</a></li>
  </ul>
  <p>General configuration and use case resources:</p>
  <ul>
  <li><a href="https://www.elastic.co/blog/opentelemetry-observability">Independence with OpenTelemetry on Elastic</a></li>
  <li><a href="https://www.elastic.co/blog/implementing-kubernetes-observability-security-opentelemetry">Modern observability and security on Kubernetes with Elastic and OpenTelemetry</a></li>
  <li><a href="https://www.elastic.co/blog/3-models-logging-opentelemetry-elastic">3 models for logging with OpenTelemetry and Elastic</a></li>
  <li><a href="https://www.elastic.co/blog/adding-free-and-open-elastic-apm-as-part-of-your-elastic-observability-deployment">Adding free and open Elastic APM as part of your Elastic Observability deployment</a></li>
  <li><a href="https://www.elastic.co/blog/custom-metrics-app-code-java-agent-plugin">Capturing custom metrics through OpenTelemetry API in code with Elastic</a></li>
  <li><a href="https://www.elastic.co/virtual-events/future-proof-your-observability-platform-with-opentelemetry-and-elastic">Future-proof your observability platform with OpenTelemetry and Elastic</a></li>
  <li><a href="https://www.elastic.co/blog/kubernetes-k8s-observability-elasticsearch-cncf">Elastic Observability: Built for open technologies like Kubernetes, OpenTelemetry, Prometheus, Istio, and more</a></li>
  </ul>
</blockquote>
<p>Don’t have an Elastic Cloud account yet? <a href="https://cloud.elastic.co/registration">Sign up for Elastic Cloud</a> and try out the auto-instrumentation capabilities that I discussed above. I would be interested in getting your feedback about your experience in gaining visibility into your application stack 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/manual-instrumentation-nodejs-apps-opentelemetry</link>
    <guid isPermaLink="false">manual-instrumentation-nodejs-apps-opentelemetry</guid>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[APM]]></category>
    <category><![CDATA[Infrastructure Monitoring]]></category>
    <dc:creator><![CDATA[Bahubali Shetti]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt20f27145e9c4a798/6a85ccfc9bf994191f0a05a9/observability-launch-series-1-node-js-manual_(1).jpg" length="0" type="image/jpeg"/>
    <pubDate>Thu, 31 Aug 2023 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Manual instrumentation of Java applications with OpenTelemetry]]></title>
    <description><![CDATA[OpenTelemetry provides an observability framework for cloud-native software, allowing us to trace, monitor, and debug applications seamlessly. In this post, we'll explore how to manually instrument a Java application using OpenTelemetry.]]></description>
    <content:encoded><![CDATA[<p>In the fast-paced universe of software development, especially in the cloud-native realm, DevOps and SRE teams are increasingly emerging as essential partners in application stability and growth.</p>
<p>DevOps engineers continuously optimize software delivery, while SRE teams act as the stewards of application reliability, scalability, and top-tier performance. The challenge? These teams require a cutting-edge observability solution, one that encompasses full-stack insights, empowering them to rapidly manage, monitor, and rectify potential disruptions before they culminate into operational challenges.</p>
<p>Observability in our modern distributed software ecosystem goes beyond mere monitoring—it demands limitless data collection, precision in processing, and the correlation of this data into actionable insights. However, the road to achieving this holistic view is paved with obstacles: from navigating version incompatibilities to wrestling with restrictive proprietary code.</p>
<p>Enter <a href="https://opentelemetry.io/">OpenTelemetry (OTel)</a>, with the following benefits for those who adopt it:</p>
<ul>
<li>Escape vendor constraints with OTel, freeing yourself from vendor lock-in and ensuring top-notch observability.</li>
<li>See the harmony of unified logs, metrics, and traces come together to provide a complete system view.</li>
<li>Improve your application oversight through richer and enhanced instrumentations.</li>
<li>Embrace the benefits of backward compatibility to protect your prior instrumentation investments.</li>
<li>Embark on the OpenTelemetry journey with an easy learning curve, simplifying onboarding and scalability.</li>
<li>Rely on a proven, future-ready standard to boost your confidence in every investment.</li>
</ul>
<p>In this blog, we will explore how you can use <a href="https://opentelemetry.io/docs/instrumentation/java/manual/">manual instrumentation in your Java</a> application using Docker, without the need to refactor any part of your application code. We will use an <a href="https://github.com/elastic/observability-examples">application called Elastiflix</a>. This approach is slightly more complex than using <a href="https://www.elastic.co/blog/auto-instrumentation-of-java-applications-opentelemetry">automatic instrumentation</a>.</p>
<p>The beauty of this is that there is <strong>no need for the otel-collector</strong>! This setup enables you to slowly and easily migrate an application to OTel with Elastic according to a timeline that best fits your business.</p>
<h2 id="applicationprerequisitesandconfig">Application, prerequisites, and config</h2>
<p>The application that we use for this blog is called <a href="https://github.com/elastic/observability-examples">Elastiflix</a>, a movie streaming application. It consists of several micro-services written in .NET, NodeJS, Go, and Python.</p>
<p>Before we instrument our sample application, we will first need to understand how Elastic can receive the telemetry data.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1a6d077c474076c7/6a85ccc2501a859004fbb36b/elastic-blog-1-config.png" alt="Elastic configuration options for OpenTelemetry" /></p>
<p>All of Elastic Observability’s APM capabilities are available with OTel data. Some of these include:</p>
<ul>
<li>Service maps</li>
<li>Service details (latency, throughput, failed transactions)</li>
<li>Dependencies between services, distributed tracing</li>
<li>Transactions (traces)</li>
<li>Machine learning (ML) correlations</li>
<li>Log correlation</li>
</ul>
<p>In addition to Elastic’s APM and a unified view of the telemetry data, you will also be able to use Elastic’s powerful machine learning capabilities to reduce the analysis, and alerting to help reduce MTTR.</p>
<h2 id="prerequisites">Prerequisites</h2>
<ul>
<li>An Elastic Cloud account — <a href="https://cloud.elastic.co/">sign up now</a></li>
<li>A clone of the <a href="https://github.com/elastic/observability-examples">Elastiflix demo application</a>, or your own Java application</li>
<li>Basic understanding of Docker — potentially install <a href="https://www.docker.com/products/docker-desktop/">Docker Desktop</a></li>
<li>Basic understanding of Java</li>
</ul>
<h2 id="viewtheexamplesourcecode">View the example source code</h2>
<p>The full source code, including the Dockerfile used in this blog, can be found on <a href="https://github.com/elastic/observability-examples/tree/main/Elastiflix/python-favorite-otel-auto">GitHub</a>. The repository also contains the <a href="https://github.com/elastic/observability-examples/tree/main/Elastiflix/python-favorite">same application without instrumentation</a>. This allows you to compare each file and see the differences.</p>
<p>In particular, we will be working through the following file:</p>
<pre><code>Elastiflix/java-favorite/src/main/java/com/movieapi/ApiServlet.java
</code></pre>
<p>The following steps will show you how to instrument this application and run it on the command line or in Docker. If you are interested in a more complete OTel example, take a look at the docker-compose file <a href="https://github.com/elastic/observability-examples/tree/main#start-the-app">here</a>, which will bring up the full project.</p>
<p>Before we begin, let’s look at the non-instrumented code first.</p>
<h2 id="stepbystepguide">Step-by-step guide</h2>
<h3 id="step0logintoyourelasticcloudaccount">Step 0. Log in to your Elastic Cloud account</h3>
<p>This blog assumes you have an Elastic Cloud account — if not, follow the <a href="https://cloud.elastic.co/registration?elektra=en-cloud-page">instructions to get started on Elastic Cloud</a>.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt593b4c58f88c6eda/6a85ccc5982926f0f6583926/elastic-blog-2-trial.png" alt="trial" /></p>
<h3 id="step1setupopentelemetry">Step 1. Set up OpenTelemetry</h3>
<p>The first step is to set up the OpenTelemetry SDK in your Java application. You can start by adding the OpenTelemetry Java SDK and its dependencies to your project's build file, such as Maven or Gradle. In our example application, we are using Maven. Add the dependencies below to your pom.xml:</p>
<pre><code>&lt;dependency&gt;
      &lt;groupId&gt;io.opentelemetry.instrumentation&lt;/groupId&gt;
      &lt;artifactId&gt;opentelemetry-logback-mdc-1.0&lt;/artifactId&gt;
      &lt;version&gt;1.25.1-alpha&lt;/version&gt;
    &lt;/dependency&gt;

    &lt;dependency&gt;
      &lt;groupId&gt;io.opentelemetry&lt;/groupId&gt;
      &lt;artifactId&gt;opentelemetry-api&lt;/artifactId&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
      &lt;groupId&gt;io.opentelemetry&lt;/groupId&gt;
      &lt;artifactId&gt;opentelemetry-sdk&lt;/artifactId&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
      &lt;groupId&gt;io.opentelemetry&lt;/groupId&gt;
      &lt;artifactId&gt;opentelemetry-exporter-otlp&lt;/artifactId&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
      &lt;groupId&gt;io.opentelemetry&lt;/groupId&gt;
      &lt;artifactId&gt;opentelemetry-semconv&lt;/artifactId&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
      &lt;groupId&gt;io.opentelemetry&lt;/groupId&gt;
      &lt;artifactId&gt;opentelemetry-exporter-otlp-logs&lt;/artifactId&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
      &lt;groupId&gt;io.opentelemetry.instrumentation&lt;/groupId&gt;
      &lt;artifactId&gt;opentelemetry-logback-appender-1.0&lt;/artifactId&gt;
      &lt;version&gt;1.25.1-alpha&lt;/version&gt;
    &lt;/dependency&gt;
</code></pre>
<p>And add the following bill of materials from OpenTelemetry too:</p>
<pre><code>&lt;dependencyManagement&gt;
    &lt;dependencies&gt;
      &lt;dependency&gt;
        &lt;groupId&gt;io.opentelemetry&lt;/groupId&gt;
        &lt;artifactId&gt;opentelemetry-bom&lt;/artifactId&gt;
        &lt;version&gt;1.25.0&lt;/version&gt;
        &lt;type&gt;pom&lt;/type&gt;
        &lt;scope&gt;import&lt;/scope&gt;
      &lt;/dependency&gt;
      &lt;dependency&gt;
        &lt;groupId&gt;io.opentelemetry&lt;/groupId&gt;
        &lt;artifactId&gt;opentelemetry-bom-alpha&lt;/artifactId&gt;
        &lt;version&gt;1.25.0-alpha&lt;/version&gt;
        &lt;type&gt;pom&lt;/type&gt;
        &lt;scope&gt;import&lt;/scope&gt;
      &lt;/dependency&gt;
    &lt;/dependencies&gt;
  &lt;/dependencyManagement&gt;
</code></pre>
<h3 id="step2addtheapplicationconfiguration">Step 2. Add the application configuration</h3>
<p>We recommend that you add the following configuration to the application’s main method, to start before any application code. Doing it like this gives you a bit more control and flexibility and ensures that OpenTelemetry will be available at any stage of the application lifecycle. In the examples, we put this code before the Spring Boot Application startup. Elastic supports OTLP over HTTP and OTLP over GRPC. In this example, we are using GRPC.</p>
<pre><code>String SERVICE_NAME = System.getenv("OTEL_SERVICE_NAME");

// set service name on all OTel signals
Resource resource = Resource.getDefault().merge(Resource.create(Attributes.of(ResourceAttributes.SERVICE_NAME,SERVICE_NAME,ResourceAttributes.SERVICE_VERSION,"1.0",ResourceAttributes.DEPLOYMENT_ENVIRONMENT,"production")));

// init OTel logger provider with export to OTLP
SdkLoggerProvider sdkLoggerProvider = SdkLoggerProvider.builder().setResource(resource).addLogRecordProcessor(BatchLogRecordProcessor.builder(OtlpGrpcLogRecordExporter.builder().setEndpoint(System.getenv("OTEL_EXPORTER_OTLP_ENDPOINT")).addHeader("Authorization", "Bearer " + System.getenv("ELASTIC_APM_SECRET_TOKEN")).build()).build()).build();

// init OTel trace provider with export to OTLP
SdkTracerProvider sdkTracerProvider = SdkTracerProvider.builder().setResource(resource).setSampler(Sampler.alwaysOn()).addSpanProcessor(BatchSpanProcessor.builder(OtlpGrpcSpanExporter.builder().setEndpoint(System.getenv("OTEL_EXPORTER_OTLP_ENDPOINT")).addHeader("Authorization", "Bearer " + System.getenv("ELASTIC_APM_SECRET_TOKEN")).build()).build()).build();

// init OTel meter provider with export to OTLP
SdkMeterProvider sdkMeterProvider = SdkMeterProvider.builder().setResource(resource).registerMetricReader(PeriodicMetricReader.builder(OtlpGrpcMetricExporter.builder().setEndpoint(System.getenv("OTEL_EXPORTER_OTLP_ENDPOINT")).addHeader("Authorization", "Bearer " + System.getenv("ELASTIC_APM_SECRET_TOKEN")).build()).build()).build();

// create sdk object and set it as global
OpenTelemetrySdk sdk = OpenTelemetrySdk.builder().setTracerProvider(sdkTracerProvider).setLoggerProvider(sdkLoggerProvider).setMeterProvider(sdkMeterProvider).setPropagators(ContextPropagators.create(W3CTraceContextPropagator.getInstance())).build();

GlobalOpenTelemetry.set(sdk);
// connect logger
GlobalLoggerProvider.set(sdk.getSdkLoggerProvider());
// Add hook to close SDK, which flushes logs
Runtime.getRuntime().addShutdownHook(new Thread(sdk::close));
</code></pre>
<h3 id="step3createthetracerandstarttheopentelemetryspaninsidethetracingfilter">Step 3. Create the Tracer and start the OpenTelemetry Span inside the TracingFilter</h3>
<p>In the Spring Boot, example you will notice that we have a TracingFilter class which extends the OncePerRequestFilter class. This Filter is a component placed at the front of the request processing chain. Its primary roles are to intercept incoming requests and outgoing responses, performing tasks such as logging, authentication, transformation of request/response entities, and more. So what we do here is intercept the request as it comes into the Favorite service, so that we can pull out the headers which may contain tracing information from upstream systems.</p>
<p>We start by using the OpenTelemetry Tracer, which is a core component of OpenTelemetry that allows you to create spans, start and stop them, and add attributes and events. In your Java code, import the necessary OpenTelemetry classes and create an instance of the Tracer within your application.</p>
<p>We use this to create a new downstream span, which will continue as a child from the span created in the upstream system using the information we got from the upstream request. In our Elastiflix example, this will be the nodejs application.</p>
<pre><code>@Override
protected void doFilterInternal(jakarta.servlet.http.HttpServletRequest request, jakarta.servlet.http.HttpServletResponse response, jakarta.servlet.FilterChain filterChain) throws jakarta.servlet.ServletException, IOException {
        Tracer tracer = GlobalOpenTelemetry.getTracer(SERVICE_NAME);

        Context extractedContext = GlobalOpenTelemetry.getPropagators()
                .getTextMapPropagator()
                .extract(Context.current(), request, getter);

        Span span = tracer.spanBuilder(request.getRequestURI())
                .setSpanKind(SpanKind.SERVER)
                .setParent(extractedContext)
                .startSpan();

        try (Scope scope = span.makeCurrent()) {
            filterChain.doFilter(request, response);
        } catch (Exception e) {
            span.setStatus(StatusCode.ERROR);
            throw e;
        } finally {
            span.end();
        }
    }
</code></pre>
<h3 id="step4instrumentotherinterestingcodewithspans">Step 4. Instrument other interesting code with spans</h3>
<p>To instrument with spans and track specific regions of your code, you can use the Tracer's SpanBuilder to create spans. To accurately measure the duration of a specific operation, make sure to start and stop the spans at the appropriate locations in your code. Use the startSpan and endSpan methods provided by the Tracer to mark the beginning and end of the span. For example, you can create a span around a specific method or operation in your code, as shown here in the handleCanary method:</p>
<pre><code>private void handleCanary() throws Exception {
        Span span = GlobalOpenTelemetry.getTracer(SERVICE_NAME).spanBuilder("handleCanary").startSpan();
        Scope scope = span.makeCurrent();

///.....


 span.setStatus(StatusCode.OK);

        span.end();

        scope.close();
    }
</code></pre>
<h3 id="step5addattributesandeventstospans">Step 5. Add attributes and events to spans</h3>
<p>You can enhance the spans with additional attributes and events to provide more context and details about the operation being tracked. Attributes can be key-value pairs that describe the span, while events can be used to mark significant points in the span's lifecycle. This is also shown in the handleCanary method:</p>
<pre><code>private void handleCanary() throws Exception {

            Span.current().setAttribute("canary", "test-new-feature");
            Span.current().setAttribute("quiz_solution", "correlations");

            span.addEvent("a span event", Attributes
                    .of(AttributeKey.longKey("someKey"), Long.valueOf(93)));
    }
</code></pre>
<h3 id="step6instrumentbackends">Step 6. Instrument backends</h3>
<p>Let's consider an example where we are instrumenting a Redis database call. We're using the Java OpenTelemetry SDK, and our goal is to create a trace that captures each "Post User Favorites" operation to the database.</p>
<p>Below is the Java method that performs the operation and collects telemetry data:</p>
<pre><code>public void postUserFavorites(String user_id, String movieID) {
  ...
}
</code></pre>
<p>Let's go through it line by line:</p>
<p><strong>Initializing a span</strong><br />
The first important line of our method is where we initialize a span. A span represents a single operation within a trace, which could be a database call, a remote procedure call (RPC), or any segment of code that you want to measure.</p>
<pre><code>Span span = GlobalOpenTelemetry.getTracer(SERVICE_NAME).spanBuilder("Redis.Post").setSpanKind(SpanKind.CLIENT).startSpan();
</code></pre>
<p><strong>Setting span attributes</strong><br />
Next, we add attributes to our span. Attributes are key-value pairs that provide additional information about the span. In order to get the backend call to appear correctly in the service map, it is critical that the attributes are set correctly for the backend call type. In this example, we set the db.system attribute to redis.</p>
<pre><code>span.setAttribute("db.system", "redis");
span.setAttribute("db.connection_string", redisHost);
span.setAttribute(
  "db.statement",
  "POST user_id " + user_id + " AND movie_id " + movieID
);
</code></pre>
<p>This will ensure calls to the backend redis backend are tracked as shown below:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt959d425fb0f0a065/6a85ccc8f5f1a08e522ec941/elastic-blog-3-flowchart.png" alt="flowchart" /></p>
<p><strong>Capturing the result of the operation</strong><br />
We then execute the operation we're interested in, within a try-catch block. If an exception occurs during the execution of the operation, we record it in the span.</p>
<pre><code>try (Scope scope = span.makeCurrent()) {
    ...
} catch (Exception e) {
    span.setStatus(StatusCode.ERROR, "Error while getting data from Redis");
    span.recordException(e);
}
</code></pre>
<p><strong>Closing resources</strong><br />
Finally, we close the Redis connection and end the span.</p>
<pre><code>finally {
    jedis.close();
    span.end();
}
</code></pre>
<h3 id="step7configurelogging">Step 7. Configure logging</h3>
<p>Logging is an essential part of application monitoring and troubleshooting. OpenTelemetry allows you to integrate with existing logging frameworks, such as Logback or Log4j, to capture logs along with the telemetry data. Configure the logging framework of your choice to capture logs related to the instrumented spans. In our example application, check out the logback configuration, which shows how to export logs directly to Elastic.</p>
<pre><code>&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;configuration debug="true"&gt;

    &lt;appender name="otel-otlp"
        class="io.opentelemetry.instrumentation.logback.appender.v1_0.OpenTelemetryAppender"&gt;
        &lt;captureExperimentalAttributes&gt;false&lt;/captureExperimentalAttributes&gt;
        &lt;captureCodeAttributes&gt;true&lt;/captureCodeAttributes&gt;
        &lt;captureKeyValuePairAttributes&gt;true&lt;/captureKeyValuePairAttributes&gt;
    &lt;/appender&gt;

    &lt;appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"&gt;
        &lt;encoder&gt;
            &lt;pattern&gt;%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n&lt;/pattern&gt;
        &lt;/encoder&gt;
    &lt;/appender&gt;

    &lt;root level="DEBUG"&gt;
     &lt;appender-ref ref="otel-otlp" /&gt;
        &lt;appender-ref ref="STDOUT" /&gt;

    &lt;/root&gt;
&lt;/configuration&gt;
</code></pre>
<h3 id="step8runningthedockerimagewithenvironmentvariables">Step 8. Running the Docker image with environment variables</h3>
<p>As specified in the <a href="https://opentelemetry.io/docs/instrumentation/java/automatic/">OTEL Java documentation</a>, we will use environment variables and pass in the configuration values to enable it to connect with <a href="https://www.elastic.co/guide/en/apm/guide/current/open-telemetry.html">Elastic Observability’s APM server</a>.</p>
<p>Because Elastic accepts OTLP natively, we just need to provide the Endpoint and authentication where the OTEL Exporter needs to send the data, as well as some other environment variables.</p>
<p><strong>Getting Elastic Cloud variables</strong><br />
You can copy the endpoints and token from Kibana under the path <code>/app/home#/tutorial/apm</code>.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta28447a53d11f965/6a85ccca33f2447adb49f54b/elastic-blog-3-apm.png" alt="apm agents" /></p>
<p>You will need to copy the following environment variable:</p>
<pre><code>OTEL_EXPORTER_OTLP_ENDPOINT
</code></pre>
<p>As well as the token from:</p>
<pre><code>OTEL_EXPORTER_OTLP_HEADERS
</code></pre>
<p><strong>Build the Docker image</strong></p>
<pre><code>docker build -t java-otel-manual-image .
</code></pre>
<p><strong>Run the Docker image</strong></p>
<pre><code>docker run \
       -e OTEL_EXPORTER_OTLP_ENDPOINT="REPLACE WITH OTEL_EXPORTER_OTLP_ENDPOINT" \
       -e ELASTIC_APM_SECRET_TOKEN="REPLACE WITH TOKEN" \
       -e OTEL_RESOURCE_ATTRIBUTES="service.version=1.0,deployment.environment=production" \
       -e OTEL_SERVICE_NAME="java-favorite-otel-manual" \
       -p 5000:5000 \
       java-otel-manual-image
</code></pre>
<p>You can now issue a few requests in order to generate trace data. Note that these requests are expected to return an error, as this service relies on a connection to Redis that you don’t currently have running. As mentioned before, you can find a more complete example using docker-compose <a href="https://github.com/elastic/observability-examples/tree/main/Elastiflix">here</a>.</p>
<pre><code>curl localhost:5000/favorites

# or alternatively issue a request every second

while true; do curl "localhost:5000/favorites"; sleep 1; done;
</code></pre>
<h3 id="step9exploretracesandlogsinelasticapm">Step 9. Explore traces and logs in Elastic APM</h3>
<p>Once you have this up and running, you can ping the endpoint for your instrumented service (in our case, this is /favorites), and you should see the app appear in Elastic APM, as shown below:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7109b37a59669a3c/6a85ccce331d7ae811c317db/elastic-blog-5-services.png" alt="services" /></p>
<p>It will begin by tracking throughput and latency critical metrics for SREs to pay attention to.</p>
<p>Digging in, we can see an overview of all our Transactions.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte8073add1961dc20/6a85ccd111893c48e9a7abba/elastic-blog-6-java-fave-otel.png" alt="java favorite otel graph" /></p>
<p>And look at specific transactions:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc65873c14d47ac1c/6a85ccd4342d6992fb21b127/elastic-blog-7-graph1.png" alt="graph2" /></p>
<p>Click on <strong>Logs</strong> , and we see that logs are also brought over. The OTel Agent will automatically bring in logs and correlate them with traces for you:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltbf3006d2bd96ea91/6a85ccd7682666dca91eac47/elastic-blog-8-graph2.png" alt="graph3" /></p>
<p>This gives you complete visibility across logs, metrics, and traces!</p>
<h2 id="wrappingup">Wrapping up</h2>
<p>Manually instrumenting your Java applications with OpenTelemetry gives you greater control over what to track and monitor. By following the steps outlined in this blog post, you can effectively monitor the performance of your Java applications, identify issues, and gain insights into the overall health of your application.</p>
<p>Remember, OpenTelemetry is a powerful tool, and proper instrumentation requires careful consideration of what metrics, traces, and logs are essential for your specific use case. Experiment with different configurations, leverage the OpenTelemetry SDK for Java documentation, and continuously iterate to achieve the observability goals of your application.</p>
<p>In this blog, we discussed the following:</p>
<ul>
<li>How to manually instrument Java with OpenTelemetry</li>
<li>How to properly initialize and instrument span</li>
<li>How to easily set the OTLP ENDPOINT and OTLP HEADERS from Elastic without the need for a collector</li>
</ul>
<p>Hopefully, this provided an easy-to-understand walk-through of instrumenting Java with OpenTelemetry and how easy it is to send traces into Elastic.</p>
<blockquote>
  <p>Developer resources:</p>
  <ul>
  <li><a href="https://www.elastic.co/blog/getting-started-opentelemetry-instrumentation-sample-app">Elastiflix application</a>, a guide to instrument different languages with OpenTelemetry</li>
  <li>Python: <a href="https://www.elastic.co/blog/auto-instrumentation-of-python-applications-opentelemetry">Auto-instrumentation</a>, <a href="https://www.elastic.co/blog/manual-instrumentation-of-python-applications-opentelemetry">Manual-instrumentation</a></li>
  <li>Java: <a href="https://www.elastic.co/blog/auto-instrumentation-of-java-applications-opentelemetry">Auto-instrumentation</a>, <a href="https://www.elastic.co/blog/manual-instrumentation-java-apps-opentelemetry">Manual-instrumentation</a></li>
  <li>Node.js: <a href="https://www.elastic.co/blog/auto-instrument-nodejs-applications-opentelemetry">Auto-instrumentation</a>, <a href="https://www.elastic.co/blog/manual-instrumentation-of-nodejs-applications-opentelemetry">Manual-instrumentation</a></li>
  <li>.NET: <a href="https://www.elastic.co/blog/auto-instrumentation-of-net-applications-opentelemetry">Auto-instrumentation</a>, <a href="https://www.elastic.co/blog/manual-instrumentation-of-net-applications-opentelemetry">Manual-instrumentation</a></li>
  <li>Go: <a href="https://elastic.co/blog/manual-instrumentation-of-go-applications-opentelemetry">Manual-instrumentation</a></li>
  <li><a href="https://www.elastic.co/blog/best-practices-instrumenting-opentelemetry">Best practices for instrumenting OpenTelemetry</a></li>
  </ul>
  <p>General configuration and use case resources:</p>
  <ul>
  <li><a href="https://www.elastic.co/blog/opentelemetry-observability">Independence with OpenTelemetry on Elastic</a></li>
  <li><a href="https://www.elastic.co/blog/implementing-kubernetes-observability-security-opentelemetry">Modern observability and security on Kubernetes with Elastic and OpenTelemetry</a></li>
  <li><a href="https://www.elastic.co/blog/3-models-logging-opentelemetry-elastic">3 models for logging with OpenTelemetry and Elastic</a></li>
  <li><a href="https://www.elastic.co/blog/adding-free-and-open-elastic-apm-as-part-of-your-elastic-observability-deployment">Adding free and open Elastic APM as part of your Elastic Observability deployment</a></li>
  <li><a href="https://www.elastic.co/blog/custom-metrics-app-code-java-agent-plugin">Capturing custom metrics through OpenTelemetry API in code with Elastic</a></li>
  <li><a href="https://www.elastic.co/virtual-events/future-proof-your-observability-platform-with-opentelemetry-and-elastic">Future-proof your observability platform with OpenTelemetry and Elastic</a></li>
  <li><a href="https://www.elastic.co/blog/kubernetes-k8s-observability-elasticsearch-cncf">Elastic Observability: Built for open technologies like Kubernetes, OpenTelemetry, Prometheus, Istio, and more</a></li>
  </ul>
</blockquote>
<p>Don’t have an Elastic Cloud account yet? <a href="https://cloud.elastic.co/registration">Sign up for Elastic Cloud</a> and try out the auto-instrumentation capabilities that I discussed above. I would be interested in getting your feedback about your experience in gaining visibility into your application stack 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/manual-instrumentation-java-apps-opentelemetry</link>
    <guid isPermaLink="false">manual-instrumentation-java-apps-opentelemetry</guid>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[APM]]></category>
    <category><![CDATA[Infrastructure Monitoring]]></category>
    <dc:creator><![CDATA[David Hope]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6bb53438aa2f6928/6a85ccdaf61d6e405e9c2b53/observability-launch-series-3-java-manual.jpg" length="0" type="image/jpeg"/>
    <pubDate>Thu, 31 Aug 2023 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Automatic instrumentation with OpenTelemetry for Python applications]]></title>
    <description><![CDATA[Learn how to auto-instrument Python applications using OpenTelemetry. With standard commands in a Docker file, applications can be instrumented quickly without writing code in multiple places, enabling rapid change, scale, and easier management.]]></description>
    <content:encoded><![CDATA[<p>DevOps and SRE teams are transforming the process of software development. While DevOps engineers focus on efficient software applications and service delivery, SRE teams are key to ensuring reliability, scalability, and performance. These teams must rely on a full-stack observability solution that allows them to manage and monitor systems and ensure issues are resolved before they impact the business.</p>
<p>Observability across the entire stack of modern distributed applications requires data collection, processing, and correlation often in the form of dashboards. Ingesting all system data requires installing agents across stacks, frameworks, and providers — a process that can be challenging and time-consuming for teams who have to deal with version changes, compatibility issues, and proprietary code that doesn't scale as systems change.</p>
<p>Thanks to <a href="http://opentelemetry.io">OpenTelemetry</a> (OTel), DevOps and SRE teams now have a standard way to collect and send data that doesn't rely on proprietary code and has a large support community reducing vendor lock-in.</p>
<p>In a <a href="https://www.elastic.co/blog/opentelemetry-observability">previous blog</a>, we also reviewed how to use the <a href="https://github.com/elastic/opentelemetry-demo">OpenTelemetry demo</a> and connect it to Elastic<sup>®</sup>, as well as some of Elastic’s capabilities with <a href="https://www.elastic.co/observability/opentelemetry">OpenTelemetry visualizations</a> and Kubernetes.</p>
<p>In this blog, we will show how to use <a href="https://opentelemetry.io/docs/instrumentation/python/">automatic instrumentation for OpenTelemetry</a> with the Python service of our <a href="https://github.com/elastic/observability-examples">application called Elastiflix</a>, which helps highlight auto-instrumentation in a simple way.</p>
<p>The beauty of this is that there is <strong>no need for the otel-collector</strong>! This setup enables you to slowly and easily migrate an application to OTel with Elastic according to a timeline that best fits your business.</p>
<h2 id="applicationprerequisitesandconfig">Application, prerequisites, and config</h2>
<p>The application that we use for this blog is called <a href="https://github.com/elastic/observability-examples">Elastiflix</a>, a movie-streaming application. It consists of several micro-services written in .NET, NodeJS, Go, and Python.</p>
<p>Before we instrument our sample application, we will first need to understand how Elastic can receive the telemetry data.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltac807a8013a63051/6a85c80627c5cdaecc5f7384/elastic-blog-1-otel-config-options.png" alt="Elastic configuration options for OpenTelemetry" /></p>
<p>All of Elastic Observability’s APM capabilities are available with OTel data. Some of these include:</p>
<ul>
<li>Service maps</li>
<li>Service details (latency, throughput, failed transactions)</li>
<li>Dependencies between services, distributed tracing</li>
<li>Transactions (traces)</li>
<li>Machine learning (ML) correlations</li>
<li>Log correlation</li>
</ul>
<p>In addition to Elastic’s APM and a unified view of the telemetry data, you will also be able to use Elastic’s powerful machine learning capabilities to reduce the analysis, and alerting to help reduce MTTR.</p>
<h3 id="prerequisites">Prerequisites</h3>
<ul>
<li>An Elastic Cloud account — <a href="https://cloud.elastic.co/">sign up now</a></li>
<li>A clone of the <a href="https://github.com/elastic/observability-examples">Elastiflix demo application</a>, or your own Python application</li>
<li>Basic understanding of Docker — potentially install <a href="https://www.docker.com/products/docker-desktop/">Docker Desktop</a></li>
<li>Basic understanding of Python</li>
</ul>
<h3 id="viewtheexamplesourcecode">View the example source code</h3>
<p>The full source code, including the Dockerfile used in this blog, can be found on <a href="https://github.com/elastic/observability-examples/tree/main/Elastiflix/python-favorite-otel-auto">GitHub</a>. The repository also contains the <a href="https://github.com/elastic/observability-examples/tree/main/Elastiflix/python-favorite">same application without instrumentation</a>. This allows you to compare each file and see the differences.</p>
<p>The following steps will show you how to instrument this application and run it on the command line or in Docker. If you are interested in a more complete OTel example, take a look at the docker-compose file <a href="https://github.com/elastic/observability-examples/tree/main#start-the-app">here</a>, which will bring up the full project.</p>
<h2 id="stepbystepguide">Step-by-step guide</h2>
<h3 id="step0logintoyourelasticcloudaccount">Step 0. Log in to your Elastic Cloud account</h3>
<p>This blog assumes you have an Elastic Cloud account — if not, follow the <a href="https://cloud.elastic.co/registration?elektra=en-cloud-page">instructions to get started on Elastic Cloud</a>.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltbdc1532cbf8a9e1e/6a85c809e2447a53d08b1396/elastic-blog-2-free-trial.png" alt="free trial" /></p>
<h3 id="step1configureautoinstrumentationforthepythonservice">Step 1. Configure auto-instrumentation for the Python Service</h3>
<p>We are going to use automatic instrumentation with Python service from the <a href="https://github.com/elastic/observability-examples">Elastiflix demo application</a>.</p>
<p>We will be using the following service from Elastiflix:</p>
<pre><code>Elastiflix/python-favorite-otel-auto
</code></pre>
<p>Per the <a href="https://opentelemetry.io/docs/instrumentation/js/automatic/">OpenTelemetry Automatic Instrumentation for Python documentation</a>, you will simply install the appropriate Python packages using pip install.</p>
<pre><code>&gt;pip install opentelemetry-distro \
    opentelemetry-exporter-otlp

&gt;opentelemetry-bootstrap -a install
</code></pre>
<p>If you are running the Python service on the command line, then you can use the following command:</p>
<pre><code>opentelemetry-instrument python main.py
</code></pre>
<p>For our application, we do this as part of the Dockerfile.</p>
<p><strong>Dockerfile</strong></p>
<pre><code>FROM python:3.9-slim as base

# get packages
COPY requirements.txt .
RUN pip install -r requirements.txt
WORKDIR /favoriteservice

#install opentelemetry packages
RUN pip install opentelemetry-distro \
    opentelemetry-exporter-otlp

RUN opentelemetry-bootstrap -a install

# Add the application
COPY . .

EXPOSE 5000
ENTRYPOINT [ "opentelemetry-instrument", "python", "main.py"]
</code></pre>
<h3 id="step2runningthedockerimagewithenvironmentvariables">Step 2. Running the Docker image with environment variables</h3>
<p>As specified in the <a href="https://opentelemetry.io/docs/instrumentation/python/automatic/#configuring-the-agent">OTEL Python documentation</a>, we will use environment variables and pass in the configuration values to enable it to connect with <a href="https://www.elastic.co/guide/en/apm/guide/current/open-telemetry.html">Elastic Observability’s APM server</a>.</p>
<p>Because Elastic accepts OTLP natively, we just need to provide the Endpoint and authentication where the OTEL Exporter needs to send the data, as well as some other environment variables.</p>
<p><strong>Getting Elastic Cloud variables</strong><br />
You can copy the endpoints and token from Kibana<sup>®</sup> under the path /app/home#/tutorial/apm.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltfbe8bebe5c4c3d96/6a85c80c4710c60b8dd3cae9/elastic-blog-3-apm-agents.png" alt="apm agents" /></p>
<p>You will need to copy the following environment variables:</p>
<pre><code>OTEL_EXPORTER_OTLP_ENDPOINT
OTEL_EXPORTER_OTLP_HEADERS
</code></pre>
<p><strong>Build the image</strong></p>
<pre><code>docker build -t  python-otel-auto-image .
</code></pre>
<p><strong>Run the image</strong></p>
<pre><code>docker run \
       -e OTEL_EXPORTER_OTLP_ENDPOINT="&lt;REPLACE WITH OTEL_EXPORTER_OTLP_ENDPOINT&gt;" \
       -e OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer%20&lt;REPLACE WITH TOKEN&gt;" \
       -e OTEL_RESOURCE_ATTRIBUTES="service.version=1.0,deployment.environment=production" \
       -e OTEL_SERVICE_NAME="python-favorite-otel-auto" \
       -p 5001:5001 \
       python-otel-auto-image
</code></pre>
<p><strong>Important:</strong> Note that the “OTEL_EXPORTER_OTLP_HEADERS” variable has the whitespace after Bearer escaped as “%20” — this is a requirement for Python.</p>
<p>You can now issue a few requests in order to generate trace data. Note that these requests are expected to return an error, as this service relies on a connection to Redis that you don’t currently have running. As mentioned before, you can find a more complete example using docker-compose <a href="https://github.com/elastic/observability-examples/tree/main/Elastiflix">here</a>.</p>
<pre><code>curl localhost:5000/favorites

# or alternatively issue a request every second

while true; do curl "localhost:5000/favorites"; sleep 1; done;
</code></pre>
<h3 id="step3exploretracesmetricsandlogsinelasticapm">Step 3: Explore traces, metrics, and logs in Elastic APM</h3>
<p>Exploring the Services section in Elastic APM, you’ll see the Python service displayed.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6f53920fc85feae7/6a85c80f682666a5e91eab8f/elastic-blog-4-services.png" alt="services" /></p>
<p>Clicking on the python-favorite-otel-auto service , you can see that it is ingesting telemetry data using OpenTelemetry.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9cd8a6f6fb6d9a22/6a85c8129bf99430930a04fb/elastic-blog-5-graph-view.png" alt="graph view" /></p>
<p>In this blog, we discussed the following:</p>
<ul>
<li>How to auto-instrument Python with OpenTelemetry</li>
<li>Using standard commands in a Dockerfile, auto-instrumentation was done efficiently and without adding code in multiple places</li>
</ul>
<p>Since Elastic can support a mix of methods for ingesting data, whether it be using auto-instrumentation of open-source OpenTelemetry or manual instrumentation with its native APM agents, you can plan your migration to OTel by focusing on a few applications first and then using OpenTelemety across your applications later on in a manner that best fits your business needs.</p>
<blockquote>
  <p>Developer resources:</p>
  <ul>
  <li><a href="https://www.elastic.co/blog/getting-started-opentelemetry-instrumentation-sample-app">Elastiflix application</a>, a guide to instrument different languages with OpenTelemetry</li>
  <li>Python: <a href="https://www.elastic.co/blog/auto-instrumentation-python-applications-opentelemetry">Auto-instrumentation</a>, <a href="https://www.elastic.co/blog/manual-instrumentation-of-python-applications-opentelemetry">Manual-instrumentation</a></li>
  <li>Java: <a href="https://www.elastic.co/blog/auto-instrumentation-of-java-applications-opentelemetry">Auto-instrumentation</a>, <a href="https://www.elastic.co/blog/manual-instrumentation-of-java-applications-opentelemetry">Manual-instrumentation</a></li>
  <li>Node.js: <a href="https://www.elastic.co/blog/auto-instrument-nodejs-applications-opentelemetry">Auto-instrumentation</a>, <a href="https://www.elastic.co/blog/manual-instrumentation-of-nodejs-applications-opentelemetry">Manual-instrumentation</a></li>
  <li>.NET: <a href="https://www.elastic.co/blog/auto-instrumentation-of-net-applications-opentelemetry">Auto-instrumentation</a>, <a href="https://www.elastic.co/blog/manual-instrumentation-of-net-applications-opentelemetry">Manual-instrumentation</a></li>
  <li>Go: <a href="https://elastic.co/blog/manual-instrumentation-of-go-applications-opentelemetry">Manual-instrumentation</a></li>
  <li><a href="https://www.elastic.co/blog/best-practices-instrumenting-opentelemetry">Best practices for instrumenting OpenTelemetry</a></li>
  </ul>
  <p>General configuration and use case resources:</p>
  <ul>
  <li><a href="https://www.elastic.co/blog/opentelemetry-observability">Independence with OpenTelemetry on Elastic</a></li>
  <li><a href="https://www.elastic.co/blog/implementing-kubernetes-observability-security-opentelemetry">Modern observability and security on Kubernetes with Elastic and OpenTelemetry</a></li>
  <li><a href="https://www.elastic.co/blog/3-models-logging-opentelemetry-elastic">3 models for logging with OpenTelemetry and Elastic</a></li>
  <li><a href="https://www.elastic.co/blog/adding-free-and-open-elastic-apm-as-part-of-your-elastic-observability-deployment">Adding free and open Elastic APM as part of your Elastic Observability deployment</a></li>
  <li><a href="https://www.elastic.co/blog/custom-metrics-app-code-java-agent-plugin">Capturing custom metrics through OpenTelemetry API in code with Elastic</a></li>
  <li><a href="https://www.elastic.co/virtual-events/future-proof-your-observability-platform-with-opentelemetry-and-elastic">Future-proof your observability platform with OpenTelemetry and Elastic</a></li>
  <li><a href="https://www.elastic.co/blog/kubernetes-k8s-observability-elasticsearch-cncf">Elastic Observability: Built for open technologies like Kubernetes, OpenTelemetry, Prometheus, Istio, and more</a></li>
  </ul>
</blockquote>
<p>Don’t have an Elastic Cloud account yet? <a href="https://cloud.elastic.co/registration">Sign up for Elastic Cloud</a> and try out the auto-instrumentation capabilities that I discussed above. I would be interested in getting your feedback about your experience in gaining visibility into your application stack 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/auto-instrumentation-python-applications-opentelemetry</link>
    <guid isPermaLink="false">auto-instrumentation-python-applications-opentelemetry</guid>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[APM]]></category>
    <category><![CDATA[Infrastructure Monitoring]]></category>
    <dc:creator><![CDATA[Bahubali Shetti]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8fad8320a5eb60f5/6a85c8159a32f1a545a7df96/observability-launch-series-2-python-auto_(1).jpg" length="0" type="image/jpeg"/>
    <pubDate>Thu, 31 Aug 2023 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Auto-instrumentation of Java applications with OpenTelemetry]]></title>
    <description><![CDATA[Instrumenting Java applications with OpenTelemetry provides insights into application performance, dependencies, and errors. We'll show you how to automatically instrument a Java application using Docker, with no changes to your application code.]]></description>
    <content:encoded><![CDATA[<p>In the fast-paced universe of software development, especially in the cloud-native realm, DevOps and SRE teams are increasingly emerging as essential partners in application stability and growth.</p>
<p>DevOps engineers continuously optimize software delivery, while SRE teams act as the stewards of application reliability, scalability, and top-tier performance. The challenge? These teams require a cutting-edge observability solution, one that encompasses full-stack insights, empowering them to rapidly manage, monitor, and rectify potential disruptions before they culminate into operational challenges.</p>
<p>Observability in our modern distributed software ecosystem goes beyond mere monitoring — it demands limitless data collection, precision in processing, and the correlation of this data into actionable insights. However, the road to achieving this holistic view is paved with obstacles, from navigating version incompatibilities to wrestling with restrictive proprietary code.</p>
<p>Enter <a href="https://opentelemetry.io/">OpenTelemetry (OTel)</a>, with the following benefits for those who adopt it:</p>
<ul>
<li>Escape vendor constraints with OTel, freeing yourself from vendor lock-in and ensuring top-notch observability.</li>
<li>See the harmony of unified logs, metrics, and traces come together to provide a complete system view.</li>
<li>Improve your application oversight through richer and enhanced instrumentations.</li>
<li>Embrace the benefits of backward compatibility to protect your prior instrumentation investments.</li>
<li>Embark on the OpenTelemetry journey with an easy learning curve, simplifying onboarding and scalability.</li>
<li>Rely on a proven, future-ready standard to boost your confidence in every investment.</li>
</ul>
<p>In this blog, we will explore how you can use <a href="https://opentelemetry.io/docs/instrumentation/java/automatic/">automatic instrumentation in your Java</a> application using Docker, without the need to refactor any part of your application code. We will use an <a href="https://github.com/elastic/observability-examples">application called Elastiflix</a>, which helps highlight auto-instrumentation in a simple way.</p>
<h2 id="applicationprerequisitesandconfig">Application, prerequisites, and config</h2>
<p>The application that we use for this blog is called <a href="https://github.com/elastic/observability-examples">Elastiflix</a>, a movie-streaming application. It consists of several micro-services written in .NET, NodeJS, Go, and Python.</p>
<p>Before we instrument our sample application, we will first need to understand how Elastic can receive the telemetry data.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7d21eeef97ab704d/6a85c7d1bc5bb34702f81a5d/elastic-blog-1-config.png" alt="Elastic configuration options for OpenTelemetry" /></p>
<p>All of Elastic Observability’s APM capabilities are available with OTel data. Some of these include:</p>
<ul>
<li>Service maps</li>
<li>Service details (latency, throughput, failed transactions)</li>
<li>Dependencies between services, distributed tracing</li>
<li>Transactions (traces)</li>
<li>Machine learning (ML) correlations</li>
<li>Log correlation</li>
</ul>
<p>In addition to Elastic’s APM and a unified view of the telemetry data, you will also be able to use Elastic’s powerful machine learning capabilities to reduce the analysis, and alerting to help reduce MTTR.</p>
<h3 id="prerequisites">Prerequisites</h3>
<ul>
<li>An Elastic Cloud account — <a href="https://cloud.elastic.co/">sign up now</a>.</li>
<li>A clone of the <a href="https://github.com/elastic/observability-examples">Elastiflix demo application</a>, or your own Java application</li>
<li>Basic understanding of Docker — potentially install <a href="https://www.docker.com/products/docker-desktop/">Docker Desktop</a></li>
<li>Basic understanding of Java</li>
</ul>
<h3 id="viewtheexamplesourcecode">View the example source code</h3>
<p>The full source code, including the Dockerfile used in this blog, can be found on <a href="https://github.com/elastic/observability-examples/tree/main/Elastiflix/python-favorite-otel-auto">GitHub</a>. The repository also contains the <a href="https://github.com/elastic/observability-examples/tree/main/Elastiflix/python-favorite">same application without instrumentation</a>. This allows you to compare each file and see the differences.</p>
<p>The following steps will show you how to instrument this application and run it on the command line or in Docker. If you are interested in a more complete OTel example, take a look at the docker-compose file <a href="https://github.com/elastic/observability-examples/tree/main#start-the-app">here</a>, which will bring up the full project.</p>
<h2 id="stepbystepguide">Step-by-step guide</h2>
<h3 id="step0logintoyourelasticcloudaccount">Step 0. Log in to your Elastic Cloud account</h3>
<p>This blog assumes you have an Elastic Cloud account — if not, follow the <a href="https://cloud.elastic.co/registration?elektra=en-cloud-page">instructions to get started on Elastic Cloud</a>.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2d5313e4a98398f4/6a85c7d4f5f1a02cef2ec861/elastic-blog-2-trial.png" alt="free trial" /></p>
<h3 id="step1configureautoinstrumentationforthejavaservice">Step 1. Configure auto-instrumentation for the Java service</h3>
<p>We are going to use automatic instrumentation with Java service from the <a href="https://github.com/elastic/observability-examples/tree/main/Elastiflix/java-favorite-otel-auto">Elastiflix demo application</a>.</p>
<p>We will be using the following service from Elastiflix:</p>
<pre><code>Elastiflix/java-favorite-otel-auto
</code></pre>
<p>Per the <a href="https://opentelemetry.io/docs/instrumentation/java/automatic/">OpenTelemetry Automatic Instrumentation for Java documentation</a> and documentation, you will simply install the appropriate Java packages.</p>
<p>Create a local OTel directory to download the OpenTelemetry Java agent. Download opentelemetry-javaagent.jar.</p>
<pre><code>&gt;mkdir /otel

&gt;curl -L https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/latest/download/opentelemetry-javaagent.jar –output /otel/opentelemetry-javaagent.jar
</code></pre>
<p>If you are going to run the service on the command line, then you can use the following command:</p>
<pre><code>java -javaagent:/otel/opentelemetry-javaagent.jar \
-jar /usr/src/app/target/favorite-0.0.1-SNAPSHOT.jar --server.port=5000
</code></pre>
<p>For our application, we will do this as part of the Dockerfile.</p>
<p><strong>Dockerfile</strong></p>
<pre><code>Start with a base image containing Java runtime
FROM maven:3.8.2-openjdk-17-slim as build

# Make port 8080 available to the world outside this container
EXPOSE 5000

# Change to the app directory
WORKDIR /usr/src/app

# Copy the local code to the container
COPY . .

# Build the application
RUN mvn clean install

USER root
RUN apt-get update &amp;&amp; apt-get install -y zip curl
RUN mkdir /otel
RUN curl -L -o /otel/opentelemetry-javaagent.jar https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/download/v1.28.0/opentelemetry-javaagent.jar

COPY start.sh /start.sh
RUN chmod +x /start.sh

ENTRYPOINT ["/start.sh"]
</code></pre>
<h3 id="step2runningthedockerimagewithenvironmentvariables">Step 2. Running the Docker Image with environment variables</h3>
<p>As specified in the <a href="https://opentelemetry.io/docs/instrumentation/java/automatic/">OTEL Java documentation</a>, we will use environment variables and pass in the configuration values to enable it to connect with <a href="https://www.elastic.co/guide/en/observability/current/apm-open-telemetry.html">Elastic Observability’s APM server</a>.</p>
<p>Because Elastic accepts OTLP natively, we just need to provide the Endpoint and authentication where the OTEL Exporter needs to send the data, as well as some other environment variables.</p>
<p><strong>Getting Elastic Cloud variables</strong><br />
You can copy the endpoints and token from Kibana under the path <code>/app/home#/tutorial/apm</code>.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5e33ab3f17634420/6a85c7d7f61d6e81459c2aa7/elastic-blog-3-apm-agents.png" alt="apm agents" /></p>
<p>You will need to copy the following environment variables:</p>
<pre><code>OTEL_EXPORTER_OTLP_ENDPOINT
OTEL_EXPORTER_OTLP_HEADERS
</code></pre>
<p><strong>Build the Docker image</strong></p>
<pre><code>docker build -t java-otel-auto-image .
</code></pre>
<p><strong>Run the Docker image</strong></p>
<pre><code>docker run \
       -e OTEL_EXPORTER_OTLP_ENDPOINT="REPLACE WITH OTEL_EXPORTER_OTLP_ENDPOINT" \
       -e ELASTIC_APM_SECRET_TOKEN="REPLACE WITH THE BIT AFTER Authorization=Bearer " \
       -e OTEL_RESOURCE_ATTRIBUTES="service.version=1.0,deployment.environment=production" \
       -e OTEL_SERVICE_NAME="java-favorite-otel-auto" \
       -p 5000:5000 \
       java-otel-auto-image
</code></pre>
<p>You can now issue a few requests in order to generate trace data. Note that these requests are expected to return an error, as this service relies on a connection to Redis that you don’t currently have running. As mentioned before, you can find a more complete example using docker-compose <a href="https://github.com/elastic/observability-examples/tree/main/Elastiflix">here</a>.</p>
<pre><code>curl localhost:5000/favorites

# or alternatively issue a request every second

while true; do curl "localhost:5000/favorites"; sleep 1; done;
</code></pre>
<h3 id="step3exploretracesandlogsinelasticapm">Step 3: Explore traces and logs in Elastic APM</h3>
<p>Once you have this up and running, you can ping the endpoint for your instrumented service (in our case, this is /favorites), and you should see the app appear in Elastic APM, as shown below:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1bf45157d67d5eb0/6a85c7da8c29446e70b88fba/elastic-blog-4-services.png" alt="services" /></p>
<p>It will begin by tracking throughput and latency critical metrics for SREs to pay attention to.</p>
<p>Digging in, we can see an overview of all our Transactions.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6d5956fe18a5cacc/6a85c7dc43c0b7cd712f05a2/elastic-blog-5-services2.png" alt="services-2" /></p>
<p>And look at specific transactions:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf78af69dc184afe3/6a85c7e0eaf2452ab3a49ee3/elastic-blog-6-graph-colored.png" alt="graph colored lines" /></p>
<p>Click on <strong>Logs,</strong> and we see that logs are also brought over. The OTel Agent will automatically bring in logs and correlate them with traces for you:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt63cfce87c6b0ae44/6a85c7e38c29445a4fb88fc2/elastic-blog-7-graph-no-colors.png" alt="graph-no-colors" /></p>
<p>This gives you complete visibility across logs, metrics, and traces!</p>
<h2 id="basicconceptshowapmworkswithjava">Basic concepts: How APM works with Java</h2>
<p>Before we continue, let's first understand a few basic concepts and terms.</p>
<ul>
<li><strong>Java Agent:</strong> This is a tool that can be used to instrument (or modify) the bytecode of class files in the Java Virtual Machine (JVM). Java agents are used for many purposes like performance monitoring, logging, security, and more.</li>
<li><strong>Bytecode:</strong> This is the intermediary code generated by the Java compiler from your Java source code. This code is interpreted or compiled on the fly by the JVM to produce machine code that can be executed.</li>
<li><strong>Byte Buddy:</strong> Byte Buddy is a code generation and manipulation library for Java. It is used to create, modify, or adapt Java classes at runtime. In the context of a Java Agent, Byte Buddy provides a powerful and flexible way to modify bytecode. <strong>Both the Elastic APM Agent and the OpenTelemetry Agent use Byte Buddy under the covers.</strong></li>
</ul>
<p><strong>Now, let's talk about how automatic instrumentation works with Byte Buddy:</strong></p>
<p>Automatic instrumentation is the process by which an agent modifies the bytecode of your application's classes, often to insert monitoring code. The agent doesn't modify the source code directly, but rather the bytecode that is loaded into the JVM. This is done while the JVM is loading the classes, so the modifications are in effect during runtime.</p>
<p>Here's a simplified explanation of the process:</p>
<ol>
<li><p><strong>Start the JVM with the agent:</strong> When starting your Java application, you specify the Java agent with the -javaagent command line option. This instructs the JVM to load your agent before the main method of your application is invoked. At this point, the agent has the opportunity to set up class transformers.</p></li>
<li><p><strong>Register a class file transformer with Byte Buddy:</strong> Your agent will register a class file transformer with Byte Buddy. A transformer is a piece of code that is invoked every time a class is loaded into the JVM. This transformer receives the bytecode of the class, and it can modify this bytecode before the class is actually used.</p></li>
<li><p><strong>Transform the bytecode:</strong> When your transformer is invoked, it will use Byte Buddy's API to modify the bytecode. Byte Buddy allows you to specify your transformations in a high-level, expressive way rather than manually writing complex bytecode. For example, you could specify a certain class and method within that class that you want to instrument and provide an "interceptor" that will add new behavior to that method.</p></li>
<li><p><strong>Use the transformed classes:</strong> Once the agent has set up its transformers, the JVM continues to load classes as usual. Each time a class is loaded, your transformers are invoked, allowing them to modify the bytecode. Your application then uses these transformed classes as if they were the original ones, but they now have the extra behavior that you've injected through your interceptor.</p></li>
</ol>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3b2426bbad6b36b4/6a85c7e543c0b72e2a2f05a6/elastic-blog-8-flowchart.png" alt="flowchart" /></p>
<p>In essence, automatic instrumentation with Byte Buddy is about modifying the behavior of your Java classes at runtime, without needing to alter the source code directly. This is especially useful for cross-cutting concerns like logging, monitoring, or security, as it allows you to centralize this code in your Java Agent, rather than scattering it throughout your application.</p>
<h2 id="summary">Summary</h2>
<p>With this Dockerfile, you've transformed your simple Java application into one that's automatically instrumented with OpenTelemetry. This will aid greatly in understanding application performance, tracing errors, and gaining insights into how users interact with your software.</p>
<p>Remember, observability is a crucial aspect of modern application development, especially in distributed systems. With tools like OpenTelemetry, understanding complex systems becomes a tad bit easier.</p>
<p>In this blog, we discussed the following:</p>
<ul>
<li>How to auto-instrument Java with OpenTelemetry.</li>
<li>Using standard commands in a Docker file, auto-instrumentation was done efficiently and without adding code in multiple places enabling manageability.</li>
<li>Using OpenTelemetry and its support for multiple languages, DevOps and SRE teams can auto-instrument their applications with ease gaining immediate insights into the health of the entire application stack and reduce mean time to resolution (MTTR).</li>
</ul>
<p>Since Elastic can support a mix of methods for ingesting data, whether it be using auto-instrumentation of open-source OpenTelemetry or manual instrumentation with its native APM agents, you can plan your migration to OTel by focusing on a few applications first and then using OpenTelemety across your applications later on in a manner that best fits your business needs.</p>
<blockquote>
  <p>Developer resources:</p>
  <ul>
  <li><a href="https://www.elastic.co/blog/getting-started-opentelemetry-instrumentation-sample-app">Elastiflix application</a>, a guide to instrument different languages with OpenTelemetry</li>
  <li>Python: <a href="https://www.elastic.co/blog/auto-instrumentation-of-python-applications-opentelemetry">Auto-instrumentation</a>, <a href="https://www.elastic.co/blog/manual-instrumentation-of-python-applications-opentelemetry">Manual-instrumentation</a></li>
  <li>Java: <a href="https://www.elastic.co/blog/auto-instrumentation-java-applications-opentelemetry">Auto-instrumentation</a>, <a href="https://www.elastic.co/blog/manual-instrumentation-of-java-applications-opentelemetry">Manual-instrumentation</a></li>
  <li>Node.js: <a href="https://www.elastic.co/blog/auto-instrument-nodejs-applications-opentelemetry">Auto-instrumentation</a>, <a href="https://www.elastic.co/blog/manual-instrumentation-of-nodejs-applications-opentelemetry">Manual-instrumentation</a></li>
  <li>.NET: <a href="https://www.elastic.co/blog/auto-instrumentation-of-net-applications-opentelemetry">Auto-instrumentation</a>, <a href="https://www.elastic.co/blog/manual-instrumentation-of-net-applications-opentelemetry">Manual-instrumentation</a></li>
  <li>Go: <a href="https://elastic.co/blog/manual-instrumentation-of-go-applications-opentelemetry">Manual-instrumentation</a></li>
  <li><a href="https://www.elastic.co/blog/best-practices-instrumenting-opentelemetry">Best practices for instrumenting OpenTelemetry</a></li>
  </ul>
  <p>General configuration and use case resources:</p>
  <ul>
  <li><a href="https://www.elastic.co/blog/opentelemetry-observability">Independence with OpenTelemetry on Elastic</a></li>
  <li><a href="https://www.elastic.co/blog/implementing-kubernetes-observability-security-opentelemetry">Modern observability and security on Kubernetes with Elastic and OpenTelemetry</a></li>
  <li><a href="https://www.elastic.co/blog/3-models-logging-opentelemetry-elastic">3 models for logging with OpenTelemetry and Elastic</a></li>
  <li><a href="https://www.elastic.co/blog/adding-free-and-open-elastic-apm-as-part-of-your-elastic-observability-deployment">Adding free and open Elastic APM as part of your Elastic Observability deployment</a></li>
  <li><a href="https://www.elastic.co/blog/custom-metrics-app-code-java-agent-plugin">Capturing custom metrics through OpenTelemetry API in code with Elastic</a></li>
  <li><a href="https://www.elastic.co/virtual-events/future-proof-your-observability-platform-with-opentelemetry-and-elastic">Future-proof your observability platform with OpenTelemetry and Elastic</a></li>
  <li><a href="https://www.elastic.co/blog/kubernetes-k8s-observability-elasticsearch-cncf">Elastic Observability: Built for open technologies like Kubernetes, OpenTelemetry, Prometheus, Istio, and more</a></li>
  </ul>
</blockquote>
<p>Don’t have an Elastic Cloud account yet? <a href="https://cloud.elastic.co/registration">Sign up for Elastic Cloud</a> and try out the auto-instrumentation capabilities that I discussed above. I would be interested in getting your feedback about your experience in gaining visibility into your application stack 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/auto-instrumentation-java-applications-opentelemetry</link>
    <guid isPermaLink="false">auto-instrumentation-java-applications-opentelemetry</guid>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[APM]]></category>
    <category><![CDATA[Infrastructure Monitoring]]></category>
    <dc:creator><![CDATA[David Hope]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt90b40c245a46b729/6a85c7e880984c7b39668f6c/observability-launch-series-3-java-auto.jpg" length="0" type="image/jpeg"/>
    <pubDate>Thu, 31 Aug 2023 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Automatic instrumentation with OpenTelemetry for Node.js applications]]></title>
    <description><![CDATA[Learn how to auto-instrument Node.js applications using OpenTelemetry. With standard commands in a Docker file, applications can be instrumented quickly without writing code in multiple places, enabling rapid change, scale, and easier management.]]></description>
    <content:encoded><![CDATA[<p>DevOps and SRE teams are transforming the process of software development. While DevOps engineers focus on efficient software applications and service delivery, SRE teams are key to ensuring reliability, scalability, and performance. These teams must rely on a full-stack observability solution that allows them to manage and monitor systems and ensure issues are resolved before they impact the business.</p>
<p>Observability across the entire stack of modern distributed applications requires data collection, processing, and correlation often in the form of dashboards. Ingesting all system data requires installing agents across stacks, frameworks, and providers — a process that can be challenging and time-consuming for teams who have to deal with version changes, compatibility issues, and proprietary code that doesn't scale as systems change.</p>
<p>Thanks to <a href="http://opentelemetry.io">OpenTelemetry</a> (OTel), DevOps and SRE teams now have a standard way to collect and send data that doesn't rely on proprietary code and have a large support community reducing vendor lock-in.</p>
<p>In a <a href="https://www.elastic.co/blog/opentelemetry-observability">previous blog</a>, we also reviewed how to use the <a href="https://github.com/elastic/opentelemetry-demo">OpenTelemetry demo</a> and connect it to Elastic<sup>®</sup>, as well as some of Elastic’s capabilities with OpenTelemetry and Kubernetes.</p>
<p>In this blog, we will show how to use <a href="https://opentelemetry.io/docs/instrumentation/js/automatic/">automatic instrumentation for OpenTelemetry</a> with the Node.js service of our <a href="https://github.com/elastic/observability-examples">application called Elastiflix</a>, which helps highlight auto-instrumentation in a simple way.</p>
<p>The beauty of this is that there is <strong>no need for the otel-collector</strong>! This setup enables you to slowly and easily migrate an application to OTel with Elastic according to a timeline that best fits your business.</p>
<h2 id="applicationprerequisitesandconfig">Application, prerequisites, and config</h2>
<p>The application that we use for this blog is called <a href="https://github.com/elastic/observability-examples">Elastiflix</a>, a movie streaming application. It consists of several micro-services written in .NET, NodeJS, Go, and Python.</p>
<p>Before we instrument our sample application, we will first need to understand how Elastic can receive the telemetry data.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt043556fcb3946f75/6a85c7c10782902dad3216f6/elastic-blog-1-otel-config-options.png" alt="options" /></p>
<p>All of Elastic Observability’s APM capabilities are available with OTel data. Some of these include:</p>
<ul>
<li>Service maps</li>
<li>Service details (latency, throughput, failed transactions)</li>
<li>Dependencies between services, distributed tracing</li>
<li>Transactions (traces)</li>
<li>Machine learning (ML) correlations</li>
<li>Log correlation</li>
</ul>
<p>In addition to Elastic’s APM and a unified view of the telemetry data, you will also be able to use Elastic’s powerful machine learning capabilities to reduce the analysis, and alerting to help reduce MTTR.</p>
<h3 id="prerequisites">Prerequisites</h3>
<ul>
<li>An Elastic Cloud account — <a href="https://cloud.elastic.co/">sign up now</a></li>
<li>A clone of the <a href="https://github.com/elastic/observability-examples">Elastiflix demo application</a>, or your own <strong>Node.js</strong> application</li>
<li>Basic understanding of Docker — potentially install <a href="https://www.docker.com/products/docker-desktop/">Docker Desktop</a></li>
<li>Basic understanding of Node.js</li>
</ul>
<h3 id="viewtheexamplesourcecode">View the example source code</h3>
<p>The full source code, including the Dockerfile used in this blog, can be found on <a href="https://github.com/elastic/observability-examples/tree/main/Elastiflix/python-favorite-otel-auto">GitHub</a>. The repository also contains the <a href="https://github.com/elastic/observability-examples/tree/main/Elastiflix/python-favorite">same application without instrumentation</a>. This allows you to compare each file and see the differences.</p>
<p>The following steps will show you how to instrument this application and run it on the command line or in Docker. If you are interested in a more complete OTel example, take a look at the docker-compose file <a href="https://github.com/elastic/observability-examples/tree/main#start-the-app">here</a>, which will bring up the full project.</p>
<h2 id="stepbystepguide">Step-by-step guide</h2>
<h3 id="step0logintoyourelasticcloudaccount">Step 0. Log in to your Elastic Cloud account</h3>
<p>This blog assumes you have an Elastic Cloud account — if not, follow the <a href="https://cloud.elastic.co/registration?elektra=en-cloud-page">instructions to get started on Elastic Cloud</a>.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd59feb054794c506/6a85c7c4d7b2e7ef9afe844e/elastic-blog-2-free-trial.png" alt="free trial" /></p>
<h3 id="step1configureautoinstrumentationforthenodejsservice">Step 1. Configure auto-instrumentation for the Node.js Service</h3>
<p>We are going to use automatic instrumentation with Node.js service from the <a href="https://github.com/elastic/observability-examples">Elastiflix demo application</a>.</p>
<p>We will be using the following service from Elastiflix:</p>
<pre><code>Elastiflix/node-server-otel-manual
</code></pre>
<p>Per the <a href="https://opentelemetry.io/docs/instrumentation/js/automatic/">OpenTelemetry JavaScript documentation</a> and <a href="https://www.npmjs.com/package/@opentelemetry/auto-instrumentations-node">@open-telemetry/auto-instrumentions-node</a> documentation, you will simply install the appropriate node packages using npm.</p>
<pre><code>npm install --save @opentelemetry/api
npm install --save @opentelemetry/auto-instrumentations-node
</code></pre>
<p>If you are running the Node.js service on the command line, then here is how you can run auto-instrument with Node.js.</p>
<pre><code>node --require '@opentelemetry/auto-instrumentations-node/register' app.js
</code></pre>
<p>For our application, we do this as part of the Dockerfile.</p>
<p><strong>Dockerfile</strong></p>
<pre><code>FROM node:14

WORKDIR /app

COPY ["package.json", "./"]
RUN ls
RUN npm install --production
COPY . .

RUN npm install --save @opentelemetry/api
RUN npm install --save @opentelemetry/auto-instrumentations-node


EXPOSE 3001

CMD ["node", "--require", "@opentelemetry/auto-instrumentations-node/register", "index.js"]
</code></pre>
<h3 id="step2runningthedockerimagewithenvironmentvariables">Step 2. Running the Docker image with environment variables</h3>
<p>As specified in the <a href="https://opentelemetry.io/docs/instrumentation/python/automatic/#configuring-the-agent">OTEL documentation</a>, we will use environment variables and pass in the configuration values to enable it to connect with <a href="https://www.elastic.co/guide/en/apm/guide/current/open-telemetry.html">Elastic Observability’s APM server</a>.</p>
<p>Because Elastic accepts OTLP natively, we just need to provide the Endpoint and authentication where the OTEL Exporter needs to send the data, as well as some other environment variables.</p>
<p><strong>Getting Elastic Cloud variables</strong><br />
You can copy the endpoints and token from Kibana<sup>®</sup> under the path /app/home#/tutorial/apm.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7ee6995ab7d0d882/6a85c7c7f61d6e9f1c9c2a9f/elastic-blog-3-apm-agents.png" alt="apm agents" /></p>
<p>You will need to copy the following environment variables:</p>
<pre><code>OTEL_EXPORTER_OTLP_ENDPOINT
OTEL_EXPORTER_OTLP_HEADERS
</code></pre>
<p><strong>Build the image</strong></p>
<pre><code>docker build -t  node-otel-auto-image .
</code></pre>
<p><strong>Run the image</strong></p>
<pre><code>docker run \
       -e OTEL_EXPORTER_OTLP_ENDPOINT="&lt;REPLACE WITH OTEL_EXPORTER_OTLP_ENDPOINT&gt;" \
       -e OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer &lt;REPLACE WITH TOKEN&gt;" \
       -e OTEL_RESOURCE_ATTRIBUTES="service.version=1.0,deployment.environment=production" \
       -e OTEL_SERVICE_NAME="node-server-otel-auto" \
       -p 3001:3001 \
       node-server-otel-auto
</code></pre>
<p>You can now issue a few requests in order to generate trace data. Note that these requests are expected to return an error, as this service relies on some downstream services that you may not have running on your machine.</p>
<pre><code>curl localhost:3001/api/login
curl localhost:3001/api/favorites

# or alternatively issue a request every second

while true; do curl "localhost:3001/api/favorites"; sleep 1; done;
</code></pre>
<h3 id="step3exploretracesmetricsandlogsinelasticapm">Step 3: Explore traces, metrics, and logs in Elastic APM</h3>
<p>Exploring the Services section in Elastic APM, you’ll see the Node service displayed.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt87470325d2447bbd/6a85c7ca80984cd08f668f5c/elastic-blog-4-services.png" alt="services" /></p>
<p>Clicking on the node-server-otel-auto service, you can see that it is ingesting telemetry data using OpenTelemetry.</p>
<h2 id="summary">Summary</h2>
<p>In this blog, we discussed the following:</p>
<ul>
<li>How to auto-instrument Node.js with OpenTelemetry</li>
<li>Using standard commands in a Dockerfile, auto-instrumentation was done efficiently and without adding code in multiple places enabling manageability</li>
</ul>
<p>Since Elastic can support a mix of methods for ingesting data, whether it be using auto-instrumentation of open-source OpenTelemetry or manual instrumentation with its native APM agents, you can plan your migration to OTel by focusing on a few applications first and then using OpenTelemety across your applications later on in a manner that best fits your business needs.</p>
<blockquote>
  <p>Developer resources:</p>
  <ul>
  <li><a href="https://www.elastic.co/blog/getting-started-opentelemetry-instrumentation-sample-app">Elastiflix application</a>, a guide to instrument different languages with OpenTelemetry</li>
  <li>Python: <a href="https://www.elastic.co/blog/auto-instrumentation-of-python-applications-opentelemetry">Auto-instrumentation</a>, <a href="https://www.elastic.co/blog/manual-instrumentation-of-python-applications-opentelemetry">Manual-instrumentation</a></li>
  <li>Java: <a href="https://www.elastic.co/blog/auto-instrumentation-of-java-applications-opentelemetry">Auto-instrumentation</a>, <a href="https://www.elastic.co/blog/manual-instrumentation-of-java-applications-opentelemetry">Manual-instrumentation</a></li>
  <li>Node.js: <a href="https://www.elastic.co/blog/auto-instrument-nodejs-apps-opentelemetry">Auto-instrumentation</a>, <a href="https://www.elastic.co/blog/manual-instrumentation-of-nodejs-applications-opentelemetry">Manual-instrumentation</a></li>
  <li>.NET: <a href="https://www.elastic.co/blog/auto-instrumentation-of-net-applications-opentelemetry">Auto-instrumentation</a>, <a href="https://www.elastic.co/blog/manual-instrumentation-of-net-applications-opentelemetry">Manual-instrumentation</a></li>
  <li>Go: <a href="https://elastic.co/blog/manual-instrumentation-of-go-applications-opentelemetry">Manual-instrumentation</a></li>
  <li><a href="https://www.elastic.co/blog/best-practices-instrumenting-opentelemetry">Best practices for instrumenting OpenTelemetry</a></li>
  </ul>
  <p>General configuration and use case resources:</p>
  <ul>
  <li><a href="https://www.elastic.co/blog/opentelemetry-observability">Independence with OpenTelemetry on Elastic</a></li>
  <li><a href="https://www.elastic.co/blog/implementing-kubernetes-observability-security-opentelemetry">Modern observability and security on Kubernetes with Elastic and OpenTelemetry</a></li>
  <li><a href="https://www.elastic.co/blog/3-models-logging-opentelemetry-elastic">3 models for logging with OpenTelemetry and Elastic</a></li>
  <li><a href="https://www.elastic.co/blog/adding-free-and-open-elastic-apm-as-part-of-your-elastic-observability-deployment">Adding free and open Elastic APM as part of your Elastic Observability deployment</a></li>
  <li><a href="https://www.elastic.co/blog/custom-metrics-app-code-java-agent-plugin">Capturing custom metrics through OpenTelemetry API in code with Elastic</a></li>
  <li><a href="https://www.elastic.co/virtual-events/future-proof-your-observability-platform-with-opentelemetry-and-elastic">Future-proof your observability platform with OpenTelemetry and Elastic</a></li>
  <li><a href="https://www.elastic.co/blog/kubernetes-k8s-observability-elasticsearch-cncf">Elastic Observability: Built for open technologies like Kubernetes, OpenTelemetry, Prometheus, Istio, and more</a></li>
  </ul>
</blockquote>
<p>Don’t have an Elastic Cloud account yet? <a href="https://cloud.elastic.co/registration">Sign up for Elastic Cloud</a> and try out the auto-instrumentation capabilities that I discussed above. I would be interested in getting your feedback about your experience in gaining visibility into your application stack 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/auto-instrument-nodejs-apps-opentelemetry</link>
    <guid isPermaLink="false">auto-instrument-nodejs-apps-opentelemetry</guid>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[APM]]></category>
    <category><![CDATA[Infrastructure Monitoring]]></category>
    <dc:creator><![CDATA[Bahubali Shetti]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0c24822f35077032/6a85c7cd9829268c70583868/observability-launch-series-1-node-js-auto_(1).jpg" length="0" type="image/jpeg"/>
    <pubDate>Wed, 30 Aug 2023 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Understanding APM: How to add extensions to the OpenTelemetry Java Agent]]></title>
    <description><![CDATA[This blog post provides a comprehensive guide for Site Reliability Engineers (SREs) and IT Operations to gain visibility and traceability into applications, especially those written with non-standard frameworks or without access to the source code.]]></description>
    <content:encoded><![CDATA[<h2 id="withoutcodeaccesssresanditoperationscannotalwaysgetthevisibilitytheyneed">Without code access, SREs and IT Operations cannot always get the visibility they need</h2>
<p>As an SRE, have you ever had a situation where you were working on an application that was written with non-standard frameworks, or you wanted to get some interesting business data from an application (number of orders processed for example) but you didn’t have access to the source code?</p>
<p>We all know this can be a challenging scenario resulting in visibility gaps, inability to fully trace code end to end, and missing critical business monitoring data that is useful for understanding the true impact of issues.</p>
<p>How can we solve this? One way we discussed in the following three blogs:</p>
<ul>
<li><a href="https://www.elastic.co/blog/create-your-own-instrumentation-with-the-java-agent-plugin">Create your own instrumentation with the Java Agent Plugin</a></li>
<li><a href="https://www.elastic.co/blog/custom-metrics-app-code-java-agent-plugin">How to capture custom metrics without app code changes using the Java Agent Plugin</a></li>
<li><a href="https://www.elastic.co/blog/regression-testing-your-java-agent-plugin">Regression testing your Java Agent Plugin</a></li>
</ul>
<p>This is where we develop a plugin for the Elastic<sup>®</sup> APM Agent to help get access to critical business data for monitoring and add tracing where none exists.</p>
<p>What we will discuss in this blog is how you can do the same with the <a href="https://opentelemetry.io/docs/instrumentation/java/automatic/">OpenTelemetry Java Agent</a> using the Extensions framework.</p>
<h2 id="basicconceptshowapmworks">Basic concepts: How APM works</h2>
<p>Before we continue, let's first understand a few basic concepts and terms.</p>
<ul>
<li><strong>Java Agent:</strong> This is a tool that can be used to instrument (or modify) the bytecode of class files in the Java Virtual Machine (JVM). Java agents are used for many purposes like performance monitoring, logging, security, and more.</li>
<li><strong>Bytecode:</strong> This is the intermediary code generated by the Java compiler from your Java source code. This code is interpreted or compiled on the fly by the JVM to produce machine code that can be executed.</li>
<li><strong>Byte Buddy:</strong> Byte Buddy is a code generation and manipulation library for Java. It is used to create, modify, or adapt Java classes at runtime. In the context of a Java Agent, Byte Buddy provides a powerful and flexible way to modify bytecode. <strong>Both the Elastic APM Agent and the OpenTelemetry Agent use Byte Buddy under the covers.</strong></li>
</ul>
<p><strong>Now, let's talk about how automatic instrumentation works with Byte Buddy:</strong></p>
<p>Automatic instrumentation is the process by which an agent modifies the bytecode of your application's classes, often to insert monitoring code. The agent doesn't modify the source code directly, but rather the bytecode that is loaded into the JVM. This is done while the JVM is loading the classes, so the modifications are in effect during runtime.</p>
<p>Here's a simplified explanation of the process:</p>
<ol>
<li><p><strong>Start the JVM with the agent:</strong> When starting your Java application, you specify the Java agent with the -javaagent command line option. This instructs the JVM to load your agent before the main method of your application is invoked. At this point, the agent has the opportunity to set up class transformers.</p></li>
<li><p><strong>Register a class file transformer with Byte Buddy:</strong> Your agent will register a class file transformer with Byte Buddy. A transformer is a piece of code that is invoked every time a class is loaded into the JVM. This transformer receives the bytecode of the class and it can modify this bytecode before the class is actually used.</p></li>
<li><p><strong>Transform the bytecode:</strong> When your transformer is invoked, it will use Byte Buddy's API to modify the bytecode. Byte Buddy allows you to specify your transformations in a high-level, expressive way rather than manually writing complex bytecode. For example, you could specify a certain class and method within that class that you want to instrument and provide an "interceptor" that will add new behavior to that method.</p></li>
<li><p><strong>Use the transformed classes:</strong> Once the agent has set up its transformers, the JVM continues to load classes as usual. Each time a class is loaded, your transformers are invoked, allowing them to modify the bytecode. Your application then uses these transformed classes as if they were the original ones, but they now have the extra behavior that you've injected through your interceptor.</p></li>
</ol>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt374de29a3dcd79d9/6a85cb1af61d6e245b9c2b1b/elastic-blog-1-flowchart-process.png" alt="flowchart process" /></p>
<p>In essence, automatic instrumentation with Byte Buddy is about modifying the behavior of your Java classes at runtime, without needing to alter the source code directly. This is especially useful for cross-cutting concerns like logging, monitoring, or security, as it allows you to centralize this code in your Java Agent, rather than scattering it throughout your application.</p>
<h2 id="applicationprerequisitesandconfig">Application, prerequisites, and config</h2>
<p>There is a really simple application in <a href="https://github.com/davidgeorgehope/custom-instrumentation-examples">this GitHub repository</a> that is used throughout this blog. What it does is it simply asks you to input some text and then it counts the number of words.</p>
<p>It’s also listed below:</p>
<pre><code>package org.davidgeorgehope;
import java.util.Scanner;
import java.util.logging.Logger;

public class Main {
    private static Logger logger = Logger.getLogger(Main.class.getName());

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        while (true) {
            System.out.println("Please enter your sentence:");
            String input = scanner.nextLine();
            Main main = new Main();
            int wordCount = main.countWords(input);
            System.out.println("The input contains " + wordCount + " word(s).");
        }
    }
    public int countWords(String input) {

        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        if (input == null || input.isEmpty()) {
            return 0;
        }

        String[] words = input.split("\s+");
        return words.length;
    }
}
</code></pre>
<p>For the purposes of this blog, we will be using Elastic Cloud to capture the data generated by OpenTelemetry — <a href="https://www.elastic.co/getting-started/observability/collect-and-analyze-logs#create-an-elastic-cloud-account">follow the instructions here</a> to <a href="https://cloud.elastic.co/registration?fromURI=/home">get started on Elastic Cloud</a>.</p>
<p>Once you are started with Elastic Cloud, go grab the OpenTelemetry config from the APM pages:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt618b544db96971cd/6a85cb1d9a32f145f8a7dfec/elastic-blog-2-apm-agents.png" alt="apm agents" /></p>
<p>You will need this later.</p>
<p>Finally, <a href="https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases">download the OpenTelemetry Agent</a>.</p>
<h2 id="firinguptheapplicationandopentelemetry">Firing up the application and OpenTelemetry</h2>
<p>If you start out with this simple application, build it and run it like so with the OpenTelemetry Agent, filling in the appropriate variables with those you got from earlier.</p>
<pre><code>java -javaagent:opentelemetry-javaagent.jar -Dotel.exporter.otlp.endpoint=XX -Dotel.exporter.otlp.headers=XX -Dotel.metrics.exporter=otlp -Dotel.logs.exporter=otlp -Dotel.resource.attributes=XX -Dotel.service.name=your-service-name -jar simple-java-1.0-SNAPSHOT.jar
</code></pre>
<p>You will find nothing happens. The reason for this is that the OpenTelemetry Agent has no way of knowing what to monitor. The way that APM with automatic instrumentation works is that it “knows” about standard frameworks, like Spring or HTTPClient, and is able to get visibility by “injecting” trace code into those standard frameworks automatically.</p>
<p>It has no knowledge of org.davidgeorgehope.Main from our simple Java application.</p>
<p>Luckily, there is a way we can add this using the <a href="https://opentelemetry.io/docs/instrumentation/java/automatic/extensions/">OpenTelemetry Extensions framework</a>.</p>
<h2 id="theopentelemetryextension">The OpenTelemetry Extension</h2>
<p>In the repository above, aside from the simple-java application, there is also a plugin for Elastic APM and an extension for OpenTelemetry. The relevant files for OpenTelemetry Extension are located <a href="https://github.com/davidgeorgehope/custom-instrumentation-examples/tree/main/opentelemetry-custom-instrumentation/src/main/java/org/davidgeorgehope">here</a> — WordCountInstrumentation.java and WordCountInstrumentationModule.java .</p>
<p>You’ll notice that OpenTelemetry Extensions and Elastic APM Plugins both make use of Byte Buddy, which is a common library for code instrumentation. There are some key differences in the way the code is bootstrapped, though.</p>
<p>The WordCountInstrumentationModule class extends an OpenTelemtry specific class InstrumentationModule, whose purpose is to describe a set of TypeInstrumentation that need to be applied together to correctly instrument a specific library. The WordCountInstrumentation class is one such instance of a TypeInstrumentation.</p>
<p>Type instrumentations grouped in a module share helper classes, muzzle runtime checks, and applicable class loader criteria, and can only be enabled or disabled as a set.</p>
<p>This is a little bit different from how the Elastic APM Plugin works because the default method to to inject code with OpenTelemetry is inline (which is the default) with OpenTelemetry, and you can inject dependencies into the core application classloader using the InstrumentationModule configurations (as shown below). The Elastic APM method is safer as it allows isolation of helper classes and makes it easier to debug with normal IDEs we are contributing this method to OpenTelemetry. Here we inject the TypeInstrumentation class and the WordCountInstrumentation class into the classloader.</p>
<pre><code>@Override
    public List&lt;String&gt; getAdditionalHelperClassNames() {
        return List.of(WordCountInstrumentation.class.getName(),"io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation");
    }
</code></pre>
<p>The other interesting part of the TypeInstrumentation class is the setup.</p>
<p>Here we give our instrumentation “group” a name. An InstrumentationModule needs to have at least one name. The user of the javaagent can suppress a chosen instrumentation by referring to it by one of its names. The instrumentation module names use kebab-case.</p>
<pre><code>public WordCountInstrumentationModule() {
        super("wordcount-demo", "wordcount");
    }
</code></pre>
<p>Apart from this, we see methods in this class to specify the order of loading this relative to other instrumentation if needed, and we specify the class that extends TypeInstrumention and are responsible for the main bulk of the instrumentation work.</p>
<p>Let's take a look at that WordCountInstrumention class, which extends TypeInstrumention now:</p>
<pre><code>// The WordCountInstrumentation class implements the TypeInstrumentation interface.
// This allows us to specify which types of classes (based on some matching criteria) will have their methods instrumented.

public class WordCountInstrumentation implements TypeInstrumentation {

    // The typeMatcher method is used to define which classes the instrumentation should apply to.
    // In this case, it's the "org.davidgeorgehope.Main" class.
    @Override
    public ElementMatcher&lt;TypeDescription&gt; typeMatcher() {
        logger.info("TEST typeMatcher");
        return ElementMatchers.named("org.davidgeorgehope.Main");
    }

    // In the transform method, we specify which methods of the classes matched above will be instrumented,
    // and also the advice (a piece of code) that will be added to these methods.
    @Override
    public void transform(TypeTransformer typeTransformer) {
        logger.info("TEST transform");
        typeTransformer.applyAdviceToMethod(namedOneOf("countWords"),this.getClass().getName() + "$WordCountAdvice");
    }

    // The WordCountAdvice class contains the actual pieces of code (advices) that will be added to the instrumented methods.
    @SuppressWarnings("unused")
    public static class WordCountAdvice {
        // This advice is added at the beginning of the instrumented method (OnMethodEnter).
        // It creates and starts a new span, and makes it active.
        @Advice.OnMethodEnter(suppress = Throwable.class)
        public static Scope onEnter(@Advice.Argument(value = 0) String input, @Advice.Local("otelSpan") Span span) {
            // Get a Tracer instance from OpenTelemetry.
            Tracer tracer = GlobalOpenTelemetry.getTracer("instrumentation-library-name","semver:1.0.0");
            System.out.print("Entering method");

            // Start a new span with the name "mySpan".
            span = tracer.spanBuilder("mySpan").startSpan();

            // Make this new span the current active span.
            Scope scope = span.makeCurrent();

            // Return the Scope instance. This will be used in the exit advice to end the span's scope.
            return scope;
        }

        // This advice is added at the end of the instrumented method (OnMethodExit).
        // It first closes the span's scope, then checks if any exception was thrown during the method's execution.
        // If an exception was thrown, it sets the span's status to ERROR and ends the span.
        // If no exception was thrown, it sets a custom attribute "wordCount" on the span, and ends the span.
        @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
        public static void onExit(@Advice.Return(readOnly = false) int wordCount,
                                  @Advice.Thrown Throwable throwable,
                                  @Advice.Local("otelSpan") Span span,
                                  @Advice.Enter Scope scope) {
            // Close the scope to end it.
            scope.close();

            // If an exception was thrown during the method's execution, set the span's status to ERROR.
            if (throwable != null) {
                span.setStatus(StatusCode.ERROR, "Exception thrown in method");
            } else {
                // If no exception was thrown, set a custom attribute "wordCount" on the span.
                span.setAttribute("wordCount", wordCount);
            }

            // End the span. This makes it ready to be exported to the configured exporter (e.g. Elastic).
            span.end();
        }
    }
}
</code></pre>
<p>The target class for our instrumentation is defined in the typeMatch method, and the method we want to instrument is defined in the transform method. We are targeting the Main class and the countWords method.</p>
<p>As you can see, we have an inner class here that does most of the work of defining an onEnter and onExit method, which tells us what to do when we enter the countWords method and when we exit the countWords method.</p>
<p>In the onEnter method, we set up a new OpenTelemetry span, and in the onExit method, we end the span. If the method successfully ends, we also grab the wordcount and append that to the attribute.</p>
<p>Now let's take a look at what happens when we run this. The good news is that we have made this extremely simple by providing a dockerfile for your use to do all the work for you.</p>
<h2 id="pullingthisalltogether">Pulling this all together</h2>
<p><a href="https://github.com/davidgeorgehope/custom-instrumentation-examples/tree/main">Clone the GitHub repository</a> if you have not already done so, and before continuing, let’s take a quick look at the dockerfile we are using.</p>
<pre><code># Build stage
FROM maven:3.8.7-openjdk-18 as build

COPY simple-java /home/app/simple-java
COPY opentelemetry-custom-instrumentation /home/app/opentelemetry-custom-instrumentation

WORKDIR /home/app/simple-java
RUN mvn install

WORKDIR /home/app/opentelemetry-custom-instrumentation
RUN mvn install

# Package stage
FROM maven:3.8.7-openjdk-18
COPY --from=build /home/app/simple-java/target/simple-java-1.0-SNAPSHOT.jar /usr/local/lib/simple-java-1.0-SNAPSHOT.jar
COPY --from=build /home/app/opentelemetry-custom-instrumentation/target/opentelemetry-custom-instrumentation-1.0-SNAPSHOT.jar /usr/local/lib/opentelemetry-custom-instrumentation-1.0-SNAPSHOT.jar

WORKDIR /

RUN curl -L -o opentelemetry-javaagent.jar https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/latest/download/opentelemetry-javaagent.jar

COPY start.sh /start.sh
RUN chmod +x /start.sh

ENTRYPOINT ["/start.sh"]
</code></pre>
<p>This dockerfile works in two parts: during the docker build process, we build the simple-java application from source followed by the custom instrumentation. After this, we download the latest OpenTelemetry Java Agent. During runtime, we simple execute the start.sh file described below:</p>
<pre><code>#!/bin/sh
java \
-javaagent:/opentelemetry-javaagent.jar \
-Dotel.exporter.otlp.endpoint=${SERVER_URL} \
-Dotel.exporter.otlp.headers="Authorization=Bearer ${SECRET_KEY}" \
-Dotel.metrics.exporter=otlp \
-Dotel.logs.exporter=otlp \
-Dotel.resource.attributes=service.name=simple-java,service.version=1.0,deployment.environment=production \
-Dotel.service.name=your-service-name \
-Dotel.javaagent.extensions=/usr/local/lib/opentelemetry-custom-instrumentation-1.0-SNAPSHOT.jar \
-Dotel.javaagent.debug=true \
-jar /usr/local/lib/simple-java-1.0-SNAPSHOT.jar
</code></pre>
<p>There are two important things to note with this script: the first is that we start the javaagent parameter set to the opentelemetry-javaagent.jar — this will start the OpenTelemetry javaagent running, which starts before any code is executed.</p>
<p>Inside this jar there has to be a class with a premain method which the JVM will look for. This bootstraps the java agent. As described above, any bytecode that is compiled is essentially filtered through the javaagent code so it can modify the class before being executed.</p>
<p>The second important thing here is the configuration of the javaagent.extensions, which loads our extension that we built to add instrumentation for our simple-java application.</p>
<p>Now run the following commands:</p>
<pre><code>docker build -t djhope99/custom-otel-instrumentation:1 .
docker run -it -e 'SERVER_URL=XXX' -e 'SECRET_KEY=XX djhope99/custom-otel-instrumentation:1
</code></pre>
<p>If you use the SERVER_URL and SECRET_KEY you got earlier in here, you should see this connect to Elastic.</p>
<p>When it starts up, it will ask you to enter a sentence, enter a few sentences, and press enter. Do this a few times — there is a sleep in here to force a long running transaction:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt155731d7462ebeb8/6a85cb20eaf245a1ada49f61/elastic-blog-3-codeblack.png" alt="code" /></p>
<p>Eventually you will see the service show up in the service map:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt209a74d642d0278a/6a85cb239bf994bab90a056f/elastic-blog-4-services.png" alt="services" /></p>
<p>Traces will appear:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt912f14b090cdde8c/6a85cb26f61d6e7e8d9c2b21/elastic-blog-5-your-service-name.png" alt="service name" /></p>
<p>And in the span you will see the wordcount attribute we collected:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7c3de41db6310bb9/6a85cb299d2b716bd4f93984/elastic-blog-6-transaction-details.png" alt="transaction details" /></p>
<p>This can be used for further dashboarding and AI/ML, including anomaly detection if you need, which is easy to do, as you can see below.</p>
<p>First click on the burger on the left side and select <strong>Dashboard</strong> to create a new dashboard:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blteeec7457390a809c/6a85cb2b2d64d53d02081d40/elastic-blog-7-manage-deployment-analytics.png" alt="analytics" /></p>
<p>From here, click <strong>Create Visualization</strong>.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltbabf439d0dac824b/6a85cb2e9bf994451b0a0573/elastic-blog-8-visualization.png" alt="visualization" /></p>
<p>Search for the wordcount label in the APM index as shown below:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb7703450e4ce97d8/6a85cb3111893c4072a7ab96/elastic-blog-9-dashboard-word.png" alt="dashboard" /></p>
<p>As you can see, because we created this attribute in the Span code as below with wordCount as a type “Integer,” we were able to automatically assign it as a numeric field in Elastic:</p>
<pre><code>span.setAttribute("wordCount", wordCount);
</code></pre>
<p>From here we can drag and drop it into the visualization for display on our Dashboard! Super easy.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7da2d84e050356a3/6a85cb34501a85781efbb327/elastic-blog-10-drag-drop.png" alt="dra and drop" /></p>
<h2 id="inconclusion">In conclusion</h2>
<p>This blog elucidates the invaluable role of OpenTelemetry Java Agent in filling the visibility gaps and obtaining crucial business monitoring data, especially when access to the source code is not feasible.</p>
<p>The blog unraveled the basic understanding of Java Agent, Bytecode, and Byte Buddy, followed by a comprehensive examination of the automatic instrumentation process with Byte Buddy.</p>
<p>The implementation of the OpenTelemetry Java Agent, using the Extensions framework, was demonstrated with the aid of a simple Java application, which underscored the agent's ability to inject trace code into the application to facilitate monitoring.</p>
<p>It detailed how to configure the agent and integrate OpenTelemetry Extension, and it outlined the operation of a sample application to help users comprehend the practical application of the information discussed. This instructive blog post is an excellent resource for SREs and IT Operations seeking to optimize their work with applications using OpenTelemetry's automatic instrumentation feature.</p>
<blockquote>
  <ul>
  <li><a href="https://www.elastic.co/blog/opentelemetry-observability">Independence with OpenTelemetry on Elastic</a></li>
  <li><a href="https://www.elastic.co/blog/implementing-kubernetes-observability-security-opentelemetry">Modern observability and security on Kubernetes with Elastic and OpenTelemetry</a></li>
  <li><a href="https://www.elastic.co/blog/3-models-logging-opentelemetry-elastic">3 models for logging with OpenTelemetry and Elastic</a></li>
  <li><a href="https://www.elastic.co/blog/adding-free-and-open-elastic-apm-as-part-of-your-elastic-observability-deployment">Adding free and open Elastic APM as part of your Elastic Observability deployment</a></li>
  <li><a href="https://www.elastic.co/blog/monitor-openai-api-gpt-models-opentelemetry-elastic">Monitor OpenAI API and GPT models with OpenTelemetry and Elastic</a></li>
  <li><a href="https://www.elastic.co/virtual-events/future-proof-your-observability-platform-with-opentelemetry-and-elastic">Future proof your observability platform with OpenTelemetry and Elastic</a></li>
  </ul>
</blockquote>
<p>Don’t have an Elastic Cloud account yet? Sign up <a href="https://cloud.elastic.co/registration">for Elastic Cloud</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/extensions-opentelemetry-java-agent</link>
    <guid isPermaLink="false">extensions-opentelemetry-java-agent</guid>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[APM]]></category>
    <dc:creator><![CDATA[David Hope]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1c9c06c4bc5e5cbd/6a85cb37bc5bb3bb24f81b01/flexible-implementation-1680X980.png" length="0" type="image/png"/>
    <pubDate>Mon, 24 Jul 2023 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[How to combine OpenTelemetry instrumentation with Elastic APM Agent features]]></title>
    <description><![CDATA[This post shows you how you can combine the OpenTelemetry tracing APIs with Elastic APM Agents. You'll learn how OpenTelemetry spans became part of a trace that Elastic APM Agents report.]]></description>
    <content:encoded><![CDATA[<p>Elastic APM supports OpenTelemetry on multiple levels. One easy-to understand scenario, which <a href="https://www.elastic.co/blog/opentelemetry-observability">we previously blogged about</a>, is the direct OpenTelemetry Protocol (OTLP) support in APM Server. This means that you can connect any OpenTelemetry agent to an Elastic APM Server and the APM Server will happily take that data, ingest it into Elasticsearch<sup>®</sup>, and you can view that OpenTelemetry data in the APM app in Kibana<sup>®</sup>.</p>
<p>This blog post will showcase a different use-case: within Elastic APM, we have <a href="https://www.elastic.co/guide/en/apm/agent/index.html">our own APM Agents</a>. Some of these have download numbers in the tens of millions, and some of them predate OpenTelemetry. Of course we realize OpenTelemetry is very important and it’s here to stay, so we wanted to make these agents OpenTelemetry compatible and illustrate them using <a href="https://www.elastic.co/observability/opentelemetry">OpenTelemetry visualizations</a> in this blog.</p>
<p>Most of our Elastic APM Agents today are able to ship OpenTelemetry spans as part of a trace. This means that if you have any component in your application that emits an OpenTelemetry span, it’ll be part of the trace the Elastic APM Agent captures. This can be a library you use that is already instrumented by the OpenTelemetry API, or it can be any other OpenTelemetry span that an application developer added into the application’s code for manual instrumentation.</p>
<p>This feature of the Elastic APM Agents not only reports those spans but also properly maintains parent-child relationships between all spans, making OpenTelemetry a first-class citizen for these agents. If, for example, an Elastic APM Agent starts a span for a specific action by auto-instrumentation and then within that span the OpenTelemetry API starts another span, then the OpenTelemetry span will be the child of the outer span created by the agent. This is reflected in the parent.id field of the spans. It’s the same the other way around as well: if a span is created by the OpenTelemetry API and within that span an Elastic APM agent captures another span, then the span created by the Elastic APM Agent will be the child of the other span created by the OpenTelemetry API.</p>
<p>This feature is present in the following agents:</p>
<ul>
<li><a href="https://www.elastic.co/guide/en/apm/agent/java/current/opentelemetry-bridge.html">Java</a></li>
<li><a href="https://www.elastic.co/guide/en/apm/agent/dotnet/master/opentelemetry-bridge.html">.NET</a></li>
<li><a href="https://www.elastic.co/guide/en/apm/agent/python/current/opentelemetry-bridge.html">Python</a></li>
<li><a href="https://www.elastic.co/guide/en/apm/agent/nodejs/current/opentelemetry-bridge.html">Node.js</a></li>
<li><a href="https://www.elastic.co/guide/en/apm/agent/go/current/opentelemetry.html">Go</a></li>
</ul>
<h2 id="capturingopentelemetryspansintheelasticnetapmagent">Capturing OpenTelemetry spans in the Elastic .NET APM Agent</h2>
<p>As a first example, let’s take an ASP.NET Core application. We’ll put the .NET Elastic APM Agent into this application, and we’ll turn on the feature, which automatically bridges OpenTelemetry spans, so the Elastic APM Agent will make those spans part of the trace it reports.</p>
<p>The following code snippet shows a controller:</p>
<pre><code>namespace SampleAspNetCoreApp.Controllers
{
    public class HomeController : Controller
    {
        private readonly SampleDataContext _sampleDataContext;
        private ActivitySource _activitySource = new ActivitySource("HomeController");
        public HomeController(SampleDataContext sampleDataContext) =&gt; _sampleDataContext = sampleDataContext;
        public async Task&lt;IActionResult&gt; Index()
        {
            await ReadGitHubStars();
            return View();
        }
        public async Task ReadGitHubStars()
        {
            using var activity = _activitySource.StartActivity();
            var httpClient = new HttpClient();
            httpClient.DefaultRequestHeaders.Add("User-Agent", "APM-Sample-App");
            var responseMsg = await httpClient.GetAsync("https://api.github.com/repos/elastic/apm-agent-dotnet");
            var responseStr = await responseMsg.Content.ReadAsStringAsync();
            // …use responseStr
        }
    }
}
</code></pre>
<p>The Index method calls the ReadGitHubStars method and after that we simply return the corresponding view from the method.</p>
<p>The incoming HTTP call and the outgoing HTTP call by the HttpClient are automatically captured by the Elastic APM Agent — this is part of the auto instrumentation we had for a very long time.</p>
<p>The ReadGitHubStars is the one where we use the OpenTelemetry API. OpenTelemetry in .NET uses the ActivitySource and Activity APIs. The _activitySource.StartActivity() call simply creates an OpenTelemetry span that automatically takes the name of the method by using the <a href="https://learn.microsoft.com/en-us/dotnet/api/system.runtime.compilerservices.callermembernameattribute?view=net-7.0">CallerMemberNameAttribute</a> C# language feature, and this span will end when the method runs to completion.</p>
<p>Additionally, within this span we call the GitHub API with the HttpClient type. For this type, the .NET Elastic APM Agent again offers auto instrumentation, so the HTTP call will be also captured as a span by the agent automatically.</p>
<p>And here is how the water-flow chart for this transaction looks in Kibana:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt74ab52d5f4ac902d/6a85cdc999083f5dd640fa25/elastic-blog-1-trace-sample.png" alt="trace sample kibana" /></p>
<p>As you can see, the agent was able to capture the OpenTelemetry span as part of the trace.</p>
<h2 id="bridgingopentelemetryspansinpythonbyusingthepythonelasticapmagent">Bridging OpenTelemetry spans in Python by using the Python Elastic APM Agent</h2>
<p>Let’s see how this works in the case of Python. The idea is the same, so all the concepts introduced previously apply to this example as well.</p>
<p>We take a very simple Django example:</p>
<pre><code>from django.http import HttpResponse
from elasticapm.contrib.opentelemetry import Tracer
import requests


def index(request):
   tracer = Tracer(__name__)
   with tracer.start_as_current_span("ReadGitHubStars"):
       url = "https://api.github.com/repos/elastic/apm-agent-python"
       response = requests.get(url)
       return HttpResponse(response)
</code></pre>
<p>The first step to turn on capturing OpenTelemetry spans in Python is to import the Tracer implementation from elasticapm.contrib.opentelemetry.</p>
<p>And then on this Tracer you can start a new span — in this case, we manually name the span ReadGitHubStars.</p>
<p>Similarly to the previous example, the call to http://127.0.0.1:8000/otelsample/ is captured by the Elastic APM Python Agent, and then the next span is created by the OpenTelemetry API, which, as you can see, is captured by the agent automatically, and then finally the HTTP call to the GitHub API is captured again by the auto instrumentation of the agent.</p>
<p>Here is how it looks in the water-flow chart:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8ca9d5ade61555bd/6a85cdcc501a854155fbb389/elastic-blog-2-trace-sample-2.png" alt="water-flow chart" /></p>
<p>As already mentioned, the agent maintains the parent-child relationship for all the OTel spans. Let’s take a look at the parent.id of the GET api.github.com call:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt88dfea9f113f54c9/6a85cdce93ffb9618fb9147b/elastic-blog-3-span-details.png" alt="OTel span details" /></p>
<p>As you can see, the id of this span is c98401c94d40b87a.</p>
<p>If we look at the span.id of the ReadGitHubStars OpenTelemetry span, then we can see that the id of this span is exactly c98401c94d40b87a — so the APM Agent internally maintains parent-child relationships across OpenTelemetry and non-OpenTelemetry spans, which makes OpenTelemetry spans first-class citizens in Elastic APM Agents.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt70765de3fe7cab11/6a85cdd199083f015d40fa29/elastic-blog-4-span-details-2.png" alt="OpenTelemetry spans first-class citizens in Elastic APM Agents" /></p>
<h2 id="otherlanguages">Other languages</h2>
<p>At this point, I'll stop to just replicate the exact same sample code in further languages — I think you already got the point here: in each language listed above, our Elastic APM Agents are able to bridge OpenTelemetry traces and show them in Kibana as native spans. We also <a href="https://www.elastic.co/blog/create-your-own-instrumentation-with-the-java-agent-plugin">blogged about using the same API in Java</a>, and you can see examples for the rest of the languages in the corresponding agent documentation (linked above).</p>
<h2 id="whentousethisfeatureandwhentousepureopentelemetrysdks">When to use this feature and when to use pure OpenTelemetry SDKs</h2>
<p>This is really up to you. If you want to only have pure OpenTelemetry usage in your applications and you really want to avoid any vendor-related software, then feel free to use OpenTelemetry SDKs directly — that is a use case we clearly support. If you go that route, this feature is not so relevant to you.</p>
<p>However, our Elastic APM Agents already have a very big user base and they offer features that are not present in OpenTelemetry. Some of these features are <a href="https://www.elastic.co/guide/en/apm/guide/current/span-compression.html">span compression</a>, <a href="https://www.elastic.co/guide/en/kibana/current/agent-configuration.html">central configuration</a>, <a href="https://www.elastic.co/guide/en/apm/agent/java/current/method-sampling-based.html">inferred spans</a>, distributed <a href="https://www.elastic.co/guide/en/apm/guide/current/configure-tail-based-sampling.html">tail based sampling</a> with multiple APM Servers, and many more.</p>
<p>If you are one of the many existing Elastic APM Agent users, or you plan to use an Elastic APM Agent because of the features mentioned above, then bridging OpenTelemetry spans enables you to still use the OpenTelemetry API and not rely on any vendor related API usage. That way your developer teams can instrument your application with OpenTelemetry, and you can also use any third-party library already instrumented by OpenTelemetry, and Elastic APM Agents will happily report those spans as part of the traces they report. With this, you can combine the vendor independent nature of OpenTelemetry and still use the feature rich Elastic APM Agents.</p>
<p>The OpenTelemetry bridge feature is also a good tool to use if you wish to change your telemetry library from an Elastic APM Agent to OpenTelemetry (and vice-versa), as it allows you to use both libraries together and switch them using atomic changes.</p>
<h2 id="nextsteps">Next steps</h2>
<p>In this blog post, we discussed how you can bridge OpenTelemetry spans with Elastic APM Agents. Of course OpenTelemetry is more than just traces. We know that, and we plan to cover further areas: currently we are working on bridging OpenTelemetry metrics in our Elastic APM Agents in a very similar fashion. You can watch the progress <a href="https://github.com/elastic/apm/issues/691">here</a>.</p>
<p><a href="https://www.elastic.co/blog/adding-free-and-open-elastic-apm-as-part-of-your-elastic-observability-deployment">Learn more about adding Elastic APM as part of your Elastic Observability deployment</a>.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/opentelemetry-instrumentation-apm-agent-features</link>
    <guid isPermaLink="false">opentelemetry-instrumentation-apm-agent-features</guid>
    <category><![CDATA[APM]]></category>
    <category><![CDATA[OpenTelemetry]]></category>
    <dc:creator><![CDATA[Greg Kalapos]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt39db6e3f1e9f8164/6a85cdd48c294404feb890a7/opentelemetry_apm-blog-720x420.jpeg" length="0" type="image/jpeg"/>
    <pubDate>Thu, 13 Jul 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[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[Monitor OpenAI API and GPT models with OpenTelemetry and Elastic]]></title>
    <description><![CDATA[Get ready to be blown away by this game-changing approach to monitoring cutting-edge ChatGPT applications! As the ChatGPT phenomenon takes the world by storm, it's time to supercharge your monitoring game with OpenTelemetry and Elastic Observability.]]></description>
    <content:encoded><![CDATA[<p>ChatGPT is so hot right now, it broke the internet. As an avid user of ChatGPT and a developer of ChatGPT applications, I am incredibly excited by the possibilities of this technology. What I see happening is that there will be exponential growth of ChatGPT-based solutions, and people are going to need to monitor those solutions.</p>
<p>Since this is a pretty new technology, we wouldn’t want to burden our shiny new code with proprietary technology, would we? No, we would not, and that is why we are going to use OpenTelemetry to monitor our ChatGPT code in this blog. This is particularly relevant for me as I recently created a service to generate meeting notes from Zoom calls. If I am to release this into the wild, how much is it going to cost me and how do I make sure it is available?</p>
<h2 id="openaiapistotherescue">OpenAI APIs to the rescue</h2>
<p>The OpenAI API is pretty awesome, there is no doubt. It also gives us the information shown below in each response to each API call, which can help us with understanding what we are being charged. By using the token counts, the model, and the pricing that OpenAI has put up on its website, we can calculate the cost. The question is, how do we get this information into our monitoring tools?</p>
<pre><code>{
  "choices": [
    {
      "finish_reason": "length",
      "index": 0,
      "logprobs": null,
      "text": "\n\nElastic is an amazing observability tool because it provides a comprehensive set of features for monitoring"
    }
  ],
  "created": 1680281710,
  "id": "cmpl-70CJq07gibupTcSM8xOWekOTV5FRF",
  "model": "text-davinci-003",
  "object": "text_completion",
  "usage": {
    "completion_tokens": 20,
    "prompt_tokens": 9,
    "total_tokens": 29
  }
}
</code></pre>
<h2 id="opentelemetrytotherescue">OpenTelemetry to the rescue</h2>
<p><a href="https://www.elastic.co/blog/opentelemetry-observability">OpenTelemetry</a> is truly a fantastic piece of work. It has had so much adoption and work committed to it over the years, and it seems to really be getting to the point where we can call it the Linux of Observability. We can use it to record logs, metrics, and traces and get those in a vendor neutral way into our favorite observability tool — in this case, Elastic Observability.</p>
<p>With the latest and greatest otel libraries in Python, we can auto-instrument external calls, and this will help us understand how OpenAI calls are performing. Let's take a sneak peek at our sample Python application, which implements Flask and the ChatGPT API and also has OpenTelemetry. If you want to try this yourself, take a look at the GitHub link at the end of this blog and follow these steps.</p>
<h3 id="setupelasticcloudaccountifyoualreadydonthaveone">Set up Elastic Cloud account (if you already don’t have one)</h3>
<ol>
<li>Sign up for a two-week free trial at <a href="https://www.elastic.co/cloud/elasticsearch-service/signup">https://www.elastic.co/cloud/elasticsearch-service/signup</a>.</li>
<li>Create a deployment.</li>
</ol>
<p>Once you are logged in, click <strong>Add integrations</strong>.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2a530a6a1d8ae18c/6a85cd3eeaf2458371a49f8f/blog-elastic-cloud-deployment-add-integrations.png" alt="elastic cloud deployment add integrations" /></p>
<p>Click on <strong>APM Integration</strong>.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt12f670bb3d7aad2c/6a85cd411aa1e1660eff8da3/blog-elastic-apm-integration.png" alt="elastic apm integration" /></p>
<p>Then scroll down to get the details you need for this blog:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltfa14098df2f3aab7/6a85cd44d6cf2912dcbb0925/blog-elastic-opentelemetry-download.png" alt="elastic opentelemetry download" /></p>
<p>Be sure to set the following Environment variables, replacing the variables with data you get from Elastic as above and OpenAI from <a href="https://platform.openai.com/account/api-keys">here</a>, and then run these export commands on the command line.</p>
<pre><code>export OPEN_AI_KEY=sk-abcdefgh5ijk2l173mnop3qrstuvwxyzab2cde47fP2g9jij
export OTEL_EXPORTER_OTLP_AUTH_HEADER=abc9ldeofghij3klmn
export OTEL_EXPORTER_OTLP_ENDPOINT=https://123456abcdef.apm.us-west2.gcp.elastic-cloud.com:443
</code></pre>
<p>And install the following Python libraries:</p>
<pre><code>pip3 install opentelemetry-api
pip3 install opentelemetry-sdk
pip3 install opentelemetry-exporter-otlp
pip3 install opentelemetry-instrumentation
pip3 install opentelemetry-instrumentation-requests
pip3 install openai
pip3 install flask
</code></pre>
<p>Here is a look at the code we are using for the example application. In the real world, this would be your own code. All this does is call OpenAI APIs with the following message: “Why is Elastic an amazing observability tool?”</p>
<pre><code>import openai
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

# OpenTelemetry setup up code here, feel free to replace the “your-service-name” attribute here.
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__)
# Set OpenAI API key
openai.api_key = os.getenv('OPEN_AI_KEY')


@app.route("/completion")
@tracer.start_as_current_span("do_work")
def completion():
    response = openai.Completion.create(
        model="text-davinci-003",
        prompt="Why is Elastic an amazing observability tool?",
        max_tokens=20,
        temperature=0
    )
    return response.choices[0].text.strip()

if __name__ == "__main__":
    app.run()
</code></pre>
<p>This code should be fairly familiar to anyone who has implemented OpenTelemetry with Python here — there is no specific magic. The magic happens inside the “monitor” code that you can use freely to instrument your own OpenAI applications.</p>
<h2 id="monkeyingaround">Monkeying around</h2>
<p>Inside the monitor.py code, you will see we do something called “Monkey Patching.” Monkey patching is a technique in Python where you dynamically modify the behavior of a class or module at runtime by modifying its attributes or methods. Monkey patching allows you to change the functionality of a class or module without having to modify its source code. It can be useful in situations where you need to modify the behavior of an existing class or module that you don't have control over or cannot modify directly.</p>
<p>What we want to do here is modify the behavior of the “Completion” call so we can steal the response metrics and add them to our OpenTelemetry spans. You can see how we do that below:</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
# Monkey-patch the openai.Completion.create function
openai.Completion.create = count_completion_requests_and_tokens(openai.Completion.create)
</code></pre>
<p>By adding all this data to our Span, we can actually send it to our OpenTelemetry OTLP endpoint (in this case it will be Elastic). The benefit of doing this is that you can easily use the data for search or to build dashboards and visualizations. In the final step, we also want to calculate the cost. We do this by implementing the following function, which will calculate the cost of a single request to the OpenAI APIs.</p>
<pre><code>def calculate_cost(response):
    if response.model in ['gpt-4', 'gpt-4-0314']:
        cost = (response.usage.prompt_tokens * 0.03 + response.usage.completion_tokens * 0.06) / 1000
    elif response.model in ['gpt-4-32k', 'gpt-4-32k-0314']:
        cost = (response.usage.prompt_tokens * 0.06 + response.usage.completion_tokens * 0.12) / 1000
    elif 'gpt-3.5-turbo' in response.model:
        cost = response.usage.total_tokens * 0.002 / 1000
    elif 'davinci' in response.model:
        cost = response.usage.total_tokens * 0.02 / 1000
    elif 'curie' in response.model:
        cost = response.usage.total_tokens * 0.002 / 1000
    elif 'babbage' in response.model:
        cost = response.usage.total_tokens * 0.0005 / 1000
    elif 'ada' in response.model:
        cost = response.usage.total_tokens * 0.0004 / 1000
    else:
        cost = 0
    return cost
</code></pre>
<h2 id="elastictotherescue">Elastic to the rescue</h2>
<p>Once we are capturing all this data, it’s time to have some fun with it in Elastic. In Discover, we can see all the data points we sent over using the OpenTelemetry library:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltfce6ddd6aa2ec67b/6a85cd460782905a9f3217aa/blog-elastic-discover-apm.png" alt="elastic discover apm" /></p>
<p>With these labels in place, it is very easy to build a dashboard. Take a look at this one I built earlier (<a href="https://github.com/davidgeorgehope/ChatGPTMonitoringWithOtel/blob/main/chatGPTDashboard.ndjson">which is also checked into my GitHub Repository</a>):</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt771ed8e0409e9e81/6a85cd4907829032893217ae/blog-elastic-labels-dashboard.png" alt="elastic labels dashboard" /></p>
<p>We can also see Transactions, Latency of the OpenAI service, and all the spans related to our ChatGPT service calls.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt090652b31aa8510a/6a85cd4c4710c62948d3cba0/blog-elastic-observability-service-name.png" alt="observability service name" /></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta8534a667cecc5f1/6a85cd4f18249c222918f803/blog-elastic-your-service-name.png" alt="elastic your service name" /></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltfc32738277650edf/6a85cd529bf994220f0a05b5/blog-elastic-api-openai.png" alt="elastic api openai" /></p>
<p>In the transaction view, we can also see how long specific OpenAI calls have taken:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0101e66241fca1b4/6a85cd54f9373dad1d96f5de/blog-elastic-latency-distribution.png" alt="elastic latency distribution" /></p>
<p>Some requests to OpenAI here have taken over 3 seconds. ChatGPT can be very slow, so it’s important for us to understand how slow this is and if users are becoming frustrated.</p>
<h2 id="summary">Summary</h2>
<p>We looked at monitoring ChatGPT with OpenTelemetry with Elastic. ChatGPT is a worldwide phenomenon and it’s going to no doubt grow and grow, and pretty soon everyone will be using it. Because it can be slow to get responses out, it is critical that people are able to understand the performance of any code that is using this service.</p>
<p>There is also the issue of cost, since it’s incredibly important to understand if this service is eating into your margins and if what you are asking for is profitable for your business. With the current economic environment, we have to keep an eye on profitability.</p>
<p>Take a look at the code for this solution <a href="https://github.com/davidgeorgehope/ChatGPTMonitoringWithOtel">here</a>. And please feel free to use the “monitor” library to instrument your own OpenAI code.</p>
<p>Interested in learning more about Elastic Observability? Check out the following resources:</p>
<ul>
<li><a href="https://www.elastic.co/virtual-events/intro-to-elastic-observability">An Introduction to Elastic Observability</a></li>
<li><a href="https://www.elastic.co/training/observability-fundamentals">Observability Fundamentals Training</a></li>
<li><a href="https://www.elastic.co/observability/demo">Watch an Elastic Observability demo</a></li>
<li><a href="https://www.elastic.co/blog/observability-predictions-trends-2023">Observability Predictions and Trends for 2023</a></li>
</ul>
<p>And sign up for our <a href="https://www.elastic.co/virtual-events/emerging-trends-in-observability">Elastic Observability Trends Webinar</a> featuring AWS and Forrester, not to be missed!</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/monitor-openai-api-gpt-models-opentelemetry</link>
    <guid isPermaLink="false">monitor-openai-api-gpt-models-opentelemetry</guid>
    <category><![CDATA[LLM Observability]]></category>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[APM]]></category>
    <dc:creator><![CDATA[David Hope]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc8ce30804f9f5a2b/6a85cd5743c0b79e872f0666/opentelemetry-graphic-ad-2-1920x1080.png" length="0" type="image/png"/>
    <pubDate>Tue, 04 Apr 2023 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Monitoring Android applications with Elastic APM]]></title>
    <description><![CDATA[Elastic has launched its APM agent for Android applications, allowing developers to track key aspects of applications to help troubleshoot issues and performance flaws with mobile applications, corresponding backend services, and their interactions.]]></description>
    <content:encoded><![CDATA[<blockquote>
  <p><strong>WARNING</strong>: This article shows information about the Android agent that is no longer accurate for versions <code>1.x</code>. Please refer to <a href="https://www.elastic.co/docs/reference/apm/agents/android">its documentation</a> to learn about its new APIs.</p>
</blockquote>
<p>People are handling more and more matters on their smartphones through mobile apps both privately and professionally. With thousands or even millions of users, ensuring great <a href="https://www.elastic.co/observability/application-performance-monitoring">monitor application performance</a> and reliability is a key challenge for providers and operators of mobile apps and related backend services. Understanding the behavior of mobile apps, the occurrences and types of crashes, the <a href="https://www.elastic.co/blog/apm-correlations-elastic-observability-root-cause-transactions">root causes of slow response times</a>, and the real user impact of backend issues is key to managing the performance of mobile apps and associated backend services.</p>
<p>Elastic has launched its application performance monitoring (<a href="https://www.elastic.co/observability/application-performance-monitoring">APM</a>) agent for Android applications, allowing developers to keep track of key aspects of their applications, from crashes and HTTP requests to screen rendering times and end-to-end distributed tracing. All of this helps troubleshoot issues and performance flaws with mobile applications, corresponding backend services, and their interaction. The Elastic APM Android Agent automatically instruments your application and its dependencies so that you can simply “plug-and-play” the agent into your application without having to worry about changing your codebase much.</p>
<p>The Elastic APM Android Agent has been developed from scratch on top of OpenTelemetry, an open standard and framework for observability. Developers will be able to take full advantage of its capabilities, as well as the support provided by a huge and active community. If you’re familiar with OpenTelemetry and your application is already instrumented with OpenTelemetry, then you can simply reuse it all when switching to the Elastic APM Android Agent. But no worries if that’s not the case — the agent is configured to handle common traceable scenarios automatically without having to deep dive into the specifics of the OpenTelemetry API.</p>
<p>[Related article: <a href="https://www.elastic.co/blog/adding-free-and-open-elastic-apm-as-part-of-your-elastic-observability-deployment">Adding free and open Elastic APM as part of your Elastic Observability deployment</a>]</p>
<h2 id="howitworks">How it works</h2>
<p>The Elastic APM Android Agent is a combination of an SDK plus a Gradle plugin. The SDK contains utilities that will let you initialize and configure the agent’s behavior, as well as prepare and initialize the OpenTelemetry SDK. You can use the SDK for programmatic configuration and initialization of the agent, in particular for advanced and special use cases.</p>
<p>In most cases, a programmatic configuration and initialization won’t be necessary. Instead, you can use the provided Gradle plugin to configure the agent and automatically instrument your app. The Gradle plugin uses Byte Buddy and the official Android Gradle plugin API under the hood to automatically inject instrumentation code into your app through compile-time transformation of your application’s and its dependencies’ classes.</p>
<p>Compiling your app with the Elastic Android APM Agent Gradle Plugin configured and enabled will make your Android app report tracing data, metrics, and different events and logs at runtime.</p>
<h2 id="usingtheelasticapmagentinanandroidapp">Using the Elastic APM Agent in an Android app</h2>
<p>By means of a <a href="https://github.com/elastic/sample-app-android-apm">simple demo application</a>, we’re going through the steps mentioned in the “<a href="https://www.elastic.co/guide/en/apm/agent/android/current/setup.html">Set up the Agent</a>” guide to set up the Elastic Android APM Agent.</p>
<h3 id="prerequisites">Prerequisites</h3>
<p>For this example, you will need the following:</p>
<ul>
<li>An Elastic Stack with APM enabled (We recommend using Elastic’s Cloud offering. <a href="https://www.elastic.co/cloud/elasticsearch-service/signup?baymax=docs-body&amp;elektra=docs">Try it for free</a>.)</li>
<li>Java 11+</li>
<li><a href="https://developer.android.com/studio?gclid=Cj0KCQiAic6eBhCoARIsANlox87QsDnyjpKObQSivZz6DHMLTiL76CmqZGXTEqf4L7h3jQO7ljm8B14aAo4xEALw_wcB&amp;gclsrc=aw.ds">Android Studio</a></li>
<li><a href="https://developer.android.com/studio/run/emulator">Android Emulator, AVD device</a></li>
</ul>
<p>You’ll also need a way to push the app’s <a href="https://opentelemetry.io/docs/concepts/signals/">signals</a> into Elastic. Therefore, you will need Elastic APM’s <a href="https://www.elastic.co/guide/en/apm/guide/current/secret-token.html#create-secret-token">secret token</a> that you’ll configure into our sample app later.</p>
<h3 id="testprojectforourexample">Test project for our example</h3>
<p>To showcase an end-to-end scenario including distributed tracing, in this example, we’ll instrument a <a href="https://github.com/elastic/sample-app-android-apm">simple weather application</a> that comprises two Android UI fragments and a simple local backend service based on Spring Boot.</p>
<p>The first fragment will have a dropdown list with some city names and also a button that takes you to the second one, where you’ll see the selected city’s current temperature. If you pick a non-European city on the first screen, you’ll get an error from the (local) backend when you head to the second screen. This is to demonstrate how network and backend errors are captured and correlated in Elastic APM.</p>
<h3 id="applyingtheelasticapmagentplugin">Applying the Elastic APM Agent plugin</h3>
<p>In the following, we will explain <a href="https://www.elastic.co/guide/en/apm/agent/android/current/setup.html">all the steps required to set up the Elastic APM Android Agent</a> from scratch for an Android application. In case you want to skip these instructions and see the agent in action right away, use the main branch of that repo and apply only Step (3.b) before continuing with the next Section (“Setting up the local backend service”).</p>
<ol>
<li>Clone the <a href="https://github.com/elastic/sample-app-android-apm">sample app</a> repo and open it in Android Studio.</li>
<li>Switch to the uninstrumented repo branch to start from a blank, uninstrumented Android application. You can run this command to switch to the uninstrumented branch:</li>
</ol>
<pre><code>git checkout uninstrumented
</code></pre>
<ol>
<li>Follow the Elastic APM Android Agent’s <a href="https://www.elastic.co/guide/en/apm/agent/android/current/setup.html">setup guide</a>:</li>
</ol>
<p>Add the co.elastic.apm.android plugin to the app/build.gradle file (please make sure to use the latest version available of the plugin, which you can find <a href="https://plugins.gradle.org/plugin/co.elastic.apm.android">here</a>).</p>
<p>Configure the agent’s connection to the Elastic APM backend by providing the ‘serverUrl’ and ‘secretToken’ in the ‘elasticAPM’ section of the app/build.gradle file.</p>
<pre><code>// Android app's build.gradle file
plugins {
    //...
    id "co.elastic.apm.android" version "[latest_version]"
}

//...

elasticApm {
    // Minimal configuration
    serverUrl = "https://your.elastic.apm.endpoint"

    // Optional
    serviceName = "weather-sample-app"
    serviceVersion = "0.0.1"
    secretToken = "your Elastic APM secret token"
}
</code></pre>
<ol>
<li>The only actual code change required is a one-liner to initialize the Elastic APM Android Agent in the Application.onCreate method. The application class for this sample app is located at app/src/main/java/co/elastic/apm/android/sample/MyApp.kt.</li>
</ol>
<pre><code>package co.elastic.apm.android.sample

import android.app.Application
import co.elastic.apm.android.sdk.ElasticApmAgent

class MyApp : Application() {

    override fun onCreate() {
        super.onCreate()
        ElasticApmAgent.initialize(this)
    }
}
</code></pre>
<p>Bear in mind that for this example, we’re not changing the agent’s default configuration — if you want more information about how to do so, take a look at the agent’s <a href="https://www.elastic.co/guide/en/apm/agent/android/current/configuration.html#_runtime_configuration">runtime configuration guide</a>.</p>
<p>Before launching our Android Weather App, we need to configure and start the local weather-backend service as described in the next section.</p>
<h3 id="settingupthelocalbackendservice">Setting up the local backend service</h3>
<p>One of the key features the agent provides is distributed tracing, which allows you to see the full end-to-end story of an HTTP transaction, starting from our mobile app and traversing instrumented backend services used by the app. Elastic APM will show you the full picture as one distributed trace, which comes in very handy for troubleshooting issues, especially the ones related to high latency and backend errors.</p>
<p>As part of our sample app, we’re going to launch a simple local backend service that will handle our app’s HTTP requests. The backend service is instrumented with the <a href="https://www.elastic.co/guide/en/apm/agent/java/current/index.html">Elastic APM Java agent</a> to collect and send its own APM data over to Elastic APM, allowing it to correlate the mobile interactions with the processing of the backend requests.</p>
<p>In order to configure the local server, we need to set our Elastic APM endpoint and secret token (the same used for our Android app in the previous step) into the backend/src/main/resources/elasticapm.properties file:</p>
<pre><code>service_name=weather-backend
application_packages=co.elastic.apm.android.sample
server_url=YOUR_ELASTIC_APM_URL
secret_token=YOUR_ELASTIC_APM_SECRET_TOKEN
</code></pre>
<h3 id="launchingthedemo">Launching the demo</h3>
<p>Our sample app will get automatic instrumentation for the agent’s currently <a href="https://www.elastic.co/guide/en/apm/agent/android/current/supported-technologies.html">supported frameworks</a>, which means that we’ll get to see screen rendering spans as well as OkHttp requests out of the box. For frameworks not currently supported, you could apply manual instrumentation to enrich your APM data (see “Manual Instrumentation” below).</p>
<p>We are ready to launch the demo. (The demo is meant to be executed on a local environment using an emulator for Android.) Therefore, we need to:</p>
<ol>
<li>Launch the backend service using this command in a terminal located in the root directory of our sample project: ./gradlew bootRun (or gradlew.bat bootRun if you’re on Windows). Alternatively, you can start the backend service from Android Studio.</li>
<li>Launch the weather sample app in an Android emulator (from Android Studio).</li>
</ol>
<p>Once everything is running, we need to navigate around in the app to generate some load that we would like to observe in Elastic APM. So, select a city, click <strong>Next</strong> and repeat it multiple times. Please, also make sure to select <strong>New York</strong> at least once. You will see that the weather forecast won’t work for New York as the city. Below, we will use Elastic APM to find out what’s going wrong when selecting New York.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt26502250a19f20aa/6a85cd9f1aa1e148b3ff8daf/blog-elastic-android-apm-city-selection.png" alt="apm android city selection" /></p>
<h2 id="firstglanceattheapmresults">First glance at the APM results</h2>
<p>Let’s open Kibana and navigate to the Observability solution.</p>
<p>Under the Services navigation item, you should see a list of two services: our Android app <strong>weather-sample-app</strong> and the corresponding backend service <strong>weather-backend</strong>. Click on the <strong>Service map</strong> tab to see a visualization of the dependencies between those services and any external services.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb626bf83786928dc/6a85cda143c0b74d4e2f067a/blog-elastic-apm-android-services.png" alt="apm android services" /></p>
<p>Click on the <strong>weather-sample-app</strong> to dive into the dashboard for the Android app. The service view for mobile applications is in technical preview at the publishing of this blog post, but you can already see insightful information about the app on that screen. You see information like the amount of active sessions in the selected time frame, number of HTTP requests emitted by the weather-sample-app, geographical distribution of the requests as well as breakdowns on device models, OS versions, network connection types, and app versions. (Information on crashes and app load times are under development.)</p>
<p>For the purpose of demonstration, we kept this demo simple, so the data is less diversified and also rather limited. However, this kind of data is particularly useful when you are monitoring a mobile app with higher usage numbers and higher diversification on device models, OS versions, etc. Troubleshooting problems and performance issues becomes way easier when you can use these properties to filter and group your APM data. You can use the quick filters at the top to do so and see how the metrics adopt depending on your selection.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt33aa6afedf3d3908/6a85cda49829262416583940/blog-elastic-apm-android-weather-sample-app.png" alt="apm android weather sample app" /></p>
<p>Now, let’s see how individual user interactions are processed, including downstream calls into the backend service. Under the Transactions tab (at the top), we see the different end-to-end transaction groups, including the two transactions for the FirstFragment and the SecondFragment.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf5598c2c3a0088c5/6a85cda793ffb939a5b91475/blog-elastic-apm-android-latency-distribution.png" alt="apm android latency distribution" /></p>
<p>Let’s deep dive into the SecondFragment - View appearing transaction, to see the metrics (e.g., latency, throughput) for this transaction group and also the invocation waterfall view for the individual user interactions. As we can see in the following screenshot, after view creation, the fragment performs an HTTP GET request to 10.0.2.2, which takes ~130 milliseconds. In the same waterfall, we see that the HTTP call is processed by the weather-backend service, which itself conducts an HTTP call to api.open-meteo.com.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt972b5b98aa165784/6a85cdaa43c0b75a5a2f067e/blog-elastic-apm-android-trace-samples.png" alt="apm android trace samples" /></p>
<p>Now, when looking at the waterfall view for a request where New York was selected as the city, we see an error happening on the backend service that explains why the forecast didn’t work for New York. By clicking on the red <strong>View related error</strong> badge, you will get details on the error and the actual root cause of the problem.</p>
<p>The exception message on the weather-backend states that “This service can only retrieve geo locations for European cities!” That’s the problem with selecting New York as the city.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltcf29b683d0b281a6/6a85cdad9d2b714ce1f93a02/blog-elastic-apm-android-weather-backend.png" alt="apm android weather backend" /></p>
<h2 id="manualinstrumentation">Manual instrumentation</h2>
<p>As previously mentioned, the Elastic APM Android Agent does a bunch of automatic instrumentation on your behalf for the <a href="https://www.elastic.co/guide/en/apm/agent/android/current/supported-technologies.html">supported frameworks</a>; however, in some cases, you might want to get extra instrumentation depending on your app’s use cases. For those cases, you’ve gotten covered by the OpenTelemetry API, which is what the Elastic APM Android Agent is based on. The OpenTelemetry Java SDK contains tools to create custom spans, metrics, and logs, and since it’s the base of the Elastic APM Android Agent, it’s available for you to use without having to add any extra dependencies into your project and without having to configure anything to connect your custom signals to your own Elastic environment either, as the agent does that for you.</p>
<p>The way to start would be by getting OpenTelemetry’s instance like so:</p>
<pre><code>OpenTelemetry openTelemetry = GlobalOpenTelemetry.get();
</code></pre>
<p>And then you can follow the instructions from the <a href="https://opentelemetry.io/docs/instrumentation/java/manual/#acquiring-a-tracer">OpenTelemetry Java documentation</a> in order to create your custom signals. See the following example for the creation of a custom span:</p>
<pre><code>OpenTelemetry openTelemetry = GlobalOpenTelemetry.get();
Tracer tracer = openTelemetry.getTracer("instrumentation-library-name", "1.0.0");
Span span = tracer.spanBuilder("my span").startSpan();

// Make the span the current span
try (Scope ss = span.makeCurrent()) {
  // In this scope, the span is the current/active span
} finally {
    span.end();
}
</code></pre>
<h2 id="conclusion">Conclusion</h2>
<p>In this blog post, we demonstrated how you can use the Elastic APM Android Agent to achieve end-to-end observability into your Android-based mobile applications. Setting up the agent is a matter of a few minutes and the provided insights allow you to analyze your app’s performance and its dependencies on backend services. With the Elastic APM Android Agent in place, you can leverage Elastic’s rich APM feature as well as the various possibilities to customize your analysis workflows through custom instrumentation and custom dashboards.</p>
<p>Are you curious? Then try it yourself. Sign up for a <a href="https://www.elastic.co/cloud/elasticsearch-service/signup">free trial on the Elastic Cloud</a>, enrich your Android app with the Elastic APM Android agent as described in this blog, and explore the data in <a href="https://www.elastic.co/observability">Elastic’s Observability solution</a>.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/monitoring-android-applications-apm</link>
    <guid isPermaLink="false">monitoring-android-applications-apm</guid>
    <category><![CDATA[APM]]></category>
    <category><![CDATA[OpenTelemetry]]></category>
    <dc:creator><![CDATA[Alexander Wert,Cesar Munoz]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4c265817c1268d81/6a85cdb0e2447a8c018b1446/illustration-indusrty-technology-social-1680x980.png" length="0" type="image/png"/>
    <pubDate>Tue, 21 Mar 2023 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Independence with OpenTelemetry on Elastic]]></title>
    <description><![CDATA[OpenTelemetry has become a key component for observability given its open standards and developer-friendly tools. See how easily Elastic Observability integrates with OTel to provide a platform that minimizes vendor lock-in and maximizes flexibility.]]></description>
    <content:encoded><![CDATA[<p>The drive for faster, more scalable services is on the rise. Our day-to-day lives depend on apps, from a food delivery app to have your favorite meal delivered, to your banking app to manage your accounts, to even apps to schedule doctor’s appointments. These apps need to be able to grow from not only a features standpoint but also in terms of user capacity. The scale and need for global reach drives increasing complexity for these high-demand cloud applications.</p>
<p>In order to keep pace with demand, most of these online apps and services (for example, mobile applications, web pages, SaaS) are moving to a distributed microservice-based architecture and Kubernetes. Once you’ve migrated your app to the cloud, how do you manage and monitor production, scale, and availability of the service? <a href="https://opentelemetry.io/">OpenTelemetry</a> is quickly becoming the de facto standard for instrumentation and collecting application telemetry data for Kubernetes applications.</p>
<p><a href="https://www.elastic.co/what-is/opentelemetry">OpenTelemetry (OTel)</a> is an open source project providing a collection of tools, APIs, and SDKs that can be used to generate, collect, and export telemetry data (metrics, logs, and traces) to understand software performance and behavior. OpenTelemetry recently became a CNCF incubating project and has a significant amount of growing community and vendor support.</p>
<p>While OTel provides a standard way to instrument applications with a standard telemetry format, it doesn’t provide any backend or analytics components. Hence using OTel libraries in applications, infrastructure, and user experience monitoring provides flexibility in choosing the appropriate <a href="https://www.elastic.co/observability">observability tool</a> of choice. There is no longer any vendor lock-in for application performance monitoring (APM).</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5ac0a045fdf76a37/6a7f193205b7b51e0118bd21/blog-elastic-otel-1.png" alt="" /></p>
<p>Elastic Observability natively supports OpenTelemetry and its OpenTelemetry protocol (OTLP) to ingest traces, metrics, and logs. All of Elastic Observability’s APM capabilities are available with OTel data. Hence the following capabilities (and more) are available for OTel data:</p>
<ul>
<li>Service maps</li>
<li>Service details (latency, throughput, failed transactions)</li>
<li>Dependencies between services</li>
<li>Transactions (traces)</li>
<li>ML correlations (specifically for latency)</li>
<li>Service logs</li>
</ul>
<p>In addition to Elastic’s APM and unified view of the telemetry data, you will now be able to use Elastic’s powerful machine learning capabilities to reduce the analysis, and alerting to help reduce MTTR.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta01b4eb3f8dbfb6f/6a7f1935e02fac237a5d698d/blog-elastic-otel-2.png" alt="" /></p>
<p>Given its open source heritage, Elastic also supports other CNCF based projects, such as Prometheus, Fluentd, Fluent Bit, Istio, Kubernetes (K8S), and many more.</p>
<p>This blog will show:</p>
<ul>
<li>How to get a popular OTel instrumented demo app (Hipster Shop) configured to ingest into <a href="http://cloud.elastic.co">Elastic Cloud</a> through a few easy steps</li>
<li>Highlight some of the Elastic APM capabilities and features around OTel data and what you can do with this data once it’s in Elastic</li>
</ul>
<p>In follow-up blogs, we will detail how to use Elastic’s machine learning with OTel telemetry data, how to instrument OTel application metrics for specific languages, how we can support Prometheus ingest through the OTel collector, and more. Stay tuned!</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 the configuration:</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>).</li>
<li>We used the OpenTelemetry Demo. Directions for using Elastic with OpenTelemetry Demo are <a href="https://github.com/elastic/opentelemetry-demo">here</a>.</li>
<li>Make sure you have <a href="https://kubernetes.io/docs/reference/kubectl/">kubectl</a> and <a href="https://helm.sh/">helm</a> also installed locally.</li>
<li>Additionally, we are using an OTel manually instrumented version of the application. No OTel automatic instrumentation was used in this blog configuration.</li>
<li>Location of our clusters. While we used Google Kubernetes Engine (GKE), you can use any Kubernetes platform of your choice.</li>
<li>While Elastic can ingest telemetry directly from OTel instrumented services, we will focus on the more traditional deployment, which uses the OpenTelemetry Collector.</li>
<li>Prometheus and FluentD/Fluent Bit — traditionally used to pull all Kubernetes data — is not being used here versus Kubernetes Agents. Follow-up blogs will showcase this.</li>
</ul>
<p>Here is the configuration we will get set up in this blog:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt757221af75648dce/6a7f193896b5a66c5387b867/blog-elastic-otel-3.png" alt="Configuration to ingest OpenTelemetry data used in this blog" /></p>
<h2 id="settingitallup">Setting it all up</h2>
<p>Over the next few steps, I’ll walk through an <a href="https://www.elastic.co/observability/opentelemetry">Opentelemetry visualization</a>:</p>
<ul>
<li>Getting an account on Elastic Cloud</li>
<li>Bringing up a GKE cluster</li>
<li>Bringing up the application</li>
<li>Configuring Kubernetes OTel Collector configmap to point to Elastic Cloud</li>
<li>Using Elastic Observability APM with OTel data for improved visibility</li>
</ul>
<h3 id="step0createanaccountonelasticcloud">Step 0: Create 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/blt588fbb3e515933fa/6a7f193aea068d34baf0a2b1/blog-elastic-otel-4.png" alt="" /></p>
<h3 id="step1bringupak8scluster">Step 1: Bring up a K8S cluster</h3>
<p>We used Google Kubernetes Engine (GKE), but you can use any Kubernetes platform of your choice.</p>
<p>There are no special requirements for Elastic to collect OpenTelemetry data from a Kubernetes cluster. Any normal Kubernetes cluster on GKE, EKS, AKS, or Kubernetes compliant cluster (self-deployed and managed) works.</p>
<h3 id="step2loadtheopentelemetrydemoapplicationonthecluster">Step 2: Load the OpenTelemetry demo application on the cluster</h3>
<p>Get your application on a Kubernetes cluster in your cloud service of choice or local Kubernetes platform. The application I am using is available <a href="https://github.com/bshetti/opentelemetry-microservices-demo/tree/main/deploy-with-collector-k8s">here</a>.</p>
<p>First clone the directory locally:</p>
<pre><code>git clone https://github.com/elastic/opentelemetry-demo.git
</code></pre>
<p>(Make sure you have <a href="https://kubernetes.io/docs/reference/kubectl/">kubectl</a> and <a href="https://helm.sh/">helm</a> also installed locally.)</p>
<p>The instructions utilize a specific opentelemetry-collector configuration for Elastic. Essentially, the Elastic <a href="https://github.com/elastic/opentelemetry-demo/blob/main/kubernetes/elastic-helm/values.yaml">values.yaml</a> file specified in the elastic/opentelemetry-demo configure the opentelemetry-collector to point to the Elastic APM Server using two main values:</p>
<p>OTEL_EXPORTER_OTLP_ENDPOINT is Elastic’s APM Server<br />
OTEL_EXPORTER_OTLP_HEADERS Elastic Authorization</p>
<p>These two values can be found in the OpenTelemetry setup instructions under the APM integration instructions (Integrations-&gt;APM) in your Elastic cloud.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte776fb3258454d4d/6a7f193d42a117a8cd95c2df/blog-elastic-apm-agents.png" alt="elastic apm agents" /></p>
<p>Once you obtain this, the first step is to create a secret key on the cluster with your Elastic APM server endpoint, and your APM Secret Token with the following instruction:</p>
<pre><code>kubectl create secret generic elastic-secret \
  --from-literal=elastic_apm_endpoint='YOUR_APM_ENDPOINT_WITHOUT_HTTPS_PREFIX' \
  --from-literal=elastic_apm_secret_token='YOUR_APM_SECRET_TOKEN'
</code></pre>
<p>Don't forget to replace:</p>
<ul>
<li>YOUR_APM_ENDPOINT_WITHOUT_HTTPS_PREFIX: your Elastic APM endpoint ( <strong>without https:// prefix</strong> ) with OTEL_EXPORTER_OTLP_ENDPOINT</li>
<li>YOUR_APM_SECRET_TOKEN: your Elastic APM secret token OTEL_EXPORTER_OTLP_HEADERS</li>
</ul>
<p>Now execute the following commands:</p>
<pre><code># switch to the kubernetes/elastic-helm directory
cd kubernetes/elastic-helm

# add the open-telemetry Helm repostiroy
helm repo add open-telemetry https://open-telemetry.github.io/opentelemetry-helm-charts

# deploy the demo through helm install
helm install -f values.yaml my-otel-demo open-telemetry/opentelemetry-demo
</code></pre>
<p>Once your application is up on Kubernetes, you will have the following pods (or some variant) running on the <strong>default</strong> namespace.</p>
<pre><code>kubectl get pods -n default
</code></pre>
<p>Output should be similar to the following:</p>
<pre><code>NAME                                                  READY   STATUS    RESTARTS      AGE
my-otel-demo-accountingservice-5c77754b4f-vwph6       1/1     Running   0             5d4h
my-otel-demo-adservice-6b8b7c7dc5-mb7j5               1/1     Running   0             5d4h
my-otel-demo-cartservice-76d94b7dcd-2g4lf             1/1     Running   0             5d4h
my-otel-demo-checkoutservice-988bbdb88-hmkrp          1/1     Running   0             5d4h
my-otel-demo-currencyservice-6cf4b5f9f6-vz9t2         1/1     Running   0             5d4h
my-otel-demo-emailservice-868c98fd4b-lpr7n            1/1     Running   6 (18h ago)   5d4h
my-otel-demo-featureflagservice-8446ff9c94-lzd4w      1/1     Running   0             5d4h
my-otel-demo-ffspostgres-867945d9cf-zzwd7             1/1     Running   0             5d4h
my-otel-demo-frauddetectionservice-5c97c589b9-z8fhz   1/1     Running   0             5d4h
my-otel-demo-frontend-d85ccf677-zg9fp                 1/1     Running   0             5d4h
my-otel-demo-frontendproxy-6c5c4fccf6-qmldp           1/1     Running   0             5d4h
my-otel-demo-kafka-68bcc66794-dsbr6                   1/1     Running   0             5d4h
my-otel-demo-loadgenerator-64c545b974-xfccq           1/1     Running   1 (36h ago)   5d4h
my-otel-demo-otelcol-fdfd9c7cf-6lr2w                  1/1     Running   0             5d4h
my-otel-demo-paymentservice-7955c68859-ff7zg          1/1     Running   0             5d4h
my-otel-demo-productcatalogservice-67c879657b-wn2wj   1/1     Running   0             5d4h
my-otel-demo-quoteservice-748d754ffc-qcwm4            1/1     Running   0             5d4h
my-otel-demo-recommendationservice-df78894c7-lwm5v    1/1     Running   0             5d4h
my-otel-demo-redis-7d48567546-h4p4t                   1/1     Running   0             5d4h
my-otel-demo-shippingservice-f6fc76ddd-2v7qv          1/1     Running   0             5d4h
</code></pre>
<h3 id="step3openkibanaandusetheapmservicemaptoviewyourotelinstrumentedservices">Step 3: Open Kibana and use the APM Service Map to view your OTel instrumented Services</h3>
<p>In the Elastic Observability UI under APM, select servicemap to see your services.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5ec18e0b8fe27ba9/6a7f194033fa8a5adb202b64/blog-elastic-observability-APM.png" alt="elastic observability APM" /></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1d2c8e5136dc6f53/6a7f19426693f8101666435d/blog-elastic-observability-OTEL-service-map.png" alt="elastic observability OTEL service map" /></p>
<p>If you are seeing this, then the OpenTelemetry Collector is sending data into Elastic:</p>
<p><em>Congratulations,</em> <em>you've instrumented the OpenTelemetry demo application using and successfully ingested the telemetry data into the Elastic!</em></p>
<h3 id="step4whatcanelasticshowme">Step 4: What can Elastic show me?</h3>
<p>Now that the OpenTelemetry data is ingested into Elastic, what can you do?</p>
<p>First, you can view the APM service map (as shown in the previous step) — this will give you a full view of all the services and the transaction flows between services.</p>
<p>Next, you can now check out individual services and the transactions being collected.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd116b5c740f25566/6a7f19456693f83ea1664361/blog-elastic-observability-frontend-overview.png" alt="elastic observability frontend overview" /></p>
<p>As you can see, the frontend details are listed. Everything from:</p>
<ul>
<li>Average service latency</li>
<li>Throughput</li>
<li>Main transactions</li>
<li>Failed traction rate</li>
<li>Errors</li>
<li>Dependencies</li>
</ul>
<p>Let’s get to the trace. In the Transactions tab, you can review all the types of transactions related to the frontend service:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf1669d81336b9a3e/6a7f194873d9bdc7e029df3b/blog-elastic-observability-frontend-transactions.png" alt="elastic observability frontend transactions" /></p>
<p>Selecting the HTTP POST transaction, we can see the full trace with all the spans:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7b4a0689cfa8ad76/6a7f194b33fa8a0360202b68/blog-elastic-observability-frontend-HTTP-POST.png" alt="Average latency for this transaction, throughput, any failures, and of course the trace!" /></p>
<p>Not only can you review the trace but you can also analyze what is related to higher than normal latency for HTTP POST .</p>
<p>Elastic uses machine learning to help identify any potential latency issues across the services from the trace. It’s as simple as selecting the Latency Correlations tab and running the correlation.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta53b6a97f8d8cacf/6a7f194ee88c653c1c00bae0/blog-elastic-latency-correlations.png" alt="elastic observability latency correlations" /></p>
<p>This shows that the high latency transactions are occurring in checkout service with a medium correlation.</p>
<p>You can then drill down into logs directly from the trace view and review the logs associated with the trace to help identify and pinpoint potential issues.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8ad97b9fd8b1ea36/6a7f19505967e50ea75dd69d/blog-elastic-latency-distribution.png" alt="elastic observability latency distribution" /></p>
<h3 id="analyzeyourdatawithelasticmachinelearningml">Analyze your data with Elastic machine learning (ML)</h3>
<p>Once OpenTelemetry metrics are in Elastic, start analyzing your data through Elastic’s ML capabilities.</p>
<p>A great review of these features can be found here: <a href="https://www.elastic.co/blog/apm-correlations-elastic-observability-root-cause-transactions">Correlating APM telemetry to determine root causes in transactions</a>. And there are many more videos and blogs on <a href="https://www.elastic.co/blog/">Elastic’s Blog</a>. We’ll follow up with additional blogs on leveraging Elastic’s machine learning capabilities for OpenTelemetry data.</p>
<h2 id="conclusion">Conclusion</h2>
<p>I hope you’ve gotten an appreciation for how Elastic Observability can help you ingest and analyze OpenTelemetry data with Elastic’s APM capabilities.</p>
<p>A quick recap of lessons and more specifically learned:</p>
<ul>
<li>How to get a popular OTel instrumented demo app (Hipster Shop) configured to ingest into <a href="http://cloud.elastic.co">Elastic Cloud</a>, through a few easy steps</li>
<li>Highlight some of the Elastic APM capabilities and features around OTel data and what you can do with this once it’s in Elastic</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>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/opentelemetry-observability</link>
    <guid isPermaLink="false">opentelemetry-observability</guid>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[APM]]></category>
    <category><![CDATA[Kubernetes]]></category>
    <category><![CDATA[Infrastructure Monitoring]]></category>
    <dc:creator><![CDATA[Bahubali Shetti]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt785e1bd8fa6dd28d/6a7f19532f00b2a466efef13/illustration-scalability-gear-1680x980_(1).jpg" length="0" type="image/jpeg"/>
    <pubDate>Tue, 15 Nov 2022 00:00:00 GMT</pubDate>
  </item>
  </channel>
</rss>