@blaxel/telemetry 0.2.49-dev.213 → 0.2.49-dev1
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/auth_refresh_exporters.d.ts +44 -0
- package/dist/auth_refresh_exporters.js +200 -0
- package/dist/cjs/.tsbuildinfo +1 -1
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.js +18 -0
- package/dist/json_logger.d.ts +8 -0
- package/dist/json_logger.js +155 -0
- package/dist/telemetry.d.ts +56 -0
- package/dist/telemetry.js +279 -0
- package/dist/telemetry_provider.d.ts +7 -0
- package/dist/telemetry_provider.js +62 -0
- package/package.json +2 -2
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.blaxelTelemetry = exports.setJsonLogger = exports.createTraceExporter = exports.createMetricExporter = exports.AuthRefreshingSpanExporter = exports.AuthRefreshingMetricExporter = void 0;
|
|
4
|
+
const core_1 = require("@blaxel/core");
|
|
5
|
+
const json_logger_1 = require("./json_logger");
|
|
6
|
+
const telemetry_1 = require("./telemetry");
|
|
7
|
+
Object.defineProperty(exports, "blaxelTelemetry", { enumerable: true, get: function () { return telemetry_1.blaxelTelemetry; } });
|
|
8
|
+
telemetry_1.blaxelTelemetry.initialize();
|
|
9
|
+
if (core_1.settings.loggerType === "json") {
|
|
10
|
+
(0, json_logger_1.setJsonLogger)();
|
|
11
|
+
}
|
|
12
|
+
var auth_refresh_exporters_1 = require("./auth_refresh_exporters");
|
|
13
|
+
Object.defineProperty(exports, "AuthRefreshingMetricExporter", { enumerable: true, get: function () { return auth_refresh_exporters_1.AuthRefreshingMetricExporter; } });
|
|
14
|
+
Object.defineProperty(exports, "AuthRefreshingSpanExporter", { enumerable: true, get: function () { return auth_refresh_exporters_1.AuthRefreshingSpanExporter; } });
|
|
15
|
+
Object.defineProperty(exports, "createMetricExporter", { enumerable: true, get: function () { return auth_refresh_exporters_1.createMetricExporter; } });
|
|
16
|
+
Object.defineProperty(exports, "createTraceExporter", { enumerable: true, get: function () { return auth_refresh_exporters_1.createTraceExporter; } });
|
|
17
|
+
var json_logger_2 = require("./json_logger");
|
|
18
|
+
Object.defineProperty(exports, "setJsonLogger", { enumerable: true, get: function () { return json_logger_2.setJsonLogger; } });
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare function setJsonLogger(): void;
|
|
2
|
+
export declare const originalLogger: {
|
|
3
|
+
info: (message?: any, ...optionalParams: any[]) => void;
|
|
4
|
+
error: (message?: any, ...optionalParams: any[]) => void;
|
|
5
|
+
warn: (message?: any, ...optionalParams: any[]) => void;
|
|
6
|
+
debug: (message?: any, ...optionalParams: any[]) => void;
|
|
7
|
+
log: (message?: any, ...optionalParams: any[]) => void;
|
|
8
|
+
};
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/* eslint-disable no-console */
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.originalLogger = void 0;
|
|
5
|
+
exports.setJsonLogger = setJsonLogger;
|
|
6
|
+
const core_1 = require("@blaxel/core");
|
|
7
|
+
const api_1 = require("@opentelemetry/api");
|
|
8
|
+
function setJsonLogger() {
|
|
9
|
+
console.debug = (message, ...args) => {
|
|
10
|
+
const msg = formatLogMessage("DEBUG", message, args);
|
|
11
|
+
exports.originalLogger.log(msg);
|
|
12
|
+
};
|
|
13
|
+
console.log = (message, ...args) => {
|
|
14
|
+
const msg = formatLogMessage("INFO", message, args);
|
|
15
|
+
exports.originalLogger.log(msg);
|
|
16
|
+
};
|
|
17
|
+
console.info = (message, ...args) => {
|
|
18
|
+
const msg = formatLogMessage("INFO", message, args);
|
|
19
|
+
exports.originalLogger.log(msg);
|
|
20
|
+
};
|
|
21
|
+
console.warn = (message, ...args) => {
|
|
22
|
+
const msg = formatLogMessage("WARN", message, args);
|
|
23
|
+
exports.originalLogger.log(msg);
|
|
24
|
+
};
|
|
25
|
+
console.error = (message, ...args) => {
|
|
26
|
+
const msg = formatLogMessage("ERROR", message, args);
|
|
27
|
+
exports.originalLogger.log(msg);
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
exports.originalLogger = {
|
|
31
|
+
info: console.info,
|
|
32
|
+
error: console.error,
|
|
33
|
+
warn: console.warn,
|
|
34
|
+
debug: console.debug,
|
|
35
|
+
log: console.log,
|
|
36
|
+
};
|
|
37
|
+
const traceIdName = core_1.env.BL_LOGGER_TRACE_ID || 'trace_id';
|
|
38
|
+
const spanIdName = core_1.env.BL_LOGGER_SPAN_ID || 'span_id';
|
|
39
|
+
const labelsName = core_1.env.BL_LOGGER_LABELS || 'labels';
|
|
40
|
+
const traceIdPrefix = core_1.env.BL_LOGGER_TRACE_ID_PREFIX || '';
|
|
41
|
+
const spanIdPrefix = core_1.env.BL_LOGGER_SPAN_ID_PREFIX || '';
|
|
42
|
+
const taskIndex = core_1.env.BL_TASK_KEY || 'TASK_INDEX';
|
|
43
|
+
const taskPrefix = core_1.env.BL_TASK_PREFIX || '';
|
|
44
|
+
const executionKey = core_1.env.BL_EXECUTION_KEY || 'BL_EXECUTION_ID';
|
|
45
|
+
const executionPrefix = core_1.env.BL_EXECUTION_PREFIX || '';
|
|
46
|
+
// Validate environment variables to prevent issues
|
|
47
|
+
function validateEnvVar(value, defaultValue, varName) {
|
|
48
|
+
if (!value || value.trim() === '') {
|
|
49
|
+
exports.originalLogger.warn(`Warning: ${varName} environment variable is empty, using default: ${defaultValue}`);
|
|
50
|
+
return defaultValue;
|
|
51
|
+
}
|
|
52
|
+
return value;
|
|
53
|
+
}
|
|
54
|
+
const validatedLabelsName = validateEnvVar(labelsName, 'labels', 'BL_LOGGER_LABELS');
|
|
55
|
+
// Enhanced error serialization to capture all properties
|
|
56
|
+
function serializeError(error) {
|
|
57
|
+
const serialized = {
|
|
58
|
+
message: error.message,
|
|
59
|
+
name: error.name,
|
|
60
|
+
stack: error.stack
|
|
61
|
+
};
|
|
62
|
+
// Capture any additional properties on the error object
|
|
63
|
+
for (const key of Object.keys(error)) {
|
|
64
|
+
if (!(key in serialized)) {
|
|
65
|
+
try {
|
|
66
|
+
const value = error[key];
|
|
67
|
+
// Avoid circular references by limiting depth
|
|
68
|
+
serialized[key] = typeof value === 'object' ? (0, core_1.stringify)(value, 2) : value;
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
serialized[key] = '[Unserializable]';
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return serialized;
|
|
76
|
+
}
|
|
77
|
+
// Enhanced stringify function with better error handling
|
|
78
|
+
function enhancedStringify(obj, maxDepth = 2) {
|
|
79
|
+
if (obj instanceof Error) {
|
|
80
|
+
return JSON.stringify(serializeError(obj));
|
|
81
|
+
}
|
|
82
|
+
// Handle circular references by using a simple set to track seen objects
|
|
83
|
+
const seen = new WeakSet();
|
|
84
|
+
const stringifyWithCircularCheck = (value, depth = 0) => {
|
|
85
|
+
if (value === null || value === undefined)
|
|
86
|
+
return value;
|
|
87
|
+
if (typeof value !== 'object')
|
|
88
|
+
return value;
|
|
89
|
+
if (seen.has(value)) {
|
|
90
|
+
return '[Circular Reference]';
|
|
91
|
+
}
|
|
92
|
+
if (depth >= maxDepth) {
|
|
93
|
+
return Array.isArray(value) ? '[Array]' : '[Object]';
|
|
94
|
+
}
|
|
95
|
+
seen.add(value);
|
|
96
|
+
if (Array.isArray(value)) {
|
|
97
|
+
return value.map(item => stringifyWithCircularCheck(item, depth + 1));
|
|
98
|
+
}
|
|
99
|
+
const result = {};
|
|
100
|
+
for (const [key, val] of Object.entries(value)) {
|
|
101
|
+
result[key] = stringifyWithCircularCheck(val, depth + 1);
|
|
102
|
+
}
|
|
103
|
+
return result;
|
|
104
|
+
};
|
|
105
|
+
try {
|
|
106
|
+
const processed = stringifyWithCircularCheck(obj);
|
|
107
|
+
return JSON.stringify(processed);
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
return (0, core_1.stringify)(obj, maxDepth);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
// Format a log message with appropriate color and prefix
|
|
114
|
+
function formatLogMessage(severity, message, args) {
|
|
115
|
+
const messageStr = typeof message === "string" ? message : enhancedStringify(message, 2);
|
|
116
|
+
const argsStr = args.map(arg => typeof arg === "string" ? arg : enhancedStringify(arg, 2)).join(" ");
|
|
117
|
+
const msg = `${messageStr}${argsStr ? " " + argsStr : ""}`;
|
|
118
|
+
const logEntry = {
|
|
119
|
+
message: msg,
|
|
120
|
+
severity
|
|
121
|
+
};
|
|
122
|
+
logEntry[validatedLabelsName] = {};
|
|
123
|
+
const currentSpan = api_1.trace.getActiveSpan();
|
|
124
|
+
if (currentSpan) {
|
|
125
|
+
const { traceId, spanId } = currentSpan.spanContext();
|
|
126
|
+
logEntry[traceIdName] = `${traceIdPrefix}${traceId}`;
|
|
127
|
+
logEntry[spanIdName] = `${spanIdPrefix}${spanId}`;
|
|
128
|
+
}
|
|
129
|
+
const taskId = core_1.env[taskIndex] || null;
|
|
130
|
+
if (taskId) {
|
|
131
|
+
logEntry[validatedLabelsName]['blaxel-task'] = `${taskPrefix}${taskId}`;
|
|
132
|
+
}
|
|
133
|
+
const executionId = core_1.env[executionKey] || null;
|
|
134
|
+
if (executionId) {
|
|
135
|
+
logEntry[validatedLabelsName]['blaxel-execution'] = `${executionPrefix}${executionId.split('-').pop()}`;
|
|
136
|
+
}
|
|
137
|
+
try {
|
|
138
|
+
return JSON.stringify(logEntry);
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
// Fallback for serialization errors
|
|
142
|
+
const fallbackEntry = {
|
|
143
|
+
message: `JSON serialization failed: ${msg}`,
|
|
144
|
+
severity,
|
|
145
|
+
error: error instanceof Error ? error.message : String(error)
|
|
146
|
+
};
|
|
147
|
+
try {
|
|
148
|
+
return JSON.stringify(fallbackEntry);
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
// Last resort fallback
|
|
152
|
+
return `{"message":"${severity}: ${msg.replace(/"/g, '\\"')}","severity":"${severity}","error":"Failed to serialize log entry"}`;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { Span } from "@opentelemetry/api";
|
|
2
|
+
import { RawResourceAttribute, Resource } from "@opentelemetry/resources";
|
|
3
|
+
import { SpanProcessor } from "@opentelemetry/sdk-trace-node";
|
|
4
|
+
export declare class BlaxelResource implements Resource {
|
|
5
|
+
attributes: Record<string, string>;
|
|
6
|
+
constructor(attributes: Record<string, string>);
|
|
7
|
+
merge(other: Resource | null): Resource;
|
|
8
|
+
getRawAttributes(): RawResourceAttribute[];
|
|
9
|
+
}
|
|
10
|
+
export declare class DefaultAttributesSpanProcessor implements SpanProcessor {
|
|
11
|
+
private defaultAttributes;
|
|
12
|
+
constructor(defaultAttributes: Record<string, string>);
|
|
13
|
+
onStart(span: Span): void;
|
|
14
|
+
onEnd(): void;
|
|
15
|
+
shutdown(): Promise<void>;
|
|
16
|
+
forceFlush(): Promise<void>;
|
|
17
|
+
}
|
|
18
|
+
export type TelemetryOptions = {
|
|
19
|
+
workspace: string | null;
|
|
20
|
+
name: string | null;
|
|
21
|
+
authorization: string | null;
|
|
22
|
+
type: string | null;
|
|
23
|
+
};
|
|
24
|
+
declare class TelemetryManager {
|
|
25
|
+
private nodeTracerProvider;
|
|
26
|
+
private meterProvider;
|
|
27
|
+
private initialized;
|
|
28
|
+
private configured;
|
|
29
|
+
private shuttingDown;
|
|
30
|
+
constructor();
|
|
31
|
+
initialize(): void;
|
|
32
|
+
setConfiguration(): Promise<void>;
|
|
33
|
+
get tracer(): import("@opentelemetry/api").Tracer;
|
|
34
|
+
get enabled(): boolean;
|
|
35
|
+
get authHeaders(): Record<string, string>;
|
|
36
|
+
sleep(ms: number): Promise<unknown>;
|
|
37
|
+
flush(): Promise<void>;
|
|
38
|
+
setupSignalHandler(): void;
|
|
39
|
+
/**
|
|
40
|
+
* Get resource attributes for OpenTelemetry.
|
|
41
|
+
*/
|
|
42
|
+
get resourceAttributes(): Record<string, string>;
|
|
43
|
+
/**
|
|
44
|
+
* Initialize and return the OTLP Metric Exporter.
|
|
45
|
+
*/
|
|
46
|
+
getMetricExporter(): import("@opentelemetry/exporter-metrics-otlp-http").OTLPMetricExporter;
|
|
47
|
+
/**
|
|
48
|
+
* Initialize and return the OTLP Trace Exporter.
|
|
49
|
+
*/
|
|
50
|
+
getTraceExporter(): import("@opentelemetry/exporter-trace-otlp-http").OTLPTraceExporter;
|
|
51
|
+
instrumentApp(): void;
|
|
52
|
+
setExporters(): void;
|
|
53
|
+
shutdownApp(): Promise<void>;
|
|
54
|
+
}
|
|
55
|
+
export declare const blaxelTelemetry: TelemetryManager;
|
|
56
|
+
export {};
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.blaxelTelemetry = exports.DefaultAttributesSpanProcessor = exports.BlaxelResource = void 0;
|
|
4
|
+
const core_1 = require("@blaxel/core");
|
|
5
|
+
const api_1 = require("@opentelemetry/api");
|
|
6
|
+
const instrumentation_1 = require("@opentelemetry/instrumentation");
|
|
7
|
+
const instrumentation_http_1 = require("@opentelemetry/instrumentation-http");
|
|
8
|
+
const resources_1 = require("@opentelemetry/resources");
|
|
9
|
+
const sdk_metrics_1 = require("@opentelemetry/sdk-metrics");
|
|
10
|
+
const sdk_trace_node_1 = require("@opentelemetry/sdk-trace-node");
|
|
11
|
+
const auth_refresh_exporters_1 = require("./auth_refresh_exporters");
|
|
12
|
+
const telemetry_provider_1 = require("./telemetry_provider");
|
|
13
|
+
class BlaxelResource {
|
|
14
|
+
attributes;
|
|
15
|
+
constructor(attributes) {
|
|
16
|
+
this.attributes = attributes;
|
|
17
|
+
}
|
|
18
|
+
merge(other) {
|
|
19
|
+
if (other?.attributes) {
|
|
20
|
+
for (const [key, value] of Object.entries(other.attributes)) {
|
|
21
|
+
if (typeof value === "string") {
|
|
22
|
+
this.attributes[key] = value;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return this;
|
|
27
|
+
}
|
|
28
|
+
getRawAttributes() {
|
|
29
|
+
return Object.entries(this.attributes).map(([key, value]) => [key, value]);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
exports.BlaxelResource = BlaxelResource;
|
|
33
|
+
class DefaultAttributesSpanProcessor {
|
|
34
|
+
defaultAttributes;
|
|
35
|
+
constructor(defaultAttributes) {
|
|
36
|
+
this.defaultAttributes = defaultAttributes;
|
|
37
|
+
}
|
|
38
|
+
onStart(span) {
|
|
39
|
+
Object.entries(this.defaultAttributes).forEach(([key, value]) => {
|
|
40
|
+
span.setAttribute(key, value);
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
onEnd() { }
|
|
44
|
+
shutdown() {
|
|
45
|
+
return Promise.resolve();
|
|
46
|
+
}
|
|
47
|
+
forceFlush() {
|
|
48
|
+
return Promise.resolve();
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
exports.DefaultAttributesSpanProcessor = DefaultAttributesSpanProcessor;
|
|
52
|
+
class HasBeenProcessedSpanProcessor extends sdk_trace_node_1.BatchSpanProcessor {
|
|
53
|
+
constructor(exporter, config) {
|
|
54
|
+
super(exporter, config);
|
|
55
|
+
}
|
|
56
|
+
onEnd(span) {
|
|
57
|
+
super.onEnd(span);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
class TelemetryManager {
|
|
61
|
+
nodeTracerProvider;
|
|
62
|
+
meterProvider;
|
|
63
|
+
initialized;
|
|
64
|
+
configured;
|
|
65
|
+
shuttingDown;
|
|
66
|
+
constructor() {
|
|
67
|
+
this.nodeTracerProvider = null;
|
|
68
|
+
this.meterProvider = null;
|
|
69
|
+
this.initialized = false;
|
|
70
|
+
this.configured = false;
|
|
71
|
+
this.shuttingDown = false;
|
|
72
|
+
}
|
|
73
|
+
// This method need to stay sync to avoid non booted instrumentations
|
|
74
|
+
initialize() {
|
|
75
|
+
if (!this.enabled || this.initialized) {
|
|
76
|
+
core_1.logger.debug(`[TelemetryManager] Initialize skipped - enabled: ${this.enabled}, initialized: ${this.initialized}`);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
core_1.logger.debug("[TelemetryManager] Starting telemetry initialization");
|
|
80
|
+
this.instrumentApp();
|
|
81
|
+
this.setupSignalHandler();
|
|
82
|
+
this.initialized = true;
|
|
83
|
+
core_1.logger.debug("[TelemetryManager] Telemetry initialized, setting configuration async");
|
|
84
|
+
this.setConfiguration().catch((error) => {
|
|
85
|
+
core_1.logger.error("[TelemetryManager] Error setting configuration:", error);
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
async setConfiguration() {
|
|
89
|
+
if (!this.enabled || this.configured) {
|
|
90
|
+
core_1.logger.debug(`[TelemetryManager] SetConfiguration skipped - enabled: ${this.enabled}, configured: ${this.configured}`);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
core_1.logger.debug("[TelemetryManager] Starting authentication for telemetry configuration");
|
|
94
|
+
await (0, core_1.authenticate)();
|
|
95
|
+
core_1.logger.debug("[TelemetryManager] Authentication completed, setting up exporters");
|
|
96
|
+
this.setExporters();
|
|
97
|
+
core_1.logger.debug("[TelemetryManager] Telemetry configuration complete");
|
|
98
|
+
this.configured = true;
|
|
99
|
+
}
|
|
100
|
+
get tracer() {
|
|
101
|
+
return api_1.trace.getTracer("blaxel");
|
|
102
|
+
}
|
|
103
|
+
get enabled() {
|
|
104
|
+
return core_1.env.BL_ENABLE_OPENTELEMETRY === "true";
|
|
105
|
+
}
|
|
106
|
+
get authHeaders() {
|
|
107
|
+
const headers = {};
|
|
108
|
+
if (core_1.settings.authorization) {
|
|
109
|
+
headers["x-blaxel-authorization"] = core_1.settings.authorization;
|
|
110
|
+
}
|
|
111
|
+
if (core_1.settings.workspace) {
|
|
112
|
+
headers["x-blaxel-workspace"] = core_1.settings.workspace;
|
|
113
|
+
}
|
|
114
|
+
return headers;
|
|
115
|
+
}
|
|
116
|
+
async sleep(ms) {
|
|
117
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
118
|
+
}
|
|
119
|
+
async flush() {
|
|
120
|
+
if (this.nodeTracerProvider) {
|
|
121
|
+
await this.nodeTracerProvider.shutdown();
|
|
122
|
+
}
|
|
123
|
+
if (this.meterProvider) {
|
|
124
|
+
await this.meterProvider.shutdown();
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
setupSignalHandler() {
|
|
128
|
+
const forward = (sig) => {
|
|
129
|
+
if (this.shuttingDown)
|
|
130
|
+
return;
|
|
131
|
+
this.shuttingDown = true;
|
|
132
|
+
this.shutdownApp()
|
|
133
|
+
.catch((error) => {
|
|
134
|
+
core_1.logger.debug("Fatal error during shutdown:", error);
|
|
135
|
+
})
|
|
136
|
+
.finally(() => {
|
|
137
|
+
try {
|
|
138
|
+
// Re-send the original signal to let the default handler terminate the process with the correct code
|
|
139
|
+
process.kill(process.pid, sig);
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
process.exit(1);
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
};
|
|
146
|
+
process.once("SIGINT", () => forward("SIGINT"));
|
|
147
|
+
process.once("SIGTERM", () => forward("SIGTERM"));
|
|
148
|
+
process.once("uncaughtException", (err) => {
|
|
149
|
+
core_1.logger.error("Uncaught exception:", err);
|
|
150
|
+
this.shutdownApp().catch((error) => {
|
|
151
|
+
core_1.logger.debug("Fatal error during shutdown:", error);
|
|
152
|
+
}).finally(() => {
|
|
153
|
+
// Re-throw on next tick so Node's default handler sets exit code and prints stack
|
|
154
|
+
setImmediate(() => {
|
|
155
|
+
throw (err instanceof Error ? err : new Error(String(err)));
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Get resource attributes for OpenTelemetry.
|
|
162
|
+
*/
|
|
163
|
+
get resourceAttributes() {
|
|
164
|
+
const resource = resources_1.envDetector.detect();
|
|
165
|
+
const attributes = resource.attributes || {};
|
|
166
|
+
if (core_1.settings.name) {
|
|
167
|
+
attributes["service.name"] = core_1.settings.name;
|
|
168
|
+
attributes["workload.id"] = core_1.settings.name;
|
|
169
|
+
}
|
|
170
|
+
if (core_1.settings.workspace) {
|
|
171
|
+
attributes["workspace"] = core_1.settings.workspace;
|
|
172
|
+
}
|
|
173
|
+
if (core_1.settings.type) {
|
|
174
|
+
attributes["workload.type"] = core_1.settings.type + "s";
|
|
175
|
+
}
|
|
176
|
+
// Only keep string values
|
|
177
|
+
const stringAttrs = {};
|
|
178
|
+
for (const [k, v] of Object.entries(attributes)) {
|
|
179
|
+
if (typeof v === "string")
|
|
180
|
+
stringAttrs[k] = v;
|
|
181
|
+
}
|
|
182
|
+
return stringAttrs;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Initialize and return the OTLP Metric Exporter.
|
|
186
|
+
*/
|
|
187
|
+
getMetricExporter() {
|
|
188
|
+
return (0, auth_refresh_exporters_1.createMetricExporter)();
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Initialize and return the OTLP Trace Exporter.
|
|
192
|
+
*/
|
|
193
|
+
getTraceExporter() {
|
|
194
|
+
return (0, auth_refresh_exporters_1.createTraceExporter)();
|
|
195
|
+
}
|
|
196
|
+
instrumentApp() {
|
|
197
|
+
core_1.telemetryRegistry.registerProvider(new telemetry_provider_1.OtelTelemetryProvider());
|
|
198
|
+
const httpInstrumentation = new instrumentation_http_1.HttpInstrumentation({
|
|
199
|
+
requireParentforOutgoingSpans: true,
|
|
200
|
+
});
|
|
201
|
+
(0, instrumentation_1.registerInstrumentations)({
|
|
202
|
+
instrumentations: [httpInstrumentation],
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
setExporters() {
|
|
206
|
+
const resource = new BlaxelResource(this.resourceAttributes);
|
|
207
|
+
core_1.logger.debug("[TelemetryManager] Setting up exporters with authentication refresh");
|
|
208
|
+
// Configure batch processor options with 1-second delay
|
|
209
|
+
const batchProcessorOptions = {
|
|
210
|
+
scheduledDelayMillis: 1000, // Export every 1 second
|
|
211
|
+
exportTimeoutMillis: 5000, // Timeout for export
|
|
212
|
+
maxExportBatchSize: 512, // Max batch size
|
|
213
|
+
maxQueueSize: 2048 // Max queue size
|
|
214
|
+
};
|
|
215
|
+
core_1.logger.debug("[TelemetryManager] Batch processor options:", batchProcessorOptions);
|
|
216
|
+
// Create auth-refreshing trace exporter
|
|
217
|
+
const traceExporter = new auth_refresh_exporters_1.AuthRefreshingSpanExporter(() => this.getTraceExporter());
|
|
218
|
+
core_1.logger.debug("[TelemetryManager] Created AuthRefreshingSpanExporter");
|
|
219
|
+
this.nodeTracerProvider = new sdk_trace_node_1.NodeTracerProvider({
|
|
220
|
+
resource,
|
|
221
|
+
sampler: new sdk_trace_node_1.AlwaysOnSampler(),
|
|
222
|
+
spanProcessors: [
|
|
223
|
+
new DefaultAttributesSpanProcessor({
|
|
224
|
+
"workload.id": core_1.settings.name || "",
|
|
225
|
+
"workload.type": core_1.settings.type ? core_1.settings.type + "s" : "",
|
|
226
|
+
workspace: core_1.settings.workspace || "",
|
|
227
|
+
}),
|
|
228
|
+
new sdk_trace_node_1.BatchSpanProcessor(traceExporter, batchProcessorOptions),
|
|
229
|
+
new HasBeenProcessedSpanProcessor(traceExporter, batchProcessorOptions),
|
|
230
|
+
],
|
|
231
|
+
});
|
|
232
|
+
this.nodeTracerProvider.register();
|
|
233
|
+
core_1.logger.debug("[TelemetryManager] Trace provider registered");
|
|
234
|
+
// Create auth-refreshing metric exporter
|
|
235
|
+
const metricExporter = new auth_refresh_exporters_1.AuthRefreshingMetricExporter(() => this.getMetricExporter());
|
|
236
|
+
core_1.logger.debug("[TelemetryManager] Created AuthRefreshingMetricExporter");
|
|
237
|
+
this.meterProvider = new sdk_metrics_1.MeterProvider({
|
|
238
|
+
resource,
|
|
239
|
+
readers: [
|
|
240
|
+
new sdk_metrics_1.PeriodicExportingMetricReader({
|
|
241
|
+
exporter: metricExporter,
|
|
242
|
+
exportIntervalMillis: 1000, // Changed from 60000 to 1000 (1 second)
|
|
243
|
+
}),
|
|
244
|
+
],
|
|
245
|
+
});
|
|
246
|
+
api_1.metrics.setGlobalMeterProvider(this.meterProvider);
|
|
247
|
+
core_1.logger.debug("[TelemetryManager] Metric provider configured with 1-second export interval");
|
|
248
|
+
}
|
|
249
|
+
async shutdownApp() {
|
|
250
|
+
try {
|
|
251
|
+
const maxSleepTime = 5000;
|
|
252
|
+
const startTime = Date.now();
|
|
253
|
+
while (!this.configured && Date.now() - startTime < maxSleepTime) {
|
|
254
|
+
await this.sleep(100);
|
|
255
|
+
}
|
|
256
|
+
const shutdownPromises = [];
|
|
257
|
+
if (this.nodeTracerProvider) {
|
|
258
|
+
shutdownPromises.push(this.nodeTracerProvider
|
|
259
|
+
.shutdown()
|
|
260
|
+
.catch((error) => core_1.logger.debug("Error shutting down tracer provider:", error)));
|
|
261
|
+
}
|
|
262
|
+
if (this.meterProvider) {
|
|
263
|
+
shutdownPromises.push(this.meterProvider
|
|
264
|
+
.shutdown()
|
|
265
|
+
.catch((error) => core_1.logger.debug("Error shutting down meter provider:", error)));
|
|
266
|
+
}
|
|
267
|
+
// Wait for all providers to shutdown with a timeout
|
|
268
|
+
await Promise.race([
|
|
269
|
+
Promise.all(shutdownPromises),
|
|
270
|
+
new Promise((resolve) => setTimeout(resolve, 5000)), // 5 second timeout
|
|
271
|
+
]);
|
|
272
|
+
core_1.logger.debug("Instrumentation shutdown complete");
|
|
273
|
+
}
|
|
274
|
+
catch (error) {
|
|
275
|
+
core_1.logger.error("Error during shutdown:", error);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
exports.blaxelTelemetry = new TelemetryManager();
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { BlaxelSpan, BlaxelSpanOptions, BlaxelTelemetryProvider } from "@blaxel/core";
|
|
2
|
+
export declare class OtelTelemetryProvider implements BlaxelTelemetryProvider {
|
|
3
|
+
private spans;
|
|
4
|
+
retrieveActiveSpanContext(): import("@opentelemetry/api").Context;
|
|
5
|
+
startSpan(name: string, options?: BlaxelSpanOptions): BlaxelSpan;
|
|
6
|
+
flush(): Promise<void>;
|
|
7
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.OtelTelemetryProvider = void 0;
|
|
4
|
+
const api_1 = require("@opentelemetry/api");
|
|
5
|
+
const telemetry_1 = require("./telemetry");
|
|
6
|
+
class OtelSpan {
|
|
7
|
+
span;
|
|
8
|
+
closed = false;
|
|
9
|
+
constructor(span) {
|
|
10
|
+
this.span = span;
|
|
11
|
+
}
|
|
12
|
+
setAttribute(key, value) {
|
|
13
|
+
this.span.setAttribute(key, value);
|
|
14
|
+
}
|
|
15
|
+
setAttributes(attributes) {
|
|
16
|
+
Object.entries(attributes).forEach(([k, v]) => this.span.setAttribute(k, v));
|
|
17
|
+
}
|
|
18
|
+
recordException(error) {
|
|
19
|
+
this.span.recordException(error);
|
|
20
|
+
this.span.setStatus({ code: api_1.SpanStatusCode.ERROR, message: error.message });
|
|
21
|
+
}
|
|
22
|
+
setStatus(status, message) {
|
|
23
|
+
this.span.setStatus({
|
|
24
|
+
code: status === 'ok' ? api_1.SpanStatusCode.OK : api_1.SpanStatusCode.ERROR,
|
|
25
|
+
message,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
end() {
|
|
29
|
+
this.closed = true;
|
|
30
|
+
this.span.end();
|
|
31
|
+
}
|
|
32
|
+
getContext() {
|
|
33
|
+
return this.span.spanContext();
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
class OtelTelemetryProvider {
|
|
37
|
+
spans = [];
|
|
38
|
+
retrieveActiveSpanContext() {
|
|
39
|
+
for (let i = this.spans.length - 1; i >= 0; i--) {
|
|
40
|
+
const span = this.spans[i];
|
|
41
|
+
if (!span.closed) {
|
|
42
|
+
return api_1.trace.setSpanContext(api_1.ROOT_CONTEXT, span.getContext());
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return api_1.context.active();
|
|
46
|
+
}
|
|
47
|
+
startSpan(name, options) {
|
|
48
|
+
const tracer = api_1.trace.getTracer("blaxel");
|
|
49
|
+
const otelOptions = {
|
|
50
|
+
attributes: options?.attributes,
|
|
51
|
+
root: options?.isRoot,
|
|
52
|
+
};
|
|
53
|
+
const ctx = this.retrieveActiveSpanContext();
|
|
54
|
+
const span = new OtelSpan(tracer.startSpan(name, otelOptions, ctx));
|
|
55
|
+
this.spans.push(span);
|
|
56
|
+
return span;
|
|
57
|
+
}
|
|
58
|
+
async flush() {
|
|
59
|
+
await telemetry_1.blaxelTelemetry.flush();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
exports.OtelTelemetryProvider = OtelTelemetryProvider;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blaxel/telemetry",
|
|
3
|
-
"version": "0.2.49-
|
|
3
|
+
"version": "0.2.49-dev1",
|
|
4
4
|
"description": "Blaxel SDK for TypeScript",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Blaxel, INC (https://blaxel.ai)",
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
"@opentelemetry/sdk-trace-base": "^2.0.0",
|
|
59
59
|
"@opentelemetry/sdk-trace-node": "^2.0.0",
|
|
60
60
|
"ai": "^5.0.82",
|
|
61
|
-
"@blaxel/core": "0.2.49-
|
|
61
|
+
"@blaxel/core": "0.2.49-dev1"
|
|
62
62
|
},
|
|
63
63
|
"devDependencies": {
|
|
64
64
|
"@eslint/js": "^9.26.0",
|