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


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


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


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


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


    # Checkpoint 3
    count = payload.get("current_count")
    if not isinstance(count, (int, float)) or count &lt; MIN_COUNT:
        return jsonify({"ok": False, "msg": "Missing or invalid current_count."}), 400<p>The endpoint runs three checkpoints before it accepts a win, matching the numbered comments in the code above:</p><ol><li><p><strong>Parse the request body.</strong> <code>request.get_json</code> takes the body sent by the webhook and parses it into a Python dict. Passing <code>silent=True</code> means an empty or malformed body won't raise an error; the endpoint keeps working and fills in unknown values with <code>{}</code>.</p></li><li><p><strong>Check the rule name.</strong> "<code>Cat Escape Alert</code>" is the enforced <code>rule_name</code>, so a stray request from another source can't trigger a win. Use this name exactly, or change it in the code. (See <code>EXPECTED_RULE_NAME = "Cat Escape Alert"</code>.)</p></li><li><p><strong>Check the attempt count.</strong> This gate stops an alert from counting as a win when no cat has actually moved. It's set to one, so at least one escape attempt must have registered. If you want a higher bar, change it in the code. (See <code>MIN_COUNT = 1</code>.)</p></li></ol><h2>How to create a Kibana alert rule with ES|QL</h2><p>This game ends one of two ways. You win if your created rule alerts properly; and you lose if any cat reaches 10 escape attempts. </p><p>To create your rule and play the game, follow these instructions. Lettered subsections give you a full breakdown of how to create a winning rule.</p><p>1. Go to <strong>Kibana</strong> → <strong>Stack Management</strong> → <strong>Rules</strong>.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt61e52c2d888d731c/6ad5f88975da6119d73d5427/image8.png" alt="Kibana Stack Management Rules page with the Create rule button for a new Elastic APM alert" /><p>2. <strong>Create new rule</strong> → <strong>Elasticsearch query</strong>. </p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc49c341bf787cae0/6ad5f8b37963af25066ce802/image10.png" alt="Create new rule menu in Kibana with the Elasticsearch query rule type selected" /><p>3. Create a rule to alert on logs or metrics. You can use Kibana Query Language (KQL) or Lucene, Query DSL, or ES|QL.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf9cb1c29d735ce9b/6ad5f8ca7963affc756ce806/image11.png" alt="Elasticsearch query rule in Kibana showing KQL, Query DSL and ES|QL query type options" /><ul><li><p>To create a winning rule, choose ES|QL. Enter the code block below into the box for creating a query. The service for this game is “<code>escape-room-cat</code>,” and “<code>cat.escape.attempts” IS NOT NULL</code> discards every row that isn’t an <code>escape.attempts</code> measurement. Since the <code>cat.escape.attempts</code> is an OTel counter, it only climbs and doesn’t reset when you restart your game. To circumvent this counter, we take the change in the counter over the window (<code>EVAL attempts = hi - lo</code>) instead of its absolute cumulative value.</p></li><li><p>ES|QL query: </p></li></ul><ul><li><p>Change the rule schedule for every 30–45 seconds.</p></li></ul><p>4. Add an action with your configured webhook, and use the configuration from the ″A webhook connector for your alert″” section of this post.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta9c32fde9922dbfd/6ad5f90c64f7e77b95e95d67/image6.png" alt="Add an action step in a Kibana alert rule for selecting a webhook connector" /><p></p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt79cc729911461d25/6ad5f9246d19162a3115bd30/image12.png" alt="Kibana connector list with the cat-escape-game webhook connector selected for the alert action" /><p></p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3e62429db9f31f82/6ad5f93e9355c03cce7b1c74/image5.png" alt="Webhook action body in Kibana passing cat_name, current_count, rule_name and fired_at to the game" />{
"cat_name": "{{context.group}}",
"current_count": {{context.value}},
"rule_name": "{{rule.name}}",
"fired_at": "{{context.date}}"
}<p>5. Click <strong>Start Game</strong>, and wait. </p><p>6. If your alert signals in time, you’ve won, detective. You sleuthed out the right cat and caught it in time. If your rule doesn’t alert by the tenth escape attempt, you lose. Try configuring a new rule and checking that it matches the correct <code>rule_name</code> for the checkpoint (including whitespace and casing) or changing your rule’s threshold or timing.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5e553172de633b3a/6ad5f9838d329411df11ce30/image13.png" alt="Great Cat Escape win screen: all cats caught, score 2100, alert fired at 3/10 attempts" /><p></p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta60d4d6efed415ce/6ad5f991cb361982d1dbe897/image9.png" alt="Great Cat Escape game over screen after Shadow reached 10 escape attempts in 90.3 seconds" /><h2>From cat escapes to credential stuffing detection</h2><p>Beneath this fun, interactive cat-catching game is an application that demonstrates simple and correctly wired observability architecture. The pieces assembled from this application are the building blocks of a production observability stack. They include OTLP/HTTP exporters shipping to an Elastic Observability endpoint and signals stamped with a <code>service.name</code> by a <code>resource</code>, along with the separate <code>MeterProvider</code>, <code>LoggerProvider</code>, and <code>TracerProvider</code> pipelines. They also include a counter that’s emitted alongside the span which describes the same event, an observable gauge sample through a callback, ES|QL queries transformed for alerting on delta values, and more. And, since the <a href="https://www.elastic.co/blog/whats-new-elastic-9-4-0">Elastic 9.4.0</a> also allows you to natively use Prometheus Query Language (PromQL) to query time series data, you have even more query possibilities.</p><p>If you replace “escape attempt” with “failed authentication” and <code>cat.name</code> with <code>user.id</code>, the same rule that you created to catch a fence-jumping tabby can become a credential-stuffing detector. Try out different rules and types of alerting, along with other query rates, and see how creative you can be with Elastic Observability in this game. </p><p>If you need any help with <em>The Great Cat Escape</em>, feel free to contact the author.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2ec87c59a08e6319/6ad5f9af7963af07e16ce80b/image4.png" alt="Cat Escape Alert rule detail in Kibana showing 23 alerts and the cat-escape-game webhook action" /><p><em>Daylight streamed through the windows. With a robust rule in hand and an alert in the other, the detective sighed with relief. Nights of grepping had turned into quick decisions and high scores for the seasoned private eye. This case had proved difficult, but no cats had escaped the watchful observability back end. Although this investigation was successful, there would be more cases. And more cats. But now the detective was ready for them.</em></p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/esql-alert-rules-elastic-apm</link>
    <guid isPermaLink="false">esql-alert-rules-elastic-apm</guid>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[Logs Analytics]]></category>
    <dc:creator><![CDATA[Sophia Solomon]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltea410685c2335f63/6ad5f6c7bcf6e0145fe57ae3/image7.png" length="0" type="image/png"/>
    <pubDate>Sun, 18 Oct 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[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[AIOps with Elastic Observability: Modern AIOps & Log Intelligence]]></title>
    <description><![CDATA[Exploring modern AIOps capabilities, including anomaly detection, log intelligence, and log analysis &amp; categorization with Elastic Observability.]]></description>
    <content:encoded><![CDATA[<h2 id="aiopsblogrefresherunlockingintelligencefromyourlogswithelastic">AIOps Blog Refresher: Unlocking Intelligence from Your Logs with Elastic</h2>
<p>Elastic has been leading the charge with AIOps, especially in the recent 9.2 update of Elastic Observability with Streams. The conversation around AIOps has shifted dramatically as we move through the year. DevOps and SRE teams aren't asking whether they need AIOps, they're asking how to leverage it more effectively to stay ahead of exponentially growing complexity.</p>
<p>The current challenge of AIOps is that modern cloud-native environments generate massive volumes of telemetry data that are magnitudes larger than past environments. But here's what many teams overlook: logs are the richest source of operational intelligence you have. Logs are able to tell you exactly what happened and why, while metrics only tell you something is wrong, and traces only tell you where. The problem is that most organizations are drowning in logs. Microservices, such as user authentications or inventories, serverless functions, and Kubernetes generate millions of log entries daily. Without AI and machine learning, finding meaningful patterns in this data takes too much time and energy.</p>
<h2 id="logintelligenceimprovementwhatsnewin2025">Log Intelligence Improvement: What's New in 2025</h2>
<p>Historically in observability, unlocking your log intelligence included long manual effort that required not only parsing through logs, but also structuring those logs. Elastic Observability has drastically changed how teams extract value from logs. Observability is not just simple signal analysis - modern tools need to have proactive, log-driven investigations. At Elastic, this modernity is Streams.</p>
<p>Streams, a new release from Elastic, is a collection of AI-driven tools that identify significant events in parsed raw logs by enriching logs with meaningful fields. With Streams, SREs can maximize the value of their data, their logs, and their systems. With system reliability as the goal, Streams helps to reduce pipeline management overhead and accelerates observability analysis. And it takes nearly no time to set up!</p>
<p>Here is how Streams powers the Elastic Observability capabilities available now.</p>
<h3 id="advancedlograteanalysis">Advanced Log Rate Analysis</h3>
<p>Log rate analysis can go far beyond only detecting spikes. Elastic's machine learning automatically identifies when log volumes deviate from expected baselines, then contextualizes these changes within your broader system performance. When your application suddenly generates more error logs, Elastic’s AIOps doesn't just alert you, it also determines whether it's a critical issue requiring immediate attention or just a temporary anomaly.</p>
<p>This matters to your analysis because not all log spikes are equal. A 10x increase in DEBUG logs might indicate verbose logging accidentally enabled in production. A 2x increase in ERROR logs could signal a cascading failure. Log rate analysis distinguishes between these scenarios automatically, giving your team the context needed to respond appropriately.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5bb4ac6d272c3925/6a7f0dc4eab5be0e4020a739/log-analysis.png" alt="Log Analysis" /></p>
<h3 id="intelligentlogcategorizationwithstreams">Intelligent Log Categorization with Streams</h3>
<p>This is where AIOps shines with log data. Streams uses machine learning algorithms in order to automatically classify and group similar log patterns, dramatically reducing noise. Instead of manually parsing millions of entries, the system identifies common structures, groups related events, and surfaces the categories that matter most.</p>
<p>Logs are unstructured by nature, making them difficult to analyze at scale. Streams corrals chaotic log streams into organized, queryable patterns. Instantly, you can see that 80% of your errors fall into three categories, helping you prioritize where to focus remediation efforts. This approach helps you reduce noise and accelerate analysis, allowing teams to act on insights faster.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt43de44a2668aba4d/6a7f0dc7e02fac4c835d65dc/categories.png" alt="Log Categorizations" /></p>
<h3 id="multidimensionalanomalydetection">Multi-Dimensional Anomaly Detection</h3>
<p><a href="https://www.elastic.co/docs/explore-analyze/machine-learning/anomaly-detection">Anomaly detection</a> now simultaneously examines relationships between logs, metrics, and traces. A slight increase in response time might not trigger an alert by itself, but when correlated with unusual log patterns and memory consumption changes, the system recognizes it as an early warning sign.</p>
<p>Logs contain a myriad of contextual information that metrics and traces can't capture: stack traces, user IDs, transaction details, error messages, etc. By correlating log anomalies with other signals, you get the full picture of what's happening in your system. This whole holistic view enables teams to catch issues earlier, as well as understand their full impact across the stack.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1c652019d828e657/6a7f0dca3ce8e26a5acf53db/anomalies.png" alt="Anomaly Detection" /></p>
<h3 id="enhancedrootcauseanalysispoweredbysignificantevents">Enhanced Root Cause Analysis Powered by Significant Events</h3>
<p>When an issue occurs, Elastic's Streams accelerates root cause analysis through AI-assisted parsing of logs and bringing about <a href="https://www.elastic.co/docs/solutions/observability/streams/management/significant-events">“Significant events.”</a> Significant event queries can be defined by AI or manually, depending on if you know what logs you are looking for or not. Then, Elastic’s AIOps traces the problem through your entire stack using these events, as well as enriched log data combined with distributed tracing. This system is able to correlate failed transactions with specific log entries, deployment events, and infrastructure changes. This helps you understand not just what broke, but why and when.</p>
<p>Streams makes the analysis of your logs quick and automatic by going across your entire distributed system within seconds, grabbing relevant log entries such as stack traces, state information, error messages, and more. What used to require hours of manual investigation and deduction now happens automatically, freeing you and your team from tedious detective work and enabling faster resolution. </p>
<h2 id="logsinactionrealworldimpact">Logs in Action: Real-World Impact</h2>
<p>Let's look at how these capabilities work together in practice. Imagine your payment processing service is experiencing intermittent failures - only 0.5% of transactions, but enough to concern your team. Traditional monitoring shows everything is mostly okay, but customers are still complaining.</p>
<p>Without Streams, an SRE might initially run some broad queries, manually sift through thousands of logs, struggle to connect all the dots, and ultimately not understand the correlation between the errors and recent system changes. </p>
<p>With Elastic Streams and AIOps, many of these potential problems are instantly mitigated:</p>
<ul>
<li><p>Streams automatically parse the payment service, adding connection timeouts to a new category of significant events</p></li>
<li><p>Log rate analysis with Streams reveal that this significant event category has been slowly growing over the past month, showing growth of the timeouts from a small number of occurrences into a larger amount</p></li>
<li><p>Elastic’s built-in anomaly detection correlates these significant events with deployment data, and identifies that they started appearing after a recent load balancer configuration</p></li>
<li><p>Root analysis pinpoints the exact database connection pool setting that is too restrictive for peak load by tracing affected transactions through previously enriched logs</p></li>
</ul>
<p>What usually takes 4-8 hours of manual log analysis is resolved in minutes, with Elastic automatically highlighting the relevant log entries that tell the complete story. This is the power of AIOps and Streams as applied to log intelligence.</p>
<h2 id="thepowerofunifiedlogintelligence">The Power of Unified Log Intelligence</h2>
<p>What sets Elastic apart is treating logs as a priority in your observability strategy. Elastic provides comprehensive log ingestion that centralizes petabytes of logs from across your infrastructure with flexible parsing and enrichment. The platform uses purpose-built machine learning models that understand log patterns, not generic algorithms retrofitted for log analysis.</p>
<p>Logs don't exist in isolation, which is why Elastic correlates log data with metrics, traces, and business events to provide complete context. And because log volumes can be massive, Elastic's tiered storage approach means you can retain years of logs for compliance and historical analysis without breaking the budget.</p>
<h2 id="whylogsmattermorethanever">Why Logs Matter More Than Ever</h2>
<p>Logs have become the cornerstone of effective AIOps for three critical reasons.</p>
<p>First off, logs capture what metrics can't. A metric tells you the CPU is at 80%, but a log tells you which process is consuming resources and why. This level of detail is essential for understanding not just that something is wrong, but what specifically is causing the problem.</p>
<p>Second, logs provide business context. Error messages contain user IDs, transaction ldetails, and business logic failures that help you understand customer impact. When you're troubleshooting an issue, knowing which customers are affected and what they were trying to do is invaluable for prioritizing your response.</p>
<p>Third, logs enable true root cause analysis. Stack traces, error messages, and application state captured in logs are essential for understanding the why behind every incident. Without this information, teams are left guessing at root causes rather than definitively identifying and fixing them.</p>
<p>The teams winning with AIOps in 2025 aren't just monitoring metrics, they're extracting intelligence from their logs at scale, turning operational data into actionable insights.</p>
<h2 id="transformyourlogstrategytoday">Transform Your Log Strategy Today</h2>
<p>Every hour your team spends manually searching through logs is an hour they're not spending on innovation. Every incident that could have been prevented through intelligent log analysis represents both technical debt and business risk.</p>
<p>Elastic Observability provides the foundation you need to unlock the intelligence hidden in your logs. With automatic categorization, anomaly detection, and ML-powered analysis, you can start seeing value immediately. Check out this recent <a href="https://www.elastic.co/observability-labs/blog/elastic-observability-streams-ai-logs-investigations">article</a> to get started with Elastic Streams and Observability today!</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/modern-aiops-elastic-observability</link>
    <guid isPermaLink="false">modern-aiops-elastic-observability</guid>
    <category><![CDATA[Agentic Observability]]></category>
    <category><![CDATA[Logs Analytics]]></category>
    <dc:creator><![CDATA[Sophia Solomon]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt64fd099b0fe44551/6a7f0dcd1967ea79c83307bb/blog-header.png" length="0" type="image/png"/>
    <pubDate>Wed, 26 Nov 2025 00:00:00 GMT</pubDate>
  </item>
  </channel>
</rss>