<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
  <channel>
    <title><![CDATA[Sylvain Juge - Elastic Observability Labs]]></title>
    <description><![CDATA[Trusted security news & research from the team at Elastic.]]></description>
    <copyright><![CDATA[© 2026. Elasticsearch B.V. All Rights Reserved]]></copyright>
    <image>
      <title><![CDATA[Sylvain Juge - Elastic Observability Labs]]></title>
      <url>https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltad972c1c27dbefc6/6a88d9782904ea5e8511d473/observability-labs-thumbnail.png</url>
      <link>https://www.elastic.co/observability-labs/author/sylvain-juge</link>
    </image>
    <link>https://www.elastic.co/observability-labs/author/sylvain-juge</link>
    <atom:link href="https://www.elastic.co/observability-labs/rss/author/sylvain-juge.xml" rel="self" type="application/rss+xml"/>
    <language><![CDATA[en]]></language>
    <lastBuildDate>Sat, 12 Sep 2026 18:32:34 GMT</lastBuildDate>
  <item>
    <title><![CDATA[OpenTelemetry Java extensions: customize traces without forking the agent]]></title>
    <description><![CDATA[One JAR, loaded at startup by the OpenTelemetry Java agent, lets you filter health checks, rename spans, add resource attributes, and control sampling with no application code changes.]]></description>
    <content:encoded><![CDATA[<p>You've just set up auto-instrumentation on a Java application. Without any code changes, traces start flowing to your observability platform.
After a few minutes, you realize health check endpoints are flooding your trace view, and transaction names reflect generic framework patterns rather than your domain operations.</p>
<p>Forking the agent would fix this, but then you own every upstream merge.
You could also use manual instrumentation for complete control, but that requires code changes and ongoing upkeep.
OpenTelemetry Java extensions give you a cleaner path: a separate JAR the agent loads at startup, giving you precise control over what gets captured and exported, without touching agent or application code.</p>
<p>For example, the following challenges are very common:</p>
<ul>
<li>Health check probes are flooding your trace view.</li>
<li>Span names reflect generic framework patterns rather than your domain operations.</li>
<li>Some span names or attributes have high cardinality creating noise in your traces.</li>
<li>Spans are missing attributes relevant to your business logic.</li>
<li>Baggage headers are propagating to downstream services when they shouldn't.</li>
<li>Resource attributes that describe your deployment are not automatically captured because they rely on custom environment variables.</li>
</ul>
<p>Some of those can be solved through configuration, or by using an intermediate OpenTelemetry Collector for processing.
However, this also might add complexity to the telemetry pipeline, and you might prefer to solve this at the source, where the data is captured.</p>
<h2 id="whatareopentelemetryjavaextensions">What are OpenTelemetry Java extensions</h2>
<p>An extension is a JAR file the agent loads at startup. It hooks into the agent's extension points through Java's Service Provider Interface (SPI) mechanism, the same mechanism the agent uses internally.</p>
<p>The extension mechanism works identically with the upstream OpenTelemetry Java agent and with <a href="https://github.com/elastic/elastic-otel-java">Elastic's OpenTelemetry distribution</a>. You write the extension once and it works with either.</p>
<p>For reference, the <a href="https://opentelemetry.io/docs/zero-code/java/agent/extensions/">upstream extension documentation</a> provides an exhaustive overview of extension points and a few examples.</p>
<p>This post does not aim to provide a complete reference, but focuses on simple use cases you're likely to reach for in production: renaming spans, filtering noisy traces, or propagating context that the agent doesn't cover in your environment.</p>
<p>Extensions also let you modify and extend the agent instrumentation itself. That goes beyond what this post covers. Here are two starting points:</p>
<ul>
<li><a href="https://opentelemetry.io/docs/zero-code/java/agent/extensions/#instrumentercustomizerprovider">Modify instrumentation using instrumenter customizers</a>.</li>
<li><a href="https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/main/examples/extension/src/main/java/com/example/javaagent/instrumentation/DemoServlet3InstrumentationModule.java">Modify or add instrumentation using the instrumentation module</a>.</li>
</ul>
<h2 id="settingupanopentelemetryjavaextensionproject">Setting up an OpenTelemetry Java extension project</h2>
<p>An extension is a standard Java Gradle project with two requirements: the output must be a shadow JAR (a fat JAR with all extension dependencies bundled), and OpenTelemetry dependencies must be declared <code>compileOnly</code> so you don't bundle the SDK itself.</p>
<p>The shadow JAR requirement exists because the agent loads the extension in its own classloader. If you declare a dependency as <code>implementation</code>, it gets bundled and may conflict with the version already in the agent. Using <code>compileOnly</code> keeps those JARs out of the extension JAR entirely.</p>
<p>Here is a minimal <code>build.gradle.kts</code> for a simple extension that does not customize instrumentation and thus relies only on the OpenTelemetry SDK/API.</p>
<pre><code>plugins {
  id("java")
  id("com.gradleup.shadow")
}

repositories {
  mavenCentral()
}

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

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

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

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

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

  @Override
  public void onEnd(ReadableSpan span) {}

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

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

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

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

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

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

  private final SpanExporter delegate;

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

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

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

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

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

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

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

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

  private final Sampler delegate;

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

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

  @Override
  public String getDescription() {
    return "HealthCheckSampler{" + delegate.getDescription() + "}";
  }
}
</code></pre>
<p>Register the HealthCheckSampler via <code>addSamplerCustomizer</code>, which gives you both the existing sampler and the resolved config:</p>
<pre><code>customizer.addSamplerCustomizer((existing, config) -&gt; new HealthCheckSampler(existing));
</code></pre>
<h2 id="communityextensionsinopentelemetryjavacontrib">Community extensions in opentelemetry-java-contrib</h2>
<p>The <a href="https://github.com/open-telemetry/opentelemetry-java-contrib">opentelemetry-java-contrib</a> repository contains several community-maintained extensions.</p>
<p>Some of them are already included in the OpenTelemetry Java agent (and inherited in the Elastic distribution), but are opt-in:</p>
<ul>
<li><a href="https://github.com/open-telemetry/opentelemetry-java-contrib/tree/main/azure-resources">azure-resources</a></li>
<li><a href="https://github.com/open-telemetry/opentelemetry-java-contrib/tree/main/aws-resources">aws-resources</a></li>
<li><a href="https://github.com/open-telemetry/opentelemetry-java-contrib/tree/main/gcp-resources">gcp-resources</a></li>
<li><a href="https://github.com/open-telemetry/opentelemetry-java-contrib/tree/main/cloudfoundry-resources">cloudfoundry-resources</a></li>
<li><a href="https://github.com/open-telemetry/opentelemetry-java-contrib/tree/main/baggage-processor">baggage-processor</a></li>
</ul>
<p>Most Elastic distribution <a href="https://www.elastic.co/docs/reference/opentelemetry/edot-sdks/java/features">features</a> exist as extensions in the contrib repository, so you can use them with the upstream agent in a vendor-neutral way.</p>
<ul>
<li><a href="https://github.com/open-telemetry/opentelemetry-java-contrib/tree/main/inferred-spans">inferred-spans</a></li>
<li><a href="https://github.com/open-telemetry/opentelemetry-java-contrib/tree/main/span-stacktrace">span-stacktrace</a></li>
</ul>
<h2 id="furtherreadingandextensionexamples">Further reading and extension examples</h2>
<p>The <a href="https://github.com/open-telemetry/opentelemetry-java-instrumentation/tree/main/examples/extension">upstream extension examples</a> cover additional extension points not shown here, including custom propagators, ID generators, and ignored-type configurers.</p>
<p>The <a href="https://github.com/elastic/elastic-otel-java/tree/main/examples/baggage">Elastic baggage example</a> shows the filtering propagator for baggage running end-to-end with a two-service application, it also demonstrates custom instrumentation to add baggage without modifying the application code.</p>
<p>This post covered the project setup and the patterns most likely to come up in production. Both links above go deeper: the upstream examples add extension points not covered here, and the baggage example shows a complete two-service implementation you can run locally.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/opentelemetry-java-extensions</link>
    <guid isPermaLink="false">opentelemetry-java-extensions</guid>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[Kubernetes]]></category>
    <category><![CDATA[What's New]]></category>
    <dc:creator><![CDATA[Sylvain Juge]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2b379be7ce7ba2c1/6a8ea21cbf814594cbd284ec/header.jpg" length="0" type="image/jpeg"/>
    <pubDate>Thu, 20 Aug 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Automatic cloud resource attributes with OpenTelemetry Java]]></title>
    <description><![CDATA[Capturing cloud resource attributes allow to describe application cloud deployment details. In this article we describe three distinct ways to enable them for Java applications using OpenTelemetry]]></description>
    <content:encoded><![CDATA[<p>With OpenTelemetry, the observed entities (application, services, processes, …) are described through resource attributes. The definitions and the values of those attributes are defined in the <a href="https://opentelemetry.io/docs/concepts/semantic-conventions/">semantic conventions</a>.\
In practice, for a typical java application running in a cloud environment like Google Cloud Platform (GCP), Amazon Web Services (AWS) or Azure, it means capturing the name of the cloud provider, the cloud service name or availability zone in addition to per-provider attributes. Those attributes are then used to describe and qualify the observability signals (logs, traces, metrics), defined by semantic conventions in the <a href="https://opentelemetry.io/docs/specs/semconv/resource/cloud/">cloud resource attributes</a> section.</p>
<p>When using the <a href="https://github.com/open-telemetry/opentelemetry-java">OpenTelemetry Java SDK</a> or the <a href="https://github.com/open-telemetry/opentelemetry-java-instrumentation">OpenTelemetry instrumentation agent</a>, those attributes are not automatically captured by default. In this article we will show you first how to enable them with the SDK, then using the instrumentation agent and then we will show you how using the <a href="https://github.com/elastic/elastic-otel-java/">Elastic OpenTelemetry Distribution</a> makes it even easier.</p>
<h2 id="opentelemetryjavasdk">OpenTelemetry Java SDK</h2>
<p>The OpenTelemetry Java SDK does not capture any cloud resource attributes, however it provides a pluggable service provider interface to register resource attributes providers and application developers have to provide the implementations.</p>
<p>Implementations for <a href="https://github.com/open-telemetry/opentelemetry-java-contrib/tree/main/gcp-resources">GCP</a> and <a href="https://github.com/open-telemetry/opentelemetry-java-contrib/tree/main/aws-resources">AWS</a> are already included in the <a href="https://github.com/open-telemetry/opentelemetry-java-contrib/">OpenTelemetry Java Contrib</a> repo, so if you are using one of those cloud providers then it's mostly a matter of adding those providers to your application dependencies. Thanks to autoconfiguration those should be automatically included and enabled once they are added to the application classpath. The <a href="https://github.com/open-telemetry/opentelemetry-java/tree/main/sdk-extensions/autoconfigure#resource-provider-spi">SDK documentation</a> provides all the details to add and configure those in your application.</p>
<p>If you are using a cloud provider for which no such implementation is available, then you still have the option to provide your own which is a straightforward implementation of the <a href="https://github.com/open-telemetry/opentelemetry-java/blob/main/sdk-extensions/autoconfigure/README.md#resource-provider-spi">ResourceProvider</a> SPI (Service Provider Interface). In order to keep things consistent, you will have to rely on the existing <a href="https://opentelemetry.io/docs/specs/semconv/resource/cloud/">cloud semantic conventions</a>.</p>
<p>For example here is an example of a simple cloud resource attributes provider for a fictitious cloud provider named "potatoes".</p>
<pre><code>package potatoes;

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

public class PotatoesResourceProvider implements ResourceProvider {

@Override
public Resource createResource(ConfigProperties configProperties) {
   return Resource.create(Attributes.of(
           CloudIncubatingAttributes.CLOUD_PROVIDER, "potatoes",
           CloudIncubatingAttributes.CLOUD_PLATFORM, "french-fries",
           CloudIncubatingAttributes.CLOUD_REGION, "garden"
           ));
  }
}
</code></pre>
<h2 id="opentelemetryjavainstrumentation">OpenTelemetry Java instrumentation</h2>
<p>The <a href="https://github.com/open-telemetry/opentelemetry-java-instrumentation">OpenTelemetry Java Instrumentation</a> provides a java agent that instruments the application at runtime automatically for an extensive set of frameworks and libraries (see <a href="https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/main/docs/supported-libraries.md">supported technologies</a>).</p>
<p>Using instrumentation means that the application bytecode and the embedded libraries are modified automatically to make them behave as if explicit modifications were made in their source code to call the OpenTelemetry SDK in order to create traces, spans and metrics.</p>
<p>When an application is deployed with the OpenTelemetry instrumentation agent, the cloud resource attributes for GCP and AWS are included but not enabled by default since version 2.2.0. You can enable them <a href="https://opentelemetry.io/docs/languages/java/automatic/configuration/#enable-resource-providers-that-are-disabled-by-default">through configuration</a> by setting the following properties:</p>
<ul>
<li><p>For AWS: <code>otel.resource.providers.aws.enabled=true</code></p></li>
<li><p>For GCP: <code>otel.resource.providers.gcp.enabled=true</code></p></li>
</ul>
<h2 id="elasticopentelemetryjavadistribution">Elastic OpenTelemetry Java Distribution</h2>
<p>The Elastic OpenTelemetry Java distribution relies on the OpenTelemetry Java instrumentation which we often refer to as the Vanilla OpenTelemetry, and it thus inherits all of its features.</p>
<p>One major difference though is that the resource attributes providers for GCP and AWS are included and enabled by default to provide a better onboarding experience without extra configuration.</p>
<p>The minor cost to this is that it might make the application startup slightly slower due to having to call an HTTP(S) endpoint. This overhead is usually negligible compared to application startup but can become significant for some setups.</p>
<p>In order to reduce the startup overhead, or when the cloud provider is known in advance, you can selectively disable unused provider implementations through configuration:</p>
<ul>
<li><p>For AWS: <code>otel.resource.providers.aws.enabled=false</code></p></li>
<li><p>For GCP: <code>otel.resource.providers.gcp.enabled=false</code></p></li>
</ul>
<h2 id="conclusion">Conclusion</h2>
<p>With this blogpost we have introduced what OpenTelemetry cloud resource attributes are and how they can be used and configured into application deployments using either OpenTelemetry SDK/API and Instrumentation agents.</p>
<p>When using the Elastic OpenTelemetry Java distribution, those resource providers are automatically provided and enabled for an easy and simple onboarding experience.</p>
<p>Another very interesting aspect of the cloud resource attribute providers available in the <a href="https://github.com/open-telemetry/opentelemetry-java-contrib">opentelemetry-java-contrib</a> repository is that they are maintained by their respective vendors (Google and Amazon). For the end-user it means those implementations should be quite well tested and be robust to changes in the underlying infrastructure. For solution vendors like Elastic, it means we don't have to re-implement and reverse-engineer the infrastructure details of every cloud provider, hence proving that investing in those common components is a net win for the broader OpenTelemetry community.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/opentelemetry-java-automatic-cloud-resource-attributes</link>
    <guid isPermaLink="false">opentelemetry-java-automatic-cloud-resource-attributes</guid>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[Infrastructure Monitoring]]></category>
    <dc:creator><![CDATA[Sylvain Juge]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd11cf83d31a10bb5/6a7f192e9090b0891b84ee03/flexible-implementation-1680X980.png" length="0" type="image/png"/>
    <pubDate>Thu, 27 Jun 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[How to easily add application monitoring in Kubernetes pods]]></title>
    <description><![CDATA[This blog walks through installing the Elastic APM K8s Attacher and shows how to configure your system for both common and non-standard deployments of Elastic APM agents.]]></description>
    <content:encoded><![CDATA[<p>The <a href="https://www.elastic.co/guide/en/apm/attacher/current/index.html">Elastic® APM K8s Attacher</a> allows auto-installation of Elastic APM application agents (e.g., the Elastic APM Java agent) into applications running in your Kubernetes clusters. The mechanism uses a <a href="https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/">mutating webhook</a>, which is a standard Kubernetes component, but you don’t need to know all the details to use the Attacher. Essentially, you can install the Attacher, add one annotation to any Kubernetes deployment that has an application you want monitored, and that’s it!</p>
<p>In this blog, we’ll walk through a full example from scratch using a Java application. Apart from the Java code and using a JVM for the application, everything else works the same for the other languages supported by the Attacher.</p>
<h2 id="prerequisites">Prerequisites</h2>
<p>This walkthrough assumes that the following are already installed on the system: JDK 17, Docker, Kubernetes, and Helm.</p>
<h2 id="theexampleapplication">The example application</h2>
<p>While the application (shown below) is a Java application, it would be easily implemented in any language, as it is just a simple loop that every 2 seconds calls the method chain methodA-&gt;methodB-&gt;methodC-&gt;methodD, with methodC sleeping for 10 milliseconds and methodD sleeping for 200 milliseconds. The choice of application is just to be able to clearly display in the Elastic APM UI that the application is being monitored.</p>
<p>The Java application in full is shown here:</p>
<pre><code>package test;

public class Testing implements Runnable {

  public static void main(String[] args) {
    new Thread(new Testing()).start();
  }

  public void run()
  {
    while(true) {
      try {Thread.sleep(2000);} catch (InterruptedException e) {}
      methodA();
    }
  }

  public void methodA() {methodB();}

  public void methodB() {methodC();}

  public void methodC() {
    System.out.println("methodC executed");
    try {Thread.sleep(10);} catch (InterruptedException e) {}
    methodD();
  }

  public void methodD() {
    System.out.println("methodD executed");
    try {Thread.sleep(200);} catch (InterruptedException e) {}
  }
}
</code></pre>
<p>We created a Docker image containing that simple Java application for you that can be pulled from the following Docker repository:</p>
<pre><code>docker.elastic.co/demos/apm/k8s-webhook-test
</code></pre>
<h2 id="deploythepod">Deploy the pod</h2>
<p>First we need a deployment config. We’ll call the config file webhook-test.yaml, and the contents are pretty minimal — just pull the image and run that as a pod &amp; container called webhook-test in the default namespace:</p>
<pre><code>apiVersion: v1
kind: Pod
metadata:
  name: webhook-test
  labels:
    app: webhook-test
spec:
  containers:
    - image: docker.elastic.co/demos/apm/k8s-webhook-test
      imagePullPolicy: Always
      name: webhook-test
</code></pre>
<p>This can be deployed normally using kubectl:</p>
<pre><code>kubectl apply -f webhook-test.yaml
</code></pre>
<p>The result is exactly as expected:</p>
<pre><code>$ kubectl get pods
NAME           READY   STATUS    RESTARTS   AGE
webhook-test   1/1     Running   0          10s

$ kubectl logs webhook-test
methodC executed
methodD executed
methodC executed
methodD executed
</code></pre>
<p>So far, this is just setting up a standard Kubernetes application with no APM monitoring. Now we get to the interesting bit: adding in auto-instrumentation.</p>
<h2 id="installelasticapmk8sattacher">Install Elastic APM K8s Attacher</h2>
<p>The first step is to install the <a href="https://www.elastic.co/guide/en/apm/attacher/current/index.html">Elastic APM K8s Attacher</a>. This only needs to be done once for the cluster — once installed, it is always available. Before installation, we will define where the monitored data will go. As you will see later, we can decide or change this any time. For now, we’ll specify our own Elastic APM server, which is at https://myserver.somecloud:443 — we also have a secret token for authorization to that Elastic APM server, which has value MY_SECRET_TOKEN. (If you want to set up a quick test Elastic APM server, you can do so at <a href="https://cloud.elastic.co/">https://cloud.elastic.co/</a>).</p>
<p>There are two additional environment variables set for the application that are not generally needed but will help when we see the resulting UI content toward the end of the walkthrough (when the agent is auto-installed, these two variables tell the agent what name to give this application in the UI and what method to trace). Now we just need to define the custom yaml file to hold these. On installation, the custom yaml will be merged into the yaml for the Attacher:</p>
<pre><code>apm:
  secret_token: MY_SECRET_TOKEN
  namespaces:
    - default
webhookConfig:
  agents:
    java:
      environment:
        ELASTIC_APM_SERVER_URL: "https://myserver.somecloud:443"
        ELASTIC_APM_TRACE_METHODS: "test.Testing#methodB"
        ELASTIC_APM_SERVICE_NAME: "webhook-test"
</code></pre>
<p>That custom.yaml file is all we need to install the attacher (note we’ve only specified the default namespace for agent auto-installation for now — this can be easily changed, as you’ll see later). Next we’ll add the Elastic charts to helm — this only needs to be done once, then all Elastic charts are available to helm. This is the usual helm add repo command, specifically:</p>
<pre><code>helm repo add elastic https://helm.elastic.co
</code></pre>
<p>Now the Elastic charts are available for installation (helm search repo would show you all the available charts). We’re going to use “elastic-webhook” as the name to install into, resulting in the following installation command:</p>
<pre><code>helm install elastic-webhook elastic/apm-attacher --namespace=elastic-apm --create-namespace --values custom.yaml
</code></pre>
<p>And that’s it, we now have the Elastic APM K8s Attacher installed and set to send data to the APM server defined in the custom.yaml file! (You can confirm installation with a helm list -A if you need.)</p>
<h2 id="autoinstallthejavaagent">Auto-install the Java agent</h2>
<p>The Elastic APM K8s Attacher is installed, but it doesn’t auto-install the APM application agents into every pod — that could lead to problems! Instead the Attacher is deliberately limited to auto-install agents into deployments defined a) by the namespaces listed in the custom.yaml, and b) to those deployments in those namespaces that have a specific annotation “co.elastic.apm/attach.”</p>
<p>So for now, restarting the webhook-test pod we created above won’t have any different effect on the pod, as it isn’t yet set to be monitored. What we need to do is add the annotation. Specifically, we need to add the annotation using the default agent configuration that was installed with the Attacher called “java” for the Java agent (we’ll see later how that agent configuration is altered — the default configuration installs the latest agent version and leaves everything else default for that version). So adding that annotation in to webhook-test yaml gives us the new yaml file contents (the additional config is shown labelled (1)):</p>
<pre><code>apiVersion: v1
kind: Pod
metadata:
  name: webhook-test
  annotations: #(1)
    co.elastic.apm/attach: java #(1)
  labels:
    app: webhook-test
spec:
  containers:
    - image: docker.elastic.co/demos/apm/k8s-webhook-test
      imagePullPolicy: Always
      name: webhook-test
</code></pre>
<p>Applying this change gives us the application now monitored:</p>
<pre><code>$ kubectl delete -f webhook-test.yaml
pod "webhook-test" deleted
$ kubectl apply -f webhook-test.yaml
pod/webhook-test created
$ kubectl logs webhook-test
… StartupInfo - Starting Elastic APM 1.45.0 …
</code></pre>
<p>And since the agent is now feeding data to our APM server, we can now see it in the UI:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltca8638b16b1aa112/6a85cb3b2d64d52d74081d44/webhook-test-k8s-blog.png" alt="webhook-test" /></p>
<p>Note that the agent identifies Testing.methodB method as a trace root because of the ELASTIC_APM_TRACE_METHODS environment variable set to test.Testing#methodB in the custom.yaml — this tells the agent to specifically trace that method. The time taken by that method will be available in the UI for each invocation, but we don’t see the sub-methods . . . currently. In the next section, we’ll see how easy it is to customize the Attacher, and in doing so we’ll see more detail about the method chain being executed in the application.</p>
<h2 id="customizingtheagents">Customizing the agents</h2>
<p>In your systems, you’ll likely have development, testing, and production environments. You’ll want to specify the version of the agent to use rather than just pull the latest version whatever that is, you’ll want to have debug on for some applications or instances, and you’ll want to have specific options set to specific values. This sounds like a lot of effort, but the attacher lets you enable these kinds of changes in a very simple way. In this section, we’ll add a configuration that specifies all these changes and we can see just how easy it is to configure and enable it.</p>
<p>We start at the custom.yaml file we defined above. This is the file that gets merged into the Attacher. Adding a new configuration with all the items listed in the last paragraph is easy — though first we need to decide a name for our new configuration. We’ll call it “java-interesting” here. The new custom.yaml in full is (the first part is just the same as before, the new config is simply appended):</p>
<pre><code>apm:
  secret_token: MY_SECRET_TOKEN
  namespaces:
    - default
webhookConfig:
  agents:
    java:
      environment:
        ELASTIC_APM_SERVER_URL: "https://myserver.somecloud:443"
        ELASTIC_APM_TRACE_METHODS: "test.Testing#methodB"
        ELASTIC_APM_SERVICE_NAME: "webhook-test"
    java-interesting:
      image: docker.elastic.co/observability/apm-agent-java:1.55.4
      artifact: "/usr/agent/elastic-apm-agent.jar"
      environment:
        ELASTIC_APM_SERVER_URL: "https://myserver.somecloud:443"
        ELASTIC_APM_TRACE_METHODS: "test.Testing#methodB"
        ELASTIC_APM_SERVICE_NAME: "webhook-test"
        ELASTIC_APM_ENVIRONMENT: "testing"
        ELASTIC_APM_LOG_LEVEL: "debug"
        ELASTIC_APM_PROFILING_INFERRED_SPANS_ENABLED: "true"
        JAVA_TOOL_OPTIONS: "-javaagent:/elastic/apm/agent/elastic-apm-agent.jar"
</code></pre>
<p>Breaking the additional config down, we have:</p>
<ul>
<li><p>The name of the new config java-interesting</p></li>
<li><p>The APM Java agent image docker.elastic.co/observability/apm-agent-java</p></li>
<li><p>With a specific version 1.43.0 instead of latest</p></li>
<li><p>We need to specify the agent jar location (the attacher puts it here)</p></li>
<li><p>artifact: "/usr/agent/elastic-apm-agent.jar"</p></li>
<li><p>And then the environment variables</p></li>
<li><p>ELASTIC_APM_SERVER_URL as before</p></li>
<li><p>ELASTIC_APM_ENVIRONMENT set to testing, useful when looking in the UI</p></li>
<li><p>ELASTIC_APM_LOG_LEVEL set to debug for more detailed agent output</p></li>
<li><p>ELASTIC_APM_PROFILING_INFERRED_SPANS_ENABLED turning this on (setting to true) will give us additional interesting information about the method chain being executed in the application</p></li>
<li><p>And lastly we need to set JAVA_TOOL_OPTIONS to the enable starting the agent "-javaagent:/elastic/apm/agent/elastic-apm-agent.jar" — this is fundamentally how the attacher auto-attaches the Java agent</p></li>
</ul>
<p>More configurations and details about configuration options are <a href="https://www.elastic.co/guide/en/apm/agent/java/current/configuration.html">here for the Java agent</a>, and <a href="https://www.elastic.co/guide/en/apm/agent/index.html">other language agents</a> are also available.</p>
<h2 id="theapplicationtracedwiththenewconfiguration">The application traced with the new configuration</h2>
<p>And finally we just need to upgrade the attacher with the changed custom.yaml:</p>
<pre><code>helm upgrade elastic-webhook elastic/apm-attacher --namespace=elastic-apm --create-namespace --values custom.yaml
</code></pre>
<p>This is the same command as the original install, but now using upgrade. That’s it — add config to the custom.yaml and upgrade the attacher, and it’s done! Simple.</p>
<p>Of course we still need to use the new config on an app. In this case, we’ll edit the existing webhook-test.yaml file, replacing java with java-interesting, so the annotation line is now:</p>
<pre><code>co.elastic.apm/attach: java-interesting
</code></pre>
<p>Applying the new pod config and restarting the pod, you can see the logs now hold debug output:</p>
<pre><code>$ kubectl delete -f webhook-test.yaml
pod "webhook-test" deleted
$ kubectl apply -f webhook-test.yaml
pod/webhook-test created
$ kubectl logs webhook-test
… StartupInfo - Starting Elastic APM 1.44.0 …
… DEBUG co.elastic.apm.agent. …
… DEBUG co.elastic.apm.agent. …
</code></pre>
<p>More interesting is the UI. Now that inferred spans is on, the full method chain is visible.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf182dab36de35372/6a85cb3d6826668f5b1eac03/trace-sample-k8s-blog.png" alt="trace sample" /></p>
<p>This gives the details for methodB (it takes 211 milliseconds because it calls methodC - 10ms - which calls methodD - 200ms). The times for methodC and methodD are inferred rather than recorded, (inferred rather than traced — if you needed accurate times you would instead add the methods to trace_methods and have them traced too).</p>
<h2 id="noteontheeckoperator">Note on the ECK operator</h2>
<p>The <a href="https://www.elastic.co/guide/en/cloud-on-k8s/master/k8s-overview.html">Elastic Cloud on Kubernetes operator</a> allows you to install and manage a number of other Elastic components on Kubernetes. At the time of publication of this blog, the <a href="https://www.elastic.co/guide/en/apm/attacher/current/index.html">Elastic APM K8s Attacher</a> is a separate component, and there is no conflict between these management mechanisms — they apply to different components and are independent of each other.</p>
<h2 id="tryityourself">Try it yourself!</h2>
<p>This walkthrough is easily repeated on your system, and you can make it more useful by replacing the example application with your own and the Docker registry with the one you use.</p>
<p><a href="https://www.elastic.co/observability/kubernetes-monitoring">Learn more about real-time monitoring with Kubernetes and Elastic Observability</a>.</p>
<p><em>The release and timing of any features or functionality described in this post remain at Elastic's sole discretion. Any features or functionality not currently available may not be delivered on time or at all.</em></p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/application-monitoring-kubernetes-pods</link>
    <guid isPermaLink="false">application-monitoring-kubernetes-pods</guid>
    <category><![CDATA[Kubernetes]]></category>
    <category><![CDATA[APM]]></category>
    <dc:creator><![CDATA[Jack Shirazi,Sylvain Juge,Alexander Wert]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltbececa85f67fdcd1/6a85cb40eaf24581f5a49f65/139689_-_Blog_Header_Banner_V1.jpg" length="0" type="image/jpeg"/>
    <pubDate>Wed, 17 Jan 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Elastic's contribution: Invokedynamic in the OpenTelemetry Java agent]]></title>
    <description><![CDATA[The instrumentation approach in OpenTelemetry's Java Agent comes with some limitations with respect to maintenance and testability. Elastic contributes an invokedynamic-based instrumentation approach that helps overcoming these limitations.]]></description>
    <content:encoded><![CDATA[<p>As the second largest and active Cloud Native Computing Foundation (CNCF) project, <a href="https://opentelemetry.io/">OpenTelemetry</a> is well on its way to becoming the ubiquitous, unified standard and framework for observability. OpenTelemetry owes this success to its comprehensive and feature-rich toolset that allows users to retrieve valuable observability data from their applications with low effort. The OpenTelemetry Java agent is one of the most mature and feature-rich components in OpenTelemetry’s ecosystem. It provides automatic instrumentation for JVM-based applications and comes with a broad coverage of auto-instrumentation modules for popular Java-frameworks and libraries.</p>
<p>The original instrumentation approach used in the OpenTelemetry Java agent left the maintenance and development of auto-instrumentation modules subject to some restrictions. As part of <a href="https://www.elastic.co/blog/transforming-observability-ai-assistant-otel-standardization-continuous-profiling-log-analytics">our reinforced commitment to OpenTelemetry</a>, Elastic® helps evolve and improve OpenTelemetry projects and components. <a href="https://www.elastic.co/blog/ecs-elastic-common-schema-otel-opentelemetry-announcement">Elastic’s contribution of the Elastic Common Schema</a> to OpenTelemetry was an important step for the open-source community. As another step in our commitment to OpenTelemetry, Elastic started contributing to the OpenTelemetry Java agent.</p>
<h2 id="elasticsinvokedynamicbasedinstrumentationapproach">Elastic’s invokedynamic-based instrumentation approach</h2>
<p>To overcome the above-mentioned limitations in developing and maintaining auto-instrumentation modules in the OpenTelemetry Java agent, Elastic started contributing its <a href="https://www.elastic.co/blog/embracing-invokedynamic-to-tame-class-loaders-in-java-agents"><strong>invokedynamic</strong></a><a href="https://www.elastic.co/blog/embracing-invokedynamic-to-tame-class-loaders-in-java-agents">-based instrumentation approach</a> to the OpenTelemetry Java agent in July 2023.</p>
<p>To explain the improvement, you should know that in Java, a common approach to do auto-instrumentation of applications is through utilizing Java agents that do bytecode instrumentation at runtime. <a href="https://bytebuddy.net/#/">Byte Buddy</a> is a popular and widespread utility that helps with bytecode instrumentation without the need to deal with Java’s bytecode directly. Instrumentation logic that collects observability data from the target application’s code lives in so-called <em>advice methods</em>. Byte Buddy provides different ways of hooking these advice methods into the target application’s methods:</p>
<ul>
<li><em>Advice inlining:</em> The advice method’s code is being copied into the instrumented target method.</li>
<li><em>Static advice dispatching:</em> The instrumented target method invokes static advice methods that need to be visible by the instrumented code.</li>
<li><em>Advice dispatching with</em>  <strong>invokedynamic</strong> __:_ The instrumented target method uses the JVM’s <strong>invokedynamic</strong> bytecode instruction to call advice methods that are isolated from the instrumented code.</li>
</ul>
<p>These different approaches are described in great detail in our related blog post on <a href="https://www.elastic.co/blog/embracing-invokedynamic-to-tame-class-loaders-in-java-agents">Elastic’s Java APM agent using invokedynamic</a>. In a nutshell, both approaches, <em>advice inlining</em> and <em>dispatching to static advice methods</em> come with some limitations with respect to writing and maintaining the advice code. So far, the OpenTelemetry Java agent has used <em>advice inlining</em> for its bytecode instrumentation. The resulting limitations on developing instrumentations are <a href="https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/v1.30.0/docs/contributing/writing-instrumentation-module.md#use-advice-classes-to-write-code-that-will-get-injected-to-the-instrumented-library-classes">documented in corresponding developer guidelines</a>. Among other things, the limitation of not being able to debug advice code is a painful restriction when developing and maintaining instrumentation code.</p>
<p>Elastic’s APM Java agent has been using the <strong>invokedynamic</strong> approach with its benefits for years — field-proven by thousands of customers. To help improve the OpenTelemetry Java agent, Elastic started contributing the <strong>invokedynamic</strong> approach with the goal to simplify and improve the development and maintainability of auto-instrumentation modules. The contribution proposal and the implementation outline is documented in more detail in <a href="https://github.com/open-telemetry/opentelemetry-java-instrumentation/issues/8999">this GitHub issue</a>.</p>
<p>With the new approach in place, Elastic will help migrate existing instrumentations so the OTel Java community can benefit from the <strong>invokedynamic</strong> -based instrumentation approach.</p>
<blockquote>
  <p>Elastic supports OTel natively, and has numerous capabilities to help you analyze your application with OTel. </p>
  <ul>
  <li><a href="https://www.elastic.co/blog/opentelemetry-observability">Native OpenTelemetry support in Elastic Observability</a></li>
  <li><a href="https://www.elastic.co/blog/best-practices-instrumenting-opentelemetry">Best Practices for instrumenting OpenTelemetry</a></li>
  <li><a href="https://www.elastic.co/blog/opentelemetry-observability">Independence with OpenTelemetry on Elastic</a></li>
  </ul>
  <p>Instrumenting with OpenTelemetry:</p>
  <ul>
  <li><a href="https://www.elastic.co/blog/getting-started-opentelemetry-instrumentation-sample-app">Elastiflix application</a>, a guide to instrument different languages with OpenTelemetry (this is the application the team built to highlight <em>all</em> the languages below)</li>
  <li>Python: <a href="https://www.elastic.co/blog/auto-instrumentation-of-python-applications-opentelemetry">Auto-instrumentation</a>, <a href="https://www.elastic.co/blog/manual-instrumentation-of-python-applications-opentelemetry">Manual instrumentation</a></li>
  <li>Java: <a href="https://www.elastic.co/blog/auto-instrumentation-of-java-applications-opentelemetry">Auto-instrumentation</a>, <a href="https://www.elastic.co/blog/manual-instrumentation-of-java-applications-opentelemetry">Manual instrumentation </a></li>
  <li>Node.js: <a href="https://www.elastic.co/blog/auto-instrument-nodejs-applications-opentelemetry">Auto-instrumentation</a>, <a href="https://www.elastic.co/blog/manual-instrumentation-of-nodejs-applications-opentelemetry">Manual instrumentation</a></li>
  <li>.NET: <a href="https://www.elastic.co/blog/auto-instrumentation-of-net-applications-opentelemetry">Auto-instrumentation</a>, <a href="https://www.elastic.co/blog/manual-instrumentation-of-net-applications-opentelemetry">Manual instrumentation</a><br />
  Go: <a href="https://elastic.co/blog/manual-instrumentation-of-go-applications-opentelemetry">Manual instrumentation</a></li>
  </ul>
</blockquote>
<p><em>The release and timing of any features or functionality described in this post remain at Elastic's sole discretion. Any features or functionality not currently available may not be delivered on time or at all.</em></p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/invokedynamic-opentelemetry-java-agent</link>
    <guid isPermaLink="false">invokedynamic-opentelemetry-java-agent</guid>
    <category><![CDATA[OpenTelemetry]]></category>
    <dc:creator><![CDATA[Alexander Wert,Jack Shirazi,Jonas Kunz,Sylvain Juge]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4923c41bb46647d7/6a85c99b18249cfe1d18f79f/24-crystals.jpeg" length="0" type="image/jpeg"/>
    <pubDate>Thu, 19 Oct 2023 00:00:00 GMT</pubDate>
  </item>
  </channel>
</rss>