> ## Documentation Index
> Fetch the complete documentation index at: https://launchdarkly-preview.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# OpenTelemetry in server-side SDKs

> This topic explains how to enable OpenTelemetry in server-side SDKs. OpenTelemetry (OTel) is an open source observability framework.

> For the complete documentation index, see [llms.txt](/llms.txt). For the full documentation snapshot, see [llms-full.txt](/llms-full.txt).

## Overview

This topic explains how to enable OpenTelemetry in server-side SDKs.

<Note title="Looking for information on OpenTelemetry in client-side SDKs?">
  For information on using OpenTelemetry in LaunchDarkly's client-side SDKs, read [OpenTelemetry in client-side SDKs](/sdk/features/opentelemetry-client-side).
</Note>

## About Open

Telemetry

[OpenTelemetry](https://opentelemetry.io/) (OTel) is an open source observability framework and toolkit designed to create and manage telemetry data such as traces, metrics, and logs. You can use LaunchDarkly's OpenTelemetry protocol (OTLP) endpoint to send OpenTelemetry traces to LaunchDarkly.

We recommend enabling OpenTelemetry in LaunchDarkly server-side SDKs if you use LaunchDarkly's [Observability](/home/observability), [Experimentation](/home/experimentation), or [Guarded rollouts](/home/releases/guarded-rollouts) features, or if you use third-party observability tools that support the OpenTelemetry framework.

Because OpenTelemetry is vendor

* and tool-agnostic, you can reuse the instrumentation that may already exist in your code to create LaunchDarkly metrics without changing application code.

## Viewing Open

Telemetry data in LaunchDarkly

In the LaunchDarkly UI, OpenTelemetry data is used in the [Observability](/home/observability), [Experimentation](/home/experimentation), and [Guarded rollouts](/home/releases/guarded-rollouts) features.

OpenTelemetry metrics, logs, and traces are available in the [Observability](/home/observability) features.

OpenTelemetry traces generate LaunchDarkly metrics, which are available in the [Experimentation](/home/experimentation) and [Guarded rollouts](/home/releases/guarded-rollouts) features. Specifically, when LaunchDarkly receives OpenTelemetry trace data, it processes and converts this data into events that LaunchDarkly metrics track over time. When trace data contains spans that carry both metrics and feature flag evaluation events, LaunchDarkly correlates these and uses the result to power guarded rollouts. To learn more, read [Metrics autogenerated from OpenTelemetry data](/home/metrics/autogen/opentelemetry).

## Sending Open

Telemetry data to LaunchDarkly

To configure and send OpenTelemetry data to LaunchDarkly when you are using LaunchDarkly server-side SDKs:

1. (Optional) Add flag evaluation information to OpenTelemetry spans in your application. To learn how, read the section for your SDK under [Server-side SDKs](#server-side-sdks), below.
   * This step is optional but highly recommended, as it means that flag-specific details are available when you review your OTel data in the LaunchDarkly UI.
   * You might choose to skip this step, at least temporarily, if you have already instrumented OpenTelemetry in your application and only want to send your existing OTel data to LaunchDarkly.
2. Configure your application or OpenTelemetry collector to use LaunchDarkly's OTel endpoints:
   * For HTTP, use `https://otel.observability.app.launchdarkly.com:4318` or `https://otel.observability.app.launchdarkly.com:443`
   * For gRPC, use `https://otel.observability.app.launchdarkly.com:4317`.
   * You may need to update your infrastructure to make sure your application can reach this domain. To learn more, read [Accessing LaunchDarkly by domain](/sdk/concepts/domain-list#accessing-launchdarkly-by-domain).
3. (Optional) Include your LaunchDarkly SDK key as a resource attribute in your telemetry data.
   * This step is required if you are using [OpenTelemetry SDKs](https://opentelemetry.io/docs/languages/). To learn how, read [Setting resource attributes](#setting-resource-attributes), below.
   * This step is **not** required if you are using LaunchDarkly server-side SDKs. If you use LaunchDarkly SDKs, this is set automatically.
4. Send the OpenTelemetry data to LaunchDarkly. LaunchDarkly supports traces, metrics, and logs. Only traces data [generates LaunchDarkly metrics](/home/metrics/autogen/opentelemetry). You can view metrics and logs in the LaunchDarkly UI when you use the [Observability](/home/observability) features.
   * You can use the [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/) to pre-filter and aggregate this data. To learn how, read [Configuring the collector](#configuring-the-collector), below.
   * Alternatively, you can send data directly from your application using [OpenTelemetry SDKs](https://opentelemetry.io/docs/languages/).

### Setting resource attributes

<Accordion title="Expand Setting resource attributes">
  If you use LaunchDarkly SDKs, resource attributes are set automatically. You do not need set the resource attributes yourself, and you can skip this section.

  If you use only [OpenTelemetry SDKs](https://opentelemetry.io/docs/languages/), you must include your LaunchDarkly SDK key as a resource attribute in your telemetry data, so that the data is correctly associated with your LaunchDarkly project and environment. You have several options for how to set the resource attribute.

  To set the resource attribute using an environment variable, use the `OTEL_RESOURCE_ATTRIBUTES` environment variable:

  <CodeGroup>
    ```bash title="Configure resource attribute with environment variable" theme={null}
          export OTEL_RESOURCE_ATTRIBUTES="launchdarkly.project_id=$YOUR_SDK_KEY"
    ```
  </CodeGroup>

  To set the resource attribute in your OpenTelemetry collector configuration, use the `resource` processor:

  <CodeGroup>
    ```yaml title="Configure resource attribute with collector configuration" theme={null}
          # excerpt from otel-collector-config.yaml
          processors:
            resource:
              attributes:
                - key: launchdarkly.project_id
                  value: YOUR_SDK_KEY
                  action: upsert
    ```
  </CodeGroup>

  For a complete example of a collector configuration, read [Configuring the collector](#configuring-the-collector), below.

  To set the resource attribute programmatically in your application code, use the [OpenTelemetry resource](https://opentelemetry.io/docs/concepts/resources/) available for your language. Here are a few examples:

  <CodeGroup>
    ```go title="Go" theme={null}
          import (
              "go.opentelemetry.io/otel/sdk/resource"
              "go.opentelemetry.io/otel/sdk/trace"
          )

          res := resource.NewWithAttributes(
              semconv.SchemaURL,
              attribute.String("launchdarkly.project_id", "YOUR_SDK_KEY"),
          )

          tp := trace.NewTracerProvider(
              trace.WithResource(res),
          )
    ```

    ```java title="Java" theme={null}
          Resource resource = Resource.getDefault().toBuilder()
              .put("launchdarkly.project_id", "YOUR_SDK_KEY")
              .build();

          OpenTelemetry openTelemetry = OpenTelemetrySdk.builder()
              .setResource(resource)
              .buildAndRegisterGlobal();
    ```

    ```javascript title="Node.js (server-side)" theme={null}
          import { Resource } from '@opentelemetry/resources';
          import { NodeSDK } from '@opentelemetry/sdk-node';

          const sdk = new NodeSDK({
            resource: new Resource({
              'launchdarkly.project_id': 'YOUR_SDK_KEY',
            }),
          });

          sdk.start();
    ```

    ```python title="Python" theme={null}
          from opentelemetry.sdk.resources import Resource
          from opentelemetry.sdk.trace import TracerProvider

          resource = Resource.create({
              "launchdarkly.project_id": "YOUR_SDK_KEY"
          })

          provider = TracerProvider(resource=resource)
    ```
  </CodeGroup>
</Accordion>

### Configuring the collector

<Accordion title="Expand Configuring the collector">
  To configure a pipeline to send trace data to LaunchDarkly, modify the collector's config file, `otel-collector-config.yaml`, as follows:

  <CodeGroup>
    ```yaml title="HTTP example, multiple environments" theme={null}
          # The receivers specify how the collector receives data.
          # In this example, it receives data using the OpenTelemetry Protocol (OTLP) over gRPC and HTTP.
          receivers:
            otlp:
              protocols:
                grpc:
                http:

          # The exporters specify how the collector sends data.
          exporters:
            otlphttp:
              endpoint: https://otel.observability.app.launchdarkly.com:4318
            otlp:
              endpoint: https://otel.observability.app.launchdarkly.com:4317

          # The processors specify how the collector processes the trace data.
          # In this example, it groups spans that belong to the same trace,
          # and drops spans that aren't needed for guarded rollouts.
          processors:

            # This processor sets the resource attribute to your SDK key.
            # If you are using a LaunchDarkly SDK, you do NOT need to include this.
            # If you are using an OpenTelemetry SDK, you do need to set the
            # resource attributes. You can set them here, set them in your application,
            # or set them using an environment variable.
            resource:
              attributes:
                  - key: launchdarkly.project_id
                    value: YOUR_SDK_KEY
                    action: upsert

            # The groupbytrace processor groups all spans that belong to the same trace together.
            # This is required to ensure LaunchDarkly receives a complete trace in a single request.
            groupbytrace:
              wait_duration: 10s

            # This filter drops all span events except for exceptions and feature flag evaluations
            # that were part of an active guarded rollout.
            # It is optional, but useful to limit the amount of data you send to LaunchDarkly.
            filter/launchdarkly-spanevents:
              error_mode: ignore
              traces:
                spanevent:
                  - 'not ((name == "feature_flag" and attributes["feature_flag.result.reason.inExperiment"] == true) or name == "exception")'

            # This filter removes all spans that do not have an HTTP route or any span events
            # remaining after the previous filter has been applied
            filter/launchdarkly-spans:
              error_mode: ignore
              traces:
                span:
                  - 'not (attributes["http.route"] != nil or Len(events) > 0)'

            batch:

          extensions:
            health_check:

          service:
            pipelines:
              # Add a new pipeline to send data to LaunchDarkly
              traces/ld:
                receivers: [otlp]
                processors:
                  [
                    filter/launchdarkly-spanevents,
                    filter/launchdarkly-spans,
                    groupbytrace,
                    batch,
                  ]
                exporters: [otlphttp/launchdarkly]
              metrics:
                receivers: [otlp]
                processors: [resource]
                exporters: [otlphttp]
              logs:
                receivers: [otlp]
                processors: [resource]
                exporters: [otlphttp]
    ```

    ```yaml title="gRPC example, multiple environments" theme={null}
          # The receivers specify how the Collector receives data.
          # In this example, it receives data using the OpenTelemetry Protocol (OTLP) over gRPC and HTTP.
          receivers:
            otlp:
              protocols:
                grpc:
                http:

          # The exporters specify how the Collector sends data.
          # In this example, it sends data to LaunchDarkly using gRPC.
          exporters:
            otlp/jaeger:
              endpoint: jaeger-all-in-one:4317
              tls:
                insecure: true
            otlp/launchdarkly:
              endpoint: https://otel.observability.app.launchdarkly.com:4318
            otlp:
              endpoint: https://otel.observability.app.launchdarkly.com:4317

          # The processors specify how the Collector processes the trace data.
          # In this example, it groups spans that belong to the same trace,
          # and drops spans that aren't needed for guarded rollouts.
          processors:
            # This processor sets the resource attribute to your SDK key.
            # If you are using a LaunchDarkly SDK, you do NOT need to include this.
            # If you are using an OpenTelemetry SDK, you do need to set the
            # resource attributes. You can set them here, set them in your application,
            # or set them using an environment variable.
            resource:
              attributes:
                  - key: launchdarkly.project_id
                    value: YOUR_SDK_KEY
                    action: upsert

            # The groupbytrace processor groups all spans that belong to the same trace together.
            # This is required to ensure LaunchDarkly receives a complete trace in a single request.
            groupbytrace:
              wait_duration: 10s

            # This filter drops all span events except for exceptions and feature flag evaluations
            # that were part of an active guarded rollout.
            # It is optional, but useful to limit the amount of data you send to LaunchDarkly.
            filter/launchdarkly-spanevents:
              error_mode: ignore
              traces:
                spanevent:
                  - 'not ((name == "feature_flag" and attributes["feature_flag.result.reason.inExperiment"] == true) or name == "exception")'

            # Remove all spans that do not have an HTTP route or any span events remaining
            # after the previous filter has been applied
            filter/launchdarkly-spans:
              error_mode: ignore
              traces:
                span:
                  - 'not (attributes["http.route"] != nil or Len(events) > 0)'

            batch:

          extensions:
            health_check:

          service:
            pipelines:
              # Add a new pipeline to send data to LaunchDarkly
              traces/ld:
                receivers: [otlp]
                processors:
                  [
                    filter/launchdarkly-spanevents,
                    filter/launchdarkly-spans,
                    groupbytrace,
                    batch,
                  ]
                exporters: [otlphttp/launchdarkly]
              metrics:
                receivers: [otlp]
                processors: [resource]
                exporters: [otlphttp]
              logs:
                receivers: [otlp]
                processors: [resource]
                exporters: [otlphttp]
    ```

    ```yaml title="HTTP example, one environment" theme={null}
          # The receivers specify how the Collector receives data.
          # In this example, it receives data using the OpenTelemetry Protocol (OTLP) over gRPC and HTTP.
          receivers:
            otlp:
              protocols:
                grpc:
                http:

          # The exporters specify how the Collector sends data.
          # In this example, it sends data to LaunchDarkly using HTTP.
          exporters:
            otlphttp:
              endpoint: https://otel.observability.app.launchdarkly.com:4318
            otlp:
              endpoint: https://otel.observability.app.launchdarkly.com:4317

          # The processors specify how the Collector processes the trace data.
          # In this example, it groups spans that belong to the same trace,
          # and drops spans that aren't needed for guarded rollouts.
          processors:

            # This processor sets the resource attribute to your SDK key.
            # If you are using a LaunchDarkly SDK, you do NOT need to include this.
            # If you are using an OpenTelemetry SDK, you do need to set the
            # resource attributes. You can set them here, set them in your application,
            # or set them using an environment variable.
            resource:
              attributes:
                  - key: launchdarkly.project_id
                    value: YOUR_SDK_KEY
                    action: upsert

            # The groupbytrace processor groups all spans that belong to the same trace together.
            # This is required to ensure LaunchDarkly receives a complete trace in a single request.
            groupbytrace:
              wait_duration: 10s

            # This filter drops all span events except for exceptions and feature flag evaluations
            # that were part of an active guarded rollout.
            # It is optional, but useful to limit the amount of data you send to LaunchDarkly.
            filter/launchdarkly-spanevents:
              error_mode: ignore
              traces:
                spanevent:
                  - 'not ((name == "feature_flag" and attributes["feature_flag.result.reason.inExperiment"] == true) or name == "exception")'

            # Remove all spans that do not have an HTTP route or any span events remaining
            # after the previous filter has been applied
            filter/launchdarkly-spans:
              error_mode: ignore
              traces:
                span:
                  - 'not (attributes["http.route"] != nil or Len(events) > 0)'

            batch:

          extensions:
            health_check:

          service:
            pipelines:
              # Add a new pipeline to send data to LaunchDarkly
              traces/ld:
                receivers: [otlp]
                processors:
                  [
                    filter/launchdarkly-spanevents,
                    filter/launchdarkly-spans,
                    groupbytrace,
                    batch,
                  ]
                exporters: [otlphttp/launchdarkly]
              metrics:
                receivers: [otlp]
                processors: [resource]
                exporters: [otlphttp]
              logs:
                receivers: [otlp]
                processors: [resource]
                exporters: [otlphttp]
    ```

    ```yaml title="gRPC example, one environment" theme={null}
          # The receivers specify how the Collector receives data.
          # In this example, it receives data using the OpenTelemetry Protocol (OTLP) over gRPC and HTTP.
          receivers:
            otlp:
              protocols:
                grpc:
                http:

          # The exporters specify how the Collector sends data.
          # In this example, it sends data to LaunchDarkly using gRPC.
          exporters:
            otlp/jaeger:
              endpoint: jaeger-all-in-one:4317
              tls:
                insecure: true

            otlphttp:
              endpoint: https://otel.observability.app.launchdarkly.com:4318
            otlp:
              endpoint: https://otel.observability.app.launchdarkly.com:4317

          # The processors specify how the Collector processes the trace data.
          # In this example, it groups spans that belong to the same trace,
          # and drops spans that aren't needed for guarded rollouts.
          processors:
            # This processor sets the resource attribute to your SDK key.
            # If you are using a LaunchDarkly SDK, you do NOT need to include this.
            # If you are using an OpenTelemetry SDK, you do need to set the
            # resource attributes. You can set them here, set them in your application,
            # or set them using an environment variable.
            resource:
              attributes:
                  - key: launchdarkly.project_id
                    value: YOUR_SDK_KEY
                    action: upsert

            # The groupbytrace processor groups all spans that belong to the same trace together.
            # This is required to ensure LaunchDarkly receives a complete trace in a single request.
            groupbytrace:
              wait_duration: 10s

            # This filter drops all span events except for exceptions and feature flag evaluations
            # that were part of an active guarded rollout.
            # It is optional, but useful to limit the amount of data you send to LaunchDarkly.
            filter/launchdarkly-spanevents:
              error_mode: ignore
              traces:
                spanevent:
                  - 'not ((name == "feature_flag" and attributes["feature_flag.result.reason.inExperiment"] == true) or name == "exception")'

            # Remove all spans that do not have an HTTP route or any span events remaining
            # after the previous filter has been applied
            filter/launchdarkly-spans:
              error_mode: ignore
              traces:
                span:
                  - 'not (attributes["http.route"] != nil or Len(events) > 0)'

            batch:

          extensions:
            health_check:

          service:
            pipelines:
              # Add a new pipeline to send data to LaunchDarkly
              traces/ld:
                receivers: [otlp]
                processors:
                  [
                    filter/launchdarkly-spanevents,
                    filter/launchdarkly-spans,
                    groupbytrace,
                    batch,
                  ]
                exporters: [otlphttp/launchdarkly]
              metrics:
                receivers: [otlp]
                processors: [resource]
                exporters: [otlphttp]
              logs:
                receivers: [otlp]
                processors: [resource]
                exporters: [otlphttp]
    ```
  </CodeGroup>
</Accordion>

## Automatic attribute normalization

[OpenTelemetry semantic conventions](https://opentelemetry.io/docs/specs/semconv/) define standard attribute names for common concepts like HTTP requests, database operations, and messaging systems. These conventions evolve over time, and attribute names are occasionally renamed or restructured. For example, `http.method` was renamed to `http.request.method` and `http.status_code` was renamed to `http.response.status_code`. For more details on how attribute names change across OpenTelemetry versions, see the [semantic conventions changelog](https://github.com/open-telemetry/semantic-conventions/blob/main/CHANGELOG.md).

LaunchDarkly automatically normalizes deprecated OpenTelemetry semantic convention attributes to their current equivalents when it ingests your telemetry data. This means you can send data using older attribute names, and LaunchDarkly will create the corresponding current attribute names for you. This normalization applies to all OpenTelemetry data LaunchDarkly ingests, whether from LaunchDarkly SDKs with tracing hooks enabled or from external OTLP endpoints.

This automatic normalization provides several benefits. LaunchDarkly ensures consistent attribute names across your telemetry data, even when services use different OpenTelemetry versions. You can search and filter using the current attribute names regardless of which version your services emit. You can also upgrade your instrumentation gradually without losing the ability to correlate data across services.

When viewing trace attributes in LaunchDarkly, the UI displays indicators showing which attributes have been remapped from deprecated conventions.

## Server-side SDKs

LaunchDarkly processes metrics, logs, and traces from your OpenTelemetry data:

* LaunchDarkly processes OpenTelemetry metrics and logs and displays them in the LaunchDarkly UI under [Observability](/home/observability)
* LaunchDarkly processes two types of data from OpenTelemetry traces:
  * HTTP span attributes, including latency, 5xx occurrences, and other errors, where the span has or overlaps with another span that has at least one feature flag span event. This includes nested spans.
  * Exception span events that occur after a feature flag span event on the same trace. If the exception occurs before the feature flag event, LaunchDarkly does not capture it.

A feature flag [span event](https://opentelemetry.io/docs/concepts/signals/traces/#span-events) is defined as any span event that contains a `feature_flag.context.key` attribute. LaunchDarkly ignores traces that do not include span events with this attribute.

In most server-side SDKs, the OTel traces are specific to the LaunchDarkly project and environment that you specify in the [collector configuration](#configuring-the-collector), described above. In the [.NET (server-side)](#net-server-side) and [Node.js (server-side)](#nodejs-server-side) SDKs, you can provide the LaunchDarkly client-side ID in the tracing hook. This enables you to send traces for multiple projects and environments using one collector.

The following sections describe, for each supported SDK, how to ensure your spans have compatible feature flag events.

<Note title="Sending feature flag event data requires feature flag evaluation">
  The OpenTelemetry tracing hook automatically attaches feature flag event data to your OTel traces for you. Feature flag event data, including the `feature_flag.context.key` attribute, is only generated when your application uses the LaunchDarkly SDK to [evaluate a feature flag](/sdk/features/evaluating).
</Note>

This feature is available in the following SDKs:

* [.NET (server-side)](#net-server-side)
* [Go](#go)
* [Java](#java)
* [Node.js (server-side)](#nodejs-server-side)
* [Python](#python)
* [Ruby](#ruby)

### .NET (server-side)

<Accordion title="Expand .NET (server-side) code sample">
  To add flag evaluation information to OpenTelemetry spans, first add the Telemetry package from NuGet:

  <CodeGroup>
    ```bash title="Import Telemetry package" theme={null}
          dotnet add package LaunchDarkly.ServerSdk.Telemetry
    ```
  </CodeGroup>

  Next, import the `LaunchDarkly.Sdk.Server.Hooks` and `LaunchDarkly.Sdk.Server.Telemetry` namespaces. Then you can create a new tracing hook and reference it in the configuration options when you initialize the SDK client.

  Here's how:

  <CodeGroup>
    ```csharp title=".NET (server-side), v8.4+" theme={null}
          using LaunchDarkly.Sdk.Server.Hooks;
          using LaunchDarkly.Sdk.Server.Telemetry;

          var config = Configuration.Builder("YOUR_SDK_KEY")
            .Hooks(Components.Hooks()
              .Add(TracingHook.Default())
            ).Build();

          var client = new LdClient(config);
    ```
  </CodeGroup>

  When this configuration is set, the SDK adds span events for each call to a `*Variation*` method to your active span. The span event information contains details of each flag evaluation, including the flag key and the context key. You can collect this information in [Guarded rollouts](/home/releases/guarded-rollouts) or other observability tools.

  Optionally, you can configure the tracing hook to:

  * create new spans that contain span events with flag evaluation information, rather than adding the events to the active span. The new spans are nested in the current active span, and are not active.
  * include the flag's evaluated flag value in the span event.

  Here's how:

  <CodeGroup>
    ```csharp title=".NET (server-side), v8.4+" theme={null}
          using LaunchDarkly.Sdk.Server.Hooks;
          using LaunchDarkly.Sdk.Server.OpenTelemetry;

          var config = Configuration.Builder("YOUR_SDK_KEY")
            .Hooks(Components.Hooks()
              .Add(TracingHook.Builder()
                .CreateActivities()
                .IncludeValue()
                .Build()
              )
            ).Build();

          var client = new LdClient(config);
    ```
  </CodeGroup>

  Starting with v1.1 of the `LaunchDarkly.Sdk.Server.Hooks` package, which requires v8.7+ of the .NET (server-side) SDK, LaunchDarkly automatically decorates any OpenTelemetry spans with project and environment information.

  When you create the hook, you can optionally pass in a client-side ID to specify the environment. Use the builder's `.EnvironmentId()` method and pass in the client-side ID. To learn more about client-side IDs, read [Keys](/sdk/concepts/client-side-server-side#keys).

  Because LaunchDarkly automatically decorates the OpenTelemetry spans, you should not need to call this method explicitly. The exception is if you are using [persistent stores](/sdk/features/storing-data), in which case you must either call `.EnvironmentId()` or use the single-environment [OpenTelemetry Collector configuration](#configuring-the-collector) to ensure that your OpenTelemetry spans have correct project and environment information.

  Here's how:

  <CodeGroup>
    ```csharp title=".NET (server-side), v8.7+" theme={null}
          using LaunchDarkly.Sdk.Server.Hooks; // v1.1 or later
          using LaunchDarkly.Sdk.Server.OpenTelemetry;

          var config = Configuration.Builder("YOUR_SDK_KEY")
            .Hooks(Components.Hooks()
              .Add(TracingHook.Builder()
                .EnvironmentId("example-client-side-id")
              )
            ).Build();

          var client = new LdClient(config);
    ```
  </CodeGroup>

  In the .NET (server-side) SDK, the tracing hook is implemented using Microsoft's [System.Diagnostics](https://learn.microsoft.com/en-us/dotnet/core/diagnostics/distributed-tracing-instrumentation-walkthroughs) APIs. Therefore, some of the terminology it uses differs from the OpenTelemetry standard. For example, the tracing hook in the .NET (server-side) SDK uses the method `CreateActivities()`, rather than `CreateSpans()`, to enable child spans.

  To learn more, read [`TracingHookBuilder`](https://launchdarkly.github.io/dotnet-core/pkgs/telemetry/api/LaunchDarkly.Sdk.Server.Telemetry.TracingHookBuilder.html) and [`ConfigurationBuilder`](https://launchdarkly.github.io/dotnet-core/pkgs/sdk/server/api/LaunchDarkly.Sdk.Server.ConfigurationBuilder.html).
</Accordion>

### Go

<Accordion title="Expand Go code sample">
  To add flag evaluation information to OpenTelemetry spans, import the `ldhooks` package. Then, create a new tracing hook and reference it in the configuration options when you initialize the SDK client.

  Here's how:

  <CodeGroup>
    ```go title="Go SDK, v7.4+" theme={null}
          import (
            ld "github.com/launchdarkly/go-server-sdk/v7"
            "github.com/launchdarkly/go-server-sdk/v7/ldhooks"
            "github.com/launchdarkly/go-server-sdk/ldotel"
          )

          var config ld.Config

          client, _ = ld.MakeCustomClient("YOUR_SDK_KEY",
            ld.Config{
              Hooks: []ldhooks.Hook{ldotel.NewTracingHook()},
            }, 5*time.Second)

    ```
  </CodeGroup>

  When this configuration is set, the SDK adds span events for each call to a `*Variation*Ctx` method to your active span.

  The span event information contains details of each flag evaluation, including the flag key and the context key. You can collect this information in [Guarded rollouts](/home/releases/guarded-rollouts) or other observability tools.

  The `*Variation*Ctx` methods are the same as the `*Variation*` methods, except that they also require a Go context. For example, `BoolVariationCtx` and `BoolVariationDetailCtx` are the same as the `BoolVariation` and `BoolVariationDetail` methods, except that they also require a Go context. This Go context is used in the hook implementation. If you are using hooks, you must update your variation calls to use the `Ctx` methods. If you are working with the [`LDScopedClient`](https://pkg.go.dev/github.com/launchdarkly/go-server-sdk/v7#LDScopedClient), it also includes `*Variation*Ctx` methods. These require a Go context and, like other `LDScopedClient` methods, do not require a LaunchDarkly context.

  <Warning title="LDScopedClient is in beta">
    `LDScopedClient` is in beta. It is still undergoing testing and active development. Its functionality may change without notice, including becoming backwards incompatible.
  </Warning>

  Optionally, you can configure the tracing hook to:

  * create new spans that contain span events with flag evaluation information, rather than adding the events to the active span. The new spans are nested in the current active span, and are not active.
  * include the flag's evaluated flag value in the span event.

  Here's how:

  <CodeGroup>
    ```go title="Go SDK, v7.4+" theme={null}
          import (
            ld "github.com/launchdarkly/go-server-sdk/v7"
            "github.com/launchdarkly/go-server-sdk/v7/ldhooks"
            "github.com/launchdarkly/go-server-sdk/ldotel"
          )

          var config ld.Config

          client, _ = ld.MakeCustomClient("YOUR_SDK_KEY",
            ld.Config{
              Hooks: []ldhooks.Hook{ldotel.NewTracingHook(ldotel.WithSpans(), ldotel.WithValue())},
            }, 5*time.Second)
    ```
  </CodeGroup>

  To learn more, read [`Config`](https://pkg.go.dev/github.com/launchdarkly/go-server-sdk/v7#Config).
</Accordion>

### Java

<Accordion title="Expand Java code sample">
  To add flag evaluation information to OpenTelemetry spans, import the LaunchDarkly Java Otel Hook package. Then, create a new tracing hook and reference it in the configuration options when you initialize the SDK client.

  Here's how:

  <CodeGroup>
    ```java title="Java SDK, v7.4+" theme={null}
          import com.launchdarkly.sdk.*;
          import com.launchdarkly.sdk.server.*;
          import com.launchdarkly.integrations.*;

          TracingHook tracingHook = new TracingHook.Builder().build();

          LDConfig config = new LDConfig.Builder()
              .hooks(
                  Components.hooks().setHooks(Collections.singletonList(tracingHook)))
              .build();

          LDClient client = new LDClient("YOUR_SDK_KEY", config);

    ```
  </CodeGroup>

  When this configuration is set, the SDK adds span events for each call to a `*Variation*` method to your active span. The span event information contains details of each flag evaluation, including the flag key and the context key. You can collect this information in [Guarded rollouts](/home/releases/guarded-rollouts) or other observability tools.

  Optionally, you can configure the tracing hook to:

  * create new spans that contain span events with flag evaluation information, rather than adding the events to the active span. The new spans are nested in the current active span, and are not active.
  * include the flag's evaluated flag value in the span event.

  Here's how:

  <CodeGroup>
    ```java title="Java SDK, v7.4+" theme={null}
          import com.launchdarkly.sdk.*;
          import com.launchdarkly.sdk.server.*;
          import com.launchdarkly.integrations.*;

          TracingHook tracingHook = new TracingHook.Builder().withSpans().withValue().build();

          LDConfig config = new LDConfig.Builder()
              .hooks(
                  Components.hooks().setHooks(Collections.singletonList(tracingHook)))
              .build();

          LDClient client = new LDClient("YOUR_SDK_KEY", config);

    ```
  </CodeGroup>

  To learn more, read [`LDConfig.Builder`](https://launchdarkly.github.io/java-core/lib/sdk/server/com/launchdarkly/sdk/server/LDConfig.Builder.html).
</Accordion>

### Node.js (server-side)

<Accordion title="Expand Node.js (server-side) code sample">
  To add flag evaluation information to OpenTelemetry spans, import the `TracingHook` package. Then, create a new tracing hook and reference it in the configuration options when you initialize the SDK client.

  Here's how:

  <CodeGroup>
    ```ts title="Node.js (server-side) SDK, v9.3+" theme={null}
          import { LDOptions, init } from '@launchdarkly/node-server-sdk';
          import { TracingHook } from '@launchdarkly/node-server-sdk-otel';

          const options: LDOptions = {
            hooks: [new TracingHook()]
          }

          const client = init('YOUR_SDK_KEY', options);

    ```
  </CodeGroup>

  When this configuration is set, the SDK adds span events for each call to a `*Variation*` method to your active span. The span event information contains details of each flag evaluation, including the flag key and the context key. You can collect this information in [Guarded rollouts](/home/releases/guarded-rollouts) or other observability tools.

  Optionally, you can configure the tracing hook to:

  * create new spans that contain span events with flag evaluation information, rather than adding the events to the active span. The new spans are nested in the current active span, and are not active.
  * include the flag's evaluated flag value in the span event.

  Here's how:

  <CodeGroup>
    ```ts title="Node.js (server-side) SDK, v9.3+" theme={null}
          import { LDOptions, init } from '@launchdarkly/node-server-sdk';
          import { TracingHook } from '@launchdarkly/node-server-sdk-otel';

          const options: LDOptions = {
            hooks: [new TracingHook({spans: true, includeValue: true})]
          }

          const client = init('YOUR_SDK_KEY', options);
    ```
  </CodeGroup>

  Starting with v1.2 of the `@LaunchDarkly/node-server-sdk-otel` package, which requires v9.9+ of the Node.js (server-side) SDK, LaunchDarkly automatically decorates any OpenTelemetry spans with project and environment information.

  When you create the hook, you can optionally pass in a client-side ID to specify the environment. Use the `environmentId` option when you create the new `TracingHook`. To learn more about client-side IDs, read [Keys](/sdk/concepts/client-side-server-side#keys).

  Because LaunchDarkly automatically decorates the OpenTelemetry spans, you should not need to set this option explicitly. The exception is if you are using [persistent stores](/sdk/features/storing-data), in which case you must either set the `environmentId` or use the single-environment [OpenTelemetry Collector configuration](#configuring-the-collector) to ensure that your OpenTelemetry spans have correct project and environment information.

  Here's how:

  <CodeGroup>
    ```ts title="Node.js (server-side) SDK, v9.9+" theme={null}
          import { LDOptions, init } from '@launchdarkly/node-server-sdk';
          import { TracingHook } from '@launchdarkly/node-server-sdk-otel'; // v1.2 or later

          const options: LDOptions = {
            hooks: [new TracingHook({
              spans: true,
              includeValue: true,
              environmentId: 'example-client-side-id'
            })]
          }

          const client = init('YOUR_SDK_KEY', options);
    ```
  </CodeGroup>

  To learn more, read [`LDOptions`](https://launchdarkly.github.io/js-core/packages/sdk/server-node/docs/interfaces/LDOptions.html).
</Accordion>

### Python

<Accordion title="Expand Python code sample">
  To add flag evaluation information to OpenTelemetry spans, first install the package:

  <CodeGroup>
    ```bash title="Install OTEL library" theme={null}
          pip install launchdarkly-server-sdk-otel
    ```
  </CodeGroup>

  Next, import `Hook` and `HookOptions` from the package. Then you can create a new tracing hook and reference it in the configuration options when you initialize the SDK client.

  Here's how:

  <CodeGroup>
    ```python title="Python, v9.4+" theme={null}
          import ldclient
          from ldclient import Config
          from ldotel.tracing import Hook, HookOptions

          config = Config(sdk_key, hooks=[Hook()])
          ldclient.set_config(config=config)
          client = ldclient.get()
    ```
  </CodeGroup>

  When this configuration is set, the SDK adds span events for each call to a `*variation*` method to your active span. The span event information contains details of each flag evaluation, including the flag key and the context key. You can collect this information in [Guarded rollouts](/home/releases/guarded-rollouts) or other observability tools.

  Optionally, you can configure the tracing hook to:

  * create new spans. The new spans are nested in the current active span, and are not active. The span events with flag evaluation information are still added to the parent span.
  * include the flag's evaluated flag value in the span event.

  Here's how:

  <CodeGroup>
    ```python title="Python, v9.4+" theme={null}
          import ldclient
          from ldclient.config import Config

          from ldotel.tracing import Hook, HookOptions

          config = Config(sdk_key, hooks=[Hook(HookOptions(add_spans=True, include_value=True))])
          ldclient.set_config(config=config)
          client = ldclient.get()
    ```
  </CodeGroup>

  To learn more, read [`Config`](https://launchdarkly-python-sdk.readthedocs.io/en/latest/api-main.html#module-ldclient.config).
</Accordion>

### Ruby

<Accordion title="Expand Ruby code sample">
  To add flag evaluation information to OpenTelemetry spans, first install the gem:

  <CodeGroup>
    ```bash title="Install OTEL library" theme={null}
          gem install launchdarkly-server-sdk-otel
    ```
  </CodeGroup>

  Then, you can create a new tracing hook and reference it in the configuration options when you initialize the SDK client.

  Here's how:

  <CodeGroup>
    ```ruby title="Ruby, v8.4+" theme={null}
          require 'ldclient-otel'

          config = LaunchDarkly::Config.new(hooks: [LaunchDarkly::Otel::TracingHook.new])

          client = LaunchDarkly::LDClient.new("YOUR_SDK_KEY", config);

    ```
  </CodeGroup>

  When this configuration is set, the SDK adds span events for each call to a `*Variation*` method to your active span. The span event information contains details of each flag evaluation, including the flag key and the context key. You can collect this information in [Guarded rollouts](/home/releases/guarded-rollouts) or other observability tools.

  Optionally, you can configure the tracing hook to:

  * create new spans. The new spans are nested in the current active span, and are not active. The span events with flag evaluation information are still added to the parent span.
  * include the flag's evaluated flag value in the span event.

  Here's how:

  <CodeGroup>
    ```ruby title="Ruby, v8.4+" theme={null}
          require 'ldclient-otel'

          tracing_hook_options = LaunchDarkly::Otel::TracingHookOptions.new(add_spans: true, include_value: true)
          hook = LaunchDarkly::Otel::TracingHook.new(tracing_hook_options)
          config = LaunchDarkly::Config.new(hooks: [hook])

          client = LaunchDarkly::LDClient.new("YOUR_SDK_KEY", config);

    ```
  </CodeGroup>

  To learn more, read [`Config`](https://launchdarkly.github.io/ruby-server-sdk/LaunchDarkly/Config).
</Accordion>
