autotel
Version:
Write Once, Observe Anywhere
1 lines • 14.3 kB
Source Map (JSON)
{"version":3,"file":"metric.cjs","names":["getConfig"],"sources":["../src/metric.ts"],"sourcesContent":["/**\n * Metrics API for OpenTelemetry\n *\n * Track business metrics for OpenTelemetry (Prometheus/Grafana).\n * For business people who think in metrics.\n *\n * @example Track business metrics\n * ```typescript\n * const metrics = new Metric('checkout')\n *\n * // Track events as metrics\n * metrics.trackEvent('order.completed', {\n * amount: 99.99,\n * currency: 'USD'\n * })\n *\n * // Track conversion funnels\n * metrics.trackFunnelStep('checkout', 'started', { cartValue: 99.99 })\n * metrics.trackFunnelStep('checkout', 'completed', { cartValue: 99.99 })\n *\n * // Track outcomes\n * metrics.trackOutcome('payment.process', 'success', { amount: 99.99 })\n * metrics.trackOutcome('payment.process', 'failure', { error: 'insufficient_funds' })\n *\n * // Track values\n * metrics.trackValue('revenue', 149.99, { currency: 'USD' })\n * ```\n */\n\nimport {\n type Counter,\n type Histogram,\n type Attributes,\n} from '@opentelemetry/api';\nimport { getConfig } from './config';\nimport { type Logger } from './logger';\nimport {\n type EventAttributes,\n type FunnelStatus,\n type OutcomeStatus,\n} from './event-subscriber';\nimport { type MetricsCollector } from './metric-testing';\n\n// Re-export types for convenience\nexport type {\n EventAttributes,\n FunnelStatus,\n OutcomeStatus,\n} from './event-subscriber';\n\n/**\n * Metrics class for tracking business metrics in OpenTelemetry\n *\n * Track critical business indicators such as:\n * - User events (signups, purchases, feature usage) as metrics\n * - Conversion funnels (signup → activation → purchase)\n * - Business outcomes (success/failure rates)\n * - Value metrics (revenue, counts, etc.)\n *\n * All metrics are sent to OpenTelemetry (OTLP/Prometheus/Grafana).\n */\n/**\n * Metric configuration for customizing metric names and descriptions\n */\nexport interface MetricConfig {\n /** Metric name (e.g., 'metrics.events' or 'custom.events') */\n name?: string;\n /** Metric description */\n description?: string;\n /** Metric unit (default: '1') */\n unit?: string;\n}\n\n/**\n * Metrics options\n */\nexport interface MetricsOptions {\n /** Optional logger for audit trail */\n logger?: Logger;\n /** Optional collector for testing (captures metrics in memory) */\n collector?: MetricsCollector;\n\n /**\n * Namespace for metrics (default: 'metrics')\n * Results in metrics like: {serviceName}.{namespace}.events\n */\n namespace?: string;\n\n /**\n * Custom metric configurations\n * Override metric names, descriptions, and units\n */\n metrics?: {\n events?: MetricConfig;\n funnel?: MetricConfig;\n outcomes?: MetricConfig;\n value?: MetricConfig;\n };\n}\n\nexport class Metric {\n private serviceName: string;\n private eventCounter: Counter;\n private funnelCounter: Counter;\n private outcomeCounter: Counter;\n private valueHistogram: Histogram;\n private logger?: Logger;\n private collector?: MetricsCollector;\n\n /**\n * Create a new Metrics instance\n *\n * @param serviceName - Service name for metric namespacing\n * @param options - Optional configuration (logger, collector, namespace, metrics)\n *\n * @example Basic usage (default 'metrics' namespace)\n * ```typescript\n * const metrics = new Metric('checkout');\n * // Creates: checkout.metrics.events, checkout.metrics.funnel, etc.\n * ```\n *\n * @example Custom namespace\n * ```typescript\n * const metrics = new Metric('api', { namespace: 'business' });\n * // Creates: api.business.events, api.business.funnel, etc.\n * ```\n *\n * @example Custom metric names and descriptions\n * ```typescript\n * const metrics = new Metric('payments', {\n * metrics: {\n * outcomes: {\n * name: 'payments.transactions',\n * description: 'Payment transaction outcomes',\n * unit: 'transactions'\n * },\n * value: {\n * name: 'payments.revenue',\n * description: 'Payment revenue in USD',\n * unit: 'USD'\n * }\n * }\n * });\n * ```\n */\n constructor(serviceName: string, options: MetricsOptions = {}) {\n this.serviceName = serviceName;\n this.logger = options.logger;\n this.collector = options.collector;\n\n const config = getConfig();\n const meter = config.meter;\n\n // Default namespace and metric configurations\n const namespace = options.namespace || 'metrics';\n const metricsConfig = options.metrics || {};\n\n // Event counter configuration\n const eventsConfig = metricsConfig.events || {};\n this.eventCounter = meter.createCounter(\n eventsConfig.name || `${serviceName}.${namespace}.events`,\n {\n description: eventsConfig.description || 'Count of business events',\n unit: eventsConfig.unit || '1',\n },\n );\n\n // Funnel counter configuration\n const funnelConfig = metricsConfig.funnel || {};\n this.funnelCounter = meter.createCounter(\n funnelConfig.name || `${serviceName}.${namespace}.funnel`,\n {\n description: funnelConfig.description || 'Conversion funnel tracking',\n unit: funnelConfig.unit || '1',\n },\n );\n\n // Outcome counter configuration\n const outcomesConfig = metricsConfig.outcomes || {};\n this.outcomeCounter = meter.createCounter(\n outcomesConfig.name || `${serviceName}.${namespace}.outcomes`,\n {\n description:\n outcomesConfig.description || 'Outcome tracking (success/failure)',\n unit: outcomesConfig.unit || '1',\n },\n );\n\n // Value histogram configuration\n const valueConfig = metricsConfig.value || {};\n this.valueHistogram = meter.createHistogram(\n valueConfig.name || `${serviceName}.${namespace}.value`,\n {\n description:\n valueConfig.description || 'Value metrics (revenue, counts, etc.)',\n unit: valueConfig.unit || '1',\n },\n );\n }\n\n /**\n * Track a business event as a metric\n *\n * Use this for tracking user actions, business events, product usage as metrics:\n * - \"user.signup\"\n * - \"order.completed\"\n * - \"feature.used\"\n *\n * @example\n * ```typescript\n * // Track user signup as metric\n * metrics.trackEvent('user.signup', {\n * userId: '123',\n * plan: 'pro'\n * })\n *\n * // Track order as metric\n * metrics.trackEvent('order.completed', {\n * orderId: 'ord_123',\n * amount: 99.99\n * })\n * ```\n */\n trackEvent(eventName: string, attributes?: EventAttributes): void {\n const attrs: Attributes = {\n service: this.serviceName,\n event: eventName,\n ...attributes,\n };\n\n this.eventCounter.add(1, attrs);\n\n this.logger?.info(\n {\n event: eventName,\n attributes,\n },\n 'Metric event tracked',\n );\n\n // Record for testing\n this.collector?.recordEvent({\n event: eventName,\n attributes,\n service: this.serviceName,\n timestamp: Date.now(),\n });\n }\n\n /**\n * Track conversion funnel steps as metrics\n *\n * Monitor where users drop off in multi-step processes.\n *\n * @example\n * ```typescript\n * // Track signup funnel\n * metrics.trackFunnelStep('signup', 'started', { userId: '123' })\n * metrics.trackFunnelStep('signup', 'email_verified', { userId: '123' })\n * metrics.trackFunnelStep('signup', 'completed', { userId: '123' })\n *\n * // Track checkout flow\n * metrics.trackFunnelStep('checkout', 'started', { cartValue: 99.99 })\n * metrics.trackFunnelStep('checkout', 'payment_info', { cartValue: 99.99 })\n * metrics.trackFunnelStep('checkout', 'completed', { cartValue: 99.99 })\n * ```\n */\n trackFunnelStep(\n funnelName: string,\n status: FunnelStatus,\n attributes?: EventAttributes,\n ): void {\n const attrs: Attributes = {\n service: this.serviceName,\n funnel: funnelName,\n status,\n ...attributes,\n };\n\n this.funnelCounter.add(1, attrs);\n\n this.logger?.info(\n {\n funnel: funnelName,\n status,\n attributes,\n },\n 'Funnel step tracked',\n );\n\n // Record for testing\n this.collector?.recordFunnelStep({\n funnel: funnelName,\n status,\n attributes,\n service: this.serviceName,\n timestamp: Date.now(),\n });\n }\n\n /**\n * Track outcomes (success/failure/partial) as metrics\n *\n * Monitor success rates of critical operations.\n *\n * @example\n * ```typescript\n * // Track email delivery\n * metrics.trackOutcome('email.delivery', 'success', {\n * recipientType: 'user',\n * emailType: 'welcome'\n * })\n *\n * metrics.trackOutcome('email.delivery', 'failure', {\n * recipientType: 'user',\n * errorCode: 'invalid_email'\n * })\n *\n * // Track payment processing\n * metrics.trackOutcome('payment.process', 'success', { amount: 99.99 })\n * metrics.trackOutcome('payment.process', 'failure', { error: 'insufficient_funds' })\n * ```\n */\n trackOutcome(\n operationName: string,\n status: OutcomeStatus,\n attributes?: EventAttributes,\n ): void {\n const attrs: Attributes = {\n service: this.serviceName,\n operation: operationName,\n status,\n ...attributes,\n };\n\n this.outcomeCounter.add(1, attrs);\n\n this.logger?.info(\n {\n operation: operationName,\n status,\n attributes,\n },\n 'Outcome tracked',\n );\n\n // Record for testing\n this.collector?.recordOutcome({\n operation: operationName,\n status,\n attributes,\n service: this.serviceName,\n timestamp: Date.now(),\n });\n }\n\n /**\n * Track value metrics\n *\n * Record numerical values like revenue, transaction amounts,\n * item counts, processing times, engagement scores, etc.\n *\n * @example\n * ```typescript\n * // Track revenue\n * metrics.trackValue('order.revenue', 149.99, {\n * currency: 'USD',\n * productCategory: 'electronics'\n * })\n *\n * // Track items per cart\n * metrics.trackValue('cart.item_count', 5, {\n * userId: '123'\n * })\n *\n * // Track processing time\n * metrics.trackValue('api.response_time', 250, {\n * unit: 'ms',\n * endpoint: '/api/checkout'\n * })\n * ```\n */\n trackValue(\n metricName: string,\n value: number,\n attributes?: EventAttributes,\n ): void {\n const attrs: Attributes = {\n service: this.serviceName,\n metric: metricName,\n ...attributes,\n };\n\n this.valueHistogram.record(value, attrs);\n\n this.logger?.debug(\n {\n metric: metricName,\n value,\n attributes,\n },\n 'Value metric tracked',\n );\n\n // Record for testing\n this.collector?.recordValue({\n metric: metricName,\n value,\n attributes,\n service: this.serviceName,\n timestamp: Date.now(),\n });\n }\n}\n\n/**\n * Global metrics instances (singleton pattern)\n */\nconst metricsInstances = new Map<string, Metric>();\n\n/**\n * Get or create a Metrics instance for a service\n *\n * @param serviceName - Service name for metric namespacing\n * @param logger - Optional logger\n * @returns Metrics instance\n *\n * @example\n * ```typescript\n * const metrics = getMetrics('checkout')\n * metrics.trackEvent('order.completed', { orderId: '123' })\n * ```\n */\nexport function getMetrics(serviceName: string, logger?: Logger): Metric {\n if (!metricsInstances.has(serviceName)) {\n metricsInstances.set(serviceName, new Metric(serviceName, { logger }));\n }\n return metricsInstances.get(serviceName)!;\n}\n\n/**\n * Reset all metrics instances (mainly for testing)\n */\nexport function resetMetrics(): void {\n metricsInstances.clear();\n}\n"],"mappings":"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"}