---
title: Custom instrumentation
description: Custom spans
url: https://coralogix.com/docs/user-guides/rum/sdk-installation/flutter/mobile/features/custom-instrumentation/
product: ingress
feature: RUM Ingress
feature_id: rum.ingress
feature_identifier: ingress/rum.ingress
last_updated: '2026-09-09'
---

# Custom instrumentation

### Custom spans

Custom spans let you mark manual business flows and correlate them with Dart-side network/interaction/error instrumentation.

**Prerequisite:** initialize the SDK with `traceParentInHeader.enable = true`. Without this, `CxFlutterPlugin.getCustomTracer()` returns `null`.

```
import 'package:cx_flutter_plugin/cx_flutter_plugin.dart';

import 'package:cx_flutter_plugin/cx_http_client.dart';



Future<void> runCheckoutFlow() async {

  final tracer = CxFlutterPlugin.getCustomTracer(

    ignoredInstruments: const [

      // Optional: disable linkage for specific Dart instruments.

      CoralogixIgnoredInstrument.userInteractions,

    ],

  );

  if (tracer == null) return;



  final global = await tracer.startGlobalSpan(

    'checkout.flow',

    labels: {'screen': 'checkout'},

  );

  if (global == null) return; // Native rejected (e.g., another global span is active).



  try {

    await global.withContext(() async {

      final client = CxHttpClient();

      try {

        await client.get(Uri.parse('https://api.example.com/checkout'));

      } finally {

        client.close();

      }

    });



    final child = await global.startCustomSpan('checkout.payment');

    await child.endSpan();

  } finally {

    await global.endSpan();

  }

}
```

`withContext` uses `runZonedGuarded` under the hood, so the active global span id is preserved across `await` chains in that flow and not leaked to unrelated async tasks.

### Time measurement

Time arbitrary spans of work with `startTimeMeasure` / `endTimeMeasure`. The duration is reported by the native SDK as a `custom-measurement` span (milliseconds) — useful when you need to measure something the SDK can't auto-instrument (checkout flows, custom render passes, asset loading, etc.).

```
await CxFlutterPlugin.startTimeMeasure('checkout', labels: {'cart_size': 3});

// ... do work ...

await CxFlutterPlugin.endTimeMeasure('checkout');
```

| Method             | Argument                        | Behavior                                                                                                                       |
| ------------------ | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| `startTimeMeasure` | `name: String`                  | Unique identifier. Empty / whitespace-only names are ignored. Duplicate `start` for an in-flight name is ignored (first wins). |
| `startTimeMeasure` | `labels: Map<String, dynamic>?` | Optional. Merged with SDK-level labels at `end`; start labels win on key collision.                                            |
| `endTimeMeasure`   | `name: String`                  | Must match a prior `start`. No-op when never started, already ended, or the session has gone idle.                             |

The bridge is a pure pass-through — no Dart-side state is kept, the native SDK owns the in-flight registry. **You are responsible for pairing every `start` with exactly one `end`** (leaked starts persist in memory until the session ends).
