@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.
- package/dist/console.d.ts +33 -0
- package/dist/console.d.ts.map +1 -0
- package/dist/console.js +86 -0
- package/dist/console.js.map +1 -0
- package/dist/exporters/index.d.ts +4 -0
- package/dist/exporters/index.d.ts.map +1 -0
- package/dist/exporters/index.js +4 -0
- package/dist/exporters/index.js.map +1 -0
- package/dist/exporters/jsonl-log-exporter.d.ts +36 -0
- package/dist/exporters/jsonl-log-exporter.d.ts.map +1 -0
- package/dist/exporters/jsonl-log-exporter.js +103 -0
- package/dist/exporters/jsonl-log-exporter.js.map +1 -0
- package/dist/exporters/jsonl-metric-exporter.d.ts +40 -0
- package/dist/exporters/jsonl-metric-exporter.d.ts.map +1 -0
- package/dist/exporters/jsonl-metric-exporter.js +104 -0
- package/dist/exporters/jsonl-metric-exporter.js.map +1 -0
- package/dist/exporters/jsonl-trace-exporter.d.ts +36 -0
- package/dist/exporters/jsonl-trace-exporter.d.ts.map +1 -0
- package/dist/exporters/jsonl-trace-exporter.js +111 -0
- package/dist/exporters/jsonl-trace-exporter.js.map +1 -0
- package/dist/fetch.d.ts +12 -0
- package/dist/fetch.d.ts.map +1 -0
- package/dist/fetch.js +82 -0
- package/dist/fetch.js.map +1 -0
- package/dist/globals.d.ts +9 -0
- package/dist/globals.d.ts.map +1 -0
- package/dist/globals.js +13 -0
- package/dist/globals.js.map +1 -0
- package/dist/http.d.ts +16 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +44 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +50 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +62 -0
- package/dist/index.js.map +1 -0
- package/dist/logger/console.d.ts +69 -0
- package/dist/logger/console.d.ts.map +1 -0
- package/dist/logger/console.js +278 -0
- package/dist/logger/console.js.map +1 -0
- package/dist/logger/index.d.ts +4 -0
- package/dist/logger/index.d.ts.map +1 -0
- package/dist/logger/index.js +3 -0
- package/dist/logger/index.js.map +1 -0
- package/dist/logger/internal.d.ts +79 -0
- package/dist/logger/internal.d.ts.map +1 -0
- package/dist/logger/internal.js +133 -0
- package/dist/logger/internal.js.map +1 -0
- package/dist/logger/user.d.ts +8 -0
- package/dist/logger/user.d.ts.map +1 -0
- package/dist/logger/user.js +7 -0
- package/dist/logger/user.js.map +1 -0
- package/dist/logger/util.d.ts +11 -0
- package/dist/logger/util.d.ts.map +1 -0
- package/dist/logger/util.js +77 -0
- package/dist/logger/util.js.map +1 -0
- package/dist/logger.d.ts +40 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +259 -0
- package/dist/logger.js.map +1 -0
- package/dist/telemetry.d.ts +71 -0
- package/dist/telemetry.d.ts.map +1 -0
- package/dist/telemetry.js +274 -0
- package/dist/telemetry.js.map +1 -0
- package/dist/tracestate.d.ts +44 -0
- package/dist/tracestate.d.ts.map +1 -0
- package/dist/tracestate.js +84 -0
- package/dist/tracestate.js.map +1 -0
- package/package.json +58 -0
- package/src/console.ts +91 -0
- package/src/exporters/README.md +217 -0
- package/src/exporters/index.ts +3 -0
- package/src/exporters/jsonl-log-exporter.ts +113 -0
- package/src/exporters/jsonl-metric-exporter.ts +120 -0
- package/src/exporters/jsonl-trace-exporter.ts +121 -0
- package/src/fetch.ts +105 -0
- package/src/globals.ts +18 -0
- package/src/http.ts +53 -0
- package/src/index.ts +82 -0
- package/src/logger/console.ts +322 -0
- package/src/logger/index.ts +3 -0
- package/src/logger/internal.ts +165 -0
- package/src/logger/user.ts +15 -0
- package/src/logger/util.ts +80 -0
- package/src/logger.ts +285 -0
- package/src/telemetry.ts +403 -0
- 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
|
+
}
|
package/src/telemetry.ts
ADDED
|
@@ -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
|
+
}
|