Send Android Data to Honeycomb

The Honeycomb OpenTelemetry Android SDK is Honeycomb’s OpenTelemetry Android distribution. It simplifies adding instrumentation to your Android applications and sending telemetry to Honeycomb.

This page briefly covers usage of the SDK. If you just want to see some code, check out the examples on GitHub.

Before You Begin 

Before you can add instrumentation to your Android application, you will need to do a few things.

Get Your Honeycomb API Key 

To send data to Honeycomb, you need to:

  1. Sign up for a Honeycomb account. To sign up, decide whether you would like Honeycomb to store your data in a US-based or EU-based location, then create a Honeycomb account in the US or create a Honeycomb account in the EU.

  2. Create a Honeycomb Ingest API Key. To get started, you can create a key that you expect to swap out when you deploy to production. Name it something helpful, perhaps noting that it’s a Getting Started key. Make note of your API key; for security reasons, you will not be able to see the key again, and you will need it later!

Tip
For setup, make sure you select the “Can create datasets” checkbox so that your data will show up in Honeycomb. Later, when you replace this key with a permanent one, you can uncheck that box.

Install the Honeycomb Android SDK 

Add the Honeycomb and OpenTelemetry Android SDKs to your application’s build.gradle.kts. When adding OpenTelemetry dependencies, make sure the library is compatible with the Honeycomb Android SDK.

dependencies {
    implementation("io.opentelemetry.android:android-agent:0.11.0-alpha")
    implementation("io.honeycomb.android:honeycomb-opentelemetry-android:0.0.10")
}

If your application’s minSDK is lower than 26, enable corelib desugaring:

android {
    // ...
    compileOptions {
        isCoreLibraryDesugaringEnabled = true
        sourceCompatibility = JavaVersion.VERSION_1_8
        targetCompatibility = JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = "1.8"
    }
}


dependencies {
    coreLibraryDesugaring(libs.desugar.jdk.libs)
}

If your application’s minSdk is lower than 24, running instrumentation tests or debug application builds requires that you:

Configuration 

Option Description
apiKey String
Your Honeycomb Ingest API Key.
Required if sending telemetry directly to Honeycomb.
tracesApiKey String
Ingest API Key to use when sending traces. Overrides apiKey for traces.
metricsApiKey String
Ingest API Key to use when sending metrics. Overrides apiKey for metrics.
logsApiKey String
Ingest API Key to use when sending logs. Overrides apiKey for logs.
dataset String
Name of the dataset to send telemetry data to.
Required if using Honeycomb Classic.
metricsDataset String
Name of the dataset to send metrics to. Overrides dataset for metrics.
apiEndpoint String
Telemetry is sent to this URL. For Honeycomb EU instances, set this to https://api.eu1.honeycomb.io:443. If you’re using an OpenTelemetry Collector, provide your collector URL instead.
Default: https://api.honeycomb.io:443 (US instance)
tracesEndpoint String
API endpoint to send traces to.
metricsEndpoint String
API endpoint to send metrics to.
logsEndpoint String
API endpoint to send logs to.
spanProcessor io.opentelemetry.sdk.trace.SpanProcessor
Additional span processor to use.
sampleRate Int
Sample rate to apply. For example, a sampleRate of 40 means 1 in 40 traces will be exported.
Default: 1
debug Boolean
Whether to enable debug logging.
Default: false.
serviceName String
The name of your application. Used as the value for service.name resource attribute.
Default: "unknown_service"
serviceVersion String
Current version of your application. Used as the value for service.version resource attribute.
resourceAttributes Map<String, String>
Attributes to attach to outgoing resources.
headers Map<String, String>
Headers to add to exported telemetry data.
tracesHeaders Map<String, String>
Headers to add to exported trace data.
metricsHeaders Map<String, String>
Headers to add to exported metrics data.
logsHeaders Map<String, String>
Headers to add to exported logs data.
timeout Duration
Timeout used by exporter when sending data.
Default: Duration = 10.seconds
tracesTimeout Duration
Timeout used by traces exporter. Overrides timeout for trace data.
metricsTimeout Duration
Timeout used by metrics exporter. Overrides timeout for metrics data.
logsTimeout Duration
Timeout used by logs exporter. Overrides timeout for logs data.
protocol OtlpProtocol
Protocol to use when sending data.
Can be one of: OtlpProtocol.GRPC, OtlpProtocol.HTTP_PROTOBUF, OtlpProtocol.HTTP_JSON.
Default: OtlpProtocol.HTTP_PROTOBUF
tracesProtocol OtlpProtocol
Overrides protocol for trace data.
metricsProtocol OtlpProtocol
Overrides protocol for metrics data.
logsProtocol OtlpProtocol
Overrides protocol for logs data.
offlineCachingEnabled Boolean
Enable offline caching for telemetry. When offline caching is enabled, telemetry is cached during network failures. The SDK will retry exporting telemetry for up to 18 hours. Offline caching also adds a minimum delay of 30 seconds to telemetry exports.
Offline caching is an alpha feature and may be unstable.
Default: false

Add Resource Attributes 

Resource attributes are available on every span your instrumentation emits. Adding custom, application-specific attributes makes it easier to correlate your data to important business information.

You can add extra resource attributes during SDK configuration with the .setResourceAttributes() method.

import android.app.Application
import io.honeycomb.opentelemetry.android.Honeycomb
import io.honeycomb.opentelemetry.android.HoneycombOptions
import io.opentelemetry.android.OpenTelemetryRum

class ExampleApp: Application() {
    var otelRum: OpenTelemetryRum? = null

    override fun onCreate() {
        super.onCreate()

        val options = HoneycombOptions.builder(this)
            .setApiKey("YOUR-API-KEY")
            .setServiceName("YOUR-SERVICE-NAME")
            .setServiceVersion("0.0.1")
            .setResourceAttributes(mapOf("app.ab_test" to "test c"))
            .setDebug(true)
            .build()

        otelRum = Honeycomb.configure(this, options)
    }
}

Enable Sampling 

The Honeycomb Android SDK includes optional deterministic head sampling. To enable sampling, call .setSampleRate() with your desired sample rate as an Int value. The sample rate is 1 by default, meaning every trace is exported.

The example below sets a sampleRate of 40, meaning 1 in 40 traces will be exported.

// ...
val options = HoneycombOptions.Builder(this)
        .setApiKey("YOUR-API-KEY")
        .setServiceName("YOUR-SERVICE-NAME")
        .setServiceVersion("0.0.1")
        .setSampleRate(40)
        .setDebug(true)
        .build()
// ...

Add Automatic Instrumentation 

Enable all OpenTelemetry auto-instrumentations by including the OpenTelemetry android-agent:

dependencies {
    implementation("io.opentelemetry.android:android-agent:0.11.0")
    implementation("io.honeycomb.android:honeycomb-opentelemetry-android:0.0.9")
}

If you don’t need all of them, you can instead add dependencies for each instrumentation you want to include:

Custom Instrumentation 

Automatic instrumentation is a fast way to instrument your code, but you get more insight into your application by adding custom, or manual, instrumentation.

To add your own custom instrumentation, import the OpenTelemetry API in to your application.

import io.opentelemetry.api.OpenTelemetry

Add Attributes to an Active Span 

You can retrieve the currently active span in a trace and add attributes to it. This lets you add more context to traces and gives you more ways to group or filter traces in your queries:

import io.opentelemetry.api.OpenTelemetry
import io.opentelemetry.api.trace.Span
import io.opentelemetry.api.common.Attributes

fun applyDiscountCode(discountCode: String) {
  val currentSpan = Span.current()

  currentSpan.setAttribute("app.cart.discount_code", discountCode)
}

In the above example, we add an app.cart.discount_code attribute to the current span. This lets us use the app.cart.discount_code field in WHERE or GROUP BY clauses in the Honeycomb query builder.

Acquire a Tracer 

To create custom spans, you need to acquire a tracer:

import io.opentelemetry.api.OpenTelemetry
import io.opentelemetry.android.OpenTelemetryRum

// ...

val otelRum = app.otelRum as OpenTelemetryRum
val tracer = otelRum.tracerProvider.tracerBuilder("my-application-tracer").build()

Create Spans 

Create custom spans to get a clear view of the critical parts in your application.

import io.opentelemetry.api.OpenTelemetry
import io.opentelemetry.api.common.Attributes
import io.opentelemetry.android.OpenTelemetryRum

// ...

val otelRum = app.otelRum as OpenTelemetryRum
val tracer = otelRum.tracerProvider.tracerBuilder("my-application-tracer").build()

fun generateNewLevel() {
  val span = tracer.spanBuilder("newLevel").startSpan()
  // do some work
  span.end()
}

Custom Span Processing 

Span processors provide hooks for when a span starts and when it ends. This lets you mutate spans after they have been created by automatic or manual instrumentation.

Here’s a basic example of a span processor that adds an attribute to spans when they start:

import io.opentelemetry.context.Context
import io.opentelemetry.sdk.trace.ReadWriteSpan
import io.opentelemetry.sdk.trace.ReadableSpan
import io.opentelemetry.sdk.trace.SpanProcessor

class BasicSpanProcessor : SpanProcessor {
    override fun onStart(
        parentContext: Context,
        span: ReadWriteSpan,
    ) {
        span.setAttribute("app.metadata", "extra metadata")
    }

    override fun isStartRequired(): Boolean {
        return true
    }

    override fun onEnd(span: ReadableSpan) {}

    override fun isEndRequired(): Boolean {
        return false
    }
}

Add the span processor as part of your SDK configuration to use it:

// ...
val options = HoneycombOptions.builder(this)
    .setApiKey("YOUR-API-KEY")
    .setServiceName("YOUR-SERVICE-NAME")
    .setSpanProcessor(BasicSpanProcessor())
    .setServiceVersion("0.0.1")
    .setDebug(true)
    .build()
// ...

Manual Context Propagation 

Kotlin Coroutines may operate across multiple threads, and do not automatically inherit the correct OpenTelemetry context.

Instead, context must be propagated manually with the OpenTelemetry Kotlin Extensions.

dependencies {
  implementation("io.opentelemetry:opentelemetry-extension-kotlin:1.47.0")
}

Once these are installed, replace any launch calls with

launch(Span.current().asContextElement()) {
  // ...
}

Troubleshooting 

To explore common issues when sending data, visit Common Issues with Sending Data in Honeycomb.