Sending error events from observability tools

This topic explains how to forward errors from your observability or error monitoring tool to LaunchDarkly as custom metric events. After your errors arrive in LaunchDarkly, you can build metrics from them and attach those metrics to guarded rollouts or experiments. This helps you determine whether a new feature increases the number of generated errors, and enables you to automatically roll back a release in response to a regression.

The approach in this topic works with any tool that lets you run code when it captures an error. This includes tools like Sentry, Datadog, Rollbar, Bugsnag, or your own error handler. It works on every LaunchDarkly plan, in every LaunchDarkly instance, and with every LaunchDarkly SDK that supports the track feature.

If you use Sentry, read Example: forwarding Sentry errors for complete code samples.

Concepts

Forwarding errors as metric events uses two LaunchDarkly building blocks:

  • Custom events. Your SDK sends a custom event with an event key you choose. You choose when to send the event, and you attach your own data to it. To learn more, read Tracking custom events.
  • Custom metrics. A LaunchDarkly metric with the same event key aggregates those events. A metric with a success criterion of “lower is better” treats a rise in errors as a regression. To learn more, read Components of a metric.

You do not need an integration, a webhook, or the events API to do this.

Choose an approach

LaunchDarkly offers several ways to get error data into a metric. Use the following table to choose the approach that fits your situation:

ApproachWhen to use itAvailability
Forwarding errors with the track feature, as described in this topicYou already capture errors in a third-party tool and want to reuse them in LaunchDarkly. You want control over the event payload, or you need this to work in an instance or on a plan where the prebuilt integration is not available.All plans, all instances
Sentry integrationYou use Sentry and prefer a configuration-only setup that does not require changing your application code.Select plans. Not available in the federal or EU instances.
LaunchDarkly error monitoringYou want LaunchDarkly to capture the errors. The observability SDKs report errors, and LaunchDarkly autogenerates metrics from them.Select plans
Importing eventsYour errors are already in a data warehouse or pipeline, or you need to backfill historical events.Select plans
You can combine approaches

Nothing prevents you from using more than one of these approaches. For example, you can capture errors with LaunchDarkly error monitoring and also forward a subset of high-severity errors from Sentry under your own event key.

Prerequisites

You need the following prerequisites to forward error events to LaunchDarkly:

  • A LaunchDarkly SDK, initialized in the application where the errors occur. To learn which SDKs support the track feature, read Tracking custom events.
  • An observability tool that lets you run your own code when it captures an error, or a place in your application where you already handle errors.
  • Permission to create metrics in your LaunchDarkly project.

Forward errors to LaunchDarkly

To use your errors as a LaunchDarkly metric, complete the following steps:

  1. Choose an event key
  2. Call track from your error handler
  3. Flush the event buffer
  4. Create a metric
  5. Attach the metric to a rollout or experiment

Choose an event key

The event key is a string you choose. LaunchDarkly groups events by this key, and the metric you create later must use the same key.

Use one stable, low-cardinality key for all of your errors, such as application-error. Put the details of each error in the event’s data object instead of in the key.

Do not put error details in the event key

If you build the event key from the error message, error code, or stack trace, you create a new event key for every distinct error. This produces a large number of keys that you cannot aggregate, and you would need a separate metric for each one. Keep the key constant and use metric filters to create multiple metrics per error type.

Event keys that begin with $ld:telemetry are reserved for LaunchDarkly observability features. Do not send events with a reserved key. To learn more, read Observability metrics.

Event keys and metric keys are different

Sending custom events to LaunchDarkly requires a unique event key. You can set the event key to anything you want. Adding this event key to your codebase lets your SDK track actions customers take in your app as events. To learn more, read Tracking custom events.

LaunchDarkly also automatically generates a metric key when you create a metric. You only use the metric key to identify the metric in API calls. To learn more, read Creating and managing metrics.

Call track from your error handler

Call the track feature in your SDK from the place where your observability tool hands you the error. Attach a flat object of details as the event data.

Here is an example track call in several client-side SDKs:

1ldClient.track('application-error', {
2 source: 'sentry',
3 platform: 'browser',
4 error_type: 'TypeError',
5 screen: 'checkout',
6});

In client-side SDKs, track uses whichever context the client has identified. In server-side SDKs, you must pass the context in the track call:

1client.track('application-error', context, {
2 source: 'sentry',
3 platform: 'node',
4 error_type: 'TypeError',
5});
Track from the context that evaluated the flag

LaunchDarkly can only attribute an event to a flag variation if the event comes from the same context that evaluated the flag. In client-side SDKs, this means calling track after you call identify. In server-side SDKs, it means passing the same context you passed to the flag evaluation.

Error handlers in server-side applications often run outside the scope of the request that caused the error, where the context is no longer available. If this is the case in your application, store the evaluation context in request-scoped storage, such as Python’s contextvars or Node.js’s AsyncLocalStorage, then read it back in your error handler.

To learn about the exact signature in your SDK, read Tracking custom events.

Flush the event buffer

SDKs buffer events and send them on an interval, which is 30 seconds by default in many SDKs. If your application crashes, is killed, or is backgrounded after an error, you lose any events still in the buffer. Because an error is often the last thing that happens before an application stops, call flush immediately after you track an error:

1ldClient.flush();

Flushing is a request to send queued events as soon as possible. It is not a guarantee that delivery completes before your application stops. To learn more, read Flushing events.

Create a metric

Create a metric that uses the event key you chose. The metric kind determines what question the metric answers:

For a metric that measures the percentage of end users who experienced an error, select the following options:

  • Event kind: Custom
  • Event key: application-error
  • Metric definition: Occurrence (Percent)

Use the metric definition: Percentage of User units that sent the event where Lower is better

Set the success criterion to "Lower is better"

Success criteria tell LaunchDarkly which direction indicates an improvement. If you leave an error metric set to “Higher is better,” LaunchDarkly reads a rise in errors as a successful release rather than a regression, and a guarded rollout will not roll back.

The metric’s analysis unit must include the randomization unit of the release you attach it to. For example, if a guarded rollout randomizes by user, an error metric analyzed by user works, but one analyzed only by account does not. To learn more, read Analysis units.

To use one event key for several metrics, add metric filters for the properties you sent in the event data. For example, you can define one metric for events where error_type is TypeError and another where platform is ios.

Metric filters do not read nested data

Filters only match top-level string, number, and boolean values in the event’s data object. LaunchDarkly ignores nested objects and arrays when it applies filters. Keep your event data flat.

Attach the metric to a rollout or experiment

Attach the metric to a guarded rollout as one of the metrics it monitors, or add it to an experiment. During a guarded rollout, LaunchDarkly compares the error rate of the contexts receiving the new variation against the contexts receiving the old one. If you enable auto-rollback, LaunchDarkly reverts the release when it detects a regression.

You can also add the metric to a release policy as a release guardrail metric, so that LaunchDarkly attaches it automatically to every guarded release where the policy applies.

Best practices

Follow these practices to keep your error events useful:

  • Keep the event key stable and low cardinality. Use one key for all errors and put the details in the event data.
  • Keep the event data flat. Metric filters ignore nested objects and arrays.
  • Truncate free-text fields. Error messages and stack traces can be very long. Truncate messages to a few hundred characters before you attach them.
  • Track the occurrence, not the render. If an error state causes a component or view to re-render, guard your tracking so you send one event per actual failure rather than one per render.
  • Forward only what you want to measure. Most tools capture warnings, informational messages, and performance data alongside errors. Filter to error-level events so your metric measures only errors.
  • Flush after every user-visible error. To learn more, read Flush the event buffer.

Example: forwarding Sentry errors

Sentry calls the beforeSend hook once for each error event, just before it sends the event to Sentry. This makes it a good place to forward the error to LaunchDarkly. To learn more, read Sentry’s filtering documentation for JavaScript, Apple, or Android.

These examples assume the Sentry SDK and the LaunchDarkly SDK are both already initialized in your application.

Return the event from beforeSend

beforeSend controls whether Sentry sends the event. Return the event to let Sentry continue processing it as usual. If you return null, Sentry discards the error and it will not appear in Sentry.

Browser JavaScript

JavaScript
1Sentry.init({
2 dsn: 'YOUR_SENTRY_DSN',
3
4 beforeSend: (event) => {
5 const isError =
6 (event.exception?.values?.length ?? 0) > 0 ||
7 event.level === 'error' ||
8 event.level === 'fatal';
9
10 // The LaunchDarkly client may not exist yet if the error happens during startup.
11 if (isError && ldClient) {
12 const exception = event.exception?.values?.[0];
13 const originalMessage = exception?.value ?? event.message ?? 'No error message';
14
15 ldClient.track('application-error', {
16 source: 'sentry',
17 platform: 'browser',
18 sentry_event_id: event.event_id ?? '',
19 error_type: exception?.type ?? 'UnknownError',
20 message: originalMessage.slice(0, 500),
21 level: event.level ?? '',
22 release: event.release ?? '',
23 environment: event.environment ?? '',
24 transaction: event.transaction ?? '',
25 });
26
27 // Immediately request sending all queued LaunchDarkly events.
28 ldClient.flush();
29 }
30
31 // Continue sending the event to Sentry.
32 return event;
33 },
34});

Sentry calls beforeSend only for error events. It calls beforeSendTransaction for performance data, so you do not need to filter transactions out yourself.

iOS

Swift
1SentrySDK.start { options in
2 options.dsn = "YOUR_SENTRY_DSN"
3
4 options.beforeSend = { event in
5 let isError =
6 !(event.exceptions?.isEmpty ?? true) ||
7 event.level == .error ||
8 event.level == .fatal
9
10 if isError, let client = LDClient.get() {
11 let exception = event.exceptions?.last
12
13 let errorType = exception?.type ?? "UnknownError"
14
15 let originalMessage =
16 exception?.value ??
17 event.message?.formatted ??
18 "No error message"
19
20 let message = String(originalMessage.prefix(500))
21
22 let data: LDValue = [
23 "source": "sentry",
24 "platform": "ios",
25 "sentry_event_id": event.eventId.sentryIdString,
26 "error_type": errorType,
27 "message": message,
28 "level": String(describing: event.level),
29 "release": event.releaseName ?? "",
30 "environment": event.environment ?? "",
31 "transaction": event.transaction ?? ""
32 ]
33
34 try? client.track(key: "application-error", data: data)
35
36 // Immediately request sending all queued LaunchDarkly events.
37 client.flush()
38 }
39
40 // Continue sending the event to Sentry.
41 return event
42 }
43}

Android

Kotlin
1SentryAndroid.init(context) { options ->
2 options.dsn = "YOUR_SENTRY_DSN"
3
4 options.setBeforeSend { event, _ ->
5 val isError =
6 event.exceptions?.isNotEmpty() == true ||
7 event.level == SentryLevel.ERROR ||
8 event.level == SentryLevel.FATAL
9
10 // LDClient.get() throws if the client is not initialized yet.
11 val client = try {
12 LDClient.get()
13 } catch (e: LaunchDarklyException) {
14 null
15 }
16
17 if (isError && client != null) {
18 val exception = event.exceptions?.lastOrNull()
19
20 val originalMessage =
21 exception?.value
22 ?: event.message?.formatted
23 ?: "No error message"
24
25 val data = LDValue.buildObject()
26 .put("source", "sentry")
27 .put("platform", "android")
28 .put("sentry_event_id", event.eventId?.toString() ?: "")
29 .put("error_type", exception?.type ?: "UnknownError")
30 .put("message", originalMessage.take(500))
31 .put("level", event.level?.name ?: "")
32 .put("release", event.release ?: "")
33 .put("environment", event.environment ?: "")
34 .put("transaction", event.transaction ?: "")
35 .build()
36
37 client.trackData("application-error", data)
38
39 // Immediately request sending all queued LaunchDarkly events.
40 client.flush()
41 }
42
43 // Continue sending the event to Sentry.
44 event
45 }
46}

Choose which Sentry fields to send

The examples above send a set of fields that are useful for both filtering and debugging:

Event propertyPurpose
source, platformLow-cardinality values that let you filter one metric down to a single tool or platform.
error_typeThe exception class. Low cardinality, so it works well as a metric filter.
levelLets you define a separate metric for fatal errors only.
sentry_event_idLets you find the full error, including its stack trace, in Sentry. High cardinality, so use it for debugging rather than filtering.
messageHuman-readable context when you review events. High cardinality, so use it for debugging rather than filtering.
release, environment, transactionLet you narrow a metric to a specific deployment, environment, or code path.

Adjust these fields to match what you want to measure. Every property you send is available as a metric filter as long as it is a top-level string, number, or boolean.

Adapt this pattern to other tools

Any tool that exposes a hook, callback, or interceptor around error capture works the same way. Find the equivalent of beforeSend in your tool, then call track and flush from it:

  • Datadog RUM provides the beforeSend callback in its initialization options. Datadog also offers a LaunchDarkly integration. To learn more, read Datadog.
  • Rollbar provides transform and checkIgnore callbacks.
  • Bugsnag provides onError callbacks.
  • OpenTelemetry lets you record exceptions on spans. You can call track from the same place you record the exception. To learn more, read OpenTelemetry.
  • Your own error handler. If you already have a central error boundary, middleware, or logging function, add the track call there. You do not need an observability tool at all.

Verify your events

After you deploy your change, confirm that the events arrive in LaunchDarkly:

  1. Trigger an error in your application.
  2. Navigate to the Metrics list in LaunchDarkly and open your metric, or review the Live events page for the environment.
  3. Confirm that events with your event key appear, and that the properties you sent are present in the event data.

To learn more, read Viewing incoming events.

If your events do not appear, check the following:

  • The SDK is sending events. Confirm you have not disabled event sending in your SDK configuration.
  • The event key in your code matches the event key in your metric exactly, including capitalization.
  • Your client is identified as a context before you call track. Events from a client that has not been identified cannot be attributed to a release.
  • Your error handler is running. Add a log statement inside the hook to confirm your tool calls it.
  • Ad blocking software in the browser is not blocking your events. To learn more, read Tracking custom events.