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);
Related
- Network Instrumentation - Automatic HTTP request tracking
- Error Instrumentation - Exception and crash tracking
- Navigation Instrumentation - Screen transition tracking