UNPKG

@salesforce/o11y-reporter

Version:

A wrapper service around o11y and o11y_schema for telemetry reporting.

315 lines (235 loc) 10.5 kB
# 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 ```typescript 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. ```typescript 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 ```typescript // 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`:** ```json { "dependencies": { "o11y_schema": "^256.154.0" } } ``` **Step 2: Import and use the schema with `logEventWithSchema(properties, schema)` (exactly two parameters):** ```typescript 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:** ```typescript 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. ```typescript // 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: ```typescript // 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: ```typescript 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: ```typescript 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:** ```typescript // 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 1. **Initialize Early**: Initialize the service as early as possible in your extension's lifecycle. 2. **Error Handling**: Always wrap telemetry calls in try-catch blocks to prevent them from affecting your main application flow. 3. **Property Naming**: Use consistent property names and avoid sending sensitive information. 4. **Enable Automatic Batching**: Use `enableAutoBatching()` to automatically batch and upload events efficiently. This is recommended for most use cases. 5. **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: ```typescript 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](LICENSE.txt) file for details.