@mukhy/mtn-telemetry-sdk
v2.2.4
Published
MTN Telemetry SDK for React and React Native applications
Maintainers
Readme
MTN Telemetry SDK
A lightweight OpenTelemetry wrapper for React and React Native applications. Collects traces, metrics, errors, and logs with minimal configuration and exports them to any OTLP-compatible backend (Elastic APM, Grafana, Jaeger, etc.).
Features
- One-call initialization for React and React Native
- Automatic instrumentation for:
- Fetch requests (HTTP method, status, URL, duration)
- React Navigation screen transitions
- AppState foreground/background transitions
- Console log/warn/error capture as OTLP log records
- Custom business event tracking (
trackEvent) - Async operation tracing with duration and error capture (
trackOperation) - Manual error recording (
recordError) - Global crash reporting for unhandled JS errors and promise rejections (
installCrashReporting) - Multi-step business journey tracking (
BusinessTracer) - Tracked UI component (
TrackedPressable) - Protobuf (binary) OTLP encoding for Elastic APM compatibility
- Debug mode with verbose OpenTelemetry diagnostic logging
- Built-in MTN SRE metadata on every telemetry entry
- TypeScript-ready with full type definitions
Requirements
| Dependency | Minimum version | | ------------ | --------------- | | React | >= 18.0.0 | | React Native | >= 0.72.0 |
Installation
npm install @mukhy/mtn-telemetry-sdkor
yarn add @mukhy/mtn-telemetry-sdkQuick Start
Create src/telemetry.ts:
import { MTNOTel, type OTelRNOptions } from '@mukhy/mtn-telemetry-sdk';
import { Platform } from 'react-native';
import DeviceInfo from 'react-native-device-info';
export async function initTelemetry(
navigationRef?: OTelRNOptions['navigationRef'],
) {
const options: OTelRNOptions = {
serviceName: `myApp-${Platform.OS === 'ios' ? 'iOS' : 'Android'}`,
environment: 'production',
debug: false,
navigationRef,
samplingRatio: 1,
release: DeviceInfo.getVersion(),
enableConsole: true,
consoleMinSeverity: 'log',
otlp: {
tracesUrl: 'https://your-apm-server:8200/v1/traces',
metricsUrl: 'https://your-apm-server:8200/v1/metrics',
logsUrl: 'https://your-apm-server:8200/v1/logs',
headers: {},
},
};
return MTNOTel.init(options);
}2. Initialize in your root component
In App.tsx:
import React, { useEffect, useRef } from 'react';
import { initTelemetry } from './telemetry';
import { installCrashReporting } from '@mukhy/mtn-telemetry-sdk';
import { navigationRef } from './routes/navigationRef';
function App() {
const telemetryRef = useRef(null);
useEffect(() => {
let uninstallCrash;
initTelemetry(navigationRef.current)
.then(instance => {
telemetryRef.current = instance;
uninstallCrash = installCrashReporting();
console.log('Telemetry SDK + crash reporting initialized');
})
.catch(err => console.warn('Telemetry init failed', err));
return () => {
uninstallCrash?.();
telemetryRef.current?.flushAndShutdown();
};
}, []);
return (
<NavigationContainer ref={navigationRef}>
{/* ... your app */}
</NavigationContainer>
);
}That's it. Fetch requests, screen transitions, AppState changes, unhandled errors, promise rejections, and console output are now automatically captured.
Configuration Options (OTelRNOptions)
| Option | Type | Default | Description |
| -------------------- | ------------------------- | -------------------- | ---------------------------------------------------------------------- |
| serviceName | string | (required) | Service name in the OpenTelemetry resource. |
| environment | string | undefined | Deployment environment tag (e.g. production, staging). |
| release | string | undefined | Release/build version identifier. |
| debug | boolean | false | When true, enables verbose OTel diagnostic logging to the console. |
| otlp | object | undefined | OTLP exporter configuration. |
| otlp.tracesUrl | string | http://localhost:4318 | OTLP HTTP endpoint for traces. |
| otlp.metricsUrl | string | undefined | OTLP HTTP endpoint for metrics. |
| otlp.logsUrl | string | undefined | OTLP HTTP endpoint for logs. |
| otlp.headers | Record<string, string> | {} | Custom headers for every OTLP export request (auth, etc.). |
| enableFetch | boolean | true | Toggle automatic fetch instrumentation. |
| enableNavigation | boolean | true | Toggle React Navigation instrumentation. |
| enableAppState | boolean | true | Toggle AppState foreground/background spans and metrics. |
| enableConsole | boolean | false | Capture console.log/warn/error and export as OTLP log records. |
| consoleMinSeverity | 'log' \| 'warn' \| 'error' | 'log' | Minimum console severity to capture (when enableConsole is true). |
| navigationRef | NavigationContainerLike | undefined | Navigation container ref to attach during init. |
| samplingRatio | number | 1.0 | Trace sampling ratio (0.0 – 1.0). |
| attributes | Record<string, any> | {} | Extra resource attributes merged into the OTel resource. |
API Reference
trackEvent(name, attributes?, isError?)
Record a discrete business event as a span. The span is created and immediately ended.
import { trackEvent } from '@mukhy/mtn-telemetry-sdk';
// Successful event
trackEvent('purchase.completed', {
'purchase.product_name': '75MB Daily Plan',
'purchase.amount': 75,
'purchase.transaction_id': 'TXN-12345',
'purchase.payment_method': 'Airtime',
});
// Error event (sets span status to ERROR)
trackEvent('purchase.failed', {
'purchase.product_name': '75MB Daily Plan',
'purchase.error': 'Insufficient balance',
}, true);| Param | Type | Description |
| ------------ | ----------------------------------------- | ------------------------------------------ |
| name | string | Event/span name. |
| attributes | Record<string, string\|number\|boolean> | Key-value pairs attached to the span. |
| isError | boolean | If true, marks the span status as ERROR. |
trackOperation(name, attributes, fn)
Wrap an async function and measure it as a span. Captures duration, result, and any thrown errors automatically.
import { trackOperation } from '@mukhy/mtn-telemetry-sdk';
const result = await trackOperation(
'auth.login_with_otp',
{ 'auth.method': 'sms', 'auth.phone': '+234...' },
() => AuthService.loginWithSMS(phone, otp),
);| Param | Type | Description |
| ------------ | ----------------------------------------- | ------------------------------------------ |
| name | string | Operation/span name. |
| attributes | Record<string, string\|number\|boolean> | Key-value pairs attached to the span. |
| fn | () => Promise<T> | The async function to execute and measure. |
Returns: The resolved value of fn(). If fn() throws, the error is recorded on the span and re-thrown.
recordError(error, attributes?)
Manually record a caught error as a traced span with stack trace.
import { recordError } from '@mukhy/mtn-telemetry-sdk';
try {
await riskyOperation();
} catch (err) {
recordError(err, { screen: 'PaymentSummary', step: 'subscription_api' });
}| Param | Type | Description |
| ------------ | ----------------------------------------- | -------------------------------------------- |
| error | unknown | The error or exception to record. |
| attributes | Record<string, string\|number\|boolean> | Extra context attached to the error span. |
installCrashReporting()
Install global handlers that automatically capture:
- Unhandled JavaScript errors (via React Native's
ErrorUtils) - Unhandled promise rejections
Call once after MTNOTel.init(). Returns a cleanup function.
import { installCrashReporting } from '@mukhy/mtn-telemetry-sdk';
const uninstall = installCrashReporting();
// On unmount:
uninstall();BusinessTracer
Track multi-step user journeys (e.g. onboarding, purchase flow).
import { BusinessTracer } from '@mukhy/mtn-telemetry-sdk';
BusinessTracer.start('PurchaseFlow', { 'bundle.category': 'DataPlan' });
BusinessTracer.step('BundleSelected', { 'bundle.name': '75MB Daily Plan' });
BusinessTracer.step('PaymentAuthorized', { 'payment.method': 'Airtime' });
BusinessTracer.complete({ 'purchase.status': 'success', 'transaction.id': 'TXN-123' });| Method | Parameters | Description |
| ---------- | ----------------------------- | --------------------------------- |
| start | (journeyName, attributes?) | Begin a named journey. |
| step | (stepName, attributes?) | Record a step within the journey. |
| complete | (attributes?) | Mark the journey as complete. |
TrackedPressable
A drop-in replacement for React Native's Pressable that automatically records user taps as business steps.
import { TrackedPressable } from '@mukhy/mtn-telemetry-sdk';
<TrackedPressable
accessibilityLabel="Buy Now"
onPress={() => handleBuyNow()}
>
<Text>Buy Now</Text>
</TrackedPressable>Each press creates a tap:Buy Now business step span.
startActiveSpan(name, fn, kind?)
Create a custom active span for full manual control. The span is automatically ended and errors are captured.
import { startActiveSpan } from '@mukhy/mtn-telemetry-sdk';
const data = await startActiveSpan('fetchUserProfile', async (span) => {
span.setAttribute('user.id', userId);
const response = await fetch(`/api/users/${userId}`);
return response.json();
});Built-in Instrumentations
| Instrumentation | What it captures |
| -------------------- | ----------------------------------------------------------------------------------------- |
| Fetch | Every fetch() call: method, URL, status code, duration. OTLP export URLs auto-excluded. |
| React Navigation | Screen transitions: route name, duration on each screen. |
| AppState | Foreground/background transitions as spans and metrics. |
| Console | console.log/warn/error output as OTLP log records with trace context correlation. |
Built-in Metadata
Every telemetry entry automatically includes the following resource attributes:
| Field (Kibana) | Value | Description |
| ----------------------- | -------------------------- | -------------------------------- |
| labels.agentName | mtn-apm-agent | Agent identifier |
| labels.maintainer | mtn-sre-team | Maintaining team |
| labels.coreMaintainer | [email protected]| Core maintainer contact |
| service.name | e.g. myMTNNG-Android | Service name from config |
| service.environment | e.g. production | Environment from config |
| service.version | App version | From DeviceInfo.getVersion() |
| labels.device_model | e.g. sdk_gphone64_arm64 | Device model |
| labels.device_os | e.g. Android 16 | OS name and version |
| labels.app_bundle | e.g. ng.mtn.nextgen | App bundle ID |
| labels.app_build | Build number | App build number |
| labels.app_isEmulator | true / false | Whether running on emulator |
Elastic APM Notes
Protobuf Encoding
The SDK uses protobuf (binary) OTLP encoding and XMLHttpRequest + Blob for reliable binary transport in React Native. This is required for Elastic APM servers that only accept application/x-protobuf payloads.
Authentication
If your Elastic APM server requires authentication:
otlp: {
tracesUrl: 'http://your-apm-server:8200/v1/traces',
headers: {
Authorization: 'Basic ' + btoa('elastic:your-password'),
},
},Android Cleartext HTTP
If your APM server uses http:// (not https://), add the server's IP to your Android network security config:
<!-- android/app/src/main/res/xml/network_security_config.xml -->
<network-security-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">10.1.153.199</domain>
</domain-config>
</network-security-config>What Shows Up in Elastic APM
| Data Type | Source | Kibana Index | Example |
| ------------------ | --------------------------- | ------------------ | --------------------------------------------- |
| Transactions | Auto fetch instrumentation | traces-apm* | HTTP GET https://api.example.com/v1/data |
| Transactions | Auto navigation | traces-apm* | Screen: HomeScreen |
| Spans | trackEvent | traces-apm* | purchase.completed, bundle.selected |
| Spans | trackOperation | traces-apm* | auth.login_with_otp (with duration) |
| Errors | recordError | logs-apm.error* | Caught exceptions with stack trace |
| Errors | installCrashReporting | logs-apm.error* | Unhandled JS errors and promise rejections |
| Errors | Auto fetch instrumentation | logs-apm.error* | Network request failures |
| Metrics | AppState instrumentation | Metrics index | App foreground/background time |
| Logs | Console instrumentation | logs-apm.app* | All console.log/warn/error output |
Kibana KQL Queries
| What | Query |
| -------------------------- | --------------------------------------------------------------------------------------- |
| All business events | service.framework.name: "mtn-sdk:events" |
| All SDK errors | service.framework.name: "mtn-sdk:errors" |
| Login attempts | transaction.name: "auth.login_with_otp" |
| Bundle selections | transaction.name: "bundle.selected" |
| Purchase completed | transaction.name: "purchase.completed" |
| Purchase failed | transaction.name: "purchase.failed" |
| Crash reports | labels.error_source: "globalHandler" OR labels.error_source: "unhandledRejection" |
| All SDK data | labels.agentName: "mtn-apm-agent" |
| Filter out dev noise | NOT url.domain: "10.0.2.2" AND NOT url.domain: "play.google.com" |
Shutdown
MTNOTel.init() returns a singleton instance with a flushAndShutdown() method that flushes any pending telemetry and cleans up resources.
const telemetry = await MTNOTel.init({ serviceName: 'my-app' });
// When the app is closing or during tests:
await telemetry.flushAndShutdown();Publishing
The SDK is published to npm as @mukhy/mtn-telemetry-sdk.
# Bump version in package.json, then:
npm run prepare # cleans and builds
npm publishContributing
- Fork the repo
- Create a feature branch:
git checkout -b feature/my-feature - Commit your changes
- Push and open a PR
