UNPKG

autotel

Version:
235 lines (233 loc) 8.13 kB
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); const require_chunk = require('./chunk-C_NdSu1c.cjs'); const require_sampling = require('./sampling.cjs'); const require_node_require = require('./node-require-CZ_PU448.cjs'); let node_fs = require("node:fs"); node_fs = require_chunk.__toESM(node_fs, 1); let node_path = require("node:path"); node_path = require_chunk.__toESM(node_path, 1); //#region src/yaml-config.ts /** * YAML configuration loader for autotel * * Supports: * - Auto-discovery of autotel.yaml in cwd * - AUTOTEL_CONFIG_FILE env var override * - Environment variable substitution: ${env:VAR} and ${env:VAR:-default} * * @example Auto-discovery * ```yaml * # autotel.yaml in project root * service: * name: my-service * exporter: * endpoint: ${env:OTEL_EXPORTER_OTLP_ENDPOINT:-http://localhost:4318} * ``` * * @example Explicit path * ```bash * AUTOTEL_CONFIG_FILE=./config/otel.yaml tsx --import autotel/auto src/index.ts * ``` */ /** * Lazy-load yaml parser (optional peer dependency) * Only loads when a YAML config file is actually found */ function loadYamlParser() { try { return require_node_require.requireModule("yaml").parse; } catch { throw new Error("YAML parser not found. Install with: pnpm add yaml"); } } /** * Environment variable substitution regex * Matches ${env:VAR_NAME} and ${env:VAR_NAME:-default} */ const ENV_VAR_PATTERN = /\$\{env:([A-Za-z_][A-Za-z0-9_]*)(?::-([^}]*))?\}/g; /** * Substitute ${env:VAR} and ${env:VAR:-default} in a string * * @param value - String potentially containing env var references * @returns String with env vars substituted * * @example * substituteEnvVars('${env:NODE_ENV:-development}') * // Returns 'production' if NODE_ENV=production, else 'development' */ function substituteEnvVars(value) { return value.replaceAll(ENV_VAR_PATTERN, (_match, varName, defaultValue) => { const envValue = process.env[varName]; if (envValue !== void 0) return envValue; if (defaultValue !== void 0) return defaultValue; console.warn(`[autotel] Environment variable ${varName} not set and no default provided`); return ""; }); } /** * Recursively substitute env vars in an object * * @param obj - Object to process * @returns Object with all string values having env vars substituted */ function substituteEnvVarsDeep(obj) { if (typeof obj === "string") return substituteEnvVars(obj); if (Array.isArray(obj)) return obj.map((item) => substituteEnvVarsDeep(item)); if (obj && typeof obj === "object") { const result = {}; for (const [key, value] of Object.entries(obj)) result[key] = substituteEnvVarsDeep(value); return result; } return obj; } /** * Find YAML config file path * * Priority: * 1. AUTOTEL_CONFIG_FILE env var (explicit path) * 2. autotel.yaml in cwd (convention) * 3. autotel.yml in cwd (alternative extension) * * @returns File path if found, null otherwise */ function findConfigFile() { const envPath = process.env.AUTOTEL_CONFIG_FILE; if (envPath) { const resolved = node_path.default.resolve(envPath); if (node_fs.existsSync(resolved)) return resolved; console.warn(`[autotel] Config file not found: ${envPath}`); return null; } const conventionPath = node_path.default.resolve(process.cwd(), "autotel.yaml"); if (node_fs.existsSync(conventionPath)) return conventionPath; const altPath = node_path.default.resolve(process.cwd(), "autotel.yml"); if (node_fs.existsSync(altPath)) return altPath; return null; } /** * Convert YAML config structure to AutotelConfig * * @param yaml - Parsed and env-substituted YAML config * @returns Partial AutotelConfig ready for merging */ function yamlToAutotelConfig(yaml) { const config = {}; if (yaml.service?.name) config.service = yaml.service.name; if (yaml.service?.version) config.version = yaml.service.version; if (yaml.service?.environment) config.environment = yaml.service.environment; if (yaml.exporter?.endpoint) config.endpoint = yaml.exporter.endpoint; if (yaml.exporter?.protocol) config.protocol = yaml.exporter.protocol; if (yaml.exporter?.headers) config.headers = yaml.exporter.headers; if (yaml.exporter?.destinations) config.destinations = yaml.exporter.destinations; if (yaml.resource) config.resourceAttributes = yaml.resource; if (yaml.autoInstrumentations) config.autoInstrumentations = yaml.autoInstrumentations; if (yaml.debug !== void 0) config.debug = yaml.debug; if (yaml.sampling?.preset) { warnOnIgnoredPresetOverrides(yaml.sampling); config.sampling = yaml.sampling.preset; } else { const sampler = createSamplerFromYaml(yaml.sampling); if (sampler) config.sampler = sampler; } return config; } function createSamplerFromYaml(sampling) { if (!sampling) return void 0; if (sampling.preset) return void 0; const type = sampling.type ?? "adaptive"; try { switch (type) { case "adaptive": return new require_sampling.AdaptiveSampler({ baselineSampleRate: sampling.baseline_rate, alwaysSampleErrors: sampling.always_sample_errors, alwaysSampleSlow: sampling.always_sample_slow, slowThresholdMs: sampling.slow_threshold_ms }); case "always_on": return new require_sampling.AlwaysSampler(); case "always_off": return new require_sampling.NeverSampler(); case "ratio": if (sampling.ratio === void 0) { console.warn("[autotel] sampling.ratio missing in YAML sampling config. Falling back to adaptive sampler."); return new require_sampling.AdaptiveSampler(); } return new require_sampling.RandomSampler(sampling.ratio); default: console.warn(`[autotel] Unknown sampling type "${type}" in YAML config. Falling back to defaults.`); return; } } catch (error) { console.warn(`[autotel] Failed to configure sampling from YAML: ${error instanceof Error ? error.message : String(error)}`); return; } } function warnOnIgnoredPresetOverrides(sampling) { const ignoredFields = [ "type", "ratio", "baseline_rate", "always_sample_errors", "always_sample_slow", "slow_threshold_ms" ].filter((field) => sampling[field] !== void 0); if (ignoredFields.length === 0) return; console.warn(`[autotel] sampling.preset="${sampling.preset}" ignores these YAML fields: ${ignoredFields.join(", ")}. Use the programmatic API with sampler or samplingPresets.*(...) for tuned presets.`); } /** * Load and parse YAML config file (auto-discovery) * * Automatically finds and loads autotel.yaml or uses AUTOTEL_CONFIG_FILE. * Returns null if no config file found (not an error - YAML config is optional). * * @returns Partial AutotelConfig or null if no config file found * * @example * const yamlConfig = loadYamlConfig(); * if (yamlConfig) { * init({ ...yamlConfig, debug: true }); * } */ function loadYamlConfig() { const filePath = findConfigFile(); if (!filePath) return null; try { const content = node_fs.readFileSync(filePath, "utf8"); return yamlToAutotelConfig(substituteEnvVarsDeep(loadYamlParser()(content))); } catch (error) { console.error(`[autotel] Failed to load YAML config from ${filePath}:`, error); return null; } } /** * Load YAML config from a specific file path * * Unlike loadYamlConfig(), this throws if the file cannot be read. * * @param filePath - Path to YAML config file * @returns Partial AutotelConfig * @throws Error if file cannot be read or parsed * * @example * import { loadYamlConfigFromFile } from 'autotel/yaml'; * import { init } from 'autotel'; * * const config = loadYamlConfigFromFile('./config/otel.yaml'); * init({ ...config, debug: true }); */ function loadYamlConfigFromFile(filePath) { const resolved = node_path.default.resolve(filePath); const content = node_fs.readFileSync(resolved, "utf8"); return yamlToAutotelConfig(substituteEnvVarsDeep(loadYamlParser()(content))); } /** * Check if a YAML config file exists (without loading it) * * @returns true if a config file would be found by loadYamlConfig() */ function hasYamlConfig() { return findConfigFile() !== null; } //#endregion exports.hasYamlConfig = hasYamlConfig; exports.loadYamlConfig = loadYamlConfig; exports.loadYamlConfigFromFile = loadYamlConfigFromFile; //# sourceMappingURL=yaml-config.cjs.map