Skip to main content

Supported instrumentations

Supported instrumentations​

InstrumentationDescription
LogsStructured logging with severity and labels (called Custom)
ErrorsUncaught & handled exceptions
NetworkRequest/response performance data
ANRANR tracking
Mobile VitalsCPU, FPS, memory, and cold/warm start times.
Custom MetricsUser-defined numeric metrics
LifecycleReport lifecycle events.
User InteractionReport user interactions in the application.
NavigationReport navigation events

Logs (Custom)​

Send a simple log with optional data and labels.

Example​

CoralogixRum.log(
severity = CoralogixLogSeverity.Info,
message = "User logged in successfully",
data = mapOf("userId" to "12345"), // optional
labels = mapOf("environment" to "staging") // optional
)

Disabling this feature​

You can disable this instrumentation by passing false in the instrumentations map in CoralogixOptions. Any call to log() would be ignored if the instrumentation in disabled:

val options = CoralogixOptions(
// ... other options ...
instrumentations = mapOf(
Instrumentation.Custom to false
)
)
Note

The passed labels map will be added to other labels set in the SDK for this event only.

Error reporting​

The Coralogix SDK automatically captures unhandled crashes and exceptions through its built-in Error Instrumentation module.

Automatic crash tracking​

Once the SDK is initialized and the error instrumentation is enabled, all unhandled exceptions and app crashes are automatically detected and sent to Coralogix.

Starting with version 2.26.0, a crash is also saved on the device as it happens. A crash the app was terminated too quickly to finish sending, or that happened while the device was offline, is sent the next time the app starts and initializes the SDK. The event keeps the time of the crash and the session and screen it happened in, so it is reported as it happened rather than as part of the new session. A crash the SDK sends as it happens joins the trace of an active global span, like any other error, unless you pass CoralogixIgnoredInstrument.ERRORS in ignoredInstruments. A saved crash the SDK sends later starts a trace of its own.

The SDK removes a saved crash once Coralogix accepts it, or refuses it for good with an HTTP 4xx other than 408 or 429. Any other answer is a failed upload, and so is an upload that gets no answer while the device is online; the SDK stops waiting for an answer after 2 minutes at most. The SDK sends saved crashes one at a time. When an upload fails, it sends no more until it tries again, 30 seconds and then 2 minutes after the crash that failed was first sent, starting each time with that crash. A Retry-After header in Coralogix's answer holds the next attempt back, by up to an hour, while the app keeps running. Crashes still saved after the last attempt are sent by the background job described below and on later launches. The SDK drops a crash after 16 failed uploads or seven days after it happened, whichever comes first. An upload attempted while offline, or cut short by the app closing or the device going offline, does not count, and the crash waits for the background job or the next launch. The device keeps at most the 10 newest crashes of each kind: uncaught exceptions, native crashes and ANRs. A crash from a session that was sampled out is not sent, ignoreErrors is applied when it is, and a crash your beforeSend drops is removed. If Coralogix accepts a crash but the app is terminated before the answer reaches the device, the saved copy is sent on the next launch and the crash can appear twice.

When the app is not opened again after a crash, or is closed before it has sent its saved crashes, a background job sends them. The SDK schedules the job when an uncaught exception is saved after the SDK is initialized, and when the app starts with crashes saved. The job runs about five minutes later, once the device is online, by starting your app's process in the background, and runs again later while crashes remain. It can only send when the SDK is initialized in Application.onCreate: if you initialize it later, as React Native and Flutter apps do from JavaScript or Dart, saved crashes wait for the next time the app starts. The job belongs to your app's main process; a crash in another process is sent the next time that process starts and initializes the SDK.

A stack trace is sent as it was thrown up to 200 frames. A longer one — a StackOverflowError arrives with thousands — first has each run of repeated frames collapsed to a single turn, and if it is still over 200 frames it keeps the innermost frames and the entry point and drops the middle. A <frames omitted> marker stands wherever frames were left out. This applies to every stack the SDK sends, including reported errors and ANR thread dumps.

A crash event carries no session replay screenshot: taking one needs the app's main thread, which a crash on that thread is already holding.

error_context.threads lists the thread that crashed first, with the stack the exception was thrown with, then every other thread of the app, each with up to 200 frames of its stack, like any stack the SDK sends.

When an exception wraps another, as Android does for an exception thrown in a Service and React Native does for one thrown in a view manager's prop setter, the event is named after the innermost exception, the one that actually failed: error_type and error_message are its type and message. The stack lists the outer exception's frames first, then a Caused by: line and the frames of each exception it wraps, as printStackTrace does. In a chain of more than eight exceptions, the stack keeps the outermost seven and the innermost one. This applies to errors you report with CoralogixRum.reportError too. ignoreErrors is matched against the innermost exception as well, so a pattern that names only the outer exception's type or message no longer filters these errors.

Native crashes​

Starting with version 2.26.0, the SDK also reports native crashes: a fatal signal (SIGSEGV, SIGABRT, SIGBUS, SIGILL, SIGTRAP or SIGFPE) raised by C or C++ code in your app, in a library it bundles, or in the React Native or Flutter engine. A native crash ends the process before anything can be sent, so the SDK saves it on the device and sends it the next time the app starts and initializes the SDK. The event keeps the time of the crash, the session and screen it happened in, and the app version and Android version it ran on. A crash that has not been sent within seven days is dropped.

In error_context, exception_type names the signal and threads holds the backtrace of the crashed thread, the one thread a native crash sends. Each frame names its library and, when the library has one, its build ID. On Android 12 (API 31) and later, the SDK adds what the system's own crash report has for the same crash: symbol names, and the abort message, such as the text of an uncaught C++ exception.

The SDK passes each signal on to the crash handler that was installed before it, so Firebase Crashlytics and the system's crash reporting keep working alongside it. Instrumentation.Error turns native crash reporting on and off together with the rest of error reporting. In a React Native or Flutter app, native crashes do not pass through the JavaScript or Dart beforeSend. To drop them, list the signal or the message in ignoreErrors.

You get native crash reports from the moment CoralogixRum.initialize returns, because the SDK installs its signal handler before then. You do not get a report for a native crash that happens before you initialize the SDK.

Manual error reporting​

You can manually report handled exceptions when you want visibility into recoverable or expected failures, such as API errors or business logic exceptions.

Example​
try {
riskyOperation()
} catch (t: Throwable) {
CoralogixRum.reportError(t)
}
Attaching custom attributes​

For richer error reporting, pass a CoralogixErrorDecorator with customAttributes. The attributes are emitted on the resulting RUM error event under the data field of errorContext (matching the iOS SDK).

val decorator = CoralogixErrorDecorator(
throwable = t,
isCrash = false
).copy(
customAttributes = mapOf(
"userId" to "12345",
"screen" to "checkout"
)
)
CoralogixRum.reportError(decorator)
Bundling a message, data, and labels​

reportError(throwable, ...) also accepts optional data and labels, so you can attach context in a single call instead of a separate log(). data is emitted under error_context.error_custom_data and labels are merged into the event's labels.

try {
riskyOperation()
} catch (t: Throwable) {
CoralogixRum.reportError(
throwable = t,
data = mapOf("cart_size" to 3, "reason" to "timeout"),
labels = mapOf("team" to "payments")
)
}

Disabling this feature​

You can disable this instrumentation by passing false in the instrumentations map in CoralogixOptions. Any call to reportError() would be ignored if the instrumentation in disabled:

val options = CoralogixOptions(
// ... other options ...
instrumentations = mapOf(
Instrumentation.Error to false
)
)

Network events reporting​

To enable RUM to intercept network events, add CoralogixOkHttpInterceptor to your network client (or use the Coralogix Gradle Plugin).

Examples​

OkHttp​
val okHttpClient = OkHttpClient.Builder()
.addInterceptor(CoralogixOkHttpInterceptor())
.build()

val request = Request.Builder()
.url("https://api.example.com/data")
.build()

val response = okHttpClient.newCall(request).execute()
println(response.body?.string())
Retrofit​
val okHttpClient = OkHttpClient.Builder()
.addInterceptor(CoralogixOkHttpInterceptor())
.build()

val retrofit = Retrofit.Builder()
.baseUrl("https://api.example.com/")
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.build()

interface ApiService {
@GET("data")
suspend fun getData(): Response<DataModel>
}

val apiService = retrofit.create(ApiService::class.java)
val response = apiService.getData()
println(response.body())
Ktor​
val httpClient = HttpClient(OkHttp) {
engine {
preconfigured = OkHttpClient.Builder()
.addInterceptor(CoralogixOkHttpInterceptor())
.build()
}
}

val response = httpClient.get("https://api.example.com/data")
println(response.bodyAsText())

Disabling this feature​

You can disable this instrumentation by passing false in the instrumentations map in CoralogixOptions:

val options = CoralogixOptions(
// ... other options ...
instrumentations = mapOf(
Instrumentation.Network to false
)
)
Note

The network instrumentation also injects the W3C traceparent header. Disabling it stops trace context propagation as well, even when traceParentInHeader is enabled.

Note

Only OkHttp clients are currently supported.

ANR events reporting​

The Coralogix SDK automatically captures and reports ANR events as errors. An ANR the SDK detects while the app is running is not a crash: its is_crash is false. Its stack is the main thread's, read when the SDK detects the ANR. Starting with version 2.26.0, error_context.threads lists the main thread first, then every other thread of the app, each with up to 200 frames of its stack, like any stack the SDK sends. In a React Native or Flutter app, the JavaScript or Dart beforeSend receives the ANR without threads; the SDK adds them back to the event your beforeSend returns, so you cannot change them there.

Starting with version 2.26.0, on Android 11 (API 30) and later, an ANR that ends with the system closing the app is reported as a crash, with is_crash set to true, the next time the app starts and initializes the SDK. The event keeps the time the app was closed and the session and screen it ended, and its stack is the main thread's stack from the system's own report. error_context.threads lists the main thread first, then every other thread the system's report has Java frames for, each with up to 200 frames of its stack, like any stack the SDK sends. The message is Application Not Responding: followed by the system's description of the ANR: an ignoreErrors entry that mentions ANR or Application Not Responding drops every ANR, and one for a particular ANR has to match that whole message. If the SDK already delivered its own event for the same ANR before the app was closed, that event is the ANR's only report, and it is not marked as a crash. In a React Native or Flutter app, the ANR reported the next time the app starts does not pass through the JavaScript or Dart beforeSend. To drop it, list it in ignoreErrors. Reporting it needs both Instrumentation.Anr and Instrumentation.Error on.

Disabling this feature​

You can disable this instrumentation by passing false in the instrumentations map in CoralogixOptions:

val options = CoralogixOptions(
// ... other options ...
instrumentations = mapOf(
Instrumentation.Anr to false
)
)

Mobile vitals reporting​

The Mobile Vitals instrumentation continuously monitors your app’s performance and responsiveness, providing real-time visibility into user experience quality. It automatically tracks key system metrics, periodic snapshots, and one-time events that represent critical app behaviors like cold starts or frame drops.

What we measure​

CategoryMetricDescription
Frame RenderingFPS (Frames Per Second)Measures how smoothly your app renders frames. Low FPS indicates UI stutters or heavy rendering work.
Frame QualitySlow Frames / Frozen FramesTracks frames that exceeded normal rendering budgets (slow) or were completely stuck (frozen).
CPU UsageTotal / Main Thread CPUCaptures overall and main-thread CPU usage percentage and timing, helping identify CPU-heavy operations.
Memory UsageResident / Heap / Java / Native MemoryTracks memory utilization from both Java and native layers to identify leaks or memory pressure.
Startup PerformanceCold Start / Warm Start TimeMeasures how long it takes for the app to become ready when launched cold or resumed from background.

Aggregation reports​

The SDK continuously aggregates performance data and periodically sends it to Coralogix as Mobile Vitals Aggregations. These reports summarize metrics like average CPU load, memory usage, and FPS over time. Reports are meant to be tied to a specific view context, to implement this idea a report is being sent on one of two scenarios:

  • The view context has changed (navigation has occurred)
  • No mobile vitals event was reported for at least 15 seconds (no navigation)

One shot events​

Alongside periodic summaries, the SDK reports important one-time measurements that occur during specific lifecycle moments:

EventDescription
Cold StartTime taken from process creation until the first activity is visible.
Warm StartTime taken when resuming the app from background.

These one-shot events are sent as soon as they occur, giving you instant visibility into startup performance and user flow responsiveness.

Starting with version 2.22.1, the SDK records the launch from process start, so the cold start is reported even when you call CoralogixRum.initialize after the first screen is shown, as React Native and Flutter apps do. When a process started in the background, for example by a push message, later shows its first screen, that launch is reported as a warm start instead. A launch sent to the background before its first frame, or one that takes longer than 60 seconds, reports no cold start. Start times end when the first frame finishes drawing.

Note

The recorder is an AndroidX Startup initializer merged into your app's manifest. To remove it, add this entry to your app's AndroidManifest.xml, whose root <manifest> element must declare xmlns:tools="http://schemas.android.com/tools". Cold starts are then reported only when you initialize the SDK before the first activity is created, for example in Application.onCreate.

<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
tools:node="merge">
<meta-data
android:name="com.coralogix.android.sdk.startup.CoralogixAppStartInitializer"
tools:node="remove" />
</provider>

Disabling this feature​

You can disable this instrumentation by passing false in the instrumentations map in CoralogixOptions:

val options = CoralogixOptions(
// ... other options ...
instrumentations = mapOf(
Instrumentation.MobileVitals to false
)
)

You can also disable specific metric collection by passing false for a specific detector in the mobileVitalsOptions map in CoralogixOptions For example to disable cpu usage:

val options = CoralogixOptions(
// ... other options ...
mobileVitalsOptions = mapOf(
MobileVitalType.CpuUsage to false // for example
)
)

Custom metrics​

Send arbitrary key-value pairs.

Example​

CoralogixRum.sendCustomMeasurement("image_upload_time_ms", 1480L)

Custom time measurement​

Measure the elapsed time of any operation.

Example​

CoralogixRum.startTimeMeasure("image-load", mapOf("source" to "network"))
loadImage()
CoralogixRum.endTimeMeasure("image-load")

See the Custom Time Measurement section in the API Reference for full behaviour details.

Lifecycle events reporting​

Captures app lifecycle activity and fragment events that occur during runtime, providing insights into the application's behavior and performance.

  • Activity lifecycle events:

    • onActivityCreated - Triggered when the activity is first created, initializing the activity and setting up the UI.
    • onActivityStarted - Called when the activity becomes visible to the user, but may not yet be interactive.
    • onActivityResumed - Invoked when the activity enters the resumed state, making it fully interactive and allowing user input.
    • onActivityPaused - Called when the activity is partially obscured, such as when another activity or a dialog appears, but the activity is still partially visible.
    • onActivityStopped - Triggered when the activity is no longer visible to the user and enters the stopped state.
    • onActivitySaveInstanceState - Called to save the current state of the activity (e.g., UI data, user input) so it can be restored if the activity is restarted.
    • onActivityDestroyed - Called just before the activity is destroyed, allowing for final cleanup of resources and references.
  • Fragment lifecycle events:

    • onFragmentAttached - Called when the fragment is attached to its host activity. This is the point where the fragment is associated with the activity lifecycle.
    • onFragmentCreated - Called when the fragment is created. This occurs only once per fragment instance, even if it is attached and detached multiple times.
    • onFragmentViewCreated - Called after the fragment’s view is created and fully inflated. At this point, the fragment's UI is ready.
    • onFragmentStarted - Triggered when the fragment enters the started phase and becomes visible to the user.
    • onFragmentResumed - Called when the fragment enters the resumed phase, meaning the fragment is now interactive and the user can interact with it.
    • onFragmentPaused - Called when the fragment is partially obscured by another UI element (e.g., another activity, dialog box), but still visible.
    • onFragmentStopped - Called when the fragment is no longer visible to the user and has entered the stopped state.
    • onFragmentSaveInstanceState - Called to save the fragment's state (e.g., UI data, input) when the system needs to save its state (during pauses, backgrounding, or configuration changes), so it can be restored later.
    • onFragmentViewDestroyed - Called when the fragment’s view is destroyed and is no longer available, typically when the fragment is removed or replaced.
    • onFragmentDestroyed - Called when the fragment instance is fully destroyed, releasing any remaining resources.
    • onFragmentDetached - Called when the fragment is detached from its host activity, ending the association between the fragment and its host.

Disabling this feature​

You can disable this instrumentation by passing false in the instrumentations map in CoralogixOptions:

val options = CoralogixOptions(
// ... other options ...
instrumentations = mapOf(
Instrumentation.Lifecycle to false
)
)

Automatic flush on backgrounding​

When the app moves to the background, the SDK immediately exports any events still held in its batch buffer, so telemetry from the end of a session is not lost if the process is killed while backgrounded. A WorkManager job is enqueued alongside it as a backstop, covering the case where the export does not finish before the system suspends the process.

This tracks the process-level background transition, so it fires once when the app actually leaves the foreground — not on activity-to-activity navigation within the app.

No configuration is required, and this is not tied to any instrumentation — it stays on even if you disable Lifecycle Events Reporting. You can also flush on demand at any point:

CoralogixRum.flush()

// Or, to react once the export completes:
CoralogixRum.flush {
Log.d("MyApp", "buffered events exported")
}

User interaction reporting​

Automatically tracks user interactions, including taps, scrolls, and swipes. Each event includes the target view, element class, and direction (where applicable).

Disabling this feature​

You can disable this instrumentation by passing false in the instrumentations map in CoralogixOptions:

val options = CoralogixOptions(
// ... other options ...
instrumentations = mapOf(
Instrumentation.UserInteraction to false
)
)

Automatically report any navigation event.

Custom spans​

Create arbitrary OpenTelemetry spans that appear in the Coralogix RUM trace view. Spans are automatically linked to the current session and view context. Child spans inherit the parent's trace ID and carry a parentSpanId.

Prerequisites​

Custom spans require W3C traceparent header injection to be enabled:

val options = CoralogixOptions(
// ...
traceParentInHeader = TraceParentInHeaderConfig(enabled = true)
)

getCustomTracer() returns null and logs a warning if this is not set.

Getting the tracer​

val tracer = CoralogixRum.getCustomTracer() ?: return
  • Returns null if the SDK is not initialized or traceParentInHeader is disabled.
  • Only one tracer instance is issued per SDK lifecycle — subsequent calls return null. Store the returned instance and reuse it.
  • Accepts an optional ignoredInstruments set to suppress specific automatic instrumentation while a global span is active (see Ignored instruments).

Starting a global span​

A global span is the root of a custom trace. Only one global span can be active at a time.

val globalSpan = tracer.startGlobalSpan(
name = "checkout-flow",
labels = mapOf("cart.item_count" to 3)
) ?: return
  • Returns null if a global span is already active.
  • labels are optional. They are merged with the SDK-level labels and forwarded with every child span started from this global span.
  • Exposes traceId and spanId properties (W3C hex strings) so you can correlate spans with your own backend:
val traceId: String = globalSpan.traceId // 32 lowercase hex chars
val spanId: String = globalSpan.spanId // 16 lowercase hex chars

Always call endSpan() when the operation is complete:

globalSpan.endSpan()

Starting child spans​

val childSpan = globalSpan.startCustomSpan(
name = "payment-step",
labels = mapOf("payment.method" to "credit_card")
)
  • Child spans inherit the global span's trace ID and set the global span as their parentSpanId.
  • labels are optional and are merged with SDK labels and the global span's labels.
  • Always call endSpan() when the child operation is complete:
childSpan.endSpan()

Setting attributes​

CoralogixCustomSpan supports four attribute types:

childSpan.setAttribute("http.method", "GET") // String
childSpan.setAttribute("http.status", 200) // Int
childSpan.setAttribute("duration.ms", 3.14) // Double
childSpan.setAttribute("from.cache", true) // Boolean

Adding events​

Add timestamped events to a span to record notable moments during its lifetime.

// Event with no extra data
childSpan.addEvent("retry-attempted")

// Event with attributes
childSpan.addEvent(
name = "cache-miss",
attributes = mapOf("key" to "user-profile", "size_bytes" to 1024)
)

// Event at a specific time (defaults to milliseconds)
childSpan.addEvent(
name = "request-sent",
timestamp = System.currentTimeMillis() - 500,
unit = TimeUnit.MILLISECONDS
)

// Event with both attributes and a custom timestamp
childSpan.addEvent(
name = "response-received",
attributes = mapOf("status" to "200"),
timestamp = System.currentTimeMillis(),
unit = TimeUnit.MILLISECONDS
)

Supported attribute value types: String, Int, Long, Double, Float, Boolean. Any other type (including null) is silently ignored.

Setting span status​

childSpan.setStatus(StatusCode.OK)
childSpan.setStatus(StatusCode.ERROR)

Cross-thread context propagation​

If you start a child span from a different thread than the global span, use withContext to make the global span the active OTel context on that thread:

globalSpan.withContext {
val childSpan = globalSpan.startCustomSpan("background-task")
// ... do work ...
childSpan.endSpan()
}

Complete example​

val tracer = CoralogixRum.getCustomTracer() ?: return

val globalSpan = tracer.startGlobalSpan(
name = "checkout-flow",
labels = mapOf("cart.item_count" to 3)
) ?: return

val paymentSpan = globalSpan.startCustomSpan(
name = "payment-step",
labels = mapOf("payment.method" to "credit_card")
)

paymentSpan.addEvent("payment-initiated")

try {
processPayment()
paymentSpan.setAttribute("payment.success", true)
paymentSpan.setStatus(StatusCode.OK)
paymentSpan.addEvent("payment-complete")
} catch (e: Exception) {
paymentSpan.setAttribute("error.message", e.message ?: "unknown")
paymentSpan.setStatus(StatusCode.ERROR)
} finally {
paymentSpan.endSpan()
globalSpan.endSpan()
}

Ignored instruments​

Pass ignoredInstruments to getCustomTracer() to prevent specific automatic instrumentation from firing while a global span is active:

val tracer = CoralogixRum.getCustomTracer(
ignoredInstruments = setOf(
CoralogixIgnoredInstrument.NETWORK_REQUESTS,
CoralogixIgnoredInstrument.USER_INTERACTIONS,
CoralogixIgnoredInstrument.ERRORS
)
) ?: return
ValueDescription
NETWORK_REQUESTSSuppresses automatic network event reporting.
USER_INTERACTIONSSuppresses automatic user-interaction event reporting.
ERRORSSuppresses automatic error/crash event reporting.

Receiving raw OTLP span data (tracesExporter)​

Supply a tracesExporter callback in CoralogixOptions to receive the raw OTLP payload for each batch of exported spans — useful for forwarding to a backend trace collector:

val options = CoralogixOptions(
// ...
tracesExporter = { data: CoralogixTraceExporterData ->
val spanCount = data.spanCount // total spans in this batch
val json = data.toJson() // full OTLP JSON string
val spans = data.resourceSpans // structured List<OtlpResourceSpans>

forwardToMyCollector(json)
}
)

Two things to know about where the callback sits in the pipeline:

  • It receives only what the SDK is about to export, so this is not every span the SDK records. While the session is idle, only crashes saved on the device reach it. Otherwise each span follows the sampling decision of the session that created it, so a batch exported after the session rotates can mix sessions: spans from a sampled-in session always pass, and spans from a sampled-out session pass only for the categories in excludeFromSampling, plus the SDK's own internal events (such as the init event) whenever that list is not empty. A saved crash reaches it once per SDK initialization that sends it, rather than on every upload, so a crash the SDK sends again after your app restarts reaches it again.
  • It runs before beforeSend, so anything your beforeSend would redact, edit or drop is still present here: request URLs, the headers and payloads you capture with networkCaptureConfig, error messages and custom labels, for example. Sanitize those attributes yourself before you forward them.

CoralogixTraceExporterData fields:

FieldTypeDescription
resourceSpansList<OtlpResourceSpans>Structured OTLP resource-spans tree.
spanCountIntTotal number of spans across all resource-spans.

CoralogixTraceExporterData exposes toJson() for convenience, returns the full OTLP JSON encoding of this batch (ready to POST).

Note

When tracesExporter is set, instrumentation_data (network request details) is stripped from the standard RUM payload to avoid double-reporting.

The SDK wraps the call in a try/catch, so a callback that throws an Exception does not stop the SDK's own reporting. Two limits worth knowing: the catch is on Exception, so anything outside that - an Error, for instance - still escapes and prevents that batch being sent; and the warning it logs goes through the SDK logger, which is silent when logging is disabled, so a callback failing in a release build leaves no trace. The SDK does not retry delivery either, so retries for your own destination are yours.

Last updated on