@salesforce/o11y-reporter
v1.8.4
Published
A wrapper service around o11y and o11y_schema for telemetry reporting.
Downloads
1,522,859
Readme
O11y Reporter
A lightweight telemetry reporting service for Salesforce extensions that enables sending metrics and events to Salesforce's observability platform.
Usage
The O11y Reporter service provides a simple way to send telemetry events and metrics to Salesforce's observability platform. Here's how to use it:
1. Initialize the Service
import { O11yService } from "@salesforce/o11y-reporter";
// Get the singleton instance
const o11yService = O11yService.getInstance(extensionName);
// Initialize with your extension name and upload endpoint (static endpoint only)
await o11yService.initialize(
"your-extension-name",
"https://your-upload-endpoint",
);Dynamic endpoint (authenticated org): To send telemetry to the current org’s endpoint (with automatic fallback to the static endpoint if the connection is unavailable), pass an optional getConnection callback and, if needed, an options object with dynamicO11yUploadEndpointPath. If you omit the path, a default is used.
import { O11yService, type Connection } from "@salesforce/o11y-reporter";
const o11yService = O11yService.getInstance(extensionName);
await o11yService.initialize(
"your-extension-name",
"https://fallback-static-endpoint",
() => yourWorkspaceContext.getConnection(),
{ dynamicO11yUploadEndpointPath: "/your/telemetry/path" },
);The Connection type is re-exported from @salesforce/core; depend on it when using getConnection.
2. Send Telemetry Events
Using Default Schema
// Send a telemetry event with properties (uses default sf_a4dInstrumentation schema)
o11yService.logEvent({
name: "extension/eventName",
properties: {
// Add your custom properties here
customProperty: "value",
},
measurements: {
// Optional measurements
duration: 100,
},
});
// Send an exception event
o11yService.logEvent({
exception: new Error("Error message"),
properties: {
// Add your custom properties here
errorType: "RuntimeError",
},
measurements: {
// Optional measurements
errorCount: 1,
},
});Using Custom Schema
The service supports consumer-provided O11y schemas. This allows you to use custom schemas from the o11y_schema package instead of the default sf_a4dInstrumentation schema.
Step 1: Add o11y_schema to your package.json:
{
"dependencies": {
"o11y_schema": "^256.154.0"
}
}Step 2: Import and use the schema with logEventWithSchema(properties, schema) (exactly two parameters):
import { O11yService } from "@salesforce/o11y-reporter";
// Import the schema object from o11y_schema
// @ts-expect-error - o11y_schema package doesn't provide TypeScript declarations
import { a4dInstrumentationSchema } from "o11y_schema/sf_a4dInstrumentation";
const o11yService = O11yService.getInstance("your-extension");
await o11yService.initialize("your-extension", "https://your-endpoint");
// Two parameters only: (properties, schema). Shape of properties depends on your schema.
o11yService.logEventWithSchema(
{ customProperty: "value" },
a4dInstrumentationSchema,
);
// You can also use different schemas for different events
// @ts-expect-error - o11y_schema package doesn't provide TypeScript declarations
import { anotherSchema } from "o11y_schema/another_schema";
o11yService.logEventWithSchema({ foo: "bar" }, anotherSchema);Note: logEventWithSchema(properties, schema) takes exactly two parameters: the event properties object and the schema. You must provide a valid schema object (e.g. Record<string, unknown>). If you don't need a custom schema, use logEvent(properties) which uses the default schema.
Behavior: Events sent with logEventWithSchema(properties, schema) are encoded using the schema you pass (e.g. PdpEvent, a4dInstrumentationSchema). They are not wrapped in the default A4dInstrumentation schema; the payload uses your schema's format directly (e.g. sf.pdp.PdpEvent in the binary payload).
Note: The o11y_schema package doesn't provide TypeScript declarations. You may need to add @ts-expect-error comments or configure your TypeScript with skipLibCheck: true in tsconfig.json.
With automatic batching enabled, events are automatically uploaded based on threshold (50KB) or periodic flush (30 seconds). You can also manually flush if needed:
await o11yService.forceFlush();3. Enable Automatic Batching
The service supports automatic batching of events for efficient telemetry collection. Events are automatically buffered and uploaded based on size threshold or periodic flush intervals.
// Enable automatic batching with default settings (30-second flush interval)
const cleanup = o11yService.enableAutoBatching();
// Or customize the batching behavior
const cleanup = o11yService.enableAutoBatching({
flushInterval: 30_000, // 30 seconds (default)
enableShutdownHook: true, // Enable shutdown hooks (default: true)
enableBeforeExitHook: true, // Enable beforeExit hook (default: true)
});
// Later, if you need to stop batching
cleanup();Batching Behavior:
- Events are buffered in memory until upload conditions are met
- Threshold-based upload: Events are uploaded when buffer size reaches 50KB
- Periodic flush: Events are automatically flushed every 30 seconds (configurable)
- Shutdown hooks: Events are automatically flushed on application shutdown (SIGINT, SIGTERM, beforeExit)
Benefits:
- Reduces network overhead by batching multiple events
- Improves performance by avoiding per-event uploads
- Ensures events are not lost on application shutdown
4. Manual Flush
You can manually flush buffered events at any time:
// Force an immediate flush of all buffered events
await o11yService.forceFlush();
// Or use the upload method (alias for forceFlush when batching is enabled)
await o11yService.upload();5. Check Batch Status
Monitor the current batch status:
const status = o11yService.getBatchStatus();
console.log(`Buffer size: ${status.estimatedByteSize} bytes`);
console.log(`Has data: ${status.hasData}`);
console.log(`Over threshold: ${status.isOverThreshold}`);Configuration
The service can be configured with the following options:
o11yUploadEndpoint: The endpoint URL for uploading telemetry data
Batching Options
When enabling automatic batching, you can configure the following options:
interface BatchingOptions {
/** Periodic flush interval in milliseconds (default: 30000) */
flushInterval?: number;
/** Buffer size threshold in bytes before triggering upload (default: 50000 = 50KB) */
thresholdBytes?: number;
/** Threshold check interval in milliseconds (default: 2000) */
checkInterval?: number;
/** Enable shutdown hooks (default: true) */
enableShutdownHook?: boolean;
/** Enable beforeExit hook (default: true). Note: beforeExit won't fire for STDIO servers where stdin stays open */
enableBeforeExitHook?: boolean;
}Configuration Examples:
// Default configuration (30-second flush, shutdown hooks enabled)
o11yService.enableAutoBatching();
// Custom flush interval (60 seconds)
o11yService.enableAutoBatching({ flushInterval: 60_000 });
// Disable shutdown hooks (not recommended)
o11yService.enableAutoBatching({ enableShutdownHook: false });
// Disable beforeExit hook (useful for STDIO servers)
o11yService.enableAutoBatching({ enableBeforeExitHook: false });Best Practices
Initialize Early: Initialize the service as early as possible in your extension's lifecycle.
Error Handling: Always wrap telemetry calls in try-catch blocks to prevent them from affecting your main application flow.
Property Naming: Use consistent property names and avoid sending sensitive information.
Enable Automatic Batching: Use
enableAutoBatching()to automatically batch and upload events efficiently. This is recommended for most use cases.Manual Flush for Critical Events: For critical events that need immediate upload, call
forceFlush()after logging the event.
Example Implementation
Here's a complete example of how to use the O11y Reporter in your extension:
import { O11yService } from "@salesforce/o11y-reporter";
class YourExtension {
private o11yService: O11yService;
constructor() {
this.o11yService = O11yService.getInstance(extensionName);
}
async initialize() {
await this.o11yService.initialize(
"your-extension",
"https://your-endpoint",
);
// Enable automatic batching for efficient telemetry collection
this.o11yService.enableAutoBatching({
flushInterval: 30_000, // 30 seconds
enableShutdownHook: true, // Flush on shutdown
});
}
async trackUserAction(actionName: string, properties: Record<string, any>) {
try {
// Using default schema
this.o11yService.logEvent({
name: `user/${actionName}`,
properties: {
...properties,
timestamp: new Date().toISOString(),
},
});
// With batching enabled, events are automatically uploaded
// For critical events, you can force an immediate flush:
await this.o11yService.forceFlush();
} catch (error) {
// Log error but don't throw
console.error("Failed to send telemetry:", error);
}
}
async trackUserActionWithSchema(
actionName: string,
properties: Record<string, any>,
schema: Record<string, unknown>,
) {
try {
// Two parameters: caller's properties (conform to schema) and schema.
this.o11yService.logEventWithSchema(properties, schema);
// With batching enabled, events are automatically uploaded
await this.o11yService.forceFlush();
} catch (error) {
console.error("Failed to send telemetry:", error);
}
}
async trackError(error: Error, context: Record<string, any>) {
try {
this.o11yService.logEvent({
exception: error,
properties: {
...context,
timestamp: new Date().toISOString(),
},
});
await this.o11yService.upload();
} catch (err) {
console.error("Failed to send error telemetry:", err);
}
}
}License
This project is licensed under the Terms of Use. See the LICENSE.txt file for details.
