@agentuity/telemetry 3.0.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/dist/console.d.ts +33 -0
  2. package/dist/console.d.ts.map +1 -0
  3. package/dist/console.js +86 -0
  4. package/dist/console.js.map +1 -0
  5. package/dist/exporters/index.d.ts +4 -0
  6. package/dist/exporters/index.d.ts.map +1 -0
  7. package/dist/exporters/index.js +4 -0
  8. package/dist/exporters/index.js.map +1 -0
  9. package/dist/exporters/jsonl-log-exporter.d.ts +36 -0
  10. package/dist/exporters/jsonl-log-exporter.d.ts.map +1 -0
  11. package/dist/exporters/jsonl-log-exporter.js +103 -0
  12. package/dist/exporters/jsonl-log-exporter.js.map +1 -0
  13. package/dist/exporters/jsonl-metric-exporter.d.ts +40 -0
  14. package/dist/exporters/jsonl-metric-exporter.d.ts.map +1 -0
  15. package/dist/exporters/jsonl-metric-exporter.js +104 -0
  16. package/dist/exporters/jsonl-metric-exporter.js.map +1 -0
  17. package/dist/exporters/jsonl-trace-exporter.d.ts +36 -0
  18. package/dist/exporters/jsonl-trace-exporter.d.ts.map +1 -0
  19. package/dist/exporters/jsonl-trace-exporter.js +111 -0
  20. package/dist/exporters/jsonl-trace-exporter.js.map +1 -0
  21. package/dist/fetch.d.ts +12 -0
  22. package/dist/fetch.d.ts.map +1 -0
  23. package/dist/fetch.js +82 -0
  24. package/dist/fetch.js.map +1 -0
  25. package/dist/globals.d.ts +9 -0
  26. package/dist/globals.d.ts.map +1 -0
  27. package/dist/globals.js +13 -0
  28. package/dist/globals.js.map +1 -0
  29. package/dist/http.d.ts +16 -0
  30. package/dist/http.d.ts.map +1 -0
  31. package/dist/http.js +44 -0
  32. package/dist/http.js.map +1 -0
  33. package/dist/index.d.ts +50 -0
  34. package/dist/index.d.ts.map +1 -0
  35. package/dist/index.js +62 -0
  36. package/dist/index.js.map +1 -0
  37. package/dist/logger/console.d.ts +69 -0
  38. package/dist/logger/console.d.ts.map +1 -0
  39. package/dist/logger/console.js +278 -0
  40. package/dist/logger/console.js.map +1 -0
  41. package/dist/logger/index.d.ts +4 -0
  42. package/dist/logger/index.d.ts.map +1 -0
  43. package/dist/logger/index.js +3 -0
  44. package/dist/logger/index.js.map +1 -0
  45. package/dist/logger/internal.d.ts +79 -0
  46. package/dist/logger/internal.d.ts.map +1 -0
  47. package/dist/logger/internal.js +133 -0
  48. package/dist/logger/internal.js.map +1 -0
  49. package/dist/logger/user.d.ts +8 -0
  50. package/dist/logger/user.d.ts.map +1 -0
  51. package/dist/logger/user.js +7 -0
  52. package/dist/logger/user.js.map +1 -0
  53. package/dist/logger/util.d.ts +11 -0
  54. package/dist/logger/util.d.ts.map +1 -0
  55. package/dist/logger/util.js +77 -0
  56. package/dist/logger/util.js.map +1 -0
  57. package/dist/logger.d.ts +40 -0
  58. package/dist/logger.d.ts.map +1 -0
  59. package/dist/logger.js +259 -0
  60. package/dist/logger.js.map +1 -0
  61. package/dist/telemetry.d.ts +71 -0
  62. package/dist/telemetry.d.ts.map +1 -0
  63. package/dist/telemetry.js +274 -0
  64. package/dist/telemetry.js.map +1 -0
  65. package/dist/tracestate.d.ts +44 -0
  66. package/dist/tracestate.d.ts.map +1 -0
  67. package/dist/tracestate.js +84 -0
  68. package/dist/tracestate.js.map +1 -0
  69. package/package.json +58 -0
  70. package/src/console.ts +91 -0
  71. package/src/exporters/README.md +217 -0
  72. package/src/exporters/index.ts +3 -0
  73. package/src/exporters/jsonl-log-exporter.ts +113 -0
  74. package/src/exporters/jsonl-metric-exporter.ts +120 -0
  75. package/src/exporters/jsonl-trace-exporter.ts +121 -0
  76. package/src/fetch.ts +105 -0
  77. package/src/globals.ts +18 -0
  78. package/src/http.ts +53 -0
  79. package/src/index.ts +82 -0
  80. package/src/logger/console.ts +322 -0
  81. package/src/logger/index.ts +3 -0
  82. package/src/logger/internal.ts +165 -0
  83. package/src/logger/user.ts +15 -0
  84. package/src/logger/util.ts +80 -0
  85. package/src/logger.ts +285 -0
  86. package/src/telemetry.ts +403 -0
  87. package/src/tracestate.ts +108 -0
package/src/logger.ts ADDED
@@ -0,0 +1,285 @@
1
+ import { format } from 'node:util';
2
+ import { safeStringify, type LogLevel, type Logger } from '@agentuity/core';
3
+ import * as LogsAPI from '@opentelemetry/api-logs';
4
+ export type { Logger } from '@agentuity/core';
5
+ import ConsoleLogger from './logger/console';
6
+
7
+ /**
8
+ * Reference to the original console object before patching.
9
+ */
10
+ export const __originalConsole: Console = Object.create(console);
11
+
12
+ export class OtelLogger implements Logger {
13
+ private readonly delegate: LogsAPI.Logger;
14
+ private readonly context: Record<string, unknown> | undefined;
15
+ private readonly logger: ConsoleLogger | undefined;
16
+ private readonly logLevel: LogLevel;
17
+
18
+ constructor(
19
+ useConsole: boolean,
20
+ delegate: LogsAPI.Logger,
21
+ context?: Record<string, unknown> | undefined,
22
+ logLevel?: LogLevel
23
+ ) {
24
+ this.delegate = delegate;
25
+ this.context = context;
26
+ this.logLevel = logLevel ?? 'info';
27
+ this.logger = useConsole ? new ConsoleLogger(context, false, this.logLevel) : undefined;
28
+ }
29
+
30
+ private formatMessage(message: unknown) {
31
+ if (typeof message === 'string') {
32
+ return message;
33
+ }
34
+ try {
35
+ return safeStringify(message);
36
+ } catch {
37
+ // Handle circular references or other unknown stringification errors
38
+ return String(message);
39
+ }
40
+ }
41
+
42
+ private getAttributes(): Record<string, unknown> | undefined {
43
+ return this.context;
44
+ }
45
+
46
+ private emit(severityNumber: LogsAPI.SeverityNumber, severityText: string, body: string) {
47
+ const attributes = this.getAttributes();
48
+
49
+ try {
50
+ this.delegate.emit({
51
+ severityNumber,
52
+ severityText,
53
+ body,
54
+ attributes: attributes as LogsAPI.LogRecord['attributes'],
55
+ });
56
+ } catch (error) {
57
+ // Log error to console if available, but don't fail the entire operation
58
+ this.logger?.error('Failed to emit log to OTLP instance:', error);
59
+ }
60
+ }
61
+
62
+ private shouldLog(level: LogsAPI.SeverityNumber): boolean {
63
+ switch (this.logLevel) {
64
+ case 'trace':
65
+ return true;
66
+ case 'debug':
67
+ return (
68
+ level === LogsAPI.SeverityNumber.DEBUG ||
69
+ level === LogsAPI.SeverityNumber.INFO ||
70
+ level === LogsAPI.SeverityNumber.WARN ||
71
+ level === LogsAPI.SeverityNumber.ERROR
72
+ );
73
+ case 'info':
74
+ return (
75
+ level === LogsAPI.SeverityNumber.INFO ||
76
+ level === LogsAPI.SeverityNumber.WARN ||
77
+ level === LogsAPI.SeverityNumber.ERROR
78
+ );
79
+ case 'warn':
80
+ return level === LogsAPI.SeverityNumber.WARN || level === LogsAPI.SeverityNumber.ERROR;
81
+ case 'error':
82
+ return level === LogsAPI.SeverityNumber.ERROR;
83
+ }
84
+ return false;
85
+ }
86
+
87
+ trace(message: unknown, ...args: unknown[]) {
88
+ if (!this.shouldLog(LogsAPI.SeverityNumber.TRACE)) {
89
+ return;
90
+ }
91
+ this.logger?.trace(message, ...args);
92
+ let body: string;
93
+ try {
94
+ body = format(this.formatMessage(message), ...args);
95
+ } catch {
96
+ // Fallback if format causes recursion
97
+ body = `${this.formatMessage(message)} ${args.map((arg) => String(arg)).join(' ')}`;
98
+ }
99
+ this.emit(LogsAPI.SeverityNumber.TRACE, 'TRACE', body);
100
+ }
101
+ debug(message: unknown, ...args: unknown[]) {
102
+ if (!this.shouldLog(LogsAPI.SeverityNumber.DEBUG)) {
103
+ return;
104
+ }
105
+ this.logger?.debug(message, ...args);
106
+ let body: string;
107
+ try {
108
+ body = format(this.formatMessage(message), ...args);
109
+ } catch {
110
+ // Fallback if format causes recursion
111
+ body = `${this.formatMessage(message)} ${args.map((arg) => String(arg)).join(' ')}`;
112
+ }
113
+ this.emit(LogsAPI.SeverityNumber.DEBUG, 'DEBUG', body);
114
+ }
115
+ info(message: unknown, ...args: unknown[]) {
116
+ if (!this.shouldLog(LogsAPI.SeverityNumber.INFO)) {
117
+ return;
118
+ }
119
+ this.logger?.info(message, ...args);
120
+ let body: string;
121
+ try {
122
+ body = format(this.formatMessage(message), ...args);
123
+ } catch {
124
+ // Fallback if format causes recursion
125
+ body = `${this.formatMessage(message)} ${args.map((arg) => String(arg)).join(' ')}`;
126
+ }
127
+ this.emit(LogsAPI.SeverityNumber.INFO, 'INFO', body);
128
+ }
129
+ warn(message: unknown, ...args: unknown[]) {
130
+ if (!this.shouldLog(LogsAPI.SeverityNumber.WARN)) {
131
+ return;
132
+ }
133
+ this.logger?.warn(message, ...args);
134
+ let body: string;
135
+ try {
136
+ body = format(this.formatMessage(message), ...args);
137
+ } catch {
138
+ // Fallback if format causes recursion
139
+ body = `${this.formatMessage(message)} ${args.map((arg) => String(arg)).join(' ')}`;
140
+ }
141
+ this.emit(LogsAPI.SeverityNumber.WARN, 'WARN', body);
142
+ }
143
+ error(message: unknown, ...args: unknown[]) {
144
+ if (!this.shouldLog(LogsAPI.SeverityNumber.ERROR)) {
145
+ return;
146
+ }
147
+ this.logger?.error(message, ...args);
148
+ let body: string;
149
+ try {
150
+ body = format(this.formatMessage(message), ...args);
151
+ } catch {
152
+ // Fallback if format causes recursion
153
+ body = `${this.formatMessage(message)} ${args.map((arg) => String(arg)).join(' ')}`;
154
+ }
155
+ this.emit(LogsAPI.SeverityNumber.ERROR, 'ERROR', body);
156
+ }
157
+ fatal(message: unknown, ...args: unknown[]): never {
158
+ this.error(message, ...args);
159
+ process.exit(1);
160
+ }
161
+ child(opts: Record<string, unknown>): Logger {
162
+ return new OtelLogger(
163
+ !!this.logger,
164
+ this.delegate,
165
+ {
166
+ ...(this.context ?? {}),
167
+ ...opts,
168
+ },
169
+ this.logLevel
170
+ );
171
+ }
172
+ }
173
+
174
+ /**
175
+ * Creates a logger that integrates with OpenTelemetry
176
+ *
177
+ * @param useConsole - Whether to also log to the console
178
+ * @param context - Additional context to include with log records
179
+ * @returns A logger instance
180
+ */
181
+ export function createLogger(
182
+ useConsole: boolean,
183
+ context?: Record<string, unknown>,
184
+ logLevel?: LogLevel
185
+ ): Logger {
186
+ const delegate = LogsAPI.logs.getLogger('default', undefined, {
187
+ scopeAttributes: context as LogsAPI.LoggerOptions['scopeAttributes'],
188
+ });
189
+ return new OtelLogger(useConsole, delegate, context, logLevel);
190
+ }
191
+
192
+ /**
193
+ * Patches the global console object to integrate with OpenTelemetry logging
194
+ *
195
+ * @param attributes - Attributes to include with all console log records
196
+ */
197
+ export function patchConsole(
198
+ enabled: boolean,
199
+ attributes: Record<string, unknown>,
200
+ logLevel: LogLevel
201
+ ) {
202
+ if (!enabled) {
203
+ return;
204
+ }
205
+ const _patch = { ...__originalConsole };
206
+ const delegate = createLogger(true, attributes, logLevel);
207
+
208
+ // Patch individual console methods instead of reassigning the whole object
209
+ _patch.log = (...args: unknown[]) => {
210
+ delegate.info(args[0] as string, ...args.slice(1));
211
+ };
212
+ _patch.error = (...args: unknown[]) => {
213
+ delegate.error(args[0] as string, ...args.slice(1));
214
+ };
215
+ _patch.warn = (...args: unknown[]) => {
216
+ delegate.warn(args[0] as string, ...args.slice(1));
217
+ };
218
+ _patch.debug = (...args: unknown[]) => {
219
+ delegate.debug(args[0] as string, ...args.slice(1));
220
+ };
221
+ _patch.info = (...args: unknown[]) => {
222
+ delegate.info(args[0] as string, ...args.slice(1));
223
+ };
224
+ _patch.dir = (...args: unknown[]) => {
225
+ let msg = '';
226
+ if (args.length === 1) {
227
+ msg = format(args[0]);
228
+ } else if (args.length >= 2) {
229
+ msg = format(args[0], args[1]);
230
+ } else {
231
+ msg = safeStringify(args);
232
+ }
233
+ delegate.debug(msg);
234
+ };
235
+ _patch.dirxml = (...args: unknown[]) => {
236
+ delegate.debug('dirxml:', ...args);
237
+ };
238
+ _patch.table = (...args: unknown[]) => {
239
+ delegate.debug('table:', ...args);
240
+ };
241
+ _patch.trace = (...args: unknown[]) => {
242
+ delegate.debug(args[0] as string, ...args.slice(1));
243
+ };
244
+ _patch.group = (...args: unknown[]) => {
245
+ delegate.debug('group:', ...args);
246
+ };
247
+ _patch.groupCollapsed = (...args: unknown[]) => {
248
+ delegate.debug('groupCollapsed:', ...args);
249
+ };
250
+ _patch.groupEnd = () => {
251
+ delegate.debug('groupEnd');
252
+ };
253
+ _patch.clear = () => {
254
+ /* no-op */
255
+ };
256
+ _patch.count = (...args: unknown[]) => {
257
+ delegate.debug('count:', ...args);
258
+ };
259
+ _patch.countReset = (...args: unknown[]) => {
260
+ delegate.debug('countReset:', ...args);
261
+ };
262
+ _patch.assert = (condition?: boolean, ...args: unknown[]) => {
263
+ if (!condition) {
264
+ delegate.error('assertion failed:', ...args);
265
+ }
266
+ };
267
+ _patch.time = (...args: unknown[]) => {
268
+ delegate.debug('time:', ...args);
269
+ };
270
+ _patch.timeLog = (...args: unknown[]) => {
271
+ delegate.debug('timeLog:', ...args);
272
+ };
273
+ _patch.timeEnd = (...args: unknown[]) => {
274
+ delegate.debug('timeEnd:', ...args);
275
+ };
276
+ _patch.profile = (...args: unknown[]) => {
277
+ delegate.debug('profile:', ...args);
278
+ };
279
+ _patch.profileEnd = (...args: unknown[]) => {
280
+ delegate.debug('profileEnd:', ...args);
281
+ };
282
+
283
+ // biome-ignore lint/suspicious/noGlobalAssign: intentionally replacing console with instrumented version
284
+ console = globalThis.console = _patch;
285
+ }
@@ -0,0 +1,403 @@
1
+ import type { SpanProcessor } from '@opentelemetry/sdk-trace-base';
2
+ import { BatchSpanProcessor, SimpleSpanProcessor } from '@opentelemetry/sdk-trace-base';
3
+ import opentelemetry, { type Meter, metrics, propagation, type Tracer } from '@opentelemetry/api';
4
+ import * as LogsAPI from '@opentelemetry/api-logs';
5
+ import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
6
+ import {
7
+ CompositePropagator,
8
+ W3CBaggagePropagator,
9
+ W3CTraceContextPropagator,
10
+ } from '@opentelemetry/core';
11
+ import { OTLPLogExporter } from '@opentelemetry/exporter-logs-otlp-http';
12
+ import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-http';
13
+ import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
14
+ import { HostMetrics } from '@opentelemetry/host-metrics';
15
+ import { CompressionAlgorithm } from '@opentelemetry/otlp-exporter-base';
16
+ import { resourceFromAttributes } from '@opentelemetry/resources';
17
+ import type { Resource } from '@opentelemetry/resources';
18
+ import {
19
+ BatchLogRecordProcessor,
20
+ LoggerProvider,
21
+ type LogRecordProcessor,
22
+ SimpleLogRecordProcessor,
23
+ } from '@opentelemetry/sdk-logs';
24
+ import { MeterProvider, PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics';
25
+ import { NodeSDK } from '@opentelemetry/sdk-node';
26
+ import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } from '@opentelemetry/semantic-conventions';
27
+ import type { Logger } from './logger';
28
+ import { ConsoleLogRecordExporter, DebugSpanExporter } from './console';
29
+ import { instrumentFetch } from './fetch';
30
+ import { createLogger, patchConsole } from './logger';
31
+ import type { LogLevel } from '@agentuity/core';
32
+ import { JSONLLogExporter, JSONLTraceExporter, JSONLMetricExporter } from './exporters';
33
+ import { telemetry as telemetryGlobal } from './globals';
34
+ import { getServiceUrls } from '@agentuity/server';
35
+
36
+ /**
37
+ * Configuration for Telemetry/OTel initialization
38
+ */
39
+ export interface TelemetryConfig {
40
+ /** Service name (default: AGENTUITY_APP_NAME env) */
41
+ name?: string;
42
+ /** Service version (default: AGENTUITY_APP_VERSION env) */
43
+ version?: string;
44
+ /** OTel collector URL (default: derived from AGENTUITY_REGION) */
45
+ url?: string;
46
+ /** Bearer token for auth (default: AGENTUITY_SDK_KEY) */
47
+ bearerToken?: string;
48
+ /** Organization ID (default: AGENTUITY_CLOUD_ORG_ID) */
49
+ orgId?: string;
50
+ /** Project ID (default: AGENTUITY_CLOUD_PROJECT_ID) */
51
+ projectId?: string;
52
+ /** Deployment ID (default: AGENTUITY_CLOUD_DEPLOYMENT_ID) */
53
+ deploymentId?: string;
54
+ /** Environment (default: AGENTUITY_ENVIRONMENT or NODE_ENV) */
55
+ environment?: string;
56
+ /** CLI version (default: AGENTUITY_CLI_VERSION) */
57
+ cliVersion?: string;
58
+ /** SDK version */
59
+ sdkVersion?: string;
60
+ /** Development mode (default: AGENTUITY_SDK_DEV_MODE) */
61
+ devmode?: boolean;
62
+ /** Custom span processors */
63
+ spanProcessors?: Array<SpanProcessor>;
64
+ /** Log level (default: 'warn') */
65
+ logLevel?: LogLevel;
66
+ /** JSONL export base path (default: AGENTUITY_CLOUD_EXPORT_DIR) */
67
+ jsonlBasePath?: string;
68
+ }
69
+
70
+ /**
71
+ * Response from Telemetry initialization
72
+ */
73
+ export interface TelemetryResponse {
74
+ tracer: Tracer;
75
+ meter: Meter;
76
+ logger: Logger;
77
+ shutdown: () => Promise<void>;
78
+ }
79
+
80
+ const devmodeExportInterval = 1_000; // 1 second
81
+ const productionExportInterval = 10_000; // 10 seconds
82
+
83
+ export const createResource = (config: Required<TelemetryConfig>): Resource => {
84
+ const {
85
+ name,
86
+ version,
87
+ orgId,
88
+ projectId,
89
+ deploymentId,
90
+ environment,
91
+ devmode,
92
+ cliVersion,
93
+ sdkVersion,
94
+ } = config;
95
+
96
+ return resourceFromAttributes({
97
+ [ATTR_SERVICE_NAME]: name,
98
+ [ATTR_SERVICE_VERSION]: version,
99
+ '@agentuity/orgId': orgId,
100
+ '@agentuity/projectId': projectId,
101
+ '@agentuity/deploymentId': deploymentId,
102
+ '@agentuity/env': environment,
103
+ '@agentuity/devmode': devmode,
104
+ '@agentuity/sdkVersion': sdkVersion,
105
+ '@agentuity/cliVersion': cliVersion,
106
+ });
107
+ };
108
+
109
+ const createLoggerProvider = ({
110
+ url,
111
+ headers,
112
+ resource,
113
+ jsonlBasePath,
114
+ useConsoleExporters,
115
+ logLevel: _logLevel,
116
+ }: {
117
+ url?: string;
118
+ headers?: Record<string, string>;
119
+ resource: Resource;
120
+ logLevel: LogLevel;
121
+ jsonlBasePath?: string;
122
+ useConsoleExporters: boolean;
123
+ }) => {
124
+ let processor: LogRecordProcessor;
125
+ let exporter: OTLPLogExporter | JSONLLogExporter | undefined;
126
+
127
+ if (useConsoleExporters) {
128
+ processor = new SimpleLogRecordProcessor(new ConsoleLogRecordExporter(true));
129
+ } else if (jsonlBasePath) {
130
+ exporter = new JSONLLogExporter(jsonlBasePath);
131
+ processor = new BatchLogRecordProcessor(exporter);
132
+ } else if (url) {
133
+ const otlpExporter = new OTLPLogExporter({
134
+ url: `${url}/v1/logs`,
135
+ headers,
136
+ compression: CompressionAlgorithm.GZIP,
137
+ timeoutMillis: 10_000,
138
+ });
139
+ exporter = otlpExporter;
140
+ processor = new BatchLogRecordProcessor(otlpExporter);
141
+ } else {
142
+ processor = new SimpleLogRecordProcessor(new ConsoleLogRecordExporter(false));
143
+ }
144
+ const provider = new LoggerProvider({
145
+ resource,
146
+ processors: [processor],
147
+ });
148
+ LogsAPI.logs.setGlobalLoggerProvider(provider);
149
+
150
+ return { processor, provider, exporter };
151
+ };
152
+
153
+ /**
154
+ * Get configuration from environment variables
155
+ */
156
+ function getConfigFromEnv(): Required<TelemetryConfig> {
157
+ const region = process.env.AGENTUITY_REGION ?? 'usc';
158
+ const serviceUrls = getServiceUrls(region);
159
+
160
+ return {
161
+ name: process.env.AGENTUITY_APP_NAME ?? 'agentuity-app',
162
+ version: process.env.AGENTUITY_APP_VERSION ?? '1.0.0',
163
+ url: serviceUrls.otel,
164
+ bearerToken: process.env.AGENTUITY_OTLP_BEARER_TOKEN ?? process.env.AGENTUITY_SDK_KEY ?? '',
165
+ orgId: process.env.AGENTUITY_CLOUD_ORG_ID ?? 'unknown',
166
+ projectId: process.env.AGENTUITY_CLOUD_PROJECT_ID ?? 'unknown',
167
+ deploymentId: process.env.AGENTUITY_CLOUD_DEPLOYMENT_ID ?? 'unknown',
168
+ environment: process.env.AGENTUITY_ENVIRONMENT ?? process.env.NODE_ENV ?? 'development',
169
+ cliVersion: process.env.AGENTUITY_CLI_VERSION ?? 'unknown',
170
+ sdkVersion: process.env.AGENTUITY_CLOUD_SDK_VERSION ?? 'unknown',
171
+ devmode: process.env.AGENTUITY_SDK_DEV_MODE === 'true',
172
+ logLevel: 'warn' as LogLevel,
173
+ jsonlBasePath: process.env.AGENTUITY_CLOUD_EXPORT_DIR ?? '',
174
+ spanProcessors: [],
175
+ };
176
+ }
177
+
178
+ /**
179
+ * Registers and initializes Telemetry with the specified configuration.
180
+ *
181
+ * Idempotent: if called again (e.g. during bun --hot reload), the previous
182
+ * instance is shut down before creating a new one.
183
+ *
184
+ * @param config - Optional configuration overrides (defaults from env vars)
185
+ * @returns An object containing the tracer, logger, and shutdown function
186
+ */
187
+ export function registerTelemetry(config?: TelemetryConfig): TelemetryResponse {
188
+ // Shut down previous instance if this is a hot reload
189
+ const previous = telemetryGlobal.get();
190
+ if (previous) {
191
+ previous.shutdown().catch(() => {});
192
+ }
193
+
194
+ // Merge provided config with env defaults
195
+ const envConfig = getConfigFromEnv();
196
+ const mergedConfig: Required<TelemetryConfig> = {
197
+ name: config?.name ?? envConfig.name,
198
+ version: config?.version ?? envConfig.version,
199
+ url: config?.url ?? envConfig.url,
200
+ bearerToken: config?.bearerToken ?? envConfig.bearerToken,
201
+ orgId: config?.orgId ?? envConfig.orgId,
202
+ projectId: config?.projectId ?? envConfig.projectId,
203
+ deploymentId: config?.deploymentId ?? envConfig.deploymentId,
204
+ environment: config?.environment ?? envConfig.environment,
205
+ cliVersion: config?.cliVersion ?? envConfig.cliVersion,
206
+ sdkVersion: config?.sdkVersion ?? envConfig.sdkVersion,
207
+ devmode: config?.devmode ?? envConfig.devmode,
208
+ logLevel: config?.logLevel ?? envConfig.logLevel,
209
+ jsonlBasePath: config?.jsonlBasePath ?? envConfig.jsonlBasePath,
210
+ spanProcessors: config?.spanProcessors ?? envConfig.spanProcessors,
211
+ };
212
+
213
+ const {
214
+ url,
215
+ name,
216
+ version,
217
+ bearerToken,
218
+ environment,
219
+ orgId,
220
+ projectId,
221
+ deploymentId,
222
+ devmode,
223
+ logLevel,
224
+ jsonlBasePath,
225
+ spanProcessors,
226
+ } = mergedConfig;
227
+
228
+ let headers: Record<string, string> | undefined;
229
+ if (bearerToken) {
230
+ headers = { Authorization: `Bearer ${bearerToken}` };
231
+ }
232
+
233
+ // Use console debug exporters for local debugging
234
+ const useConsoleExporters = process.env.AGENTUITY_DEBUG_OTEL_CONSOLE === 'true';
235
+
236
+ const resource = createResource(mergedConfig);
237
+ const loggerProvider = createLoggerProvider({
238
+ url,
239
+ headers,
240
+ resource,
241
+ logLevel,
242
+ jsonlBasePath: jsonlBasePath || undefined,
243
+ useConsoleExporters,
244
+ });
245
+
246
+ const attrs = {
247
+ '@agentuity/orgId': orgId,
248
+ '@agentuity/projectId': projectId,
249
+ '@agentuity/deploymentId': deploymentId,
250
+ '@agentuity/env': environment,
251
+ '@agentuity/devmode': devmode,
252
+ '@agentuity/language': 'javascript',
253
+ };
254
+ const logger = createLogger(!!url, attrs, logLevel);
255
+
256
+ // Don't patch console if using console exporters (avoid double logging)
257
+ if (!useConsoleExporters) {
258
+ patchConsole(!!url, attrs, logLevel);
259
+ }
260
+
261
+ // Build trace exporter (OTLP or JSONL)
262
+ const traceExporter = jsonlBasePath
263
+ ? new JSONLTraceExporter(jsonlBasePath)
264
+ : url
265
+ ? new OTLPTraceExporter({
266
+ url: `${url}/v1/traces`,
267
+ headers,
268
+ keepAlive: true,
269
+ compression: CompressionAlgorithm.GZIP,
270
+ })
271
+ : undefined;
272
+
273
+ // Build metric exporter (OTLP or JSONL)
274
+ const metricExporter = jsonlBasePath
275
+ ? new JSONLMetricExporter(jsonlBasePath)
276
+ : url
277
+ ? new OTLPMetricExporter({
278
+ url: `${url}/v1/metrics`,
279
+ headers,
280
+ keepAlive: true,
281
+ compression: CompressionAlgorithm.GZIP,
282
+ })
283
+ : undefined;
284
+
285
+ // Create span processors
286
+ const allSpanProcessors: SpanProcessor[] = [];
287
+
288
+ if (traceExporter) {
289
+ allSpanProcessors.push(new BatchSpanProcessor(traceExporter));
290
+ }
291
+
292
+ if (useConsoleExporters) {
293
+ allSpanProcessors.push(new SimpleSpanProcessor(new DebugSpanExporter()));
294
+ }
295
+
296
+ // Add custom span processors
297
+ allSpanProcessors.push(...spanProcessors);
298
+
299
+ // Create metric readers
300
+ const sdkMetricReader = metricExporter
301
+ ? new PeriodicExportingMetricReader({
302
+ exporter: metricExporter,
303
+ exportTimeoutMillis: devmode ? devmodeExportInterval : productionExportInterval,
304
+ exportIntervalMillis: devmode ? devmodeExportInterval : productionExportInterval,
305
+ })
306
+ : undefined;
307
+
308
+ const hostMetricReader = metricExporter
309
+ ? new PeriodicExportingMetricReader({
310
+ exporter: metricExporter,
311
+ exportTimeoutMillis: devmode ? devmodeExportInterval : productionExportInterval,
312
+ exportIntervalMillis: devmode ? devmodeExportInterval : productionExportInterval,
313
+ })
314
+ : undefined;
315
+
316
+ const meterProvider = hostMetricReader
317
+ ? new MeterProvider({ resource, readers: [hostMetricReader] })
318
+ : undefined;
319
+
320
+ if (meterProvider) {
321
+ metrics.setGlobalMeterProvider(meterProvider);
322
+ }
323
+
324
+ const hostMetrics = meterProvider ? new HostMetrics({ meterProvider }) : undefined;
325
+
326
+ let running = false;
327
+ let instrumentationSDK: NodeSDK | undefined;
328
+
329
+ if (url || useConsoleExporters) {
330
+ const propagator = new CompositePropagator({
331
+ propagators: [new W3CTraceContextPropagator(), new W3CBaggagePropagator()],
332
+ });
333
+ propagation.setGlobalPropagator(propagator);
334
+
335
+ instrumentFetch();
336
+
337
+ instrumentationSDK = new NodeSDK({
338
+ logRecordProcessor: loggerProvider.processor,
339
+ metricReader: sdkMetricReader,
340
+ instrumentations: [getNodeAutoInstrumentations()],
341
+ resource,
342
+ textMapPropagator: propagator,
343
+ spanProcessors: allSpanProcessors,
344
+ });
345
+ instrumentationSDK.start();
346
+ hostMetrics?.start();
347
+
348
+ logger.debug('Telemetry configured successfully');
349
+ logger.debug('Sending telemetry data to %s', url);
350
+ running = true;
351
+ }
352
+
353
+ const tracer = opentelemetry.trace.getTracer(name, version);
354
+ const meter = metrics.getMeter(name, version);
355
+
356
+ const shutdown = async () => {
357
+ if (running) {
358
+ running = false;
359
+ logger.debug('shutting down OpenTelemetry');
360
+ await loggerProvider.provider
361
+ .forceFlush()
362
+ .catch((e) => logger.warn('error in forceFlush. %s', e));
363
+ await loggerProvider.exporter
364
+ ?.shutdown()
365
+ .catch((e) => !devmode && logger.warn('error in shutdown of exporter. %s', e));
366
+ await instrumentationSDK
367
+ ?.shutdown()
368
+ .catch((e) => !devmode && logger.warn('error in shutdown of instrumentation. %s', e));
369
+ logger.debug('shut down OpenTelemetry');
370
+ }
371
+ };
372
+
373
+ if (url && bearerToken) {
374
+ logger.info('connected to Agentuity Agent Cloud');
375
+ }
376
+
377
+ const instance: TelemetryResponse = { tracer, meter, logger, shutdown };
378
+ telemetryGlobal.set(instance);
379
+ return instance;
380
+ }
381
+
382
+ /**
383
+ * Alias for registerTelemetry (shorter name)
384
+ */
385
+ export const register = registerTelemetry;
386
+
387
+ /**
388
+ * Get the current telemetry instance (or undefined if not initialized)
389
+ */
390
+ export function getTelemetry(): TelemetryResponse | undefined {
391
+ return telemetryGlobal.get();
392
+ }
393
+
394
+ /**
395
+ * Ensure telemetry is initialized (auto-init from env vars if needed)
396
+ */
397
+ export function ensureInitialized(): TelemetryResponse {
398
+ let instance = telemetryGlobal.get();
399
+ if (!instance) {
400
+ instance = registerTelemetry();
401
+ }
402
+ return instance;
403
+ }