Skip to main content

Custom Instrumentation

Generates: Spans

Learn how to capture performance data on specific operations in your application using custom spans.

Setup

Ensure the Pulse SDK is properly initialized. See Quick Start for setup instructions.

What is a Span?

A span represents a single operation within your application. Spans are used to measure the duration of specific operations and can include additional context through attributes and events.

When you instrument your application, you create spans for operations you want to measure - like database queries, API calls, image processing, or any custom logic.

When you start a span, it becomes active, meaning it's the current span being tracked. Any errors that occur while a span is active are automatically associated with that span. This makes it easy to correlate errors with the operations that caused them.

Basic Usage

Automatic Span Management

The simplest way to create a span is using Pulse.trackSpan(). This method automatically handles starting and ending the span:

import { Pulse } from '@horizoneng/pulse-react-native';

function processData() {
const result = Pulse.trackSpan(
'process_user_data',
{ attributes: { operation: 'data_processing' } },
() => {
// Your code here
return computeResult();
}
);
return result;
}

Async Operations

trackSpan() works seamlessly with async operations:

async function fetchUserProfile(userId: string) {
return await Pulse.trackSpan(
'fetch_user_profile',
{ attributes: { userId } },
async () => {
const response = await fetch(`/api/users/${userId}`);
return response.json();
}
);
}

Manual Span Control

For more control over the span lifecycle, use Pulse.startSpan():

import { Pulse, SpanStatusCode } from '@horizoneng/pulse-react-native';

function complexOperation() {
const span = Pulse.startSpan('complex_operation', {
attributes: { step: 'initialization' }
});

try {
// Your operation
const result = performWork();

span.setAttributes({ itemsProcessed: result.count });
span.end(SpanStatusCode.OK);

return result;
} catch (error) {
span.recordException(error);
span.end(SpanStatusCode.ERROR);
throw error;
}
}

Important: Always ensure spans are properly ended, especially when using manual span control. Use try/finally blocks to guarantee cleanup.

Adding Attributes

Attributes add context to your spans, making them more useful for debugging and analysis.

Supported attribute types: string, number, boolean, and arrays of these types.

Pulse.startSpan('database_query', {
attributes: {
table: 'users',
tags: ['analytics', 'user']
}
});

You can also update attributes during the span's lifetime:

const span = Pulse.startSpan('image_processing', {
attributes: { format: 'jpeg' }
});

span.setAttributes({
width: 1920,
height: 1080,
compressionLevel: 85
});

span.end();

Recording Events

Events mark specific moments within a span's duration:

const span = Pulse.startSpan('user_registration');

span.addEvent('validation_started');
// ... validation logic ...

span.addEvent('validation_complete', { errors: 0 });
// ... database write ...

span.addEvent('email_sent', { provider: 'sendgrid' });

span.end(SpanStatusCode.OK);

Nested Spans

Create parent-child relationships to understand complex operations:

function processOrder(orderId: string) {
const parentSpan = Pulse.startSpan('process_order', {
attributes: { orderId }
});

// Validate order
const validationSpan = Pulse.startSpan('validate_order');
const isValid = validateOrder(orderId);
validationSpan.end();

if (!isValid) {
parentSpan.end(SpanStatusCode.ERROR);
return;
}

// Process payment
const paymentSpan = Pulse.startSpan('process_payment', {
attributes: { gateway: 'stripe' }
});
processPayment(orderId);
paymentSpan.end();

// Update inventory
const inventorySpan = Pulse.startSpan('update_inventory');
updateInventory(orderId);
inventorySpan.end();

parentSpan.end(SpanStatusCode.OK);
}

Span Status Codes

Indicate the outcome of an operation using status codes:

import { SpanStatusCode } from '@horizoneng/pulse-react-native';

// Success
span.end(SpanStatusCode.OK);

// Failure
span.end(SpanStatusCode.ERROR);

// No explicit status (default)
span.end(SpanStatusCode.UNSET);
span.end(); // Same as UNSET

Sample Telemetry

Spans generate performance telemetry with the following structure:

Type: Span
Span Name: Custom (defined by you)
Span Kind: INTERNAL

Example: Simple Span

{
"name": "process_user_data",
"kind": "INTERNAL",
"startTimeUnixNano": "1732738721000000000",
"endTimeUnixNano": "1732738722001100000",
"duration": "1.1s",
"status": "OK",
"attributes": {
"operation": "data_processing",
"platform": "react-native",
"session.id": "dce09977c69b0a5c15aa5fd01f817514"
}
}

Example: Span with Events and Attributes

{
"name": "user_registration",
"kind": "INTERNAL",
"startTimeUnixNano": "1732738686000000000",
"endTimeUnixNano": "1732738711528000000",
"duration": "25.28s",
"status": "OK",
"attributes": {
"provider": "email",
"validated": true,
"platform": "react-native",
"session.id": "dce09977c69b0a5c15aa5fd01f817514"
},
"events": [
{
"name": "validation_started",
"timeUnixNano": "1732738690000000000"
},
{
"name": "validation_complete",
"timeUnixNano": "1732738695000000000",
"attributes": {
"errors": 0
}
},
{
"name": "email_sent",
"timeUnixNano": "1732738700000000000",
"attributes": {
"provider": "sendgrid"
}
}
]
}

Note: All custom spans automatically include global attributes (service, device, OS, session, network, etc.). See Global Attributes for the complete list.

Best Practices

Use Descriptive Names

Choose clear, specific span names that indicate what operation is being measured:

// ✅ Good
Pulse.startSpan('fetch_user_profile');
Pulse.startSpan('validate_payment_card');
Pulse.startSpan('resize_product_image');

// ❌ Avoid
Pulse.startSpan('api_call');
Pulse.startSpan('process');
Pulse.startSpan('handler');

Add Meaningful Attributes

Include context that helps with debugging and analysis:

Pulse.startSpan('database_query', {
attributes: {
table: 'orders',
operation: 'select',
userId: '12345',
limit: 100
}
});

Always End Spans

Ensure spans are properly closed, especially when using manual control:

const span = Pulse.startSpan('critical_operation');

try {
await performOperation();
span.end(SpanStatusCode.OK);
} catch (error) {
span.recordException(error);
span.end(SpanStatusCode.ERROR);
throw error;
}

Record Exceptions

Capture errors within spans for better debugging:

try {
await riskyOperation();
} catch (error) {
span.recordException(error);
span.end(SpanStatusCode.ERROR);
}

Use Nested Spans for Complex Operations

Break down complex operations into logical steps:

const checkoutSpan = Pulse.startSpan('checkout');

const validateSpan = Pulse.startSpan('validate_cart');
// validation logic
validateSpan.end();

const paymentSpan = Pulse.startSpan('process_payment');
// payment logic
paymentSpan.end();

checkoutSpan.end(SpanStatusCode.OK);