<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>Elastic Observability Labs - Python</title>
        <link>https://www.elastic.co/observability-labs</link>
        <description>Trusted security news &amp; research from the team at Elastic.</description>
        <lastBuildDate>Fri, 10 Jul 2026 05:08:02 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>Elastic Observability Labs - Python</title>
            <url>https://www.elastic.co/observability-labs/assets/observability-labs-thumbnail.png</url>
            <link>https://www.elastic.co/observability-labs</link>
        </image>
        <copyright>© 2026. Elasticsearch B.V. All Rights Reserved</copyright>
        <item>
            <title><![CDATA[Automatic instrumentation with OpenTelemetry for Python applications]]></title>
            <link>https://www.elastic.co/observability-labs/blog/auto-instrumentation-python-applications-opentelemetry</link>
            <guid isPermaLink="false">auto-instrumentation-python-applications-opentelemetry</guid>
            <pubDate>Thu, 31 Aug 2023 00:00:00 GMT</pubDate>
            <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&lt;sup&gt;®&lt;/sup&gt;, 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>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://www.elastic.co/observability-labs/assets/images/auto-instrumentation-python-applications-opentelemetry/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>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>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>Step-by-step guide</h2>
<h3>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://www.elastic.co/observability-labs/assets/images/auto-instrumentation-python-applications-opentelemetry/elastic-blog-2-free-trial.png" alt="free trial" /></p>
<h3>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 class="language-bash">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 class="language-bash">&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 class="language-bash">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 class="language-dockerfile">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 [ &quot;opentelemetry-instrument&quot;, &quot;python&quot;, &quot;main.py&quot;]
</code></pre>
<h3>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&lt;sup&gt;®&lt;/sup&gt; under the path /app/home#/tutorial/apm.</p>
<p><img src="https://www.elastic.co/observability-labs/assets/images/auto-instrumentation-python-applications-opentelemetry/elastic-blog-3-apm-agents.png" alt="apm agents" /></p>
<p>You will need to copy the following environment variables:</p>
<pre><code class="language-bash">OTEL_EXPORTER_OTLP_ENDPOINT
OTEL_EXPORTER_OTLP_HEADERS
</code></pre>
<p><strong>Build the image</strong></p>
<pre><code class="language-bash">docker build -t  python-otel-auto-image .
</code></pre>
<p><strong>Run the image</strong></p>
<pre><code class="language-bash">docker run \
       -e OTEL_EXPORTER_OTLP_ENDPOINT=&quot;&lt;REPLACE WITH OTEL_EXPORTER_OTLP_ENDPOINT&gt;&quot; \
       -e OTEL_EXPORTER_OTLP_HEADERS=&quot;Authorization=Bearer%20&lt;REPLACE WITH TOKEN&gt;&quot; \
       -e OTEL_RESOURCE_ATTRIBUTES=&quot;service.version=1.0,deployment.environment=production&quot; \
       -e OTEL_SERVICE_NAME=&quot;python-favorite-otel-auto&quot; \
       -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 class="language-bash">curl localhost:5000/favorites

# or alternatively issue a request every second

while true; do curl &quot;localhost:5000/favorites&quot;; sleep 1; done;
</code></pre>
<h3>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://www.elastic.co/observability-labs/assets/images/auto-instrumentation-python-applications-opentelemetry/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://www.elastic.co/observability-labs/assets/images/auto-instrumentation-python-applications-opentelemetry/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>
            <category>observability-labs</category>
            <enclosure url="https://www.elastic.co/observability-labs/assets/images/auto-instrumentation-python-applications-opentelemetry/observability-launch-series-2-python-auto_(1).jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[EDOT SDK central configuration using OpAmp in Elastic Observability]]></title>
            <link>https://www.elastic.co/observability-labs/blog/elastic-distribution-opentelemetry-sdk-central-configuration-opamp</link>
            <guid isPermaLink="false">elastic-distribution-opentelemetry-sdk-central-configuration-opamp</guid>
            <pubDate>Tue, 11 Nov 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[Learn how to configure the Elastic Distributions of OpenTelemetry (EDOT) SDKs centrally via the EDOT Collector using OpAmp in Elastic Observability at scale.]]></description>
            <content:encoded><![CDATA[<p>Managing configuration changes to the large number of services using Elastic Distribution of OpenTelemetry (EDOT) SDKs can be challenging and time-consuming.
OpenTelemetry has the <a href="https://opentelemetry.io/docs/specs/opamp/">Open Agent Management Protocol</a> (OpAMP) which allows Elastic to manage these SDKs through the EDOT collector.
Elastic's APM Agents provided this capability in Elastic Observability.
Using this functionality together with OpAMP you can now centrally manage your SDKs from Elastic Observability, via the EDOT Collector which uses OpAMP to help manage configuration changes to the multitude of services using the EDOT SDKs.</p>
<p>In this article, we will explore the central configuration capabilities for EDOT SDKs with the EDOT Gateway Collector. You will learn how to configure the EDOT SDKs and the EDOT Gateway Collector to enable central configuration. Finally, we'll cover the configuration settings supported through central configuration.</p>
<h2>Central configuration based on the OpenTelemetry Open Agent Management Protocol</h2>
<p>The OpenTelemetry project provides OpAMP for the remote management of large fleets of data collection Agents among other capabilities.
The central management of EDOT SDKs leverages OpAMP for dispatching the configurations.
It's a client-server network protocol where the OpAMP server is part of the EDOT Collector and the OpAMP client is part of the EDOT SDK.
The EDOT SDK polls at regular intervals the OpAMP server for configuration updates.
The OpAMP server in the EDOT collector is part of the <a href="https://github.com/elastic/opentelemetry-collector-components/blob/main/extension/apmconfigextension/README.md">Elastic APM central configuration extension</a>.
This extension reads the configuration for the EDOT SDKs from Elasticsearch.
The <code>apmconfigextension</code>, which is the technical name of the Elastic APM central configuration extension, is included and needs to be configured in the EDOT Collector configuration to be activated.
The OpAMP specification includes WebSocket and plain HTTP connection for transport.
The EDOT SDKs pull the configuration utilizing the plain HTTP connection for transport from the EDOT Collector.</p>
<p><img src="https://www.elastic.co/observability-labs/assets/images/elastic-distribution-opentelemetry-sdk-central-configuration-opamp/central-config-edot.png" alt="The central configuration architecture with EDOT SDKs and EDOT Collector" /></p>
<h2>Prerequisites</h2>
<p>Central configuration of EDOT SDKs requires a standalone EDOT Collector running in Gateway mode.
Other collectors like the OpenTelemetry contrib collector or a custom distribution of the collector you build yourself require the Elastic APM central configuration extension.</p>
<h3>EDOT versions supporting central configuration</h3>
<p>The following table gives an overview of the versions of the EDOT SDKs and EDOT Collector that provide central configuration support.
Applications and services have to be instrumented with the EDOT SDKs with a version listed below to pull and apply central configuration changes.</p>
<table>
<thead>
<tr>
<th>EDOT</th>
<th>Version</th>
</tr>
</thead>
<tbody>
<tr>
<td>Android</td>
<td>1.2.0+</td>
</tr>
<tr>
<td>iOS</td>
<td>1.4.0+</td>
</tr>
<tr>
<td>Java</td>
<td>1.5.0+</td>
</tr>
<tr>
<td>Node.js</td>
<td>1.2.0+</td>
</tr>
<tr>
<td>PHP</td>
<td>1.1.1+</td>
</tr>
<tr>
<td>Python</td>
<td>1.4.0+</td>
</tr>
<tr>
<td>Collector (Gateway mode)</td>
<td>8.19, 9.1+</td>
</tr>
</tbody>
</table>
<p>Central configuration is not blocking application startup for EDOT Java, Node.js, PHP and Python.
The application starts with default configuration or the configuration provided by environment variables.
When the central configuration settings are successfully pulled, they will take precedence over local configuration settings.
EDOT .NET currently does not support central configuration although it’s planned to add support.</p>
<p>Furthermore, central configuration for EDOT SDKs is not supported on Elastic Cloud Serverless or the Elastic Cloud Managed OTLP Endpoint, yet.
The following table gives an overview of the versions of the Elastic stack that support central configuration of EDOT SDKs.</p>
<table>
<thead>
<tr>
<th>Elastic</th>
<th>Version</th>
</tr>
</thead>
<tbody>
<tr>
<td>Self-managed</td>
<td>9.1.0+</td>
</tr>
<tr>
<td>Elastic Cloud Hosted</td>
<td>9.1.0+</td>
</tr>
</tbody>
</table>
<h3>Retrieve Elasticsearch endpoint and API key</h3>
<p>The Elastic APM central configuration extension needs the Elasticsearch endpoint in the configuration to be able to read the central configuration settings for the EDOT SDKs.
The Elasticsearch endpoint is the same that the Elasticsearch exporter uses to export telemetry data to Elasticsearch.
The <a href="https://www.elastic.co/docs/reference/opentelemetry/central-configuration">central configuration documentation</a> describes the steps to obtain the endpoint and the API key in more detail.</p>
<h2>Enable central configuration for EDOT</h2>
<p>To enable central configuration, the EDOT Collector needs the <code>apmconfigextension</code> to be configured as part of the <code>extensions</code> section.
This requires the Elasticsearch endpoint obtained above and an Elasticsearch API key.
The environment variable <code>ELASTIC_OTEL_OPAMP_ENDPOINT</code> needs to be set to enable central configuration in the EDOT SDK.</p>
<p>In the following, the configuration is explained for the EDOT Gateway Collector and the EDOT SDKs.</p>
<h3>Configure the EDOT Collector to enable central configuration</h3>
<p>Central configuration support in the EDOT Collector is enabled by adding the configuration of the Elastic APM central configuration extension configuration to the configuration file.
For the authentication of the <code>apmconfigextension</code> with the Elasticsearch endpoint, <code>bearertokenauth</code> authenticator is configured.
This configures a client type authenticator for outgoing requests to the Elasticsearch endpoint.
The <code>apmconfigextension</code> acts as client and Elasticsearch endpoint as server.
The <code>apmconfig</code> section configures the OpAMP server endpoint. EDOT SDKs will connect to the endpoint to fetch the configuration.
The <code>service</code> section activates the <code>apmconfig</code> and <code>bearertokenauth</code> extension.
The following code snippet shows the configuration excerpt of the EDOT Collector including the <code>bearertoken</code> authenticator configuration and the <code>apmconfig</code> configuration for central configuration.</p>
<pre><code class="language-yaml">extensions:
  bearertokenauth:
    scheme: &quot;APIKey&quot;
    token: &quot;&lt;ENCODED_ELASTICSEARCH_APIKEY&gt;&quot;
  source:
     elasticsearch:
       endpoint: &quot;&lt;YOUR_ELASTICSEARCH_ENDPOINT&gt;&quot;
       auth:
         authenticator: bearertokenauth
  apmconfig:
    opamp:
      protocols:
        http:
          # Default is localhost:4320
          # To specify a custom endpoint, uncomment the following line
          # and set the endpoint to the custom endpoint
          # endpoint: &quot;&lt;CUSTOM_OPAMP_ENDPOINT&gt;&quot;
  service:
    extensions: [bearertokenauth, apmconfig]
</code></pre>
<p><a href="https://www.elastic.co/docs/reference/edot-collector/download">Download</a> the EDOT collector and include the configuration from the snippet above in the <code>otel.yml</code> configuration file to enable central configuration.
The <code>otel.yml</code> configuration file examples are available in the <a href="https://www.elastic.co/docs/reference/edot-collector/config/default-config-standalone#agent-mode">EDOT Collector documentation</a>.
Consider the example for direct ingestion into Elasticsearch.</p>
<h3>Configure the EDOT SDKs to enable central configuration</h3>
<p>To enable central configuration in the EDOT SDKs Java, Node.js, PHP and Python, set the <code>ELASTIC_OTEL_OPAMP_ENDPOINT</code> environment variable to the OpAMP server endpoint of the EDOT Collector and set the required resource attributes.</p>
<h4>Enable central configuration of EDOT SDKs</h4>
<p>The following code snippet shows how to set the <code>ELASTIC_OTEL_OPAMP_ENDPOINT</code> environment variable with the <code>export</code> command in a shell.</p>
<pre><code class="language-bash">export ELASTIC_OTEL_OPAMP_ENDPOINT=&quot;http://&lt;your-opamp-end-point&gt;:4320/v1/opamp&quot;
</code></pre>
<p><code>&lt;your-opamp-end-point&gt;</code> must be set to the address or host name of the EDOT Gateway Collector that provides the OpAMP server endpoint for central configuration.</p>
<p>When you are using EDOT SDKs for mobile, the documentation shows how to activate central configuration support in <a href="https://www.elastic.co/docs/reference/opentelemetry/edot-sdks/android/configuration#central-configuration">EDOT Android</a> and <a href="https://www.elastic.co/docs/reference/opentelemetry/edot-sdks/ios/configuration#central-configuration-edot">EDOT iOS</a>.</p>
<h4>Configure resource attributes</h4>
<p>Central configuration requires the OpenTelemetry resource attributes <code>service.name</code> and <code>deployment.environment.name</code> to be set.
While <code>service.name</code> is mandatory, <code>deployment.environment.name</code> is optional but recommended.
If <code>deployment.environment.name</code> is unset, no configuration can be created that applies to a whole environment.</p>
<p>Set the <code>OTEL_RESOURCE_ATTRIBUTES</code> environment variable including the <code>service.name</code> and <code>deployment.environment.name</code> like in the following code snippet.
The key-value pairs are concatenated with a comma as separator and provided as value to the <code>OTEL_RESOURCE_ATTRIBUTES</code> environment variable.</p>
<pre><code class="language-bash">export OTEL_RESOURCE_ATTRIBUTES=&quot;deployment.environment.name=production,service.name=my-app&quot;
</code></pre>
<h2>Supported configuration settings</h2>
<p>The following tables give an overview of the supported central configuration settings at the time of writing.</p>
<h3>Non-mobile EDOT SDKs</h3>
<p>The table shows the supported central configuration settings of EDOT Java, Node.js, PHP, and Python SDK.</p>
<table>
<thead>
<tr>
<th>Setting</th>
<th>Description</th>
<th>Java</th>
<th>Node.js</th>
<th>PHP</th>
<th>Python</th>
<th>Kibana</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>logging_level</code></td>
<td>The EDOT SDKs own logging level</td>
<td>1.5.0+</td>
<td>1.2.0+</td>
<td>1.1.0+</td>
<td>1.4.0+</td>
<td>9.1.0+</td>
</tr>
<tr>
<td><code>deactivate_instrumentations</code></td>
<td>Turn off <strong>selected</strong> instrumentations</td>
<td>1.5.0+</td>
<td>1.2.0+</td>
<td>-</td>
<td>-</td>
<td>9.1.0+</td>
</tr>
<tr>
<td><code>deactivate_all_instrumentations</code></td>
<td>Turn off <strong>all</strong> instrumentations</td>
<td>1.5.0+</td>
<td>1.2.0+</td>
<td>-</td>
<td>-</td>
<td>9.1.0+</td>
</tr>
<tr>
<td><code>send_traces</code></td>
<td>Controls if traces should be sent</td>
<td>1.5.0+</td>
<td>1.3.0+</td>
<td>-</td>
<td>-</td>
<td>9.1.0+</td>
</tr>
<tr>
<td><code>send_metrics</code></td>
<td>Controls if metrics should be sent</td>
<td>1.5.0+</td>
<td>1.3.0+</td>
<td>-</td>
<td>-</td>
<td>9.1.0+</td>
</tr>
<tr>
<td><code>send_logs</code></td>
<td>Controls if logs should be sent</td>
<td>1.5.0+</td>
<td>1.3.0+</td>
<td>-</td>
<td>-</td>
<td>9.1.0+</td>
</tr>
<tr>
<td><code>opamp_polling_interval</code></td>
<td>Time between consecutive central configuration pull requests</td>
<td>1.6.0+</td>
<td>-</td>
<td>-</td>
<td>-</td>
<td>9.2.0+ (planned)</td>
</tr>
<tr>
<td><code>sampling_rate</code></td>
<td>Trace sampling rate for head-based sampling</td>
<td>1.6.0+</td>
<td>-</td>
<td>-</td>
<td>1.7.0+</td>
<td>9.2.0+ (planned)</td>
</tr>
<tr>
<td><code>infer_spans</code></td>
<td>Activates/Deactivates inferred spans</td>
<td>1.7.0+</td>
<td>-</td>
<td>-</td>
<td>-</td>
<td>9.2.0+ (planned)</td>
</tr>
</tbody>
</table>
<h3>Mobile EDOT SDKs</h3>
<p>The table below shows the supported central configuration settings of EDOT Android, and iOS SDK.</p>
<table>
<thead>
<tr>
<th>Setting</th>
<th>Description</th>
<th>Android</th>
<th>iOS</th>
<th>Kibana</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>recording</code></td>
<td>Record and send telemetry</td>
<td>1.2.0+</td>
<td>1.4.0+</td>
<td>9.1.0+</td>
</tr>
<tr>
<td><code>session_sample_rate</code></td>
<td>Sampling rate for session-based sampling</td>
<td>1.2.0+</td>
<td>1.4.0+</td>
<td>9.1.0+</td>
</tr>
</tbody>
</table>
<h2>Use Elastic Observability to change configuration settings of EDOT SDKs</h2>
<p>An application must produce and send telemetry data otherwise the EDOT SDK will not appear in the Agent configuration UI in Elastic Observability.
Because the Agent configuration in Elastic Observability has no knowledge about the existence of the EDOT SDK until it's receiving telemetry data.
The OpenTelemetry resource attribute <code>service.name</code> is used as the key to assign a configuration to an EDOT SDK.
Currently, EDOT SDKs will not show up in the Agent Explorer.</p>
<p>Go to Kibana -&gt; Observability -&gt; Applications -&gt; Service Inventory -&gt; Settings -&gt; Agent Configuration to create a new configuration for your EDOT SDK.</p>
<p><img src="https://www.elastic.co/observability-labs/assets/images/elastic-distribution-opentelemetry-sdk-central-configuration-opamp/edot-sdk-configuration-deactivate-all-instrumentations.png" alt="Deactivate all instrumentations of EDOT Java SDK in Kibana" /></p>
<p>The EDOT Java SDK configuration above deactivates all instrumentations by setting <code>deactivate_all_instrumentations</code> to <code>true</code>. This is useful in situations when switching off the instrumentations is necessary.
The <code>sampling_rate</code> setting becomes handy when the sampling rate of the EDOT SDK should be changed. A sampling rate can be selected according to the needs as shown below.</p>
<p><img src="https://www.elastic.co/observability-labs/assets/images/elastic-distribution-opentelemetry-sdk-central-configuration-opamp/edot-sdk-configuration-sampling-rate.png" alt="Set the sampling rate of EDOT Java SDK in Kibana" /></p>
<h2>Disable central configuration</h2>
<p>Disabling central configuration is easy. Remove the environment variable <code>ELASTIC_OTEL_OPAMP_ENDPOINT</code> and restart the application to disable central configuration support in the EDOT SDKs.
Remove the <code>apmconfig</code> extension from the <code>service</code> section of the EDOT Collector configuration and restart the collector to disable the Elastic APM central configuration extension in the EDOT Collector.</p>
<h2>Elastic's contribution to OpenTelemetry</h2>
<p>Elastic is committed to the OpenTelemetry project.
Elastic contributed the Java OpAMP client implementation to the OpenTelemetry project (<a href="https://github.com/open-telemetry/opentelemetry-java-contrib/pull/2021">Github PR</a>), and is working on the contribution for Python (<a href="https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3635">Github PR</a>) and Node.js.
The OpAMP client for PHP will be part of a larger contribution (<a href="https://github.com/open-telemetry/community/issues/2846">Github issue</a>) that Elastic is doing.</p>
<h2>Conclusion</h2>
<p>In this article, you learned how to configure the EDOT SDKs centrally in Elastic Observability with the Gateway Collector and OpAMP at scale.
You learned how to configure the <code>apmconfigextension</code> in the collector and how to set the <code>ELASTIC_OTEL_OPAMP_ENDPOINT</code> environment variable to enable central configuration in the EDOT SDKs, which versions of the SDKs and collector support central configuration, and what configuration settings are currently supported.
Now you can leverage central configuration in large deployments to manage the configuration of the EDOT SDKs.</p>
]]></content:encoded>
            <category>observability-labs</category>
            <enclosure url="https://www.elastic.co/observability-labs/assets/images/elastic-distribution-opentelemetry-sdk-central-configuration-opamp/elastic-distribution-opentelemetry-sdk-central-configuration-opamp.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[3 signals, 2 env vars, 0 collectors: OpenTelemetry with Python and Elastic's Managed OTLP Endpoint]]></title>
            <link>https://www.elastic.co/observability-labs/blog/elastic-managed-otlp-endpoint-python</link>
            <guid isPermaLink="false">elastic-managed-otlp-endpoint-python</guid>
            <pubDate>Mon, 08 Jun 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Instrument a Flask API with OpenTelemetry and ship traces, metrics, and logs to Elastic Cloud using just 2 environment variables, no collector needed.]]></description>
            <content:encoded><![CDATA[<p>Elastic's <a href="https://www.elastic.co/docs/reference/opentelemetry/motlp">Managed OTLP Endpoint</a> lets your Python app send traces, metrics, and logs directly to Elastic Cloud using the standard <a href="https://opentelemetry.io/">OpenTelemetry</a> SDK and just 2 environment variables. No <a href="https://opentelemetry.io/docs/collector/">Collector</a> to deploy, configure, or maintain. This post walks through a fully instrumented Flask API with log-to-trace correlation, from first span to Kibana service map.</p>
<p><img src="https://www.elastic.co/observability-labs/assets/images/elastic-managed-otlp-endpoint-python/header.png" alt="Architecture: Python Flask API sending OTLP data to the Elastic Managed OTLP Endpoint, with Elasticsearch and Kibana" /></p>
<h2>Prerequisites</h2>
<ul>
<li>An Elastic Cloud account (<a href="https://www.elastic.co/docs/solutions/observability/get-started/opentelemetry/quickstart/serverless">Serverless</a> or <a href="https://www.elastic.co/observability-labs/blog/elastic-managed-otlp-endpoint-ga-elastic-cloud-hosted">Cloud Hosted v9.0+</a>)</li>
<li>Python 3.9+</li>
<li>Kibana's built-in sample eCommerce orders dataset (<strong>Add data &gt; Sample data &gt; Sample eCommerce orders</strong>)</li>
</ul>
<p><img src="https://www.elastic.co/observability-labs/assets/images/elastic-managed-otlp-endpoint-python/kibana-sample-data.png" alt="Kibana Add data page with the Sample eCommerce orders dataset highlighted" /></p>
<p>Install the required packages:</p>
<pre><code class="language-bash">pip install flask elasticsearch python-dotenv \
  opentelemetry-api opentelemetry-sdk \
  opentelemetry-exporter-otlp
</code></pre>
<h2>What is the Elastic managed OTLP endpoint?</h2>
<p>The Elastic Managed OTLP Endpoint is a hosted ingestion layer that accepts standard OTLP over HTTP or gRPC directly from your application, with no collector required.</p>
<p>OpenTelemetry uses <a href="https://opentelemetry.io/docs/specs/otlp/">OTLP (OpenTelemetry Protocol)</a> to transport telemetry data.</p>
<p>In a traditional setup, your application sends data to an OpenTelemetry Collector, which then forwards it to a backend like Elasticsearch.</p>
<p>The Collector handles batching, retries, and routing, but it also means one more component to deploy, configure, and maintain.</p>
<p>Elastic's <a href="https://www.elastic.co/observability-labs/blog/elastic-managed-otlp-endpoint-ga-elastic-cloud-hosted">managed OTLP endpoint</a>, recently GA, removes that step.</p>
<p>Your application sends OTLP data directly to an endpoint that Elastic operates for you.</p>
<p>The endpoint accepts standard OTLP over HTTP or gRPC, backed by a managed ingestion layer that handles scaling, buffering, and durability.</p>
<p>You use the same <code>opentelemetry-exporter-otlp</code> packages you would use with any backend.</p>
<h2>Building a Python Flask API to instrument with OpenTelemetry</h2>
<p>We'll build a small REST API in Python with Flask that lists and looks up eCommerce orders stored in Elasticsearch.</p>
<p>As a data source, we'll use Kibana's built-in sample data.</p>
<p>You can find the complete application code in the <a href="https://github.com/Delacrobix/OART-Elastic-Managed-Endpoint-Python-Example">companion repository</a>.</p>
<p>The walkthrough below builds the code step by step, but you can clone the repo and follow along if you prefer.</p>
<p>Before adding telemetry, let's look at the base application: a Flask API that queries Kibana's sample eCommerce index.</p>
<p>It has two endpoints: list recent orders and look up a single order by ID.</p>
<p>Create a file called <a href="https://github.com/Delacrobix/OART-Elastic-Managed-Endpoint-Python-Example/blob/main/app.py"><code>app.py</code></a>:</p>
<pre><code class="language-python">import os

from dotenv import load_dotenv
from elasticsearch import Elasticsearch
from flask import Flask, jsonify

load_dotenv()

# Elasticsearch client
es = Elasticsearch(
    hosts=[os.environ[&quot;ES_URL&quot;]],
    api_key=os.environ[&quot;ES_API_KEY&quot;],
)
INDEX = &quot;kibana_sample_data_ecommerce&quot;

app = Flask(__name__)

@app.route(&quot;/orders&quot;)
def list_orders():
    response = es.search(
        index=INDEX,
        size=10,
        sort=[{&quot;order_date&quot;: &quot;desc&quot;}],
        aggs={&quot;total_revenue&quot;: {&quot;sum&quot;: {&quot;field&quot;: &quot;taxful_total_price&quot;}}},
    )

    hits = response[&quot;hits&quot;][&quot;hits&quot;]
    total_revenue = response[&quot;aggregations&quot;][&quot;total_revenue&quot;][&quot;value&quot;]
    orders = [
        {
            &quot;order_id&quot;: h[&quot;_source&quot;][&quot;order_id&quot;],
            &quot;customer&quot;: h[&quot;_source&quot;][&quot;customer_full_name&quot;],
            &quot;total&quot;: h[&quot;_source&quot;][&quot;taxful_total_price&quot;],
            &quot;date&quot;: h[&quot;_source&quot;][&quot;order_date&quot;],
        }
        for h in hits
    ]

    return jsonify({&quot;orders&quot;: orders, &quot;total_revenue&quot;: total_revenue})

@app.route(&quot;/orders/&lt;order_id&gt;&quot;)
def get_order(order_id):
    response = es.search(
        index=INDEX,
        size=1,
        query={&quot;term&quot;: {&quot;order_id&quot;: order_id}},
    )

    hits = response[&quot;hits&quot;][&quot;hits&quot;]
    if not hits:
        return jsonify({&quot;error&quot;: &quot;Order not found&quot;}), 404

    return jsonify(hits[0][&quot;_source&quot;])

if __name__ == &quot;__main__&quot;:
    app.run(host=&quot;0.0.0.0&quot;, port=5001)
</code></pre>
<p>Create a <code>.env</code> file with your Elasticsearch credentials.</p>
<p>To get the OTLP endpoint and API key, follow the <a href="https://www.elastic.co/docs/solutions/observability/get-started/quickstart-elastic-cloud-otel-endpoint#retrieve-your-endpoint-and-api-key">Quickstart: Send OTLP data to Elastic</a> guide.</p>
<pre><code class="language-bash">OTEL_EXPORTER_OTLP_ENDPOINT=
OTEL_EXPORTER_OTLP_HEADERS=Authorization=ApiKey &lt;api_key&gt;
ES_URL=
ES_API_KEY=
</code></pre>
<p>This is a working API with no instrumentation. The next sections add traces, metrics, and logs one signal at a time.</p>
<h2>Sending traces from Python</h2>
<p>Let's start with traces using the <a href="https://opentelemetry.io/docs/languages/python/">Python OpenTelemetry SDK</a>.</p>
<p>We'll wrap the Elasticsearch calls in spans so we can see how long each query takes.</p>
<h3>Set up the tracer</h3>
<p>Add the following to the top of <code>app.py</code>:</p>
<pre><code class="language-python">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
from opentelemetry.sdk.resources import Resource, SERVICE_NAME

# Create a resource identifying your service
resource = Resource.create({
    SERVICE_NAME: &quot;my-python-app&quot;
})

# Set up the tracer provider with OTLP export
tracer_provider = TracerProvider(resource=resource)
tracer_provider.add_span_processor(BatchSpanProcessor(OTLPSpanExporter()))
trace.set_tracer_provider(tracer_provider)

tracer = trace.get_tracer(__name__)
</code></pre>
<p>The <code>OTLPSpanExporter()</code> reads the endpoint and headers from the environment variables we set earlier, so no arguments are needed in the code.</p>
<h3>Add spans to your endpoints</h3>
<p>Now wrap each route handler in a parent span and nest a child span around the Elasticsearch call. This gives you a two-level trace: one span for the HTTP request and one for the database query inside it.</p>
<pre><code class="language-python">@app.route(&quot;/orders&quot;)
def list_orders():
    with tracer.start_as_current_span(&quot;list-orders&quot;) as span:

        with tracer.start_as_current_span(&quot;es.search&quot;) as es_span:
            es_span.set_attribute(&quot;db.system&quot;, &quot;elasticsearch&quot;)
            es_span.set_attribute(&quot;db.elasticsearch.index&quot;, INDEX)

            response = es.search(
                index=INDEX,
                size=10,
                sort=[{&quot;order_date&quot;: &quot;desc&quot;}],
                aggs={&quot;total_revenue&quot;: {&quot;sum&quot;: {&quot;field&quot;: &quot;taxful_total_price&quot;}}},
            )

        hits = response[&quot;hits&quot;][&quot;hits&quot;]
        total_revenue = response[&quot;aggregations&quot;][&quot;total_revenue&quot;][&quot;value&quot;]
        orders = [
            {
                &quot;order_id&quot;: h[&quot;_source&quot;][&quot;order_id&quot;],
                &quot;customer&quot;: h[&quot;_source&quot;][&quot;customer_full_name&quot;],
                &quot;total&quot;: h[&quot;_source&quot;][&quot;taxful_total_price&quot;],
                &quot;date&quot;: h[&quot;_source&quot;][&quot;order_date&quot;],
            }
            for h in hits
        ]

        span.set_attribute(&quot;orders.returned&quot;, len(orders))
        return jsonify({&quot;orders&quot;: orders, &quot;total_revenue&quot;: total_revenue})
</code></pre>
<p>Each request creates a parent span (<code>list-orders</code>) with a child span (<code>es.search</code>) wrapping the Elasticsearch call.</p>
<p>The <code>db.system</code> and <code>db.elasticsearch.index</code> attributes are what Kibana uses to recognize the call as an Elasticsearch query in the trace waterfall.</p>
<h2>Sending metrics from Python</h2>
<p>Metrics reveal patterns across all requests: how many calls hit each endpoint, how response times trend over time. Let's add a counter and a histogram to capture that.</p>
<h3>Set up the meter</h3>
<p>Add the following to <code>app.py</code>:</p>
<pre><code class="language-python">from opentelemetry import metrics
from opentelemetry.sdk.metrics import MeterProvider
from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader
from opentelemetry.exporter.otlp.proto.http.metric_exporter import OTLPMetricExporter

# Set up the metric reader and provider
metric_reader = PeriodicExportingMetricReader(
    OTLPMetricExporter(),
    export_interval_millis=10000  # Export every 10 seconds
)
meter_provider = MeterProvider(
    resource=resource,
    metric_readers=[metric_reader]
)
metrics.set_meter_provider(meter_provider)

meter = metrics.get_meter(__name__)
</code></pre>
<h3>Create instruments</h3>
<pre><code class="language-python"># Counter for total requests
request_counter = meter.create_counter(
    name=&quot;app.requests.total&quot;,
    description=&quot;Total number of requests&quot;,
    unit=&quot;1&quot;
)

# Histogram for request duration
request_duration = meter.create_histogram(
    name=&quot;app.request.duration&quot;,
    description=&quot;Request duration in seconds&quot;,
    unit=&quot;s&quot;
)
</code></pre>
<h3>Record metrics in your endpoints</h3>
<p>Update the route handler to record metrics:</p>
<pre><code class="language-python">import time

@app.route(&quot;/orders&quot;)
def list_orders():
    start_time = time.time()

    with tracer.start_as_current_span(&quot;list-orders&quot;) as span:

        with tracer.start_as_current_span(&quot;es.search&quot;) as es_span:
            es_span.set_attribute(&quot;db.system&quot;, &quot;elasticsearch&quot;)
            es_span.set_attribute(&quot;db.elasticsearch.index&quot;, INDEX)

            response = es.search(
                index=INDEX,
                size=10,
                sort=[{&quot;order_date&quot;: &quot;desc&quot;}],
                aggs={&quot;total_revenue&quot;: {&quot;sum&quot;: {&quot;field&quot;: &quot;taxful_total_price&quot;}}},
            )

        hits = response[&quot;hits&quot;][&quot;hits&quot;]
        total_revenue = response[&quot;aggregations&quot;][&quot;total_revenue&quot;][&quot;value&quot;]
        orders = [
            {
                &quot;order_id&quot;: h[&quot;_source&quot;][&quot;order_id&quot;],
                &quot;customer&quot;: h[&quot;_source&quot;][&quot;customer_full_name&quot;],
                &quot;total&quot;: h[&quot;_source&quot;][&quot;taxful_total_price&quot;],
                &quot;date&quot;: h[&quot;_source&quot;][&quot;order_date&quot;],
            }
            for h in hits
        ]

        duration = time.time() - start_time
        span.set_attribute(&quot;orders.returned&quot;, len(orders))
        request_counter.add(1, {&quot;endpoint&quot;: &quot;/orders&quot;, &quot;status&quot;: &quot;200&quot;})
        request_duration.record(duration, {&quot;endpoint&quot;: &quot;/orders&quot;})

        return jsonify({&quot;orders&quot;: orders, &quot;total_revenue&quot;: total_revenue})
</code></pre>
<h2>Sending logs from Python</h2>
<p>Traces and metrics tell you what happened and how often. Logs capture the details: error messages, parameter values, and debug context. OpenTelemetry bridges Python's standard <code>logging</code> module so your existing log statements are exported as OTLP data alongside traces and metrics.</p>
<p>When you emit a log inside a span context, the SDK automatically attaches the trace and span IDs, so you can jump from a log line to the exact trace that produced it.</p>
<h3>Set up the log provider</h3>
<pre><code class="language-python">import logging
from opentelemetry._logs import set_logger_provider
from opentelemetry.sdk._logs import LoggerProvider, LoggingHandler
from opentelemetry.sdk._logs.export import BatchLogRecordProcessor
from opentelemetry.exporter.otlp.proto.http._log_exporter import OTLPLogExporter

# Set up the logger provider
logger_provider = LoggerProvider(resource=resource)
logger_provider.add_log_record_processor(
    BatchLogRecordProcessor(OTLPLogExporter())
)
set_logger_provider(logger_provider)

# Bridge Python's logging to OTel
handler = LoggingHandler(
    level=logging.INFO,
    logger_provider=logger_provider
)
logging.getLogger().addHandler(handler)
logging.getLogger().addHandler(logging.StreamHandler())  # Also print to terminal
logging.getLogger().setLevel(logging.INFO)

logger = logging.getLogger(__name__)
</code></pre>
<p>The <code>StreamHandler()</code> ensures logs also appear in your terminal while developing.</p>
<p>Without it, logs only go to Elastic and the terminal stays silent, which can be confusing during testing.</p>
<h3>Emit logs from your endpoints</h3>
<pre><code class="language-python">@app.route(&quot;/orders&quot;)
def list_orders():
    start_time = time.time()

    with tracer.start_as_current_span(&quot;list-orders&quot;) as span:
        logger.info(&quot;Listing recent orders&quot;)

        with tracer.start_as_current_span(&quot;es.search&quot;) as es_span:
            es_span.set_attribute(&quot;db.system&quot;, &quot;elasticsearch&quot;)
            es_span.set_attribute(&quot;db.elasticsearch.index&quot;, INDEX)

            response = es.search(
                index=INDEX,
                size=10,
                sort=[{&quot;order_date&quot;: &quot;desc&quot;}],
                aggs={&quot;total_revenue&quot;: {&quot;sum&quot;: {&quot;field&quot;: &quot;taxful_total_price&quot;}}},
            )

        hits = response[&quot;hits&quot;][&quot;hits&quot;]
        total_revenue = response[&quot;aggregations&quot;][&quot;total_revenue&quot;][&quot;value&quot;]
        orders = [
            {
                &quot;order_id&quot;: h[&quot;_source&quot;][&quot;order_id&quot;],
                &quot;customer&quot;: h[&quot;_source&quot;][&quot;customer_full_name&quot;],
                &quot;total&quot;: h[&quot;_source&quot;][&quot;taxful_total_price&quot;],
                &quot;date&quot;: h[&quot;_source&quot;][&quot;order_date&quot;],
            }
            for h in hits
        ]

        duration = time.time() - start_time
        span.set_attribute(&quot;orders.returned&quot;, len(orders))
        logger.info(
            &quot;Orders listed&quot;,
            extra={&quot;orders.returned&quot;: len(orders), &quot;duration_s&quot;: round(duration, 4)}
        )

        request_counter.add(1, {&quot;endpoint&quot;: &quot;/orders&quot;, &quot;status&quot;: &quot;200&quot;})
        request_duration.record(duration, {&quot;endpoint&quot;: &quot;/orders&quot;})

        return jsonify({&quot;orders&quot;: orders, &quot;total_revenue&quot;: total_revenue})
</code></pre>
<p>Because the <code>logger.info()</code> calls happen inside a span context, OpenTelemetry automatically attaches the trace ID and span ID to each log record.</p>
<p>In Kibana, this means you can jump from a log line directly to the related trace.</p>
<h2>Running the instrumented Flask app and generating traffic</h2>
<p>With traces, metrics, and logs wired up, the application is fully instrumented. Let's run it, send a few requests, and verify the data arrives in Kibana.</p>
<pre><code class="language-bash">python app.py
</code></pre>
<p><strong>Note:</strong> Flask's built-in development server is fine for this tutorial. For production workloads, use a WSGI server like <a href="https://gunicorn.org/">gunicorn</a>.</p>
<p>Generate some traffic:</p>
<pre><code class="language-bash">curl http://localhost:5001/orders
curl http://localhost:5001/orders/584677
curl http://localhost:5001/orders/does-not-exist
</code></pre>
<p>After a few seconds, open Kibana and go to <strong>Observability &gt; APM &gt; Services</strong>.</p>
<p>You should see <code>my-python-app</code> listed.</p>
<p><img src="https://www.elastic.co/observability-labs/assets/images/elastic-managed-otlp-endpoint-python/apm-service-inventory.png" alt="APM Service inventory in Kibana showing the my-python-app service" /></p>
<h2>Viewing traces, metrics, and logs in Kibana</h2>
<p>Now that telemetry data is flowing, let's walk through where each signal lands in Kibana and how they connect to each other.</p>
<h3>Traces</h3>
<p>Go to <strong>APM &gt; Services &gt; my-python-app &gt; Transactions</strong>. Click on a <code>list-orders</code> transaction to open the trace waterfall.</p>
<p>You should see the parent span <code>list-orders</code> with a child span <code>es.search</code>.</p>
<p>The <code>db.system</code> and <code>db.elasticsearch.index</code> attributes we set earlier appear in the span details, and Kibana recognizes the child as an Elasticsearch query.</p>
<p><img src="https://www.elastic.co/observability-labs/assets/images/elastic-managed-otlp-endpoint-python/trace-list-orders.png" alt="Trace waterfall in Kibana for the list-orders transaction with an es.search child span" /></p>
<h3>Failed transactions</h3>
<p>For error visibility, open a <code>get-order</code> transaction for the <code>/orders/does-not-exist</code> request.</p>
<p>The transaction details show <code>event.outcome: failure</code> along with the <code>order.id: does-not-exist</code> attribute we attached.</p>
<p>This confirms that error status and custom attributes propagate correctly through the managed endpoint.</p>
<p><img src="https://www.elastic.co/observability-labs/assets/images/elastic-managed-otlp-endpoint-python/trace-get-order-failure.png" alt="Trace waterfall for the get-order transaction marked as failure" /></p>
<p><img src="https://www.elastic.co/observability-labs/assets/images/elastic-managed-otlp-endpoint-python/transaction-details-error.png" alt="Transaction details panel showing event.outcome failure and the order.id attribute" /></p>
<h3>Correlated logs</h3>
<p>Go to <strong>Discover</strong> and filter by <code>service.name: my-python-app</code>.</p>
<p>Expand a log document to see the <code>body.text</code>, <code>trace_id</code>, and <code>span_id</code> fields.</p>
<p>Because we emitted logs inside a span context, every log record carries the trace ID.</p>
<p>You can copy the <code>trace_id</code> value and go to <strong>APM</strong> to jump directly to the trace that produced that log.</p>
<p><img src="https://www.elastic.co/observability-labs/assets/images/elastic-managed-otlp-endpoint-python/discover-logs.png" alt="Discover view showing log documents for my-python-app with trace_id and span_id fields" /></p>
<h3>Metrics</h3>
<p>In <strong>Discover</strong>, switch to the <code>metrics-*</code> data view.</p>
<p>You should see the <code>app.requests.total</code> counter and <code>app.request.duration</code> histogram arriving at regular intervals.</p>
<p>You can also create a Lens visualization with <code>app.requests.total</code> on the Y axis and <code>@timestamp</code> on the X axis to see request volume over time.</p>
<p><img src="https://www.elastic.co/observability-labs/assets/images/elastic-managed-otlp-endpoint-python/discover-metrics.png" alt="Discover view showing the app.requests.total metric arriving from my-python-app" /></p>
<h3>Service map</h3>
<p>Kibana builds the <a href="https://www.elastic.co/docs/solutions/observability/apm/service-map">service map</a> automatically from the span attributes we set earlier.</p>
<p>Because we tagged our Elasticsearch spans with <code>db.system: elasticsearch</code>, Kibana draws the dependency between <code>my-python-app</code> and <code>elasticsearch</code>, giving you a visual overview of your service and its backends.</p>
<p><img src="https://www.elastic.co/observability-labs/assets/images/elastic-managed-otlp-endpoint-python/service-map.png" alt="APM service map showing my-python-app connected to elasticsearch" /></p>
<h2>Conclusion: full OpenTelemetry observability in Python without a collector</h2>
<p>In this walkthrough, we instrumented a Flask API with traces, metrics, and logs using the standard OpenTelemetry SDK and two environment variables, then verified all three signals in Kibana with log-to-trace correlation built in.</p>
<p>The managed OTLP endpoint takes care of scaling, buffering, and durability, so you can focus on your application instead of operating ingestion infrastructure.</p>
<p>From here, consider auto-instrumenting with EDOT Python to remove the manual spans, or adding custom metrics that match your domain.</p>
<h2>Next steps</h2>
<ul>
<li>Explore the <a href="https://www.elastic.co/docs/reference/opentelemetry/motlp">Elastic Managed OTLP Endpoint documentation</a> for advanced configuration.</li>
<li>Try <code>opentelemetry-instrument</code> or <a href="https://www.elastic.co/docs/reference/opentelemetry/edot-sdks/python/configuration">EDOT Python</a> to auto-instrument Flask and the Elasticsearch client without manual spans.</li>
<li>Check the <a href="https://opentelemetry.io/docs/languages/python/">OpenTelemetry Python SDK docs</a> for additional instrumentation options.</li>
<li>For fine-grained control over histogram temporality, see the <a href="https://www.elastic.co/docs/reference/opentelemetry/motlp">managed OTLP endpoint documentation</a>.</li>
</ul>
]]></content:encoded>
            <category>observability-labs</category>
            <enclosure url="https://www.elastic.co/observability-labs/assets/images/elastic-managed-otlp-endpoint-python/cover.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[LLM Observability with Elastic, OpenLIT and OpenTelemetry]]></title>
            <link>https://www.elastic.co/observability-labs/blog/elastic-opentelemetry-langchain-openlit-tracing</link>
            <guid isPermaLink="false">elastic-opentelemetry-langchain-openlit-tracing</guid>
            <pubDate>Thu, 29 Aug 2024 00:00:00 GMT</pubDate>
            <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 the OpenLIT instrumentation library to ingest traces into Elastic Observability APM.]]></description>
            <content:encoded><![CDATA[<p>The realm of technology is evolving rapidly, and Large Language Models (LLMs) are at the forefront of this transformation. From chat bots to intelligent application copilots, LLMs are becoming increasingly sophisticated. As these applications grow more complex, ensuring their reliability and performance is paramount. This is where observability steps in, aided by OpenTelemetry and Elastic through the <a href="https://github.com/openlit/openlit">OpenLIT</a> instrumentation library. </p>
<p>OpenLIT is an open-source Observability and Evaluation tool that helps take your LLM apps from playground to debug to production. With OpenLit you get an ability to choose from a <a href="https://docs.openlit.io/latest/integrations/introduction">range of Integrations</a> (across LLMs, VectorDBs, frameworks, and GPUs) to start tracking LLM performance, usage, and costs without hassle. In this blog we will look at tracking OpenAI and LangChain. to send telemetry to an OpenTelemetry compatible endpoint like Elastic.</p>
<p><a href="https://www.elastic.co/guide/en/observability/current/apm-open-telemetry.html">Elastic supports OpenTelemetry natively</a>, it can take telemetry directly from the application (via the OpenTelemetry SDKs) or through a native OTel collector. No special agents are needed. Additionally <a href="https://www.elastic.co/observability-labs/blog/elastic-distributions-opentelemetry">Elastic's EDOT</a> provides a supported set of OTel SDKs and an OTel Collector. In this blog we will connect our application directly to Elastic without a collector for simplicity.</p>
<h2>Why Observability Matters for LLM Applications</h2>
<p>Monitoring LLM applications is crucial for several reasons.</p>
<ol>
<li>
<p>It’s vital to keep track of how often LLMs are being used for usage and cost tracking.</p>
</li>
<li>
<p>Latency is important to track since the response time from the model can vary based on the inputs passed to the LLM.</p>
</li>
<li>
<p>Rate limiting is a common challenge, particularly for external LLMs, as applications depend more on these external API calls. When rate limits are hit, it can hinder these applications from performing their essential functions using these LLMs.</p>
</li>
</ol>
<p>By keeping a close eye on these aspects, you can not only save costs but also avoid hitting request limits, ensuring your LLM applications perform optimally.</p>
<h2>What are the signals that you should be looking at?</h2>
<p>Using Large Language Models (LLMs) in applications differs from traditional machine learning (ML) models. Primarily, LLMs are often accessed through external API calls instead of being run locally or in-house. It is crucial to capture the sequence of events (using traces), especially in a RAG-based application where there can be events before and after LLM usage. Also, analyzing the aggregated data (through metrics) provides a quick overview like request, tokens and cost is important for optimizing performance and managing costs. Here are the key signals to monitor:</p>
<h3>Traces</h3>
<p><strong>Request Metadata</strong>: This is important in the context of LLMs, given the variety of parameters (like temperature and top_p) that can drastically affect both the response quality and the cost. Specific aspects to monitor are:</p>
<ol>
<li>
<p>Temperature: Indicates the level of creativity or randomness desired from the model’s outputs. Varying this parameter can significantly impact the nature of the generated content.</p>
</li>
<li>
<p>top_p: Decides how selective the model is by choosing from a certain percentage of most likely words. A high “top_p” value means the model considers a wider range of words, making the text more varied.</p>
</li>
<li>
<p>Model Name or Version: Essential for tracking over time, as updates to the LLM might affect performance or response characteristics.</p>
</li>
<li>
<p>Prompt Details: The exact inputs sent to the LLM, which, unlike in-house ML models where inputs might be more controlled and homogeneous, can vary wildly and affect output complexity and cost implications.</p>
</li>
</ol>
<p><strong>Response Metadata</strong>: Given the API-based interaction with LLMs, tracking the specifics of the response is key for cost management and quality assessment:</p>
<ol>
<li>
<p>Tokens: Directly impacts cost and is a measure of response length and complexity.</p>
</li>
<li>
<p>Cost: Critical for budgeting, as API-based costs can scale with the number of requests and the complexity of each request.</p>
</li>
<li>
<p>Completion Details: Similar to the prompt details but from the response perspective, providing insights into the model’s output characteristics and potential areas of inefficiency or unexpected cost.</p>
</li>
</ol>
<h3>Metrics</h3>
<p><strong>Request Volume</strong>: The total number of requests made to the LLM service. This helps in understanding the demand patterns and identifying any anomaly in usage, such as sudden spikes or drops.</p>
<p><strong>Request Duration</strong>: The time it takes for a request to be processed and a response to be received from the LLM. This includes network latency and the time the LLM takes to generate a response, providing insights into the performance and reliability of the LLM service.</p>
<p><strong>Costs and Tokens Counters</strong>: Keeping track of the total cost accrued and tokens consumed over time is essential for budgeting and cost optimization strategies. Monitoring these metrics can alert you to unexpected increases that may indicate inefficient use of the LLM or the need for optimization.</p>
<h2>Implementing Automatic Instrumentation with OpenLIT</h2>
<p><a href="https://openlit.io/">OpenLIT</a> automates telemetry data capture, simplifying the process for developers. Here’s a step-by-step guide to setting it up:</p>
<p><strong>1. Install the OpenLIT SDK</strong>:</p>
<p>First, you must install the following package: </p>
<pre><code class="language-bash">pip install openlit
</code></pre>
<p><strong>Note:</strong> OpenLIT currently supports Python, a popular language for Generative AI. The team is also working on expanding support to JavaScript soon.</p>
<p><strong>2. Get your Elastic APM Credentials</strong></p>
<ol>
<li>
<p>Sign in to your <a href="https://cloud.elastic.co">Elastic cloud account</a>.</p>
</li>
<li>
<p>Open the side navigation and click on APM under Observability.</p>
</li>
<li>
<p>Make sure the APM Server is running</p>
</li>
</ol>
<p><img src="https://www.elastic.co/observability-labs/assets/images/elastic-opentelemetry-langchain-openlit-tracing/LangChainAppOTelAPMsetup.png" alt="LangChainChat App in Elastic APM" /></p>
<ol>
<li>
<p>In the APM Agents section, Select OpenTelemetry and directly jump to Step 5 (Configure OpenTelemetry in your application):</p>
</li>
<li>
<p>Copy and save the configuration value for <code>OTEL_EXPORTER_OTLP_ENDPOINT</code> and <code>OTEL_EXPORTER_OTLP_HEADERS</code></p>
</li>
</ol>
<p><strong>3. Set Environment Variables</strong>:</p>
<p>OpenTelemetry Environment variables for Elastic can be set as follows in linux (or in the code). <a href="https://www.elastic.co/guide/en/observability/current/apm-open-telemetry-direct.html">Elastic OTel Documentation</a></p>
<pre><code class="language-bash">export OTEL_EXPORTER_OTLP_ENDPOINT=&quot;YOUR_ELASTIC_APM_OTLP_URL&quot;
export OTEL_EXPORTER_OTLP_HEADERS=&quot;YOUR_ELASTIC_APM_AUTH&quot;
</code></pre>
<p><strong>Note:</strong> Make sure to replace the space after Bearer with %20:</p>
<p><code>OTEL_EXPORTER_OTLP_HEADERS=“Authorization=Bearer%20[APIKEY]”</code></p>
<p><strong>4. Initialize the SDK</strong>:</p>
<p>You will need to add the following to the LLM Application code.</p>
<pre><code class="language-python">import openlit
openlit.init()
</code></pre>
<p>Optionally, you can customize the application name and environment by setting the <code>application_name</code> and <code>environment</code> attributes when initializing OpenLIT in your application. These variables configure the OTel attributes <code>service.name</code> and <code>deployment.environment</code>, respectively. For more details on other configuration settings, check out the <a href="https://github.com/openlit/openlit/tree/main/sdk/python#configuration">OpenLIT GitHub Repository</a>.</p>
<pre><code class="language-python">openlit.init(application_name=&quot;YourAppName&quot;,environment=&quot;Production&quot;)
</code></pre>
<p>The most popular libraries in GenAI are OpenAI (for accessing LLMs) and Langchain (for orchestrating steps). An example instrumentation of a Langchain and OpenAI based LLM Application will look like:</p>
<pre><code class="language-python">import getpass
import os
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, SystemMessage
import openlit 

# Auto-instruments LLM and VectorDB calls, sending OTel traces and metrics to the configured endpoint
openlit.init()

os.environ[&quot;OPENAI_API_KEY&quot;] = getpass.getpass()
model = ChatOpenAI(model=&quot;gpt-4&quot;)
messages = [
    SystemMessage(content=&quot;Translate the following from English into Italian&quot;),
    HumanMessage(content=&quot;hi!&quot;),
]
model.invoke(messages)
</code></pre>
<h2>Visualizing Data with Kibana</h2>
<p>Once your LLM application is instrumented, visualizing the collected data is the next step. Follow the below steps to import a pre-built Kibana dashboard to get yourself started:</p>
<ol>
<li>
<p>Copy the dashboard NDJSON provided <a href="https://docs.openlit.io/latest/connections/elastic#dashboard">here</a> and save it in a file with an extension <code>.ndjson</code>.</p>
</li>
<li>
<p>Log into your Elastic Instance.</p>
</li>
<li>
<p>Go to Stack Management &gt; Saved Objects.</p>
</li>
<li>
<p>Click Import and upload your file containing the dashboard NDJSON.</p>
</li>
<li>
<p>Click Import and you should have the dashboard available.</p>
</li>
</ol>
<p><img src="https://www.elastic.co/observability-labs/assets/images/elastic-opentelemetry-langchain-openlit-tracing/elastic-dashboard-1.jpg" alt="Elastic-dashboard-1" /></p>
<p><img src="https://www.elastic.co/observability-labs/assets/images/elastic-opentelemetry-langchain-openlit-tracing/elastic-dashboard-2.jpg" alt="Elastic-dashboard-2" /></p>
<p>The dashboard provides an in-depth overview of system metrics through eight key areas: Total Successful Requests, Request Duration Distribution, Request Rates, Usage Cost and Tokens, Top GenAI Models, GenAI Requests by Platform and Environment, Token Consumption vs. Cost. These metrics collectively help identify peak usage times, latency issues, rate limits, and resource allocation, facilitating performance tuning and cost management. This comprehensive breakdown aids in understanding LLM performance, ensuring consistent operation across environments, budget needs, and troubleshooting issues, ultimately optimizing overall system efficiency.</p>
<p>Also, you can see OpenTelemetry Traces from OpenLIT in Elastic APM, letting you look into each LLM request in detail. This setup ensures better system efficiency by helping with model performance checks, smooth running across environments, budget planning, and troubleshooting.</p>
<p><img src="https://www.elastic.co/observability-labs/assets/images/elastic-opentelemetry-langchain-openlit-tracing/elastic-dashboard-3.jpg" alt="Elastic-dashboard-3" /></p>
<p><img src="https://www.elastic.co/observability-labs/assets/images/elastic-opentelemetry-langchain-openlit-tracing/elastic-dashboard-4.jpg" alt="Elastic-dashboard-4" /></p>
<h2>Conclusion</h2>
<p>Observability is crucial for the efficient operation of LLM applications. OpenTelemetry's open standards and extensive support, combined with <a href="https://www.elastic.co/observability/application-performance-monitoring">Elastic's APM</a>, <a href="https://www.elastic.co/observability/aiops">AIOps</a>, and <a href="https://www.elastic.co/observability/log-monitoring">analytics</a> and <a href="https://docs.openlit.io/latest/introduction">OpenLIT's</a> powerful and easy auto-instrumentation for 20+ GenAI tools from LLMs to VectorDBs, enable complete visibility into LLM performance. </p>
<p>Hopefully, this provides an easy-to-understand walk-through of instrumenting Langchain with OpenTelemetry and OpenLit 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>
<ul>
<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>
</li>
</ul>
]]></content:encoded>
            <category>observability-labs</category>
            <enclosure url="https://www.elastic.co/observability-labs/assets/images/elastic-opentelemetry-langchain-openlit-tracing/elastic-openlit-tracing.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Observing Langchain applications with Elastic, OpenTelemetry, and Langtrace]]></title>
            <link>https://www.elastic.co/observability-labs/blog/elastic-opentelemetry-langchain-tracing-langtrace</link>
            <guid isPermaLink="false">elastic-opentelemetry-langchain-tracing-langtrace</guid>
            <pubDate>Mon, 02 Sep 2024 00:00:00 GMT</pubDate>
            <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>
<h1>Pre-requisites:</h1>
<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>
<h1>App Overview and output in Elastic:</h1>
<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://www.elastic.co/observability-labs/assets/images/elastic-opentelemetry-langchain-tracing-langtrace/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://www.elastic.co/observability-labs/assets/images/elastic-opentelemetry-langchain-tracing-langtrace/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://www.elastic.co/observability-labs/assets/images/elastic-opentelemetry-langchain-tracing-langtrace/langchainchat-dependency.png" alt="Dependencies" /></p>
<p><img src="https://www.elastic.co/observability-labs/assets/images/elastic-opentelemetry-langchain-tracing-langtrace/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://www.elastic.co/observability-labs/assets/images/elastic-opentelemetry-langchain-tracing-langtrace/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://www.elastic.co/observability-labs/assets/images/elastic-opentelemetry-langchain-tracing-langtrace/langchainchat-tools-span.png" alt="Span details" /></p>
<h1>Configuration:</h1>
<p>How do we make all this show up in Elastic? Let's go over the steps:</p>
<h2>OpenTelemetry Configuration</h2>
<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>
<h3>OpenTelemetry Environment variables:</h3>
<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 class="language-bash">OTEL_EXPORTER_OTLP_ENDPOINT=12345.apm.us-west-2.aws.cloud.es.io:443
OTEL_EXPORTER_OTLP_HEADERS=&quot;Authorization=Bearer%20ZZZZZZZ&quot;
OTEL_RESOURCE_ATTRIBUTES=&quot;service.name=langchainChat,service.version=1.0,deployment.environment=production&quot;
</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://www.elastic.co/observability-labs/assets/images/elastic-opentelemetry-langchain-tracing-langtrace/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>
<h2>Langtrace Library:</h2>
<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 class="language-python">pip install langtrace-python-sdk 
</code></pre>
<p>After installation, you can add the following code to your project:</p>
<pre><code class="language-python">from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter

from langtrace_python_sdk import langtrace, with_langtrace_root_span
</code></pre>
<h2>Instrumentation:</h2>
<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 class="language-bash">opentelemetry-instrument python langtrace-elastic-demo.py
</code></pre>
<p><img src="https://www.elastic.co/observability-labs/assets/images/elastic-opentelemetry-langchain-tracing-langtrace/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>
<h1>Conclusion</h1>
<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>
<ul>
<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>
</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>
            <category>observability-labs</category>
            <enclosure url="https://www.elastic.co/observability-labs/assets/images/elastic-opentelemetry-langchain-tracing-langtrace/elastic-langtrace.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Tracing LangChain apps with Elastic, OpenLLMetry, and OpenTelemetry]]></title>
            <link>https://www.elastic.co/observability-labs/blog/elastic-opentelemetry-langchain-tracing</link>
            <guid isPermaLink="false">elastic-opentelemetry-langchain-tracing</guid>
            <pubDate>Fri, 02 Aug 2024 00:00:00 GMT</pubDate>
            <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>
<h1>Pre-requisites:&lt;a id=&quot;pre-requisites&quot;&gt;&lt;/a&gt;</h1>
<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>
<h1>Overview</h1>
<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://www.elastic.co/observability-labs/assets/images/elastic-opentelemetry-langchain-tracing/LangChainAppCLI.png" alt="Chat Interaction" /></p>
<p><img src="https://www.elastic.co/observability-labs/assets/images/elastic-opentelemetry-langchain-tracing/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://www.elastic.co/observability-labs/assets/images/elastic-opentelemetry-langchain-tracing/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>
<h1>OpenTelemetry Configuration&lt;a id=&quot;opentelemetry-configuration&quot;&gt;&lt;/a&gt;</h1>
<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>
<h2>OpenTelemetry Environment variables:&lt;a id=&quot;opentelemetry-environment-variables&quot;&gt;&lt;/a&gt;</h2>
<p>OpenTelemetry Environment variables for Elastic can be set as follows in linux (or in the code).</p>
<pre><code class="language-bash">OTEL_EXPORTER_OTLP_ENDPOINT=12345.apm.us-west-2.aws.cloud.es.io:443
OTEL_EXPORTER_OTLP_HEADERS=&quot;Authorization=Bearer%20ZZZZZZZ&quot;
OTEL_RESOURCE_ATTRIBUTES=&quot;service.name=langchainChat,service.version=1.0,deployment.environment=production&quot;
</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://www.elastic.co/observability-labs/assets/images/elastic-opentelemetry-langchain-tracing/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>OpenLLMetry Library:&lt;a id=&quot;openllmetry-library&quot;&gt;&lt;/a&gt;</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 class="language-python">from opentelemetry.instrumentation.langchain import LangchainInstrumentor
LangchainInstrumentor().instrument()
</code></pre>
<h2>Instrumentation&lt;a id=&quot;instrumentation&quot;&gt;&lt;/a&gt;</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 class="language-bash">opentelemetry-instrument python tavilyAzureApp.py
</code></pre>
<p><img src="https://www.elastic.co/observability-labs/assets/images/elastic-opentelemetry-langchain-tracing/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>Manual-instrumentation&lt;a id=&quot;manual-instrumentation&quot;&gt;&lt;/a&gt;</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(&quot;getting user query&quot;) as span:</code> below</p>
<pre><code class="language-python"># Creates a tracer from the global tracer provider
tracer = trace.get_tracer(&quot;newsQuery&quot;)

async def chat_interface():
    print(&quot;Welcome to the AI Chat Interface!&quot;)
    print(&quot;Type 'quit' to exit the chat.&quot;)
    
    with tracer.start_as_current_span(&quot;getting user query&quot;) as span:
        while True:
            user_input = input(&quot;\nYou: &quot;).strip()
            
            if user_input.lower() == 'quit':
                print(&quot;Thank you for chatting. Goodbye!&quot;)
                break
        
            print(&quot;AI: Thinking...&quot;)
            try:
                result = await chain.ainvoke({&quot;query&quot;: user_input})
                print(f&quot;AI: {result.content}&quot;)
            except Exception as e:
                print(f&quot;An error occurred: {str(e)}&quot;)


if __name__ == &quot;__main__&quot;:
    asyncio.run(chat_interface())
</code></pre>
<p>As you can see, with manual instrumentation, we get the following trace:</p>
<p><img src="https://www.elastic.co/observability-labs/assets/images/elastic-opentelemetry-langchain-tracing/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>
<h1>Conclusion&lt;a id=&quot;conclusion&quot;&gt;&lt;/a&gt;</h1>
<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>
<ul>
<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>
</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>
            <category>observability-labs</category>
            <enclosure url="https://www.elastic.co/observability-labs/assets/images/elastic-opentelemetry-langchain-tracing/LangChainBlogMainImage.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Instrumenting your OpenAI-powered Python, Node.js, and Java Applications with EDOT]]></title>
            <link>https://www.elastic.co/observability-labs/blog/elastic-opentelemetry-openai</link>
            <guid isPermaLink="false">elastic-opentelemetry-openai</guid>
            <pubDate>Thu, 23 Jan 2025 00:00:00 GMT</pubDate>
            <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>Introduction</h2>
<p>Last year, <a href="https://www.elastic.co/observability-labs/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 &quot;Which version of a model generated this response?&quot; or &quot;What was the exact chat prompt created by my RAG application?&quot;</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>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://www.elastic.co/observability-labs/assets/images/elastic-opentelemetry-openai/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 &quot;zero code&quot; (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 &quot;openai-example&quot;</p>
<p><a href="http://localhost:5601/app/apm/services/openai-example/transactions">http://localhost:5601/app/apm/services/openai-example/transactions</a></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://www.elastic.co/observability-labs/assets/images/elastic-opentelemetry-openai/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://www.elastic.co/observability-labs/assets/images/elastic-opentelemetry-openai/kibana-transaction-logs.png" alt="Kibana transaction logs" /></p>
<p>You can also go to the Metrics Explorer and make a graph of &quot;gen_ai.client.token.usage&quot; or &quot;gen_ai.client.operation.duration&quot; over all the times you ran the application:</p>
<p><a href="http://localhost:5601/app/metrics/explorer">http://localhost:5601/app/metrics/explorer</a></p>
<p><img src="https://www.elastic.co/observability-labs/assets/images/elastic-opentelemetry-openai/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>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 class="language-bash">python3 -m venv .venv
source .venv/bin/activate
pip install openai &quot;python-dotenv[cli]&quot; elastic-opentelemetry
</code></pre>
<p>Next, run <code>edot-bootstrap</code> which analyzes the code to install any relevant instrumentation available:</p>
<pre><code class="language-bash">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 class="language-python">import os

import openai

CHAT_MODEL = os.environ.get(&quot;CHAT_MODEL&quot;, &quot;gpt-4o-mini&quot;)


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

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

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

if __name__ == &quot;__main__&quot;:
  main()
</code></pre>
<p>Now you can run everything with:</p>
<pre><code class="language-bash">dotenv run -- opentelemetry-instrument python chat.py
</code></pre>
<p>Finally, look for a trace for the service named &quot;openai-example&quot; in Kibana. You should see a transaction named &quot;chat gpt-4o-mini&quot;.</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>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 class="language-java">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(&quot;CHAT_MODEL&quot;, &quot;gpt-4o-mini&quot;);

    OpenAIClient client = OpenAIOkHttpClient.fromEnv();

    String message = &quot;Answer in up to 3 words: Which ocean contains Bouvet Island?&quot;;
    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 class="language-bash">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 class="language-bash">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 class="language-bash">./shdotenv java -javaagent:elastic-otel-javaagent.jar -jar openai-example-all.jar
</code></pre>
<p>Finally, look for a trace for the service named &quot;openai-example&quot; in Kibana. You should see a transaction named &quot;chat gpt-4o-mini&quot;.</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>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 class="language-bash">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 class="language-javascript">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 class="language-bash">node --env-file .env --require @elastic/opentelemetry-node index.js
</code></pre>
<p>Finally, look for a trace for the service named &quot;openai-example&quot; in Kibana. You should see a transaction named &quot;chat gpt-4o-mini&quot;.</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>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>
            <category>observability-labs</category>
            <enclosure url="https://www.elastic.co/observability-labs/assets/images/elastic-opentelemetry-openai/elastic-opentelemetry-openai.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Introducing Elastic Distribution for OpenTelemetry Python]]></title>
            <link>https://www.elastic.co/observability-labs/blog/elastic-opentelemetry-distribution-python</link>
            <guid isPermaLink="false">elastic-opentelemetry-distribution-python</guid>
            <pubDate>Sun, 07 Jul 2024 00:00:00 GMT</pubDate>
            <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>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>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>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 class="language-bash">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 class="language-bash">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 class="language-bash">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 class="language-python">from urllib.parse import quote
quote(&quot;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 class="language-bash">export OTEL_RESOURCE_ATTRIBUTES=service.name=&lt;service-name&gt;
export OTEL_EXPORTER_OTLP_HEADERS=&quot;Authorization=&lt;url encoded apikey header value&gt;&quot;
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 class="language-bash">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://www.elastic.co/observability-labs/assets/images/elastic-opentelemetry-distribution-python/traces-original.png" alt="trace sample screenshot" /></p>
<h2>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>Resources</h2>
<ul>
<li><a href="https://www.elastic.co/blog/elastic-opentelemetry-sdk-distributions">https://www.elastic.co/blog/elastic-opentelemetry-sdk-distributions</a></li>
<li><a href="https://www.elastic.co/observability-labs/blog/elastic-distribution-opentelemetry-java-agent">https://www.elastic.co/observability-labs/blog/elastic-distribution-opentelemetry-java-agent</a></li>
<li><a href="https://www.elastic.co/observability-labs/blog/elastic-opentelemetry-distribution-dotnet-applications">https://www.elastic.co/observability-labs/blog/elastic-opentelemetry-distribution-dotnet-applications</a></li>
<li><a href="https://www.elastic.co/observability-labs/blog/elastic-opentelemetry-distribution-node-js">https://www.elastic.co/observability-labs/blog/elastic-opentelemetry-distribution-node-js</a></li>
<li><a href="https://www.elastic.co/observability-labs/blog/manual-instrumentation-python-apps-opentelemetry">https://www.elastic.co/observability-labs/blog/manual-instrumentation-python-apps-opentelemetry</a></li>
<li><a href="https://www.elastic.co/observability-labs/blog/auto-instrumentation-python-applications-opentelemetry">https://www.elastic.co/observability-labs/blog/auto-instrumentation-python-applications-opentelemetry</a></li>
<li><a href="https://www.elastic.co/observability-labs/blog/opentelemetry-observability">https://www.elastic.co/observability-labs/blog/opentelemetry-observability</a></li>
</ul>
]]></content:encoded>
            <category>observability-labs</category>
            <enclosure url="https://www.elastic.co/observability-labs/assets/images/elastic-opentelemetry-distribution-python/python.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Manual instrumentation with OpenTelemetry for Python applications]]></title>
            <link>https://www.elastic.co/observability-labs/blog/manual-instrumentation-python-apps-opentelemetry</link>
            <guid isPermaLink="false">manual-instrumentation-python-apps-opentelemetry</guid>
            <pubDate>Thu, 31 Aug 2023 00:00:00 GMT</pubDate>
            <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&lt;sup&gt;®&lt;/sup&gt;, 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>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://www.elastic.co/observability-labs/assets/images/manual-instrumentation-python-apps-opentelemetry/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>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>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 class="language-python">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(&quot;app&quot;)
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={
        &quot;event.dataset&quot;: &quot;favorite.log&quot;,
        &quot;user.id&quot;: 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={
        &quot;event.dataset&quot;: &quot;favorite.log&quot;,
        &quot;user.id&quot;: user_id
    })
    return { &quot;favorites&quot;: favorites}

logger.info('App startup')
app.run(host='0.0.0.0', port=application_port)
logger.info('App Stopped')
</code></pre>
<h2>Step-by-step guide</h2>
<h3>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://www.elastic.co/observability-labs/assets/images/manual-instrumentation-python-apps-opentelemetry/elastic-blog-2-trial.png" alt="trial" /></p>
<h3>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 class="language-python">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 class="language-bash">OTEL_EXPORTER_OTLP_HEADERS
OTEL_EXPORTER_OTLP_ENDPOINT
</code></pre>
<p>And then initialize the exporter.</p>
<pre><code class="language-python">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 class="language-python">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 class="language-python">resourceAttributes = {
     &quot;service.name&quot;: otel_service_name,
     &quot;service.version&quot;: result_dict['service.version'],
     &quot;deployment.environment&quot;: 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 class="language-python">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 class="language-python">FlaskInstrumentor().instrument_app(app)
RequestsInstrumentor().instrument()
RedisInstrumentor().instrument()
</code></pre>
<h3>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 class="language-python">with tracer.start_as_current_span(&quot;add_favorite_movies&quot;, set_status_on_exception=True) as span:
        ...
</code></pre>
<p>The wrapped code is as follows:</p>
<pre><code class="language-python">@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(&quot;get_favorite_movies&quot;) as span:
        user_id = str(request.args.get('user_id'))

        logger.info('Getting favorites for user ' + user_id, extra={
            &quot;event.dataset&quot;: &quot;favorite.log&quot;,
            &quot;user.id&quot;: 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={
            &quot;event.dataset&quot;: &quot;favorite.log&quot;,
            &quot;user.id&quot;: 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 class="language-python">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 class="language-python">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&quot;Authorization=Bearer%20{secret_token}&quot;

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 = {
     &quot;service.name&quot;: result_dict['service.name'],
     &quot;service.version&quot;: result_dict['service.version'],
     &quot;deployment.environment&quot;: 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(&quot;favorite&quot;)


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(&quot;app&quot;)
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(&quot;get_favorite_movies&quot;) as span:
        user_id = str(request.args.get('user_id'))

        logger.info('Getting favorites for user ' + user_id, extra={
            &quot;event.dataset&quot;: &quot;favorite.log&quot;,
            &quot;user.id&quot;: 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={
            &quot;event.dataset&quot;: &quot;favorite.log&quot;,
            &quot;user.id&quot;: user_id
        })
        return { &quot;favorites&quot;: favorites}

logger.info('App startup')
app.run(host='0.0.0.0', port=application_port)
logger.info('App Stopped')
</code></pre>
<h3>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&lt;sup&gt;®&lt;/sup&gt; under the path <code>/app/home#/tutorial/apm</code>.</p>
<p><img src="https://www.elastic.co/observability-labs/assets/images/manual-instrumentation-python-apps-opentelemetry/elastic-blog-3-apm.png" alt="apm agents" /></p>
<p>You will need to copy the following environment variables:</p>
<pre><code class="language-bash">OTEL_EXPORTER_OTLP_ENDPOINT
OTEL_EXPORTER_OTLP_HEADERS
</code></pre>
<p><strong>Build the image</strong></p>
<pre><code class="language-bash">docker build -t  python-otel-manual-image .
</code></pre>
<p><strong>Run the image</strong></p>
<pre><code class="language-bash">docker run \
       -e OTEL_EXPORTER_OTLP_ENDPOINT=&quot;&lt;REPLACE WITH OTEL_EXPORTER_OTLP_ENDPOINT&gt;&quot; \
       -e OTEL_EXPORTER_OTLP_HEADERS=&quot;Authorization=Bearer &lt;REPLACE WITH TOKEN&gt;&quot; \
       -e OTEL_RESOURCE_ATTRIBUTES=&quot;service.version=1.0,deployment.environment=production,service.name=python-favorite-otel-manual&quot; \
       -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 class="language-bash">curl localhost:500/favorites
# or alternatively issue a request every second

while true; do curl &quot;localhost:5000/favorites&quot;; sleep 1; done;
</code></pre>
<h3>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://www.elastic.co/observability-labs/assets/images/manual-instrumentation-python-apps-opentelemetry/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://www.elastic.co/observability-labs/assets/images/manual-instrumentation-python-apps-opentelemetry/elastic-blog-5-graph2.png" alt="graph-2" /></p>
<h2>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>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/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>
            <category>observability-labs</category>
            <enclosure url="https://www.elastic.co/observability-labs/assets/images/manual-instrumentation-python-apps-opentelemetry/observability-launch-series-2-python-manual_(1).jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Automating User Journeys for Synthetic Monitoring with MCP in Elastic]]></title>
            <link>https://www.elastic.co/observability-labs/blog/mcp-elastic-synthetics</link>
            <guid isPermaLink="false">mcp-elastic-synthetics</guid>
            <pubDate>Wed, 17 Sep 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[This post explores how you can automatically create user journeys with Synthetic Monitoring in Elastic Observability, TypeScript, and FastMCP, and walks through the app and its workflow.]]></description>
            <content:encoded><![CDATA[<p><a href="https://www.elastic.co/docs/solutions/observability/synthetics">Synthetic Monitoring in Elastic Observability</a> enables you to track user pathways using a global testing infrastructure, emulating the full user path to measure the impact of web applications. It also provides comprehensive insight into your website's performance, functionality, and availability from development to production, allowing you to identify and resolve issues before they affect your customers.</p>
<p>One of the main components of Elastic's Synthetic Monitoring is the ability to create user journeys, which can be done with or without code. There is a <a href="https://github.com/elastic/synthetics">Synthetics agent,</a>, a CLI tool that guides you through the process of creating both heartbeat monitors and user journeys and deploying your code to Elastic Observability. If you are using code to create user journeys, you are using <a href="https://playwright.dev/">Playwright</a> under the hood with some additional configuration to make it easier to work with Elastic Observability.</p>
<p>To automatically create user journeys using TypeScript, you can create Playwright tests based on a prompt using <a href="https://www.warp.dev">Warp</a>, an AI-assisted terminal, <a href="https://deepmind.google/models/gemini/pro/">Gemini 2.5 Pro</a>, and <a href="https://modelcontextprotocol.io/docs/getting-started/intro">MCP</a>. This application was built using Python and <a href="https://gofastmcp.com/getting-started/welcome">FastMCP</a>, which wraps the synthetic agent to deploy browser tests to Elastic automatically. This blog post will guide you through how the application works, how to use it, and its development process. You can find the complete code on <a href="https://github.com/JessicaGarson/MCP-Elastic-Synthetics">GitHub</a>.</p>
<h2>Solution overview</h2>
<p><img src="https://www.elastic.co/observability-labs/assets/images/mcp-elastic-synthetics/01-diagram.jpg" alt="diagram" /></p>
<p>Currently, this solution is set up to run inside Warp as an <a href="https://docs.warp.dev/knowledge-and-collaboration/mcp">MCP server</a>; however, you can also use another client, such as <a href="https://claude.ai/download">Claude Desktop</a> or <a href="https://cursormcp.com/en">Cursor</a>. From there, you create a Python script using <a href="https://gofastmcp.com/getting-started/welcome">FastMCP</a>, which allows you to create functions that are callable by an LLM. Within Warp, you can make a configuration file in JSON that enables you to point to your Python script and pass in all the environment variables you are working with. From there, you'll want to toggle agent mode and ask a question about creating synthetic testing or call the MCP function directly. There are many options for which LLM you can select, be sure to check out <a href="https://docs.warp.dev/agents/using-agents">Warp's documentation</a> to learn more about the options available.</p>
<p>After that, you should ask a question about creating synthetic testing or call the MCP function you are looking for. The following three functions can be used:</p>
<ul>
<li>
<p><code>diagnose_warp_mcp_config</code>
Used for debugging environment variable issues that may arise. This function likely won't be needed unless there is an issue with your configuration.</p>
</li>
<li>
<p><code>create_and_deploy_browser_test</code>
Will automatically create Playwright tests if given the test name, the URL you want to test, and a schedule. This approach uses a template-based method, rather than a machine learning-based method, and all the tests it outputs will appear similar.</p>
</li>
<li>
<p><code>llm_create_and_deploy_test_from_prompt</code>
Similar to <code>create_and_deploy_browser_test</code>, but the main difference is that it uses an LLM to create tests based on a prompt you give it. The tests should reflect the prompt you provided. To run this function you'll provide a test name, URL, prompt, and schedule.</p>
</li>
</ul>
<h2>Why create this solution as an MCP server?</h2>
<p>The reason this was developed as an MCP server, as opposed to just a standalone script or a standard CLI, is that it can be structured and interacted with in a more conversational manner. It enables an LLM to generate dynamic Playwright testing while maintaining consistent arguments, environment variables, and responses to ensure accuracy and reliability. Thus, it becomes a reliable workflow that other agents or developers can compose with additional tools. In other words, the MCP layer turns your LLM-based test authoring into a standardized, reusable capability instead of a one-off script. To learn more about the direction of MCP, be sure to check out our article on the <a href="https://www.elastic.co/search-labs/blog/mcp-current-state">topic.</a></p>
<h2>Implementation considerations</h2>
<p>When creating a solution like this one, one thing to be mindful of is your use of tokens. An early version of this solution took approximately twenty minutes to create synthetic tests and ultimately led to severe rate-limiting.</p>
<p>Another issue faced during the building process was striking a balance between creating a template that facilitates the creation of a Playwright script and having an LLM create Playwright scripts based on prompts that didn't feel cookie-cutter. While using a more LLM approach an issue faced was that the scripts often didn't work or were based on parameters that didn't exist and a more templated approach was more reliable but felt repetitive. The final version of this solution attempted to balance this by using elements of the template while adjusting the LLM parameter of temperature, which controls the randomness or creativity of a large language model's output.</p>
<p>While testing this solution, a failing test also emerged that required navigating past a pop-up. In more complex cases, this may serve as a building block that requires additional domain knowledge to create a complete passing Playwright test.</p>
<h2>How to get started</h2>
<h3>Prerequisites</h3>
<ul>
<li>The version of Python that is used is Python 3.12.1 but you can use any version of Python higher than 3.10.</li>
<li>This application uses Elastic Observability version 9.1.2, but you can use any version of Elastics Observability that is higher than 8.10. You can also use <a href="https://www.elastic.co/cloud/serverless">Elastic Cloud Serverless</a> as well.</li>
<li>You will also need an OpenAI API key to use the LLM capabilities of this application. You will want to configure an environment variable for your OpenAI API Key, which you can find on the API keys page in <a href="https://platform.openai.com/api-keys">OpenAI's developer portal</a>.</li>
</ul>
<h3>Step 1: Install the packages and clone the repository</h3>
<p>In order for this MCP server to run locally you will need to install the the following packages:</p>
<pre><code class="language-shell">pip install fastmcp openai
npm install -g playwright @elastic/synthetics
</code></pre>
<p>You will use <a href="https://gofastmcp.com/getting-started/welcome">FastMCP 2.0</a> to create the MCP server, and <a href="https://github.com/openai/openai-python">OpenAI</a> to generate tests based on prompts that you provide. Additionally, you will want to clone the repository to obtain a local copy of the server.</p>
<h3>Step 2: Set up a configuration file in Warp</h3>
<p>Inside of Warp, you will want to go to the side panel, where it says MCP servers and where it says “add”.</p>
<p><img src="https://www.elastic.co/observability-labs/assets/images/mcp-elastic-synthetics/02-add-mcp.jpg" alt="Add MCP Server" /></p>
<p>After that, you will be prompted to add a JSON configuration file that should resemble the following. Be sure to add your own Kibana URL, update the correct path, and include your own keys and tokens.</p>
<pre><code class="language-json">{
 &quot;elastic-synthetics&quot;: {
   &quot;command&quot;: &quot;python&quot;,
   &quot;args&quot;: [&quot;elastic_synthetics_server.py&quot;],
   &quot;env&quot;: {
     &quot;PYTHONPATH&quot;: &quot;.&quot;,
     &quot;ELASTIC_KIBANA_URL&quot;: &quot;https://your-kibana-url.elastic-cloud.com&quot;,
     &quot;ELASTIC_API_KEY&quot;: &quot;your-api-key-here&quot;,
     &quot;ELASTIC_PROJECT_ID&quot;: &quot;mcp-synthetics-demo&quot;,
     &quot;ELASTIC_SPACE&quot;: &quot;default&quot;,
     &quot;ELASTIC_AUTO_PUSH&quot;: &quot;true&quot;,
     &quot;ELASTIC_USE_JAVASCRIPT&quot;: &quot;false&quot;,
     &quot;ELASTIC_INSTALL_DEPENDENCIES&quot;: &quot;true&quot;,
     &quot;OPENAI_API_KEY&quot;: &quot;sk-your-openai-key&quot;,
     &quot;LLM_MODEL&quot;: &quot;gpt-4o&quot;
   },
   &quot;working_directory&quot;: &quot;/path/to/your/file&quot;,
   &quot;start_on_launch&quot;: true 
   }
}
</code></pre>
<h3>Step 3: Ask a question or call the tools directly</h3>
<p>Now that you've set up locally, you will want to toggle agent mode and select the LLM you wish to use. The reason why Gemini-Pro-2.5 was chosen for this blog post is that it provides a straightforward answer, while other LLMs selected returned a very lengthy response.</p>
<p><img src="https://www.elastic.co/observability-labs/assets/images/mcp-elastic-synthetics/03-agent-mode.jpg" alt="Agent mode" /></p>
<p>To start using the MCP tools, from your MCP server, you can ask a question that contains the test name, URL, prompt, and schedule.</p>
<p><img src="https://www.elastic.co/observability-labs/assets/images/mcp-elastic-synthetics/04-full-question-answer.jpg" alt="Full question and answer" /></p>
<p>You can also call the directly by typing <code>llm_create_and_deploy_test_from_prompt()</code> and the program will prompt you for the relevant details:<br />
<img src="https://www.elastic.co/observability-labs/assets/images/mcp-elastic-synthetics/05-call-mcp-tool.jpg" alt="Call MCP Tool" /></p>
<p>Inside Kibana, you should see your monitor listed if you click under Applications and select Monitors listed under Synthetics. You can also find a link to your monitor in the response of your MCP tool.</p>
<p><img src="https://www.elastic.co/observability-labs/assets/images/mcp-elastic-synthetics/06-kibana-monitors.jpg" alt="Monitors in Kibana" /></p>
<h2>What's Going On Inside</h2>
<p>This code sample consists of three primary functions, which are MCP tools that you can call from your MCP client, including <code>diagnose_warp_mcp_config</code>, <code>create_and_deploy_browser_test</code> and <code>llm_create_and_deploy_test_from_prompt</code>.</p>
<h3>Debugging environment issues</h3>
<p>There were various issues that came up while creating this application around environment variable loading, so there was a need to create an MCP that could be called depending on errors that may be present.</p>
<p>The tool <code>diagnose_warp_mcp_config</code> kicks off with a decorator <code>@mcp.tool()</code> which allows it to be called and listed in the list of available tools. This tool is designed to help debug issues with Elastic-specific environment variables for troubleshooting purposes. First, it loads in the environment variables and looks for the Elastic specific variables, after it does some security masking so it doesn't show any variables and hides sensitive information like API keys in the output, showing only the first eight characters followed by &quot;...&quot;. This tool determines if the minimum required credentials (Kibana URL and API Key) are present to proceed with deployment and provides a report letting you know to address any issues that may exist.</p>
<pre><code class="language-py">@mcp.tool()
def diagnose_warp_mcp_config() -&gt; Dict[str, Any]:
   &quot;&quot;&quot;Diagnose Warp MCP environment configuration for Elastic Synthetics&quot;&quot;&quot;
   try:
       env_vars = load_env_from_warp_mcp()
      
       # Check for required variables
       kibana_url = env_vars.get('ELASTIC_KIBANA_URL') or env_vars.get('KIBANA_URL')
       api_key = env_vars.get('ELASTIC_API_KEY') or env_vars.get('API_KEY')
       project_id = env_vars.get('ELASTIC_PROJECT_ID') or env_vars.get('PROJECT_ID')
       space = env_vars.get('ELASTIC_SPACE') or env_vars.get('SPACE', 'default')
      
       # Mask sensitive values for display
       masked_vars = {}
       for key, value in env_vars.items():
           if 'API_KEY' in key or 'TOKEN' in key:
               masked_vars[key] = f&quot;{value[:8]}...&quot; if value and len(value) &gt; 8 else &quot;***&quot;
           else:
               masked_vars[key] = value
      
       deployment_ready = bool(kibana_url and api_key)
      
       return safe_json_response({
           &quot;status&quot;: &quot;success&quot;,
           &quot;environment_variables&quot;: masked_vars,
           &quot;required_check&quot;: {
               &quot;kibana_url&quot;: bool(kibana_url),
               &quot;api_key&quot;: bool(api_key),
               &quot;project_id&quot;: bool(project_id),
               &quot;space&quot;: bool(space)
           },
           &quot;deployment_ready&quot;: deployment_ready,
           &quot;recommendations&quot;: [
               &quot;Environment variables detected&quot; if env_vars else &quot;No environment variables found&quot;,
               &quot;Kibana URL configured&quot; if kibana_url else &quot;Missing ELASTIC_KIBANA_URL or KIBANA_URL&quot;,
               &quot;API Key configured&quot; if api_key else &quot;Missing ELASTIC_API_KEY or API_KEY&quot;,
               &quot;Ready for deployment&quot; if deployment_ready else &quot;Missing required credentials&quot;
           ]
       })
      
   except Exception as e:
       return safe_json_response({
           &quot;status&quot;: &quot;error&quot;,
           &quot;error&quot;: str(e),
           &quot;error_type&quot;: type(e).__name__
       })
</code></pre>
<h3>Creating synthetic tests based on a template</h3>
<p>While developing this solution to generate tests based on a prompt, the process wasn't always smooth. Early versions encountered issues with accuracy, hallucinations, and the creation of loops. To make progress, a version that relied on creating a test template to verify the mechanics of the solution, such as whether the test could pass and be deployed to Elastic correctly, was a logical next step.</p>
<p>This solution automates the entire process of creating a synthetic browser test that will regularly check if a website is working correctly, then deploys it to Elastic Observability Synthetics. Similar to <code>diagnose_warp_mcp_config</code>, the MCP tool <code>create_and_deploy_browser_test</code> starts with the decorator <code>@mcp.tool()</code> and checks to make sure that the proper environment variables are loaded.</p>
<p>From there, it creates a TypeScript test file that is based on templates and generates dynamic test steps based on the target website's characteristics, including navigating to the website, verifying the page title exists, checking page load performance, taking a screenshot, verifying page content is visible, and finally saves the test file in a <code>synthetic_tests</code> directory.</p>
<p>Finally, it wraps Elastic's CLI tool <code>@elastic/synthetics</code> to push the test to Kibana, allowing you to set which geographic locations to run tests from, how often to run the test, and the project and workspace settings.</p>
<p>You check out the full code for this MCP tool <a href="https://github.com/JessicaGarson/MCP-Elastic-Synthetics/blob/main/elastic_synthetics_server.py#L943">here.</a></p>
<h3>Creating synthetic tests based on a prompt</h3>
<p>While creating browser tests based on a templated approach is a good starting point, it felt generic and cookie-cutter. But it made a helpful structure to build an LLM-based function on top of.</p>
<p>The MCP tool <code>llm_create_and_deploy_test_from_prompt</code> begins by ensuring that basic parameters, including locations, schedule, and directories, are listed. Additionally, it aims to learn more about the target website to inform the AI and initialize the OpenAI client and model, which is GPT-4o.</p>
<p>After setting up the LLM, it converts natural language requests into actual Playwright test code, then cleans and validates the AI-generated code to prevent issues like injection attacks or malformed syntax. It draws inspiration from the templated approach, wrapping AI-generated steps within a proven, reliable test framework template. Finally, it deploys the test to Elastic in a similar manner to the previous tool.</p>
<p>You can find the code for this tool <a href="https://github.com/JessicaGarson/MCP-Elastic-Synthetics/blob/main/elastic_synthetics_server.py#L1559">here</a>.</p>
<h2>Conclusion and next steps</h2>
<p>Synthetic monitoring in Elastic Observability makes it easy to test complete user journeys and keep your site reliable, with simple setup and a Playwright integration. A tool like this can provide a starting point for tests that you can iterate on after.</p>
<p>A solution like this is just the start of an MCP implementation that automatically generates Playwright tests for you and can be expanded in the future to include heartbeat monitors, utilize the <a href="https://github.com/microsoft/playwright-mcp">Playwright MCP server</a>, or consider experimenting with <a href="https://www.anthropic.com/news/claude-for-chrome">Claude for Chrome</a> to create synthetic testing.</p>
<p>Check out more articles on <a href="https://www.elastic.co/observability-labs/blog/tag/synthetics">Observability Labs on Synthetic Monitoring</a></p>
]]></content:encoded>
            <category>observability-labs</category>
            <enclosure url="https://www.elastic.co/observability-labs/assets/images/mcp-elastic-synthetics/retro.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[ML and AI Ops Observability with OpenTelemetry and Elastic]]></title>
            <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>
            <pubDate>Tue, 31 Mar 2026 00:00:00 GMT</pubDate>
            <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>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>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>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 class="language-bash">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 class="language-bash">export OTEL_EXPORTER_OTLP_ENDPOINT=&quot;https://&lt;motlp-endpoint&gt;&quot; # No need when using start-local with EDOT
export OTEL_EXPORTER_OTLP_HEADERS=&quot;Authorization=ApiKey &lt;key&gt;&quot; # No need when using start-local with EDOT
export OTEL_RESOURCE_ATTRIBUTES=&quot;deployment.environment=prod,service.version=1.0.0&quot; # 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://www.elastic.co/observability-labs/assets/images/ml-ai-ops-observability-opentelemetry-elastic/step-1-logs.png" alt="Logs in Elastic" />
<img src="https://www.elastic.co/observability-labs/assets/images/ml-ai-ops-observability-opentelemetry-elastic/step-1-log-errors.png" alt="Log errors in Elastic" />
<img src="https://www.elastic.co/observability-labs/assets/images/ml-ai-ops-observability-opentelemetry-elastic/step-1-alerts-on-log-errors.png" alt="Alerts on log errors" />
<img src="https://www.elastic.co/observability-labs/assets/images/ml-ai-ops-observability-opentelemetry-elastic/step-1-metrics.png" alt="System and process metrics" />
<img src="https://www.elastic.co/observability-labs/assets/images/ml-ai-ops-observability-opentelemetry-elastic/step-1-auto-instrumented-traces.png" alt="Auto instrumented traces" />
<img src="https://www.elastic.co/observability-labs/assets/images/ml-ai-ops-observability-opentelemetry-elastic/step-1-service-map.png" alt="Service map in Elastic" />
<img src="https://www.elastic.co/observability-labs/assets/images/ml-ai-ops-observability-opentelemetry-elastic/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>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 class="language-python">logger.info(&quot;training metrics&quot;, extra={
    &quot;ml.run_id&quot;: run_id,
    &quot;ml.training_accuracy&quot;: train_accuracy,
    &quot;ml.validation_accuracy&quot;: val_accuracy,
    &quot;ml.drift_detected&quot;: 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://www.elastic.co/observability-labs/assets/images/ml-ai-ops-observability-opentelemetry-elastic/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://www.elastic.co/observability-labs/assets/images/ml-ai-ops-observability-opentelemetry-elastic/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://www.elastic.co/observability-labs/assets/images/ml-ai-ops-observability-opentelemetry-elastic/step-2-custom-spans.png" alt="Custom spans in code" /></p>
<pre><code class="language-python">from opentelemetry import trace

tracer = trace.get_tracer(&quot;ml.pipeline&quot;)

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

with tracer.start_as_current_span(&quot;train_model&quot;) as span:
    span.set_attribute(&quot;ml.model_version&quot;, model_version)
    span.set_attribute(&quot;ml.learning_rate&quot;, 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://www.elastic.co/observability-labs/assets/images/ml-ai-ops-observability-opentelemetry-elastic/step-2-custom-spans-ui-in-elastic.png" alt="Custom spans UI in Elastic" />
<img src="https://www.elastic.co/observability-labs/assets/images/ml-ai-ops-observability-opentelemetry-elastic/step-2-analysing-spans.png" alt="Analysing spans" />
<img src="https://www.elastic.co/observability-labs/assets/images/ml-ai-ops-observability-opentelemetry-elastic/step-2-latency-and-avg-latency-of-spans.png" alt="Latency and avg latency of spans" />
<img src="https://www.elastic.co/observability-labs/assets/images/ml-ai-ops-observability-opentelemetry-elastic/step-2-alerts-on-custom-log-metrics.png" alt="Alerts on custom log metrics" /></p>
<h2>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://www.elastic.co/observability-labs/assets/images/ml-ai-ops-observability-opentelemetry-elastic/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://www.elastic.co/observability-labs/assets/images/ml-ai-ops-observability-opentelemetry-elastic/step-3-multiple-services.png" alt="Multiple services request flow" />
<img src="https://www.elastic.co/observability-labs/assets/images/ml-ai-ops-observability-opentelemetry-elastic/step-3-spans-per-service.png" alt="Spans per service" />
<img src="https://www.elastic.co/observability-labs/assets/images/ml-ai-ops-observability-opentelemetry-elastic/step-3-go-service-logs.png" alt="Go service logs" />
<img src="https://www.elastic.co/observability-labs/assets/images/ml-ai-ops-observability-opentelemetry-elastic/step-3-go-traces-in-discover.png" alt="Go traces in discover" /></p>
<h2>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>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>
            <category>observability-labs</category>
            <enclosure url="https://www.elastic.co/observability-labs/assets/images/ml-ai-ops-observability-opentelemetry-elastic/header.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Monitor your Python data pipelines with OTEL]]></title>
            <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>
            <pubDate>Thu, 08 Aug 2024 00:00:00 GMT</pubDate>
            <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>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>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 start="2">
<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 start="3">
<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>Steps for Instrumentation</h2>
<p>Here are the steps to automatically instrument your Python script for exporting OTEL traces, metrics, and logs.</p>
<h3>Step 1: Import Required Libraries</h3>
<p>We first need to install the following libraries.</p>
<pre><code class="language-sh">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>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>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://www.elastic.co/observability-labs/assets/images/monitor-your-python-data-pipelines-with-otel/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://www.elastic.co/observability-labs/assets/images/monitor-your-python-data-pipelines-with-otel/otel-variables-2.png" alt="2 - Get OTEL variables step 2" /></p>
<p><strong>Find OTLP Endpoint</strong>:</p>
<ul>
<li>Look for the section related to OpenTelemetry or OTLP configuration.</li>
<li>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>.</li>
</ul>
<p><strong>Obtain OTLP Headers</strong>:</p>
<ul>
<li>In the same section, you should find instructions or a field for OTLP headers. These headers are often used for authentication purposes.</li>
<li>Copy the necessary headers provided by the interface. They might look like <code>Authorization: Bearer &lt;your-token&gt;</code>.</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 class="language-sh">#!/bin/bash
echo &quot;--- :otel: Setting OTEL variables&quot;
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=&quot;otlp,console&quot;
</code></pre>
<p>With these variables set, we are ready for auto-instrumentation without needing to add anything to the code.</p>
<h4>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 &quot;otlp&quot; means that logs will be exported using the OTLP protocol. Adding &quot;console&quot; 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 &quot;otlp&quot; by default, which means that metrics and traces will be exported using the OTLP protocol.</p>
<h3>Running Python ETLs</h3>
<p>We run Python ETLs with the following command:</p>
<pre><code class="language-sh">OTEL_RESOURCE_ATTRIBUTES=&quot;service.name=x-ETL,service.version=1.0,deployment.environment=production&quot; &amp;&amp; opentelemetry-instrument python3 X_ETL.py 
</code></pre>
<h4>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>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 class="language-python">from opentelemetry import trace

if __name__ == &quot;__main__&quot;:

    tracer = trace.get_tracer(&quot;main&quot;)
    with tracer.start_as_current_span(&quot;initialization&quot;) as span:
            # Init code
            … 
    with tracer.start_as_current_span(&quot;search&quot;) as span:
            # Step 1 - Search code
            …
   with tracer.start_as_current_span(&quot;transform&quot;) as span:
           # Step 2 - Transform code
           …
   with tracer.start_as_current_span(&quot;load&quot;) 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://www.elastic.co/observability-labs/assets/images/monitor-your-python-data-pipelines-with-otel/Traces-APM-Observability-Elastic.png" alt="3 - APM Traces view" /></p>
<h3>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://www.elastic.co/observability-labs/assets/images/monitor-your-python-data-pipelines-with-otel/otel-metrics-apm-view.png" alt="4 - APM Metrics view" /></p>
<h3>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 class="language-python">        job.result()  # Waits for table load to complete
        job_details = client.get_job(job.job_id)  # Get job details

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

        logging.info(&quot;BigQuery load operation successful&quot;, 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://www.elastic.co/observability-labs/assets/images/monitor-your-python-data-pipelines-with-otel/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>Explanation of Logging Modification</h4>
<ul>
<li>
<p><strong>logging.info</strong>: This logs an informational message. The message &quot;BigQuery load operation successful&quot; 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>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>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://www.elastic.co/observability-labs/assets/images/monitor-your-python-data-pipelines-with-otel/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 &quot;labels.status: error&quot; 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://www.elastic.co/observability-labs/assets/images/monitor-your-python-data-pipelines-with-otel/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 class="language-python">logging.info(
            &quot;Elasticsearch search operation successful&quot;,
            extra={
                &quot;event&quot;: &quot;Elasticsearch Search&quot;,
                &quot;status&quot;: &quot;success&quot;,
                &quot;category&quot;: category,
                &quot;index&quot;: index,
            },
        )
</code></pre>
<h3>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://www.elastic.co/observability-labs/assets/images/monitor-your-python-data-pipelines-with-otel/apm_custom_threshold_latency.png" alt="8 - APM Custom Threshold - Latency" /></p>
<p><img src="https://www.elastic.co/observability-labs/assets/images/monitor-your-python-data-pipelines-with-otel/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://www.elastic.co/observability-labs/assets/images/monitor-your-python-data-pipelines-with-otel/apm_anomaly_rule_config.png" alt="10 - APM Anomaly Rule - Config" /></p>
<h3>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 [Single metric job] (<a href="https://www.elastic.co/guide/en/machine-learning/current/ml-anomaly-detection-job-types.html#multi-metric-jobs">https://www.elastic.co/guide/en/machine-learning/current/ml-anomaly-detection-job-types.html#multi-metric-jobs</a>) to detect any anomalies with the incoming data source.</p>
<p><img src="https://www.elastic.co/observability-labs/assets/images/monitor-your-python-data-pipelines-with-otel/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://www.elastic.co/observability-labs/assets/images/monitor-your-python-data-pipelines-with-otel/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://www.elastic.co/observability-labs/assets/images/monitor-your-python-data-pipelines-with-otel/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>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://www.elastic.co/observability-labs/assets/images/monitor-your-python-data-pipelines-with-otel/custom_dashboard.png" alt="14 - Custom Dashboard" /></p>
<h2>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>
            <category>observability-labs</category>
            <enclosure url="https://www.elastic.co/observability-labs/assets/images/monitor-your-python-data-pipelines-with-otel/main_image.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Tracing, logs, and metrics for a RAG based Chatbot with Elastic Distributions of OpenTelemetry]]></title>
            <link>https://www.elastic.co/observability-labs/blog/openai-tracing-elastic-opentelemetry</link>
            <guid isPermaLink="false">openai-tracing-elastic-opentelemetry</guid>
            <pubDate>Fri, 24 Jan 2025 00:00:00 GMT</pubDate>
            <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>
<h1>Prerequisites:</h1>
<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>
<h1>Application OpenTelemetry output in Elastic</h1>
<h2>Chatbot-rag-app</h2>
<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://www.elastic.co/observability-labs/assets/images/openai-tracing-elastic-opentelemetry/Chatbotapp-general.png" alt="Chatbot app main page" /></p>
<p><img src="https://www.elastic.co/observability-labs/assets/images/openai-tracing-elastic-opentelemetry/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>
<h2>Traces, logs, and metrics from EDOT in Elastic</h2>
<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>
<h3>Logs:</h3>
<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://www.elastic.co/observability-labs/assets/images/openai-tracing-elastic-opentelemetry/chatbot-reg-logs.png" alt="Chatbot-logs" /></p>
<p><img src="https://www.elastic.co/observability-labs/assets/images/openai-tracing-elastic-opentelemetry/chatbot-reg-logs-patterns.png" alt="Chatbot-log-patterns" /></p>
<h3>Traces:</h3>
<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://www.elastic.co/observability-labs/assets/images/openai-tracing-elastic-opentelemetry/chatbot-reg-trace.png" alt="CHatbot-traces" /></p>
<h3>Metrics:</h3>
<p>In addition to logs, and traces, any instrumented metrics will also get ingested into Elastic.</p>
<p><img src="https://www.elastic.co/observability-labs/assets/images/openai-tracing-elastic-opentelemetry/chatbot-reg-metrics.png" alt="Chatbot app metrics" /></p>
<h1>Setting it all up with Docker</h1>
<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://www.elastic.co/observability-labs/assets/images/openai-tracing-elastic-opentelemetry/otel-credentials.png" alt="OTel credentials" /></p>
<p>Envs for sending the OTel instrumentation you will need the following:</p>
<pre><code class="language-bash">OTEL_EXPORTER_OTLP_ENDPOINT=&quot;https://123456789.apm.us-west-2.aws.cloud.es.io:443&quot;
OTEL_EXPORTER_OTLP_HEADERS=&quot;Authorization=Bearer%20xxxxx&quot;
</code></pre>
<p>Notice the <code>%20</code> in the headers. This will be needed to account for the space in credentials.</p>
<ol start="3">
<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 class="language-bash">LLM_TYPE=openai
OPENAI_API_KEY=XXXX
CHAT_MODEL=gpt-4o-mini
</code></pre>
<ol start="5">
<li>Run the docker container as noted</li>
</ol>
<pre><code class="language-bash">docker compose up --build --force-recreate
</code></pre>
<ol start="6">
<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>
<h1>Run chatbot-rag-app on Kubernetes</h1>
<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 start="3">
<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 class="language-bash">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 class="language-bash">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: [&quot;python3&quot;, &quot;-m&quot;, &quot;flask&quot;, &quot;--app&quot;, &quot;app&quot;, &quot;create-index&quot;]
        env:
        - name: FLASK_APP
          value: &quot;app&quot;
        - name: LLM_TYPE
          value: &quot;openai&quot;
        - name: CHAT_MODEL
          value: &quot;gpt-4o-mini&quot;
        - name: ES_INDEX
          value: &quot;workplace-app-docs&quot;
        - name: ES_INDEX_CHAT_HISTORY
          value: &quot;workplace-app-docs-chat-history&quot;
        - 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 class="language-bash">apiVersion: v1
kind: Secret
metadata:
  name: chatbot-regular-secrets
type: Opaque
stringData:
  ELASTICSEARCH_URL: &quot;https://yourelasticcloud.es.us-west-2.aws.found.io&quot;
  ELASTICSEARCH_USER: &quot;elastic&quot;
  ELASTICSEARCH_PASSWORD: &quot;elastic&quot;
  OTEL_EXPORTER_OTLP_HEADERS: &quot;Authorization=Bearer%20xxxx&quot;
  OTEL_EXPORTER_OTLP_ENDPOINT: &quot;https://12345.apm.us-west-2.aws.cloud.es.io:443&quot;
  OPENAI_API_KEY: &quot;YYYYYYYY&quot;

---
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: &quot;openai&quot;
        - name: CHAT_MODEL
          value: &quot;gpt-4o-mini&quot;
        - name: OTEL_RESOURCE_ATTRIBUTES
          value: &quot;service.name=chatbot-regular,service.version=0.0.1,deployment.environment=dev&quot;
        - name: OTEL_SDK_DISABLED
          value: &quot;false&quot;
        - name: OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT
          value: &quot;true&quot;
        - name: OTEL_EXPERIMENTAL_RESOURCE_DETECTORS
          value: &quot;process_runtime,os,otel,telemetry_distro&quot;
        - name: OTEL_EXPORTER_OTLP_PROTOCOL
          value: &quot;http/protobuf&quot;
        - name: OTEL_METRIC_EXPORT_INTERVAL
          value: &quot;3000&quot;
        - name: OTEL_BSP_SCHEDULE_DELAY
          value: &quot;3000&quot;
        envFrom:
        - secretRef:
            name: chatbot-regular-secrets
        resources:
          requests:
            memory: &quot;512Mi&quot;
            cpu: &quot;250m&quot;
          limits:
            memory: &quot;1Gi&quot;
            cpu: &quot;500m&quot;

---
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 class="language-bash">% 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 start="4">
<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>
<h1>Conclusion</h1>
<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>
            <category>observability-labs</category>
            <enclosure url="https://www.elastic.co/observability-labs/assets/images/openai-tracing-elastic-opentelemetry/edot-openai-tracing.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Tracing a RAG based Chatbot with Elastic Distributions of OpenTelemetry and Langtrace]]></title>
            <link>https://www.elastic.co/observability-labs/blog/openai-tracing-langtrace-elastic</link>
            <guid isPermaLink="false">openai-tracing-langtrace-elastic</guid>
            <pubDate>Thu, 06 Feb 2025 00:00:00 GMT</pubDate>
            <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>
<h1>Pre-requisites:</h1>
<p>In order to follow along, these few pre-requisites are needed</p>
<ul>
<li>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</li>
</ul>
<ul>
<li>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.</li>
</ul>
<ul>
<li>An account on your favorite LLM (OpenAI, AzureOpen AI, etc), with API keys</li>
</ul>
<ul>
<li>Be familiar with EDOT to understand how we bring in logs, metrics, and traces from the application through the OTel Collector</li>
</ul>
<ul>
<li>Kubernetes cluster - I’ll be using Amazon EKS</li>
</ul>
<ul>
<li>Look at <a href="https://docs.langtrace.ai/introduction">Langtrace</a> documentation also.</li>
</ul>
<h1>Application OpenTelemetry output in Elastic</h1>
<h2>Chatbot-rag-app</h2>
<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://www.elastic.co/observability-labs/assets/images/openai-tracing-langtrace-elastic/Chatbotapp-general.png" alt="Chatbot app main page" /></p>
<p><img src="https://www.elastic.co/observability-labs/assets/images/openai-tracing-langtrace-elastic/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>
<h2>Traces, logs, and metrics from EDOT in Elastic</h2>
<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>
<h3>Logs:</h3>
<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://www.elastic.co/observability-labs/assets/images/openai-tracing-langtrace-elastic/Chatbotapp-logs.png" alt="Chatbot-logs" /></p>
<p><img src="https://www.elastic.co/observability-labs/assets/images/openai-tracing-langtrace-elastic/Chatbotapp-log-patterns.png" alt="Chatbot-log-patterns" /></p>
<p><img src="https://www.elastic.co/observability-labs/assets/images/openai-tracing-langtrace-elastic/Chatbotapp-logs-detailed.png" alt="Chatbot-log-details" /></p>
<h3>Traces:</h3>
<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://www.elastic.co/observability-labs/assets/images/openai-tracing-langtrace-elastic/Chatbotapp-service-traces.png" alt="CHatbot-traces" /></p>
<h3>Metrics:</h3>
<p>In addition to logs, and traces, any instrumented metrics will also get ingested into Elastic.</p>
<p><img src="https://www.elastic.co/observability-labs/assets/images/openai-tracing-langtrace-elastic/chatbot-reg-metrics.png" alt="Chatbot app metrics" /></p>
<h1>Setting it all up</h1>
<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 start="6">
<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>
<h2>Modify the code for tracing with Langtrace</h2>
<p>Once you curl the app and untar, go to the chatbot-rag-app directory:</p>
<pre><code class="language-bash">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 class="language-bash">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 class="language-bash">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=&quot;../frontend/build&quot;, static_url_path=&quot;/&quot;)
CORS(app)

FlaskInstrumentor().instrument_app(app)

@app.route(&quot;/&quot;)
</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>
<h2>Create the docker container</h2>
<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 class="language-bash">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>
<h2>Collecting the proper env variables:</h2>
<p>First collect the env variables from Elastic:</p>
<p>Envs for index initialization in Elastic:</p>
<pre><code class="language-bash">
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 class="language-bash">OTEL_EXPORTER_OTLP_ENDPOINT=&quot;https://123456789.apm.us-west-2.aws.cloud.es.io:443&quot;
OTEL_EXPORTER_OTLP_HEADERS=&quot;Authorization=Bearer xxxxx&quot;
</code></pre>
<p>These credentials are found in Elastic under APM integration and under OpenTelemetry</p>
<p><img src="https://www.elastic.co/observability-labs/assets/images/openai-tracing-langtrace-elastic/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 class="language-bash">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>
<h2>Setup K8s cluster and load up OTel Collector with EDOT</h2>
<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>
<h2>Setup secrets, initialize indices, and start the app</h2>
<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 class="language-bash">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 class="language-bash">apiVersion: v1
kind: Secret
metadata:
  name: genai-chatbot-langtrace-secrets
type: Opaque
stringData:
  OTEL_EXPORTER_OTLP_HEADERS: &quot;Authorization=Bearer%20xxxx&quot;
  OTEL_EXPORTER_OTLP_ENDPOINT: &quot;https://1234567.apm.us-west-2.aws.cloud.es.io:443&quot;
 ELASTICSEARCH_URL: &quot;YOUR_ELASTIC_SEARCH_URL&quot;
  ELASTICSEARCH_USER: &quot;elastic&quot;
  ELASTICSEARCH_PASSWORD: &quot;elastic&quot;
  OPENAI_API_KEY: &quot;XXXXXXX&quot;  

---
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: &quot;openai&quot;
        - name: CHAT_MODEL
          value: &quot;gpt-4o-mini&quot;
        - name: OTEL_SDK_DISABLED
          value: &quot;false&quot;
        - name: OTEL_RESOURCE_ATTRIBUTES
          value: &quot;service.name=genai-chatbot-langtrace,service.version=0.0.1,deployment.environment=dev&quot;
        - name: OTEL_EXPORTER_OTLP_PROTOCOL
          value: &quot;http/protobuf&quot;
        envFrom:
        - secretRef:
            name: genai-chatbot-langtrace-secrets
        resources:
          requests:
            memory: &quot;512Mi&quot;
            cpu: &quot;250m&quot;
          limits:
            memory: &quot;1Gi&quot;
            cpu: &quot;500m&quot;

---
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 class="language-bash">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: [&quot;python3&quot;, &quot;-m&quot;, &quot;flask&quot;, &quot;--app&quot;, &quot;app&quot;, &quot;create-index&quot;]
        env:
        - name: FLASK_APP
          value: &quot;app&quot;
        - name: LLM_TYPE
          value: &quot;openai&quot;
        - name: CHAT_MODEL
          value: &quot;gpt-4o-mini&quot;
        - name: ES_INDEX
          value: &quot;workplace-app-docs&quot;
        - name: ES_INDEX_CHAT_HISTORY
          value: &quot;workplace-app-docs-chat-history&quot;
        - 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>
<h2>Open App with LoadBalancer URL</h2>
<p>Run the kubectl get services command and get the URL for the chatbot app</p>
<pre><code class="language-bash">% 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>
<h1>Conclusion</h1>
<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>
            <category>observability-labs</category>
            <enclosure url="https://www.elastic.co/observability-labs/assets/images/openai-tracing-langtrace-elastic/edot-openai-tracing.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[How to combine OpenTelemetry instrumentation with Elastic APM Agent features]]></title>
            <link>https://www.elastic.co/observability-labs/blog/opentelemetry-instrumentation-apm-agent-features</link>
            <guid isPermaLink="false">opentelemetry-instrumentation-apm-agent-features</guid>
            <pubDate>Thu, 13 Jul 2023 00:00:00 GMT</pubDate>
            <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&lt;sup&gt;®&lt;/sup&gt;, and you can view that OpenTelemetry data in the APM app in Kibana&lt;sup&gt;®&lt;/sup&gt;.</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>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 class="language-csharp">namespace SampleAspNetCoreApp.Controllers
{
	public class HomeController : Controller
	{
		private readonly SampleDataContext _sampleDataContext;
		private ActivitySource _activitySource = new ActivitySource(&quot;HomeController&quot;);
		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(&quot;User-Agent&quot;, &quot;APM-Sample-App&quot;);
			var responseMsg = await httpClient.GetAsync(&quot;https://api.github.com/repos/elastic/apm-agent-dotnet&quot;);
			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://www.elastic.co/observability-labs/assets/images/opentelemetry-instrumentation-apm-agent-features/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>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 class="language-python">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(&quot;ReadGitHubStars&quot;):
       url = &quot;https://api.github.com/repos/elastic/apm-agent-python&quot;
       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 <a href="http://127.0.0.1:8000/otelsample/">http://127.0.0.1:8000/otelsample/</a> 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://www.elastic.co/observability-labs/assets/images/opentelemetry-instrumentation-apm-agent-features/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://www.elastic.co/observability-labs/assets/images/opentelemetry-instrumentation-apm-agent-features/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://www.elastic.co/observability-labs/assets/images/opentelemetry-instrumentation-apm-agent-features/elastic-blog-4-span-details-2.png" alt="OpenTelemetry spans first-class citizens in Elastic APM Agents" /></p>
<h2>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>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>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>
            <category>observability-labs</category>
            <enclosure url="https://www.elastic.co/observability-labs/assets/images/opentelemetry-instrumentation-apm-agent-features/opentelemetry_apm-blog-720x420.jpeg" length="0" type="image/jpeg"/>
        </item>
    </channel>
</rss>