autotel
Version:
Write Once, Observe Anywhere
1 lines • 13.7 kB
Source Map (JSON)
{"version":3,"file":"yaml-config.cjs","names":["requireModule","path","nodeFs","AdaptiveSampler","AlwaysSampler","NeverSampler","RandomSampler"],"sources":["../src/yaml-config.ts"],"sourcesContent":["/**\n * YAML configuration loader for autotel\n *\n * Supports:\n * - Auto-discovery of autotel.yaml in cwd\n * - AUTOTEL_CONFIG_FILE env var override\n * - Environment variable substitution: ${env:VAR} and ${env:VAR:-default}\n *\n * @example Auto-discovery\n * ```yaml\n * # autotel.yaml in project root\n * service:\n * name: my-service\n * exporter:\n * endpoint: ${env:OTEL_EXPORTER_OTLP_ENDPOINT:-http://localhost:4318}\n * ```\n *\n * @example Explicit path\n * ```bash\n * AUTOTEL_CONFIG_FILE=./config/otel.yaml tsx --import autotel/auto src/index.ts\n * ```\n */\n\n// namespace import for browser-bundler compat — see node-require.ts\nimport * as nodeFs from 'node:fs';\nimport path from 'node:path';\nimport type { AutotelConfig, OtlpSignal } from './init';\nimport {\n AdaptiveSampler,\n AlwaysSampler,\n NeverSampler,\n RandomSampler,\n type SamplingPreset,\n} from './sampling';\n\n/**\n * Lazy-load yaml parser (optional peer dependency)\n * Only loads when a YAML config file is actually found\n */\nimport { requireModule } from './node-require';\n\nfunction loadYamlParser(): (content: string) => unknown {\n try {\n const mod = requireModule<{ parse: (content: string) => unknown }>('yaml');\n return mod.parse;\n } catch {\n throw new Error('YAML parser not found. Install with: pnpm add yaml');\n }\n}\n\n/**\n * YAML config structure\n * Maps to AutotelConfig with user-friendly naming\n */\nexport interface YamlConfig {\n service?: {\n name?: string;\n version?: string;\n environment?: string;\n };\n exporter?: {\n endpoint?: string;\n protocol?: 'http' | 'grpc';\n headers?: Record<string, string>;\n destinations?: Array<{\n endpoint: string;\n protocol?: 'http' | 'grpc';\n headers?: Record<string, string>;\n signals?: OtlpSignal[];\n }>;\n };\n resource?: Record<string, string | number | boolean>;\n sampling?: {\n preset?: SamplingPreset;\n type?: 'adaptive' | 'always_on' | 'always_off' | 'ratio';\n ratio?: number;\n baseline_rate?: number;\n always_sample_errors?: boolean;\n always_sample_slow?: boolean;\n slow_threshold_ms?: number;\n };\n autoInstrumentations?: string[] | Record<string, { enabled?: boolean }>;\n debug?: boolean;\n}\n\n/**\n * Environment variable substitution regex\n * Matches ${env:VAR_NAME} and ${env:VAR_NAME:-default}\n */\nconst ENV_VAR_PATTERN = /\\$\\{env:([A-Za-z_][A-Za-z0-9_]*)(?::-([^}]*))?\\}/g;\n\n/**\n * Substitute ${env:VAR} and ${env:VAR:-default} in a string\n *\n * @param value - String potentially containing env var references\n * @returns String with env vars substituted\n *\n * @example\n * substituteEnvVars('${env:NODE_ENV:-development}')\n * // Returns 'production' if NODE_ENV=production, else 'development'\n */\nfunction substituteEnvVars(value: string): string {\n return value.replaceAll(\n ENV_VAR_PATTERN,\n (_match, varName: string, defaultValue?: string) => {\n const envValue = process.env[varName];\n if (envValue !== undefined) return envValue;\n if (defaultValue !== undefined) return defaultValue;\n console.warn(\n `[autotel] Environment variable ${varName} not set and no default provided`,\n );\n return '';\n },\n );\n}\n\n/**\n * Recursively substitute env vars in an object\n *\n * @param obj - Object to process\n * @returns Object with all string values having env vars substituted\n */\nfunction substituteEnvVarsDeep(obj: unknown): unknown {\n if (typeof obj === 'string') {\n return substituteEnvVars(obj);\n }\n if (Array.isArray(obj)) {\n return obj.map((item) => substituteEnvVarsDeep(item));\n }\n if (obj && typeof obj === 'object') {\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(obj)) {\n result[key] = substituteEnvVarsDeep(value);\n }\n return result;\n }\n return obj;\n}\n\n/**\n * Find YAML config file path\n *\n * Priority:\n * 1. AUTOTEL_CONFIG_FILE env var (explicit path)\n * 2. autotel.yaml in cwd (convention)\n * 3. autotel.yml in cwd (alternative extension)\n *\n * @returns File path if found, null otherwise\n */\nfunction findConfigFile(): string | null {\n // Check env var first (explicit takes priority)\n const envPath = process.env.AUTOTEL_CONFIG_FILE;\n if (envPath) {\n const resolved = path.resolve(envPath);\n if (nodeFs.existsSync(resolved)) return resolved;\n console.warn(`[autotel] Config file not found: ${envPath}`);\n return null;\n }\n\n // Auto-discover autotel.yaml in cwd\n const conventionPath = path.resolve(process.cwd(), 'autotel.yaml');\n if (nodeFs.existsSync(conventionPath)) return conventionPath;\n\n // Also check .yml extension\n const altPath = path.resolve(process.cwd(), 'autotel.yml');\n if (nodeFs.existsSync(altPath)) return altPath;\n\n return null;\n}\n\n/**\n * Convert YAML config structure to AutotelConfig\n *\n * @param yaml - Parsed and env-substituted YAML config\n * @returns Partial AutotelConfig ready for merging\n */\nfunction yamlToAutotelConfig(yaml: YamlConfig): Partial<AutotelConfig> {\n const config: Partial<AutotelConfig> = {};\n\n // Service configuration\n if (yaml.service?.name) config.service = yaml.service.name;\n if (yaml.service?.version) config.version = yaml.service.version;\n if (yaml.service?.environment) config.environment = yaml.service.environment;\n\n // Exporter configuration\n if (yaml.exporter?.endpoint) config.endpoint = yaml.exporter.endpoint;\n if (yaml.exporter?.protocol) config.protocol = yaml.exporter.protocol;\n if (yaml.exporter?.headers) config.headers = yaml.exporter.headers;\n if (yaml.exporter?.destinations) {\n config.destinations = yaml.exporter.destinations;\n }\n\n // Resource attributes (flattened)\n if (yaml.resource) config.resourceAttributes = yaml.resource;\n\n // Integrations\n if (yaml.autoInstrumentations)\n config.autoInstrumentations = yaml.autoInstrumentations;\n\n // Debug mode\n if (yaml.debug !== undefined) config.debug = yaml.debug;\n\n // Sampling configuration\n if (yaml.sampling?.preset) {\n warnOnIgnoredPresetOverrides(yaml.sampling);\n config.sampling = yaml.sampling.preset;\n } else {\n const sampler = createSamplerFromYaml(yaml.sampling);\n if (sampler) config.sampler = sampler;\n }\n\n return config;\n}\n\nfunction createSamplerFromYaml(\n sampling?: YamlConfig['sampling'],\n): AutotelConfig['sampler'] {\n if (!sampling) return undefined;\n if (sampling.preset) return undefined;\n\n const type = sampling.type ?? 'adaptive';\n\n try {\n switch (type) {\n case 'adaptive': {\n return new AdaptiveSampler({\n baselineSampleRate: sampling.baseline_rate,\n alwaysSampleErrors: sampling.always_sample_errors,\n alwaysSampleSlow: sampling.always_sample_slow,\n slowThresholdMs: sampling.slow_threshold_ms,\n });\n }\n case 'always_on': {\n return new AlwaysSampler();\n }\n case 'always_off': {\n return new NeverSampler();\n }\n case 'ratio': {\n if (sampling.ratio === undefined) {\n console.warn(\n '[autotel] sampling.ratio missing in YAML sampling config. Falling back to adaptive sampler.',\n );\n return new AdaptiveSampler();\n }\n return new RandomSampler(sampling.ratio);\n }\n default: {\n console.warn(\n `[autotel] Unknown sampling type \"${type}\" in YAML config. Falling back to defaults.`,\n );\n return undefined;\n }\n }\n } catch (error) {\n console.warn(\n `[autotel] Failed to configure sampling from YAML: ${error instanceof Error ? error.message : String(error)}`,\n );\n return undefined;\n }\n}\n\nfunction warnOnIgnoredPresetOverrides(\n sampling: NonNullable<YamlConfig['sampling']>,\n): void {\n const ignoredFields = [\n 'type',\n 'ratio',\n 'baseline_rate',\n 'always_sample_errors',\n 'always_sample_slow',\n 'slow_threshold_ms',\n ].filter((field) => sampling[field as keyof typeof sampling] !== undefined);\n\n if (ignoredFields.length === 0) {\n return;\n }\n\n console.warn(\n `[autotel] sampling.preset=\"${sampling.preset}\" ignores these YAML fields: ${ignoredFields.join(', ')}. ` +\n 'Use the programmatic API with sampler or samplingPresets.*(...) for tuned presets.',\n );\n}\n\n/**\n * Load and parse YAML config file (auto-discovery)\n *\n * Automatically finds and loads autotel.yaml or uses AUTOTEL_CONFIG_FILE.\n * Returns null if no config file found (not an error - YAML config is optional).\n *\n * @returns Partial AutotelConfig or null if no config file found\n *\n * @example\n * const yamlConfig = loadYamlConfig();\n * if (yamlConfig) {\n * init({ ...yamlConfig, debug: true });\n * }\n */\nexport function loadYamlConfig(): Partial<AutotelConfig> | null {\n const filePath = findConfigFile();\n if (!filePath) return null;\n\n try {\n const content = nodeFs.readFileSync(filePath, 'utf8');\n const parseYaml = loadYamlParser();\n const rawYaml = parseYaml(content) as YamlConfig;\n const substituted = substituteEnvVarsDeep(rawYaml) as YamlConfig;\n return yamlToAutotelConfig(substituted);\n } catch (error) {\n console.error(\n `[autotel] Failed to load YAML config from ${filePath}:`,\n error,\n );\n return null;\n }\n}\n\n/**\n * Load YAML config from a specific file path\n *\n * Unlike loadYamlConfig(), this throws if the file cannot be read.\n *\n * @param filePath - Path to YAML config file\n * @returns Partial AutotelConfig\n * @throws Error if file cannot be read or parsed\n *\n * @example\n * import { loadYamlConfigFromFile } from 'autotel/yaml';\n * import { init } from 'autotel';\n *\n * const config = loadYamlConfigFromFile('./config/otel.yaml');\n * init({ ...config, debug: true });\n */\nexport function loadYamlConfigFromFile(\n filePath: string,\n): Partial<AutotelConfig> {\n const resolved = path.resolve(filePath);\n const content = nodeFs.readFileSync(resolved, 'utf8');\n const parseYaml = loadYamlParser();\n const rawYaml = parseYaml(content) as YamlConfig;\n const substituted = substituteEnvVarsDeep(rawYaml) as YamlConfig;\n return yamlToAutotelConfig(substituted);\n}\n\n/**\n * Check if a YAML config file exists (without loading it)\n *\n * @returns true if a config file would be found by loadYamlConfig()\n */\nexport function hasYamlConfig(): boolean {\n return findConfigFile() !== null;\n}\n"],"mappings":"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"}