@delta-base/observability
Version:
Observability framework for delta-base applications
1 lines • 137 kB
Source Map (JSON)
{"version":3,"sources":["../package.json","../src/config.ts","../src/wide-events.ts","../src/index.ts","../src/middleware.ts","../src/tracing.ts","../src/utils.ts","../src/error-handling.ts"],"sourcesContent":["{\n \"name\": \"@delta-base/observability\",\n \"version\": \"0.0.2\",\n \"description\": \"Observability framework for delta-base applications\",\n \"main\": \"dist/index.js\",\n \"module\": \"dist/index.mjs\",\n \"types\": \"dist/index.d.ts\",\n \"exports\": {\n \".\": {\n \"types\": \"./dist/index.d.ts\",\n \"import\": \"./dist/index.mjs\",\n \"require\": \"./dist/index.js\"\n },\n \"./scripts/*\": {\n \"require\": \"./dist/scripts/*.js\"\n }\n },\n \"bin\": {\n \"collect-build-info\": \"./dist/scripts/collect-build-info.js\"\n },\n \"files\": [\n \"dist\",\n \"LICENSE\",\n \"README.md\"\n ],\n \"scripts\": {\n \"typecheck\": \"tsc --noEmit\",\n \"build\": \"tsup\",\n \"dev\": \"tsup --watch\",\n \"test\": \"vitest run --passWithNoTests\",\n \"test:watch\": \"vitest\",\n \"prepublishOnly\": \"pnpm run build\"\n },\n \"keywords\": [\n \"delta-base\",\n \"observability\",\n \"opentelemetry\",\n \"tracing\",\n \"monitoring\",\n \"logging\"\n ],\n \"author\": \"Nibbio LLC\",\n \"license\": \"LicenseRef-LICENSE\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/nibbio-co/delta-base.git\",\n \"directory\": \"packages/libs/deltabase/observability\"\n },\n \"dependencies\": {\n \"@delta-base/toolkit\": \"workspace:*\",\n \"@opentelemetry/api\": \"^1.9.0\",\n \"@opentelemetry/sdk-trace-base\": \"^1.30.1\",\n \"hono\": \"catalog:\",\n \"zod\": \"catalog:\"\n },\n \"devDependencies\": {\n \"@types/node\": \"^20.0.0\",\n \"@hono/zod-openapi\": \"catalog:\",\n \"tsup\": \"^8.3.6\",\n \"typescript\": \"^5.0.0\",\n \"vitest\": \"^3.1.1\"\n },\n \"peerDependencies\": {\n \"@hono/zod-openapi\": \"^0.17.1\"\n },\n \"peerDependenciesMeta\": {\n \"@hono/zod-openapi\": {\n \"optional\": true\n }\n },\n \"engines\": {\n \"node\": \">=16.0.0\"\n },\n \"publishConfig\": {\n \"access\": \"public\"\n },\n \"sideEffects\": false\n}\n","import packageJson from '../package.json';\nimport type { WideEventsWrapper } from './wide-events';\n\n/**\n * Detect the current environment using standard Node.js patterns\n */\nfunction detectEnvironment(): string {\n // Check NODE_ENV first (standard Node.js environment variable)\n if (process.env.NODE_ENV) {\n return process.env.NODE_ENV;\n }\n\n // Check for Cloudflare environment indicators\n if (process.env.CF_ENVIRONMENT) {\n return process.env.CF_ENVIRONMENT;\n }\n\n // Check for other common environment indicators\n if (process.env.ENVIRONMENT) {\n return process.env.ENVIRONMENT;\n }\n\n // Check for development indicators\n if (\n process.env.NODE_ENV === undefined &&\n (process.env.DEVELOPMENT || process.env.DEV)\n ) {\n return 'development';\n }\n\n // Default to development if no environment is detected\n return 'development';\n}\n\n/**\n * Configuration for context schema - defines which fields are tracked for each category\n */\nexport interface ContextSchemaConfig {\n /** User-related context fields */\n user: string[];\n /** Entity-related context fields */\n entity: string[];\n /** Operation-related context fields */\n operation: string[];\n /** Infrastructure-related context fields */\n infrastructure: string[];\n /** Error-related context fields */\n error: string[];\n /** HTTP-related context fields */\n http: string[];\n /** Performance/timing-related context fields */\n performance: string[];\n /** Service-related context fields */\n service: string[];\n}\n\n/**\n * Error response pattern for automatic error detection and categorization\n */\nexport interface ErrorResponsePattern {\n /** Human-readable name for the error pattern */\n name: string;\n /** Function that tests if a response body matches this error pattern */\n matcher: (body: any) => boolean;\n /** Function that tracks the error with appropriate context and categorization */\n tracker: (wideEvent: WideEventsWrapper, body: any) => void;\n}\n\n/**\n * Tracing configuration options\n */\nexport interface TracingConfig {\n /** Whether to enable OpenTelemetry tracing */\n enabled: boolean;\n /** Service name for tracing */\n serviceName?: string;\n /** Service version for tracing */\n serviceVersion?: string;\n /** Environment name */\n environment?: string;\n /** Custom attributes to add to all spans */\n defaultAttributes?: Record<string, string | number | boolean>;\n}\n\n/**\n * HTTP tracking configuration\n */\nexport interface HttpTrackingConfig {\n /** Whether to automatically track HTTP request details */\n trackRequests: boolean;\n /** Whether to automatically track HTTP response details */\n trackResponses: boolean;\n /** Whether to track request/response bodies */\n trackBodies: boolean;\n /** Maximum body size to track (in bytes) */\n maxBodySize: number;\n /** Headers to exclude from tracking */\n excludeHeaders: string[];\n /** Headers to include in tracking (if specified, only these will be tracked) */\n includeHeaders?: string[];\n}\n\n/**\n * Error tracking configuration\n */\nexport interface ErrorTrackingConfig {\n /** Whether to automatically detect and track error responses */\n autoTrackErrorResponses: boolean;\n /** Whether to automatically categorize errors as expected/unexpected */\n autoCategorizeErrors: boolean;\n /** Custom error response patterns */\n errorPatterns: ErrorResponsePattern[];\n /** Default error categorization for unknown errors */\n defaultUnexpected: boolean;\n}\n\n/**\n * Performance tracking configuration\n */\nexport interface PerformanceConfig {\n /** Whether to automatically track operation timing */\n trackTiming: boolean;\n /** Whether to categorize performance (fast, slow, etc.) */\n categorizePerformance: boolean;\n /** Thresholds for performance categorization (in milliseconds) */\n performanceThresholds: {\n fast: number;\n slow: number;\n };\n /** Whether to automatically create spans for common operations */\n autoCreateSpans: boolean;\n}\n\n/**\n * Comprehensive observability configuration\n */\nexport interface ObservabilityConfig {\n /** Context schema configuration */\n contextSchema: ContextSchemaConfig;\n /** Tracing configuration */\n tracing: TracingConfig;\n /** HTTP tracking configuration */\n http: HttpTrackingConfig;\n /** Error tracking configuration */\n errors: ErrorTrackingConfig;\n /** Performance tracking configuration */\n performance: PerformanceConfig;\n /** Whether to log debug information */\n debug: boolean;\n}\n\n/**\n * Simplified options interface for backward compatibility\n */\nexport interface ObservabilityOptions {\n /** Custom service name (defaults to environment variable SERVICE_NAME) */\n serviceName?: string;\n /** Whether to automatically track HTTP request/response (defaults to true) */\n autoTrackHttp?: boolean;\n /** Whether to automatically detect and track error responses (defaults to true) */\n autoTrackErrorResponses?: boolean;\n /** Whether to enable debug logging (defaults to false) */\n debug?: boolean;\n /** Build info to initialize (optional) */\n buildInfo?: Record<string, string | number | boolean>;\n}\n\n/**\n * Default context schema configuration\n */\nexport const DEFAULT_CONTEXT_SCHEMA: ContextSchemaConfig = {\n user: ['id', 'email', 'first_name', 'last_name', 'actor_type'],\n entity: ['type', 'id', 'status'],\n operation: ['type', 'name', 'domain', 'layer'],\n infrastructure: [\n 'event_store.stream_id',\n 'event_store.expected_version',\n 'event_store.next_version',\n 'event_store.created_new_stream',\n 'database.operation',\n 'database.table',\n 'database.duration_ms',\n 'external_service.name',\n 'external_service.operation',\n 'external_service.duration_ms',\n ],\n error: [\n 'type',\n 'message',\n 'unexpected',\n 'validation.issue_count',\n 'validation.fields',\n 'business_rule.rule',\n 'business_rule.violation',\n ],\n http: [\n 'method',\n 'path',\n 'status_code',\n 'user_agent',\n 'content_type',\n 'content_length',\n ],\n performance: [\n 'duration_ms',\n 'category',\n 'timing.total_ms',\n 'timing.domain_ms',\n 'timing.validation_ms',\n 'timing.event_store_ms',\n 'timing.database_ms',\n ],\n service: ['name', 'version', 'environment', 'build_info'],\n};\n\n/**\n * Default error response patterns\n */\nexport const DEFAULT_ERROR_PATTERNS: ErrorResponsePattern[] = [\n {\n name: 'OpenAPIHono_ZodValidation',\n matcher: (body) =>\n body?.success === false &&\n body?.error?.name === 'ZodError' &&\n Array.isArray(body?.error?.issues),\n tracker: (wideEvent, body) => {\n wideEvent.trackError({\n type: 'ValidationError',\n message: 'Request validation failed',\n validation: {\n issueCount: body.error.issues.length,\n fields: body.error.issues.map((issue: any) =>\n Array.isArray(issue.path)\n ? issue.path.join('.')\n : String(issue.path)\n ),\n },\n unexpected: false, // Expected - user submitted invalid data\n });\n },\n },\n {\n name: 'BusinessValidation',\n matcher: (body) =>\n body?.success === false &&\n typeof body?.message === 'string' &&\n !body?.error, // No nested error object\n tracker: (wideEvent, body) => {\n wideEvent.trackError({\n type: 'BusinessValidationError',\n message: body.message,\n businessRule: {\n rule: 'business_validation',\n violation: body.message,\n },\n unexpected: false, // Expected - business rule violation\n });\n },\n },\n {\n name: 'GenericError',\n matcher: (body) =>\n body?.success === false ||\n body?.error ||\n typeof body?.message === 'string',\n tracker: (wideEvent, body) => {\n wideEvent.trackError({\n type: 'GenericError',\n message: body?.message || body?.error?.message || 'Unknown error',\n unexpected: true, // Unexpected - could be system issue\n });\n },\n },\n];\n\n/**\n * Default observability configuration\n */\nexport const DEFAULT_OBSERVABILITY_CONFIG: ObservabilityConfig = {\n contextSchema: DEFAULT_CONTEXT_SCHEMA,\n tracing: {\n enabled: true,\n serviceName: process.env.SERVICE_NAME || packageJson.name,\n serviceVersion: process.env.SERVICE_VERSION || packageJson.version,\n environment: detectEnvironment(),\n defaultAttributes: {},\n },\n http: {\n trackRequests: true,\n trackResponses: true,\n trackBodies: false,\n maxBodySize: 1024 * 1024, // 1MB\n excludeHeaders: ['authorization', 'cookie', 'x-api-key'],\n includeHeaders: undefined,\n },\n errors: {\n autoTrackErrorResponses: true,\n autoCategorizeErrors: true,\n errorPatterns: DEFAULT_ERROR_PATTERNS,\n defaultUnexpected: true,\n },\n performance: {\n trackTiming: true,\n categorizePerformance: true,\n performanceThresholds: {\n fast: 100,\n slow: 1000,\n },\n autoCreateSpans: true,\n },\n debug: false,\n};\n\n/**\n * Create observability configuration from simplified options\n */\nexport function createObservabilityConfig(\n options: ObservabilityOptions = {}\n): ObservabilityConfig {\n const config = { ...DEFAULT_OBSERVABILITY_CONFIG };\n\n // Apply simple options to comprehensive config\n if (options.serviceName) {\n config.tracing.serviceName = options.serviceName;\n }\n\n if (options.autoTrackHttp !== undefined) {\n config.http.trackRequests = options.autoTrackHttp;\n config.http.trackResponses = options.autoTrackHttp;\n }\n\n if (options.autoTrackErrorResponses !== undefined) {\n config.errors.autoTrackErrorResponses = options.autoTrackErrorResponses;\n }\n\n if (options.debug !== undefined) {\n config.debug = options.debug;\n }\n\n return config;\n}\n\n/**\n * Utility class for managing observability configuration\n */\nexport class ObservabilityConfigManager {\n private config: ObservabilityConfig;\n\n constructor(config: ObservabilityConfig = DEFAULT_OBSERVABILITY_CONFIG) {\n this.config = { ...config };\n }\n\n /**\n * Get current configuration\n */\n getConfig(): ObservabilityConfig {\n return { ...this.config };\n }\n\n /**\n * Update configuration\n */\n updateConfig(updates: Partial<ObservabilityConfig>): void {\n this.config = { ...this.config, ...updates };\n }\n\n /**\n * Add custom context schema fields\n */\n addContextFields(\n category: keyof ContextSchemaConfig,\n fields: string[]\n ): void {\n this.config.contextSchema[category] = [\n ...this.config.contextSchema[category],\n ...fields,\n ];\n }\n\n /**\n * Add custom error pattern\n */\n addErrorPattern(pattern: ErrorResponsePattern): void {\n this.config.errors.errorPatterns.push(pattern);\n }\n\n /**\n * Remove error pattern by name\n */\n removeErrorPattern(name: string): void {\n this.config.errors.errorPatterns = this.config.errors.errorPatterns.filter(\n (pattern) => pattern.name !== name\n );\n }\n\n /**\n * Get context schema for a category\n */\n getContextSchema(category: keyof ContextSchemaConfig): string[] {\n return [...this.config.contextSchema[category]];\n }\n\n /**\n * Get all context schema fields (flattened)\n */\n getAllContextFields(): string[] {\n return Object.values(this.config.contextSchema).flat();\n }\n\n /**\n * Get error patterns\n */\n getErrorPatterns(): ErrorResponsePattern[] {\n return [...this.config.errors.errorPatterns];\n }\n}\n\n/**\n * Global configuration manager instance\n */\nexport const observabilityConfig = new ObservabilityConfigManager();\n\n/**\n * Configure observability framework with simple options\n */\nexport function configureObservability(options: ObservabilityOptions): void {\n const config = createObservabilityConfig(options);\n observabilityConfig.updateConfig(config);\n}\n\n/**\n * Configure observability framework with comprehensive configuration\n */\nexport function configureObservabilityAdvanced(\n config: Partial<ObservabilityConfig>\n): void {\n observabilityConfig.updateConfig(config);\n}\n","import type { Span } from '@opentelemetry/api';\nimport { context, SpanKind, SpanStatusCode, trace } from '@opentelemetry/api';\n\n// Default build info that can be overridden\nexport const DEFAULT_BUILD_INFO = {\n 'service.name': 'unknown-service',\n 'service.version': '0.0.0',\n 'service.environment': 'development',\n} as const;\n\n// Allow consumers to set their own build info\nlet customBuildInfo: Record<string, string | number | boolean> = {};\n\nexport function setBuildInfo(\n buildInfo: Record<string, string | number | boolean>\n) {\n customBuildInfo = buildInfo;\n}\n\nexport function getBuildInfo(): Record<string, string | number | boolean> {\n return { ...DEFAULT_BUILD_INFO, ...customBuildInfo };\n}\n\n/**\n * Initialize build info from a generated BUILD_INFO constant\n * This is typically called at application startup\n */\nexport function initializeBuildInfo(\n buildInfo: Record<string, string | number | boolean>\n) {\n setBuildInfo(buildInfo);\n}\n\n/**\n * Data categories we can track in wide events\n */\nexport interface UserData {\n /** User identifier */\n id?: string;\n /** User email address */\n email?: string;\n /** User's first name */\n firstName?: string;\n /** User's last name */\n lastName?: string;\n /** Type of actor (user, service, system, etc.) */\n actorType?: string;\n /** Additional user properties */\n [key: string]: string | number | boolean | undefined;\n}\n\nexport interface EntityData {\n /** Type of entity (user, order, product, etc.) */\n type: string;\n /** Entity identifier */\n id: string;\n /** Entity-specific properties */\n [key: string]: string | number | boolean | undefined;\n}\n\nexport interface OperationData {\n /** Domain operation being performed */\n domain?: string;\n /** Operation layer (validation, business, persistence, etc.) */\n layer?: string;\n /** Operation-specific properties */\n [key: string]: string | number | boolean | undefined;\n}\n\nexport interface InfrastructureData {\n /** Database operation details */\n database?: {\n operation?: string;\n table?: string;\n query?: string;\n duration?: number;\n };\n /** Event store details */\n eventStore?: {\n streamId?: string;\n expectedVersion?: number;\n nextVersion?: number;\n createdNewStream?: boolean;\n };\n /** External service calls */\n externalService?: {\n name?: string;\n operation?: string;\n duration?: number;\n };\n /** Custom infrastructure properties */\n [key: string]: string | number | boolean | object | undefined;\n}\n\nexport interface ErrorData {\n /** Error type/category */\n type?: string;\n /** Error message */\n message?: string;\n /** Validation-specific error details */\n validation?: {\n issueCount?: number;\n fields?: string[];\n };\n /** Business rule violation details */\n businessRule?: {\n rule?: string;\n violation?: string;\n };\n /** Whether this was an unexpected error */\n unexpected?: boolean;\n /** Additional error properties */\n [key: string]: string | number | boolean | object | undefined;\n}\n\n/**\n * Simplified Wide Events wrapper for comprehensive observability.\n *\n * This class provides a fluent API for tracking different types of context\n * in your application operations. It automatically handles OpenTelemetry\n * span management and converts everything to structured wide events.\n *\n * Key methods:\n * - `setOperation()` - Set the operation type and context (e.g., 'user.create')\n * - `trackUser()` - Track user/actor information\n * - `trackEntity()` - Track the primary entity being operated on\n * - `trackInfrastructure()` - Track technical details like database operations\n * - `trackError()` - Track error information\n *\n * @example\n * ```typescript\n * const tracker = new WideEventsWrapper('Create User', 'user.create');\n *\n * await tracker.execute(async (t) => {\n * t.setOperation('user.create', { domain: 'user-management', layer: 'api' })\n * .trackUser({ id: '123', email: 'john@example.com' })\n * .trackEntity({ type: 'user', id: '123' });\n *\n * // Your business logic here\n * });\n * ```\n */\nexport class WideEventsWrapper {\n private requestId: string;\n private rootSpan: Span;\n private trackedData: Record<string, string | number | boolean> = {};\n private hasError = false;\n\n /**\n * Creates a new wide events wrapper for an operation.\n *\n * @param operationName - Human-readable name for the operation (e.g., \"Create User\")\n * @param operationType - Machine-readable operation type (e.g., \"user.create\")\n * @param serviceName - Name of the service (defaults to environment variable)\n */\n constructor(\n operationName: string,\n operationType: string,\n serviceName: string = process.env.SERVICE_NAME || 'unknown-service'\n ) {\n this.requestId = crypto.randomUUID();\n\n const buildInfo = getBuildInfo();\n const tracer = trace.getTracer(\n serviceName,\n buildInfo['service.version'] as string\n );\n\n this.rootSpan = tracer.startSpan(operationName, {\n kind: SpanKind.SERVER,\n attributes: {\n 'request.id': this.requestId,\n 'operation.type': operationType,\n 'operation.name': operationName,\n ...buildInfo,\n },\n });\n\n this.trackedData = {\n request_id: this.requestId,\n operation: operationType,\n };\n }\n\n /**\n * Set the operation type and context for this operation.\n *\n * This is the primary method for identifying what operation is being performed.\n * It sets both the operation type identifier and any contextual metadata.\n *\n * @param operationType - The operation type identifier (e.g., \"user.create\", \"user.change-password\")\n * @param context - Optional contextual information about the operation\n * @returns This instance for method chaining\n *\n * @example\n * ```typescript\n * // Simple operation\n * tracker.setOperation('user.create');\n *\n * // With context\n * tracker.setOperation('user.create', {\n * domain: 'user-management',\n * layer: 'api'\n * });\n *\n * // With custom properties\n * tracker.setOperation('user.change-password', {\n * domain: 'user-management',\n * layer: 'business-logic',\n * trigger: 'user-initiated'\n * });\n * ```\n */\n setOperation(operationType: string, context?: OperationData): this {\n const attributes: Record<string, string | number | boolean> = {\n 'operation.type': operationType,\n };\n\n // Add context if provided\n if (context) {\n if (context.domain) attributes['operation.domain'] = context.domain;\n if (context.layer) attributes['operation.layer'] = context.layer;\n\n // Add custom operation properties\n for (const [key, value] of Object.entries(context)) {\n if (value !== undefined && !['domain', 'layer'].includes(key)) {\n attributes[`operation.${key}`] = value;\n }\n }\n }\n\n this.rootSpan.setAttributes(attributes);\n Object.assign(this.trackedData, {\n operation: operationType,\n ...attributes,\n });\n\n return this;\n }\n\n /**\n * Track user/actor information for this operation.\n *\n * Use this to record who is performing the operation, including\n * user details, authentication context, and actor type.\n *\n * @param userData - User and actor information\n * @returns This instance for method chaining\n *\n * @example\n * ```typescript\n * tracker.trackUser({\n * id: '123',\n * email: 'john@example.com',\n * actorType: 'authenticated_user'\n * });\n * ```\n */\n trackUser(userData: UserData): this {\n const attributes: Record<string, string | number | boolean> = {};\n\n if (userData.id) attributes['user.id'] = userData.id;\n if (userData.email) attributes['user.email'] = userData.email;\n if (userData.firstName) attributes['user.first_name'] = userData.firstName;\n if (userData.lastName) attributes['user.last_name'] = userData.lastName;\n if (userData.actorType) attributes['actor.type'] = userData.actorType;\n\n // Add custom user properties\n for (const [key, value] of Object.entries(userData)) {\n if (\n value !== undefined &&\n !['id', 'email', 'firstName', 'lastName', 'actorType'].includes(key)\n ) {\n attributes[`user.${key}`] = value;\n }\n }\n\n this.rootSpan.setAttributes(attributes);\n Object.assign(this.trackedData, attributes);\n\n return this;\n }\n\n /**\n * Track the primary entity/resource being operated on.\n *\n * Use this to record what thing your operation is working with,\n * such as a user being created, an order being processed, etc.\n *\n * @param entityData - Entity/resource information\n * @returns This instance for method chaining\n *\n * @example\n * ```typescript\n * tracker.trackEntity({\n * type: 'user',\n * id: 'user-123',\n * status: 'active'\n * });\n * ```\n */\n trackEntity(entityData: EntityData): this {\n const attributes: Record<string, string | number | boolean> = {\n 'entity.type': entityData.type,\n 'entity.id': entityData.id,\n };\n\n // Add custom entity properties\n for (const [key, value] of Object.entries(entityData)) {\n if (value !== undefined && !['type', 'id'].includes(key)) {\n attributes[`entity.${key}`] = value;\n }\n }\n\n this.rootSpan.setAttributes(attributes);\n Object.assign(this.trackedData, attributes);\n\n return this;\n }\n\n /**\n * Track infrastructure and technical details.\n *\n * Use this to record technical aspects of your operation,\n * such as database operations, external service calls, etc.\n *\n * @param infrastructureData - Infrastructure operation details\n * @returns This instance for method chaining\n *\n * @example\n * ```typescript\n * tracker.trackInfrastructure({\n * database: {\n * operation: 'INSERT',\n * table: 'users',\n * duration: 45\n * },\n * eventStore: {\n * streamId: 'user-123',\n * expectedVersion: 0\n * }\n * });\n * ```\n */\n trackInfrastructure(infrastructureData: InfrastructureData): this {\n const attributes: Record<string, string | number | boolean> = {};\n\n // Handle database details\n if (infrastructureData.database) {\n const db = infrastructureData.database;\n if (db.operation) attributes['db.operation'] = db.operation;\n if (db.table) attributes['db.table'] = db.table;\n if (db.query) attributes['db.query'] = db.query;\n if (db.duration) attributes['db.duration_ms'] = db.duration;\n }\n\n // Handle event store details\n if (infrastructureData.eventStore) {\n const es = infrastructureData.eventStore;\n if (es.streamId) attributes['event_store.stream_id'] = es.streamId;\n if (es.expectedVersion !== undefined)\n attributes['event_store.expected_version'] = es.expectedVersion;\n if (es.nextVersion !== undefined)\n attributes['event_store.next_version'] = es.nextVersion;\n if (es.createdNewStream !== undefined)\n attributes['event_store.created_new_stream'] = es.createdNewStream;\n }\n\n // Handle external service details\n if (infrastructureData.externalService) {\n const ext = infrastructureData.externalService;\n if (ext.name) attributes['external_service.name'] = ext.name;\n if (ext.operation)\n attributes['external_service.operation'] = ext.operation;\n if (ext.duration)\n attributes['external_service.duration_ms'] = ext.duration;\n }\n\n // Handle custom infrastructure properties\n for (const [key, value] of Object.entries(infrastructureData)) {\n if (\n value !== undefined &&\n !['database', 'eventStore', 'externalService'].includes(key)\n ) {\n if (\n typeof value === 'string' ||\n typeof value === 'number' ||\n typeof value === 'boolean'\n ) {\n attributes[`infrastructure.${key}`] = value;\n }\n }\n }\n\n this.rootSpan.setAttributes(attributes);\n Object.assign(this.trackedData, attributes);\n\n return this;\n }\n\n /**\n * Track error information.\n *\n * Use this to record error details, including error type,\n * validation failures, business rule violations, etc.\n *\n * @param errorData - Error information\n * @returns This instance for method chaining\n *\n * @example\n * ```typescript\n * tracker.trackError({\n * type: 'ValidationError',\n * message: 'Email is required',\n * validation: {\n * issueCount: 2,\n * fields: ['email', 'firstName']\n * }\n * });\n * ```\n */\n trackError(errorData: ErrorData): this {\n const attributes: Record<string, string | number | boolean> = {};\n\n if (errorData.type) attributes['error.type'] = errorData.type;\n if (errorData.message) attributes['error.message'] = errorData.message;\n if (errorData.unexpected !== undefined)\n attributes['error.unexpected'] = errorData.unexpected;\n\n // Handle validation errors\n if (errorData.validation) {\n const val = errorData.validation;\n if (val.issueCount)\n attributes['error.validation.issue_count'] = val.issueCount;\n if (val.fields)\n attributes['error.validation.fields'] = val.fields.join(',');\n }\n\n // Handle business rule violations\n if (errorData.businessRule) {\n const rule = errorData.businessRule;\n if (rule.rule) attributes['error.business_rule.rule'] = rule.rule;\n if (rule.violation)\n attributes['error.business_rule.violation'] = rule.violation;\n }\n\n // Handle custom error properties\n for (const [key, value] of Object.entries(errorData)) {\n if (\n value !== undefined &&\n ![\n 'type',\n 'message',\n 'unexpected',\n 'validation',\n 'businessRule',\n ].includes(key)\n ) {\n if (\n typeof value === 'string' ||\n typeof value === 'number' ||\n typeof value === 'boolean'\n ) {\n attributes[`error.${key}`] = value;\n }\n }\n }\n\n this.rootSpan.setAttributes(attributes);\n Object.assign(this.trackedData, attributes);\n\n // Also record as OpenTelemetry error\n this.rootSpan.setStatus({\n code: SpanStatusCode.ERROR,\n message: errorData.message || 'Operation failed',\n });\n\n // Mark that an error has been tracked\n this.hasError = true;\n\n return this;\n }\n\n /**\n * Record a business event during the operation.\n *\n * Use this to record significant business events that occur\n * during your operation, such as \"validation.completed\" or\n * \"user.created\".\n *\n * @param eventName - Name of the business event\n * @param eventData - Additional event data\n * @returns This instance for method chaining\n *\n * @example\n * ```typescript\n * tracker.recordEvent('user.created', {\n * userId: '123',\n * source: 'api'\n * });\n * ```\n */\n recordEvent(\n eventName: string,\n eventData: Record<string, string | number | boolean> = {}\n ): this {\n this.rootSpan.addEvent(eventName, eventData);\n return this;\n }\n\n /**\n * Create a child span for a sub-operation.\n *\n * Use this to create detailed spans for specific parts of your\n * operation, such as validation, database operations, etc.\n *\n * @param name - Name of the sub-operation\n * @param attributes - Additional span attributes\n * @returns OpenTelemetry span for the sub-operation\n *\n * @example\n * ```typescript\n * const validationSpan = tracker.createOperationSpan('validation', {\n * 'operation.layer': 'validation'\n * });\n *\n * try {\n * // validation logic\n * validationSpan.setStatus({ code: SpanStatusCode.OK });\n * } catch (error) {\n * validationSpan.recordException(error);\n * } finally {\n * validationSpan.end();\n * }\n * ```\n */\n createOperationSpan(\n name: string,\n attributes: Record<string, string | number | boolean> = {}\n ): Span {\n const buildInfo = getBuildInfo();\n const tracer = trace.getTracer(\n process.env.SERVICE_NAME || 'unknown-service',\n buildInfo['service.version'] as string\n );\n\n return tracer.startSpan(\n name,\n {\n kind: SpanKind.INTERNAL,\n attributes,\n },\n trace.setSpan(context.active(), this.rootSpan)\n );\n }\n\n /**\n * Automatically track HTTP request details.\n *\n * This method extracts and tracks HTTP-specific information\n * from the request, including method, URL, headers, etc.\n *\n * @param request - HTTP request object\n * @returns This instance for method chaining\n *\n * @internal This method is typically called by middleware\n */\n trackHttpRequest(request: Request): this {\n const url = new URL(request.url);\n\n this.rootSpan.setAttributes({\n 'http.method': request.method,\n 'http.url': request.url,\n 'http.scheme': url.protocol.slice(0, -1),\n 'http.host': url.host,\n 'http.target': url.pathname + url.search,\n 'http.user_agent': request.headers.get('user-agent') || '',\n 'http.request_content_length':\n request.headers.get('content-length') || '',\n });\n\n this.rootSpan.addEvent('http.request_received', {\n 'http.method': request.method,\n 'http.path': url.pathname,\n });\n\n return this;\n }\n\n /**\n * Automatically track HTTP response details.\n *\n * This method extracts and tracks HTTP response information,\n * including status code, headers, and sets appropriate span status.\n *\n * @param response - HTTP response object\n * @returns This instance for method chaining\n *\n * @internal This method is typically called by middleware\n */\n trackHttpResponse(response: Response): this {\n this.rootSpan.setAttributes({\n 'http.status_code': response.status,\n 'http.status_text': response.statusText,\n 'http.response_content_length':\n response.headers.get('content-length') || '',\n });\n\n this.trackedData['http.status_code'] = response.status;\n\n // Only set span status if it hasn't been set by trackError()\n // This prevents overwriting detailed error messages with generic HTTP status\n if (!this.hasError) {\n if (response.status >= 400) {\n this.rootSpan.setStatus({\n code: SpanStatusCode.ERROR,\n message: `HTTP ${response.status}`,\n });\n } else {\n this.rootSpan.setStatus({ code: SpanStatusCode.OK });\n }\n } else {\n // Even if error was already tracked, we still need to set success for 2xx responses\n if (response.status >= 200 && response.status < 300) {\n this.rootSpan.setStatus({ code: SpanStatusCode.OK });\n this.hasError = false; // Reset error flag for successful responses\n }\n }\n\n this.rootSpan.addEvent('http.response_sent', {\n 'http.status_code': response.status,\n success: response.status >= 200 && response.status < 300,\n });\n\n return this;\n }\n\n /**\n * Execute the operation with proper OpenTelemetry context.\n *\n * This method wraps your operation in the appropriate OpenTelemetry\n * context and ensures spans are properly managed and exported.\n *\n * @param fn - Operation function to execute\n * @returns Promise with the operation result\n *\n * @example\n * ```typescript\n * const result = await tracker.execute(async (t) => {\n * t.trackUser({ id: '123' });\n * // Your operation logic here\n * return { success: true };\n * });\n * ```\n */\n async execute<R>(fn: (wrapper: this) => Promise<R>): Promise<R> {\n return context.with(\n trace.setSpan(context.active(), this.rootSpan),\n async () => {\n try {\n const result = await fn(this);\n\n // Only set success if no HTTP status was set\n if (!this.trackedData['http.status_code']) {\n this.rootSpan.setStatus({ code: SpanStatusCode.OK });\n }\n\n return result;\n } catch (error) {\n if (error instanceof Error) {\n this.rootSpan.recordException(error);\n this.rootSpan.setStatus({\n code: SpanStatusCode.ERROR,\n message: error.message,\n });\n }\n throw error;\n } finally {\n this.rootSpan.end();\n }\n }\n );\n }\n\n /**\n * Get all tracked data for this operation.\n *\n * @returns Copy of all tracked data\n *\n * @internal Primarily used for testing and debugging\n */\n getTrackedData(): Record<string, string | number | boolean> {\n return { ...this.trackedData };\n }\n}\n\n/**\n * Factory function to create a wide events wrapper for user operations.\n *\n * @param operationName - Specific operation name (e.g., \"create\", \"update\")\n * @returns Configured wide events wrapper\n *\n * @example\n * ```typescript\n * const tracker = createUserOperationTracker('create');\n * ```\n */\nexport function createUserOperationTracker(\n operationName: string\n): WideEventsWrapper {\n return new WideEventsWrapper(\n `User ${operationName}`,\n `user.${operationName.toLowerCase()}`\n );\n}\n\n/**\n * Factory function to create a generic wide events wrapper.\n *\n * @param entity - Entity type (e.g., \"user\", \"order\")\n * @param operation - Operation name (e.g., \"create\", \"update\")\n * @returns Configured wide events wrapper\n *\n * @example\n * ```typescript\n * const tracker = createOperationTracker('order', 'create');\n * ```\n */\nexport function createOperationTracker(\n entity: string,\n operation: string\n): WideEventsWrapper {\n return new WideEventsWrapper(\n `${entity} ${operation}`,\n `${entity.toLowerCase()}.${operation.toLowerCase()}`\n );\n}\n","/**\n * @fileoverview Delta Base Observability Framework\n *\n * This module provides a comprehensive observability framework built on top of OpenTelemetry\n * with semantic wide events, automatic middleware, and utility functions for common patterns.\n *\n * @example\n * ```typescript\n * import { observabilityMiddleware, WideEventsWrapper, withDomainTracking, configureObservability } from '@delta-base/observability';\n *\n * // Basic setup with middleware\n * app.use('*', observabilityMiddleware());\n *\n * // Configure observability globally\n * configureObservability({\n * serviceName: 'my-service',\n * autoTrackHttp: true,\n * debug: true\n * });\n *\n * // In route handlers\n * app.post('/users', async (c) => {\n * const tracker = c.get('wideEvent');\n * tracker.trackUser({ email: 'user@example.com' });\n * });\n * ```\n */\n\n// Import for internal use in default export\nimport {\n configureObservability,\n configureObservabilityAdvanced,\n observabilityConfig,\n} from './config';\nimport {\n addContextFields,\n addErrorPattern,\n type ObservabilityOptions,\n observabilityMiddleware,\n} from './middleware';\nimport {\n withDomainTracking,\n withEventStoreTracking,\n withValidationTracking,\n} from './utils';\nimport {\n createOperationTracker,\n getBuildInfo,\n initializeBuildInfo,\n setBuildInfo,\n WideEventsWrapper,\n} from './wide-events';\n\n// Configuration system\nexport {\n type ContextSchemaConfig,\n configureObservability,\n configureObservabilityAdvanced,\n createObservabilityConfig,\n DEFAULT_CONTEXT_SCHEMA,\n DEFAULT_ERROR_PATTERNS,\n DEFAULT_OBSERVABILITY_CONFIG,\n type ErrorResponsePattern,\n type ErrorTrackingConfig,\n type HttpTrackingConfig,\n type ObservabilityConfig,\n ObservabilityConfigManager,\n observabilityConfig,\n type PerformanceConfig,\n type TracingConfig,\n} from './config';\n// Error handling utilities\nexport {\n categorizeError,\n createErrorGuard,\n DEFAULT_EXPECTED_ERROR_GUARDS,\n type ErrorCategorizationConfig,\n handleAndTrackError,\n isExpectedError,\n} from './error-handling';\n// Middleware for automatic setup\nexport {\n addContextFields,\n addErrorPattern,\n createObservabilityErrorHandler,\n createObservabilityMiddleware,\n extractUserContext,\n type ObservabilityOptions,\n observabilityMiddleware,\n trackOperationTiming,\n} from './middleware';\nexport type { BuildInfoOptions } from './scripts';\n// Build info utilities - NOTE: collectBuildInfo is only available as CLI tool, not as import\n// Low-level tracing utilities (for advanced use cases)\nexport {\n createSpan,\n initializeTracing,\n type TraceOptions,\n withSpan,\n} from './tracing';\n// Hono types for extending in consuming services\nexport type {\n BaseHonoBindings,\n BaseHonoVariables,\n ExtendHonoBindings,\n ExtendHonoVariables,\n} from './types/hono';\n// Utility functions for common patterns\nexport {\n createSimpleTracker,\n trackUserFromSources,\n withDatabaseTracking,\n withDomainTracking,\n withEventStoreTracking,\n withExternalServiceTracking,\n withValidationTracking,\n} from './utils';\n// Core observability classes and types\nexport {\n createOperationTracker,\n createUserOperationTracker,\n DEFAULT_BUILD_INFO,\n type EntityData,\n type ErrorData,\n getBuildInfo,\n type InfrastructureData,\n initializeBuildInfo,\n type OperationData,\n setBuildInfo,\n type UserData,\n WideEventsWrapper,\n} from './wide-events';\n\n/**\n * Quick start function for new services.\n *\n * This function provides a one-line setup for basic observability in new services.\n *\n * @param serviceName - Name of your service\n * @param options - Optional configuration options\n * @returns Configured observability middleware\n *\n * @example\n * ```typescript\n * import { quickStart } from '@delta-base/observability';\n *\n * const app = new OpenAPIHono();\n * app.use('*', quickStart('my-service'));\n *\n * // With additional options\n * app.use('*', quickStart('my-service', { debug: true }));\n * ```\n */\nexport function quickStart(\n serviceName: string,\n options: Omit<ObservabilityOptions, 'serviceName'> = {}\n) {\n return observabilityMiddleware({ serviceName, ...options });\n}\n\n/**\n * Default export for convenient importing\n */\nexport default {\n // Core functionality\n middleware: observabilityMiddleware,\n WideEventsWrapper,\n createOperationTracker,\n quickStart,\n\n // Configuration\n configure: configureObservability,\n configureAdvanced: configureObservabilityAdvanced,\n config: observabilityConfig,\n addErrorPattern: addErrorPattern,\n addContextFields: addContextFields,\n\n // Utility functions\n withDomainTracking,\n withEventStoreTracking,\n withValidationTracking,\n\n // Build info\n initializeBuildInfo,\n setBuildInfo,\n getBuildInfo,\n // NOTE: collectBuildInfo is only available as CLI tool, not as import\n};\n","import {\n IllegalStateError,\n isDeltaBaseError,\n ValidationError,\n} from '@delta-base/toolkit';\nimport { createMiddleware } from 'hono/factory';\nimport { HTTPException } from 'hono/http-exception';\nimport { ZodError } from 'zod';\nimport type { ContextSchemaConfig, ErrorResponsePattern } from './config';\nimport { createObservabilityConfig, observabilityConfig } from './config';\nimport { initializeTracing } from './tracing';\nimport type { BaseHonoBindings, BaseHonoVariables } from './types/hono';\nimport { initializeBuildInfo, WideEventsWrapper } from './wide-events';\n\n/**\n * Known error response patterns that we can detect and track.\n *\n * This registry provides a clean, extensible way to detect different types of\n * error responses and track them with appropriate context and categorization.\n * Each pattern includes a matcher function and a tracker function that properly\n * categorizes errors as expected (user/client errors) or unexpected (system errors).\n *\n * Note: Error patterns are now defined in config.ts and can be customized.\n */\n\n/**\n * Registry of error response patterns for clean detection and tracking.\n *\n * This registry enables clean, maintainable error detection by:\n * - Separating error detection logic from tracking logic\n * - Properly categorizing errors as expected vs unexpected\n * - Providing extensible pattern matching for different error types\n * - Avoiding brittle if-statement chains\n *\n * Expected errors (unexpected: false) include:\n * - Validation errors (user submitted invalid data)\n * - Business rule violations (duplicate email, insufficient permissions)\n * - Authentication/authorization failures\n * - Rate limiting\n *\n * Unexpected errors (unexpected: true) include:\n * - System failures (database down, network issues)\n * - Programming errors (null pointer exceptions, type errors)\n * - Infrastructure issues (service unavailable, timeouts)\n * - Unhandled edge cases\n *\n * Note: Error patterns are now configurable via the observability config system.\n * Use observabilityConfig.addErrorPattern() to add custom patterns.\n */\n\n/**\n * Configuration options for the observability middleware (simplified interface)\n *\n * For more advanced configuration, use configureObservabilityAdvanced()\n * or import and modify the observabilityConfig directly.\n */\nexport interface ObservabilityOptions {\n /**\n * Custom service name (defaults to environment variable SERVICE_NAME)\n */\n serviceName?: string;\n\n /**\n * Whether to automatically track HTTP request/response (defaults to true)\n */\n autoTrackHttp?: boolean;\n\n /**\n * Whether to automatically detect and track error responses (defaults to true)\n */\n autoTrackErrorResponses?: boolean;\n\n /**\n * Whether to enable debug logging (defaults to false)\n */\n debug?: boolean;\n\n /**\n * Build info to initialize (optional)\n */\n buildInfo?: Record<string, string | number | boolean>;\n}\n\n/**\n * Middleware that automatically sets up wide events tracking for all routes.\n *\n * This middleware:\n * - Initializes OpenTelemetry tracing\n * - Creates a WideEventsWrapper for the request\n * - Automatically tracks HTTP request/response details\n * - Detects and tracks error responses using pattern matching\n * - Makes the tracker available via `c.get('wideEvent')`\n * - Provides a simple fallback for operation type derivation\n *\n * @param options - Configuration options for the middleware\n * @returns Hono middleware function\n *\n * @example\n * ```typescript\n * const app = new OpenAPIHono();\n * app.use('*', observabilityMiddleware());\n *\n * // Optional: Add the error handler for exceptions that bypass validation\n * app.onError(createObservabilityErrorHandler());\n *\n * // Set operation type explicitly in your route handlers:\n * app.post('/users/:id/change-password', async (c) => {\n * const tracker = c.get('wideEvent');\n * tracker.setOperationType('user.change-password');\n * // ... route logic\n * });\n * ```\n */\nexport function observabilityMiddleware(options: ObservabilityOptions = {}) {\n // Initialize build info if provided\n if (options.buildInfo) {\n initializeBuildInfo(options.buildInfo);\n }\n\n // Apply simple options to the global config\n if (Object.keys(options).length > 0) {\n const config = createObservabilityConfig(options);\n observabilityConfig.updateConfig(config);\n }\n\n // Get the current configuration\n const config = observabilityConfig.getConfig();\n const serviceName = config.tracing.serviceName || 'unknown-service';\n const autoTrackHttp = config.http.trackRequests && config.http.trackResponses;\n const autoTrackErrorResponses = config.errors.autoTrackErrorResponses;\n\n return createMiddleware<{\n Bindings: BaseHonoBindings;\n Variables: BaseHonoVariables;\n }>(async (c, next) => {\n // Initialize tracing once per request\n initializeTracing();\n\n // Auto-derive operation details from route\n const method = c.req.method;\n const path = c.req.path;\n const operationName = `${method} ${path}`;\n const operationType = simpleOperationTypeDerivation(method, path);\n\n // Create wide event wrapper\n const wideEvent = new WideEventsWrapper(\n operationName,\n operationType,\n serviceName\n );\n\n // Automatically track HTTP request details\n if (autoTrackHttp) {\n wideEvent.trackHttpRequest(c.req.raw);\n }\n\n // Store in context for route handlers\n c.set('wideEvent', wideEvent);\n\n await wideEvent.execute(async () => {\n await next();\n\n // Automatically track HTTP response details\n if (autoTrackHttp && c.res) {\n wideEvent.trackHttpResponse(c.res);\n }\n\n // Check for error responses and track them\n if (autoTrackErrorResponses && c.res && isErrorResponse(c.res.status)) {\n await detectAndTrackErrorResponse(wideEvent, c.res);\n }\n });\n });\n}\n\n/**\n * Simple operation type derivation as a fallback.\n *\n * This is just a basic fallback - you should set the operation type\n * explicitly in your route handlers using tracker.setOperation().\n *\n * @param method - HTTP method\n * @param path - Request path\n * @returns Simple operation type\n */\nfunction simpleOperationTypeDerivation(method: string, path: string): string {\n // Extract first path segment as resource\n const resourceMatch = path.match(/^\\/([^/?]+)/);\n let resource = resourceMatch?.[1] || 'unknown';\n\n // Convert plural resources to singular for consistent operation naming\n if (resource.endsWith('s') && resource.length > 1) {\n resource = resource.slice(0, -1);\n }\n\n // Simple method mapping\n const methodMap: Record<string, string> = {\n GET: 'read',\n POST: 'create',\n PUT: 'update',\n PATCH: 'update',\n DELETE: 'delete',\n };\n\n const action = methodMap[method.toUpperCase()] || 'operation';\n return `${resource}.${action}`;\n}\n\n/**\n * Check if a status code indicates an error response\n */\nfunction isErrorResponse(status: number): boolean {\n return status >= 400;\n}\n\n/**\n * Detect and track error responses using pattern matching.\n *\n * This function uses a registry of error patterns to cleanly detect\n * different types of error responses and track them appropriately.\n * It provides proper error categorization (expected vs unexpected) and\n * avoids the need for brittle if-statement chains.\n *\n * The function:\n * 1. Clones the response to avoid consuming the original body\n * 2. Parses the response as JSON (only processes JSON responses)\n * 3. Matches against known error patterns in order\n * 4. Tracks the first matching pattern with proper categorization\n * 5. Falls back to generic error tracking based on HTTP status codes\n *\n * Error categorization:\n * - 4xx responses: Expected errors (user/client errors)\n * - 5xx responses: Unexpected errors (system/server errors)\n *\n * @param wideEvent - Wide events wrapper to track error details\n * @param response - HTTP response to check for error patterns\n */\nasync function detectAndTrackErrorResponse(\n wideEvent: WideEventsWrapper,\n response: Response\n): Promise<void> {\n try {\n // Only process JSON responses\n const contentType = response.headers.get('content-type');\n if (!conten