@vtvlive/interactive-apm 0.0.2
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/LICENSE +21 -0
- package/README.md +320 -0
- package/dist/factories/tracing-provider.factory.d.ts +81 -0
- package/dist/factories/tracing-provider.factory.d.ts.map +1 -0
- package/dist/factories/tracing-provider.factory.js +93 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +35 -0
- package/dist/init/elastic-apm-init.d.ts +51 -0
- package/dist/init/elastic-apm-init.d.ts.map +1 -0
- package/dist/init/elastic-apm-init.js +86 -0
- package/dist/init/opentelemetry-init.d.ts +51 -0
- package/dist/init/opentelemetry-init.d.ts.map +1 -0
- package/dist/init/opentelemetry-init.js +156 -0
- package/dist/interfaces/tracing-provider.interface.d.ts +43 -0
- package/dist/interfaces/tracing-provider.interface.d.ts.map +1 -0
- package/dist/interfaces/tracing-provider.interface.js +2 -0
- package/dist/modules/tracing.module.d.ts +96 -0
- package/dist/modules/tracing.module.d.ts.map +1 -0
- package/dist/modules/tracing.module.js +162 -0
- package/dist/providers/elastic-apm.tracing-provider.d.ts +57 -0
- package/dist/providers/elastic-apm.tracing-provider.d.ts.map +1 -0
- package/dist/providers/elastic-apm.tracing-provider.js +163 -0
- package/dist/providers/opentelemetry.tracing-provider.d.ts +68 -0
- package/dist/providers/opentelemetry.tracing-provider.d.ts.map +1 -0
- package/dist/providers/opentelemetry.tracing-provider.js +288 -0
- package/dist/services/tracing.service.d.ts +88 -0
- package/dist/services/tracing.service.d.ts.map +1 -0
- package/dist/services/tracing.service.js +103 -0
- package/dist/types/apm-provider.type.d.ts +47 -0
- package/dist/types/apm-provider.type.d.ts.map +1 -0
- package/dist/types/apm-provider.type.js +32 -0
- package/dist/utils/tracing.helper.d.ts +68 -0
- package/dist/utils/tracing.helper.d.ts.map +1 -0
- package/dist/utils/tracing.helper.js +115 -0
- package/package.json +105 -0
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ElasticApmTracingProvider = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Elastic APM Tracing Provider Implementation
|
|
6
|
+
* Sử dụng elastic-apm-node agent để gửi traces về Elastic APM server
|
|
7
|
+
*/
|
|
8
|
+
class ElasticApmTracingProvider {
|
|
9
|
+
constructor(config = {}) {
|
|
10
|
+
this.apm = null;
|
|
11
|
+
this.serviceName = config.serviceName || process.env.ELASTIC_APM_SERVICE_NAME || 'interactive-backend';
|
|
12
|
+
this.environment = config.environment || process.env.ELASTIC_APM_ENVIRONMENT || 'development';
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Initialize Elastic APM agent
|
|
16
|
+
* Should be called before using the provider
|
|
17
|
+
*/
|
|
18
|
+
initialize(config) {
|
|
19
|
+
try {
|
|
20
|
+
// @ts-ignore - Optional peer dependency
|
|
21
|
+
const apm = require('elastic-apm-node');
|
|
22
|
+
// If agent is already started, just get reference
|
|
23
|
+
if (apm.isStarted()) {
|
|
24
|
+
this.apm = apm;
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
// Start the agent with provided config
|
|
28
|
+
this.apm = apm.start({
|
|
29
|
+
serviceName: config?.serviceName || this.serviceName,
|
|
30
|
+
serverUrl: config?.serverUrl || process.env.ELASTIC_APM_SERVER_URL || 'http://localhost:8200',
|
|
31
|
+
secretToken: config?.secretToken || process.env.ELASTIC_APM_SECRET_TOKEN,
|
|
32
|
+
serviceVersion: config?.serviceVersion || process.env.npm_package_version || '1.0.0',
|
|
33
|
+
environment: config?.environment || this.environment,
|
|
34
|
+
logLevel: 'error',
|
|
35
|
+
});
|
|
36
|
+
console.log(`[ElasticAPM] Service: ${this.serviceName}, Environment: ${this.environment}`);
|
|
37
|
+
console.log('[ElasticAPM] Initialized');
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
console.error('[ElasticAPM] Failed to initialize:', error);
|
|
41
|
+
throw error;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Bắt đầu một span mới
|
|
46
|
+
*/
|
|
47
|
+
startSpan(name, attributes, spanKind = 'INTERNAL') {
|
|
48
|
+
if (!this.apm) {
|
|
49
|
+
console.warn('[ElasticAPM] Not initialized');
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
// Elastic APM cần transaction để tạo span
|
|
53
|
+
let transaction = this.apm.currentTransaction;
|
|
54
|
+
let createdTransaction = false;
|
|
55
|
+
if (!transaction) {
|
|
56
|
+
const type = this.mapSpanKindToType(spanKind);
|
|
57
|
+
transaction = this.apm.startTransaction(name, type);
|
|
58
|
+
if (!transaction) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
createdTransaction = true;
|
|
62
|
+
}
|
|
63
|
+
const span = this.apm.startSpan(name, 'custom');
|
|
64
|
+
if (!span) {
|
|
65
|
+
if (createdTransaction && transaction) {
|
|
66
|
+
transaction.end();
|
|
67
|
+
}
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
// Set labels từ attributes
|
|
71
|
+
if (attributes) {
|
|
72
|
+
for (const [key, value] of Object.entries(attributes)) {
|
|
73
|
+
span.setLabel(key, String(value));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
// Set span type dựa trên kind
|
|
77
|
+
span.setType(this.mapSpanKindToType(spanKind));
|
|
78
|
+
// Override end() để end transaction sau
|
|
79
|
+
const originalEnd = span.end.bind(span);
|
|
80
|
+
span.end = () => {
|
|
81
|
+
originalEnd();
|
|
82
|
+
if (createdTransaction && transaction) {
|
|
83
|
+
transaction.end();
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
return span;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Thực thi function với tracing context
|
|
90
|
+
*/
|
|
91
|
+
async startSpanWithParent(name, fn, attributes) {
|
|
92
|
+
const span = this.startSpan(name, attributes);
|
|
93
|
+
try {
|
|
94
|
+
const result = await fn(span);
|
|
95
|
+
return result;
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
this.apm?.captureError(error);
|
|
99
|
+
if (span) {
|
|
100
|
+
span.outcome = 'failure';
|
|
101
|
+
}
|
|
102
|
+
throw error;
|
|
103
|
+
}
|
|
104
|
+
finally {
|
|
105
|
+
if (span) {
|
|
106
|
+
span.end();
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Capture error vào APM
|
|
112
|
+
*/
|
|
113
|
+
captureError(error) {
|
|
114
|
+
if (this.apm) {
|
|
115
|
+
this.apm.captureError(error);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Set label cho current transaction/span
|
|
120
|
+
*/
|
|
121
|
+
setAttribute(key, value) {
|
|
122
|
+
const span = this.apm?.currentSpan;
|
|
123
|
+
if (span) {
|
|
124
|
+
span.setLabel(key, String(value));
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* End span manually
|
|
129
|
+
*/
|
|
130
|
+
endSpan(span) {
|
|
131
|
+
const activeSpan = span || this.apm?.currentSpan;
|
|
132
|
+
if (activeSpan) {
|
|
133
|
+
activeSpan.end();
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Flush và shutdown gracefully
|
|
138
|
+
*/
|
|
139
|
+
async shutdown() {
|
|
140
|
+
if (this.apm) {
|
|
141
|
+
await this.apm.flush();
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Map string span kind to Elastic APM span type
|
|
146
|
+
*/
|
|
147
|
+
mapSpanKindToType(kind) {
|
|
148
|
+
switch (kind.toUpperCase()) {
|
|
149
|
+
case 'SERVER':
|
|
150
|
+
return 'request';
|
|
151
|
+
case 'CLIENT':
|
|
152
|
+
return 'db';
|
|
153
|
+
case 'PRODUCER':
|
|
154
|
+
return 'messaging';
|
|
155
|
+
case 'CONSUMER':
|
|
156
|
+
return 'messaging';
|
|
157
|
+
case 'INTERNAL':
|
|
158
|
+
default:
|
|
159
|
+
return 'code';
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
exports.ElasticApmTracingProvider = ElasticApmTracingProvider;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { ITracingProvider } from '../interfaces/tracing-provider.interface';
|
|
2
|
+
/**
|
|
3
|
+
* OpenTelemetry Tracing Provider Implementation
|
|
4
|
+
* Sử dụng OpenTelemetry SDK với OTLP exporter để gửi traces về Elastic APM
|
|
5
|
+
*/
|
|
6
|
+
export declare class OpenTelemetryTracingProvider implements ITracingProvider {
|
|
7
|
+
private sdk;
|
|
8
|
+
private tracer;
|
|
9
|
+
private readonly serviceName;
|
|
10
|
+
private readonly environment;
|
|
11
|
+
private readonly otlpEndpoint;
|
|
12
|
+
private readonly secretToken;
|
|
13
|
+
private readonly otlpAuthToken;
|
|
14
|
+
private readonly otlpHeaders;
|
|
15
|
+
private readonly enableConsoleExporter;
|
|
16
|
+
private initialized;
|
|
17
|
+
constructor(config?: {
|
|
18
|
+
serviceName?: string;
|
|
19
|
+
environment?: string;
|
|
20
|
+
otlpEndpoint?: string;
|
|
21
|
+
secretToken?: string;
|
|
22
|
+
otlpAuthToken?: string;
|
|
23
|
+
otlpHeaders?: Record<string, string>;
|
|
24
|
+
enableConsoleExporter?: boolean;
|
|
25
|
+
});
|
|
26
|
+
/**
|
|
27
|
+
* Initialize OpenTelemetry SDK
|
|
28
|
+
* Should be called before using the provider
|
|
29
|
+
*/
|
|
30
|
+
initialize(config?: {
|
|
31
|
+
serviceName?: string;
|
|
32
|
+
otlpEndpoint?: string;
|
|
33
|
+
secretToken?: string;
|
|
34
|
+
otlpAuthToken?: string;
|
|
35
|
+
otlpHeaders?: Record<string, string>;
|
|
36
|
+
enableConsoleExporter?: boolean;
|
|
37
|
+
environment?: string;
|
|
38
|
+
}): Promise<void>;
|
|
39
|
+
/**
|
|
40
|
+
* Bắt đầu một span mới
|
|
41
|
+
*/
|
|
42
|
+
startSpan(name: string, attributes?: Record<string, string | number>, spanKind?: string): any;
|
|
43
|
+
/**
|
|
44
|
+
* Thực thi function với tracing context
|
|
45
|
+
*/
|
|
46
|
+
startSpanWithParent<T>(name: string, fn: (span: any) => Promise<T>, attributes?: Record<string, string | number>): Promise<T>;
|
|
47
|
+
/**
|
|
48
|
+
* Capture error vào active span
|
|
49
|
+
*/
|
|
50
|
+
captureError(error: Error): void;
|
|
51
|
+
/**
|
|
52
|
+
* Set attribute cho active span
|
|
53
|
+
*/
|
|
54
|
+
setAttribute(key: string, value: string | number): void;
|
|
55
|
+
/**
|
|
56
|
+
* End span manually
|
|
57
|
+
*/
|
|
58
|
+
endSpan(span?: any): void;
|
|
59
|
+
/**
|
|
60
|
+
* Shutdown gracefully
|
|
61
|
+
*/
|
|
62
|
+
shutdown(): Promise<void>;
|
|
63
|
+
/**
|
|
64
|
+
* Map string span kind to OpenTelemetry SpanKind enum
|
|
65
|
+
*/
|
|
66
|
+
private mapSpanKind;
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=opentelemetry.tracing-provider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"opentelemetry.tracing-provider.d.ts","sourceRoot":"","sources":["../../src/providers/opentelemetry.tracing-provider.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,0CAA0C,CAAC;AAE5E;;;GAGG;AACH,qBAAa,4BAA6B,YAAW,gBAAgB;IACnE,OAAO,CAAC,GAAG,CAAa;IACxB,OAAO,CAAC,MAAM,CAAa;IAC3B,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAyB;IACrD,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAAU;IAChD,OAAO,CAAC,WAAW,CAAS;gBAEhB,MAAM,GAAE;QAClB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACrC,qBAAqB,CAAC,EAAE,OAAO,CAAC;KAC5B;IAUN;;;OAGG;IACG,UAAU,CAAC,MAAM,CAAC,EAAE;QACxB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACrC,qBAAqB,CAAC,EAAE,OAAO,CAAC;QAChC,WAAW,CAAC,EAAE,MAAM,CAAC;KACtB,GAAG,OAAO,CAAC,IAAI,CAAC;IAwIjB;;OAEG;IACH,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,EAAE,QAAQ,GAAE,MAAmB,GAAG,GAAG;IAqBzG;;OAEG;IACG,mBAAmB,CAAC,CAAC,EACzB,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,OAAO,CAAC,CAAC,CAAC,EAC7B,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,GAC3C,OAAO,CAAC,CAAC,CAAC;IAoBb;;OAEG;IACH,YAAY,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI;IAchC;;OAEG;IACH,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAavD;;OAEG;IACH,OAAO,CAAC,IAAI,CAAC,EAAE,GAAG,GAAG,IAAI;IAazB;;OAEG;IACG,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAM/B;;OAEG;IACH,OAAO,CAAC,WAAW;CAiBpB"}
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.OpenTelemetryTracingProvider = void 0;
|
|
37
|
+
/**
|
|
38
|
+
* OpenTelemetry Tracing Provider Implementation
|
|
39
|
+
* Sử dụng OpenTelemetry SDK với OTLP exporter để gửi traces về Elastic APM
|
|
40
|
+
*/
|
|
41
|
+
class OpenTelemetryTracingProvider {
|
|
42
|
+
constructor(config = {}) {
|
|
43
|
+
this.sdk = null;
|
|
44
|
+
this.tracer = null;
|
|
45
|
+
this.initialized = false;
|
|
46
|
+
this.serviceName = config.serviceName || process.env.ELASTIC_APM_SERVICE_NAME || 'interactive-backend';
|
|
47
|
+
this.environment = config.environment || process.env.ELASTIC_APM_ENVIRONMENT || 'development';
|
|
48
|
+
this.otlpEndpoint = config.otlpEndpoint || process.env.ELASTIC_OTLP_ENDPOINT || 'http://localhost:8200/v1/traces';
|
|
49
|
+
this.secretToken = config.secretToken || process.env.ELASTIC_APM_SECRET_TOKEN || '';
|
|
50
|
+
this.otlpAuthToken = config.otlpAuthToken || process.env.ELASTIC_OTLP_AUTH_TOKEN || '';
|
|
51
|
+
this.otlpHeaders = config.otlpHeaders || {};
|
|
52
|
+
this.enableConsoleExporter = config.enableConsoleExporter ?? process.env.ELASTIC_OTLP_ENABLE_CONSOLE_EXPORTER === 'true';
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Initialize OpenTelemetry SDK
|
|
56
|
+
* Should be called before using the provider
|
|
57
|
+
*/
|
|
58
|
+
async initialize(config) {
|
|
59
|
+
if (this.initialized) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
try {
|
|
63
|
+
// Dynamic import to avoid errors when @opentelemetry packages are not installed
|
|
64
|
+
// @ts-ignore - Optional peer dependency
|
|
65
|
+
const { NodeSDK } = await Promise.resolve().then(() => __importStar(require('@opentelemetry/sdk-node')));
|
|
66
|
+
// @ts-ignore - Optional peer dependency
|
|
67
|
+
const { HttpInstrumentation } = await Promise.resolve().then(() => __importStar(require('@opentelemetry/instrumentation-http')));
|
|
68
|
+
// @ts-ignore - Optional peer dependency
|
|
69
|
+
const { ExpressInstrumentation } = await Promise.resolve().then(() => __importStar(require('@opentelemetry/instrumentation-express')));
|
|
70
|
+
// @ts-ignore - Optional peer dependency
|
|
71
|
+
const { OTLPTraceExporter } = await Promise.resolve().then(() => __importStar(require('@opentelemetry/exporter-trace-otlp-http')));
|
|
72
|
+
// @ts-ignore - Optional peer dependency
|
|
73
|
+
const { resourceFromAttributes } = await Promise.resolve().then(() => __importStar(require('@opentelemetry/resources')));
|
|
74
|
+
// @ts-ignore - Optional peer dependency
|
|
75
|
+
const { trace } = await Promise.resolve().then(() => __importStar(require('@opentelemetry/api')));
|
|
76
|
+
console.log(`[OpenTelemetry] Service: ${this.serviceName}, Environment: ${this.environment}`);
|
|
77
|
+
console.log(`[OpenTelemetry] Endpoint: ${config?.otlpEndpoint || this.otlpEndpoint}`);
|
|
78
|
+
// OTLP Exporter - Build headers with Authorization
|
|
79
|
+
const buildHeaders = () => {
|
|
80
|
+
const headers = {
|
|
81
|
+
...this.otlpHeaders,
|
|
82
|
+
...(config?.otlpHeaders || {}),
|
|
83
|
+
};
|
|
84
|
+
const token = config?.otlpAuthToken || this.otlpAuthToken || config?.secretToken || this.secretToken;
|
|
85
|
+
if (token) {
|
|
86
|
+
headers['Authorization'] = `Bearer ${token}`;
|
|
87
|
+
}
|
|
88
|
+
return Object.keys(headers).length > 0 ? headers : undefined;
|
|
89
|
+
};
|
|
90
|
+
const otlpExporter = new OTLPTraceExporter({
|
|
91
|
+
url: config?.otlpEndpoint || this.otlpEndpoint,
|
|
92
|
+
headers: buildHeaders(),
|
|
93
|
+
});
|
|
94
|
+
// Wrap OTLP exporter - only log errors
|
|
95
|
+
const otlpExporterWithLogging = {
|
|
96
|
+
export: (spans, resultCallback) => {
|
|
97
|
+
otlpExporter.export(spans, (result) => {
|
|
98
|
+
if (result.error) {
|
|
99
|
+
const endpoint = config?.otlpEndpoint || this.otlpEndpoint;
|
|
100
|
+
console.error(`[OpenTelemetry] Export to ${endpoint} failed: ${result.error instanceof Error ? result.error.message : result.error}`);
|
|
101
|
+
}
|
|
102
|
+
resultCallback(result);
|
|
103
|
+
});
|
|
104
|
+
},
|
|
105
|
+
shutdown: async () => {
|
|
106
|
+
await otlpExporter.shutdown();
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
// Use console exporter if enabled
|
|
110
|
+
const shouldEnableConsole = config?.enableConsoleExporter ?? this.enableConsoleExporter;
|
|
111
|
+
let traceExporter = otlpExporterWithLogging;
|
|
112
|
+
if (shouldEnableConsole) {
|
|
113
|
+
// @ts-ignore - Optional peer dependency
|
|
114
|
+
const { ConsoleSpanExporter } = await Promise.resolve().then(() => __importStar(require('@opentelemetry/sdk-trace-base')));
|
|
115
|
+
const consoleExporter = new ConsoleSpanExporter();
|
|
116
|
+
// Combined Exporter để export ra cả console và OTLP
|
|
117
|
+
traceExporter = {
|
|
118
|
+
export: (spans, resultCallback) => {
|
|
119
|
+
let completed = 0;
|
|
120
|
+
let hasError = false;
|
|
121
|
+
const errors = [];
|
|
122
|
+
const checkComplete = () => {
|
|
123
|
+
if (completed === 2) {
|
|
124
|
+
resultCallback(hasError ? { code: 1, error: new Error(errors.map(e => e.message).join('; ')) } : { code: 0 });
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
// Console exporter
|
|
128
|
+
consoleExporter.export(spans, (result) => {
|
|
129
|
+
if (result.error) {
|
|
130
|
+
hasError = true;
|
|
131
|
+
errors.push(result.error);
|
|
132
|
+
}
|
|
133
|
+
completed++;
|
|
134
|
+
checkComplete();
|
|
135
|
+
});
|
|
136
|
+
// OTLP exporter with logging
|
|
137
|
+
otlpExporterWithLogging.export(spans, (result) => {
|
|
138
|
+
if (result.error) {
|
|
139
|
+
hasError = true;
|
|
140
|
+
errors.push(result.error);
|
|
141
|
+
}
|
|
142
|
+
completed++;
|
|
143
|
+
checkComplete();
|
|
144
|
+
});
|
|
145
|
+
},
|
|
146
|
+
shutdown: async () => {
|
|
147
|
+
await Promise.all([consoleExporter.shutdown(), otlpExporterWithLogging.shutdown()]);
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
// Resource attributes
|
|
152
|
+
const sdkResource = resourceFromAttributes({
|
|
153
|
+
'service.name': config?.serviceName || this.serviceName,
|
|
154
|
+
'deployment.environment': config?.environment || this.environment,
|
|
155
|
+
'service.version': process.env.npm_package_version || '1.0.0',
|
|
156
|
+
'service.instance.id': `${process.pid}`,
|
|
157
|
+
'host.name': require('os').hostname(),
|
|
158
|
+
});
|
|
159
|
+
// Khởi tạo SDK
|
|
160
|
+
this.sdk = new NodeSDK({
|
|
161
|
+
serviceName: config?.serviceName || this.serviceName,
|
|
162
|
+
traceExporter,
|
|
163
|
+
instrumentations: [new HttpInstrumentation(), new ExpressInstrumentation()],
|
|
164
|
+
resource: sdkResource,
|
|
165
|
+
});
|
|
166
|
+
// Khởi động SDK
|
|
167
|
+
this.sdk.start();
|
|
168
|
+
// Get tracer
|
|
169
|
+
this.tracer = trace.getTracer(config?.serviceName || this.serviceName);
|
|
170
|
+
this.initialized = true;
|
|
171
|
+
console.log('[OpenTelemetry] Initialized');
|
|
172
|
+
}
|
|
173
|
+
catch (error) {
|
|
174
|
+
console.error('[OpenTelemetry] Failed to initialize:', error);
|
|
175
|
+
throw error;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Bắt đầu một span mới
|
|
180
|
+
*/
|
|
181
|
+
startSpan(name, attributes, spanKind = 'INTERNAL') {
|
|
182
|
+
if (!this.initialized || !this.tracer) {
|
|
183
|
+
console.warn('[OpenTelemetry] Not initialized');
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
// @ts-ignore - Optional peer dependency
|
|
187
|
+
const { context, SpanKind } = require('@opentelemetry/api');
|
|
188
|
+
const span = this.tracer.startSpan(name, {
|
|
189
|
+
attributes,
|
|
190
|
+
kind: this.mapSpanKind(spanKind),
|
|
191
|
+
}, context.active());
|
|
192
|
+
return span;
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Thực thi function với tracing context
|
|
196
|
+
*/
|
|
197
|
+
async startSpanWithParent(name, fn, attributes) {
|
|
198
|
+
// @ts-ignore - Optional peer dependency
|
|
199
|
+
const { context, trace, SpanStatusCode } = require('@opentelemetry/api');
|
|
200
|
+
return context.with(trace.setSpan(context.active(), this.startSpan(name, attributes)), async () => {
|
|
201
|
+
const span = trace.getActiveSpan();
|
|
202
|
+
try {
|
|
203
|
+
const result = await fn(span);
|
|
204
|
+
return result;
|
|
205
|
+
}
|
|
206
|
+
catch (error) {
|
|
207
|
+
span?.recordException(error);
|
|
208
|
+
span?.setStatus({ code: SpanStatusCode.ERROR, message: error.message });
|
|
209
|
+
throw error;
|
|
210
|
+
}
|
|
211
|
+
finally {
|
|
212
|
+
span?.end();
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Capture error vào active span
|
|
218
|
+
*/
|
|
219
|
+
captureError(error) {
|
|
220
|
+
if (!this.initialized) {
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
// @ts-ignore - Optional peer dependency
|
|
224
|
+
const { trace, SpanStatusCode } = require('@opentelemetry/api');
|
|
225
|
+
const span = trace.getActiveSpan();
|
|
226
|
+
if (span) {
|
|
227
|
+
span.recordException(error);
|
|
228
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message: error.message });
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Set attribute cho active span
|
|
233
|
+
*/
|
|
234
|
+
setAttribute(key, value) {
|
|
235
|
+
if (!this.initialized) {
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
// @ts-ignore - Optional peer dependency
|
|
239
|
+
const { trace } = require('@opentelemetry/api');
|
|
240
|
+
const span = trace.getActiveSpan();
|
|
241
|
+
if (span) {
|
|
242
|
+
span.setAttribute(key, value);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* End span manually
|
|
247
|
+
*/
|
|
248
|
+
endSpan(span) {
|
|
249
|
+
if (!this.initialized) {
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
// @ts-ignore - Optional peer dependency
|
|
253
|
+
const { trace } = require('@opentelemetry/api');
|
|
254
|
+
const activeSpan = span || trace.getActiveSpan();
|
|
255
|
+
if (activeSpan) {
|
|
256
|
+
activeSpan.end();
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Shutdown gracefully
|
|
261
|
+
*/
|
|
262
|
+
async shutdown() {
|
|
263
|
+
if (this.sdk) {
|
|
264
|
+
await this.sdk.shutdown();
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Map string span kind to OpenTelemetry SpanKind enum
|
|
269
|
+
*/
|
|
270
|
+
mapSpanKind(kind) {
|
|
271
|
+
// @ts-ignore - Optional peer dependency
|
|
272
|
+
const { SpanKind } = require('@opentelemetry/api');
|
|
273
|
+
switch (kind.toUpperCase()) {
|
|
274
|
+
case 'SERVER':
|
|
275
|
+
return SpanKind.SERVER;
|
|
276
|
+
case 'CLIENT':
|
|
277
|
+
return SpanKind.CLIENT;
|
|
278
|
+
case 'PRODUCER':
|
|
279
|
+
return SpanKind.PRODUCER;
|
|
280
|
+
case 'CONSUMER':
|
|
281
|
+
return SpanKind.CONSUMER;
|
|
282
|
+
case 'INTERNAL':
|
|
283
|
+
default:
|
|
284
|
+
return SpanKind.INTERNAL;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
exports.OpenTelemetryTracingProvider = OpenTelemetryTracingProvider;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { ITracingProvider } from '../interfaces/tracing-provider.interface';
|
|
2
|
+
/**
|
|
3
|
+
* Tracing Service - Wrapper cho ITracingProvider
|
|
4
|
+
* Service này được inject vào controllers/services để sử dụng tracing
|
|
5
|
+
*
|
|
6
|
+
* Provider implementation (OpenTelemetry hoặc Elastic APM) được inject thông qua DI
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* // NestJS usage
|
|
10
|
+
* constructor(private readonly tracingService: TracingService) {}
|
|
11
|
+
*
|
|
12
|
+
* someMethod() {
|
|
13
|
+
* const span = this.tracingService.startSpan('my.operation', { 'key': 'value' }, 'SERVER');
|
|
14
|
+
* try {
|
|
15
|
+
* // ... code
|
|
16
|
+
* } finally {
|
|
17
|
+
* span.end();
|
|
18
|
+
* }
|
|
19
|
+
* }
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* // Standalone usage
|
|
23
|
+
* const provider = createTracingProvider();
|
|
24
|
+
* const tracingService = new TracingService(provider);
|
|
25
|
+
*/
|
|
26
|
+
export declare class TracingService {
|
|
27
|
+
private readonly provider;
|
|
28
|
+
constructor(provider: ITracingProvider);
|
|
29
|
+
/**
|
|
30
|
+
* Bắt đầu một span mới để trace operation
|
|
31
|
+
* @param name Tên của span
|
|
32
|
+
* @param attributes Các attributes metadata
|
|
33
|
+
* @param spanKind Loại span (mặc định: INTERNAL)
|
|
34
|
+
* - INTERNAL: Internal operation
|
|
35
|
+
* - SERVER: Server-side request handler (API endpoints)
|
|
36
|
+
* - CLIENT: Client-side call (outgoing HTTP, database)
|
|
37
|
+
* - PRODUCER: Message producer
|
|
38
|
+
* - CONSUMER: Message consumer
|
|
39
|
+
* @returns Span object - cần gọi end() khi hoàn thành
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* const span = this.tracingService.startSpan('my.operation', { 'key': 'value' }, 'SERVER');
|
|
43
|
+
* try {
|
|
44
|
+
* // ... code
|
|
45
|
+
* } finally {
|
|
46
|
+
* span.end();
|
|
47
|
+
* }
|
|
48
|
+
*/
|
|
49
|
+
startSpan(name: string, attributes?: Record<string, string | number>, spanKind?: string): any;
|
|
50
|
+
/**
|
|
51
|
+
* Thực thi function với context tracing (auto-close span)
|
|
52
|
+
* @param name Tên của span
|
|
53
|
+
* @param fn Function cần trace
|
|
54
|
+
* @param attributes Các attributes metadata
|
|
55
|
+
* @returns Kết quả của function
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* const result = await this.tracingService.startSpanWithParent(
|
|
59
|
+
* 'my.operation',
|
|
60
|
+
* async (span) => {
|
|
61
|
+
* span.setAttribute('custom', 'value');
|
|
62
|
+
* return await someAsyncOperation();
|
|
63
|
+
* }
|
|
64
|
+
* );
|
|
65
|
+
*/
|
|
66
|
+
startSpanWithParent<T>(name: string, fn: (span: any) => Promise<T>, attributes?: Record<string, string | number>): Promise<T>;
|
|
67
|
+
/**
|
|
68
|
+
* Capture error vào active span hiện tại
|
|
69
|
+
* @param error Error object
|
|
70
|
+
*/
|
|
71
|
+
captureError(error: Error): void;
|
|
72
|
+
/**
|
|
73
|
+
* Set attribute cho active span hiện tại
|
|
74
|
+
* @param key Tên attribute
|
|
75
|
+
* @param value Giá trị attribute
|
|
76
|
+
*/
|
|
77
|
+
setAttribute(key: string, value: string | number): void;
|
|
78
|
+
/**
|
|
79
|
+
* Kết thúc span manually
|
|
80
|
+
* @param span Span cần end (nếu không truyền, sẽ end active span)
|
|
81
|
+
*/
|
|
82
|
+
endSpan(span?: any): void;
|
|
83
|
+
/**
|
|
84
|
+
* Flush và shutdown APM provider (cho graceful shutdown)
|
|
85
|
+
*/
|
|
86
|
+
shutdown(): Promise<void>;
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=tracing.service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tracing.service.d.ts","sourceRoot":"","sources":["../../src/services/tracing.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,0CAA0C,CAAC;AAE5E;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,qBAAa,cAAc;IACb,OAAO,CAAC,QAAQ,CAAC,QAAQ;gBAAR,QAAQ,EAAE,gBAAgB;IAEvD;;;;;;;;;;;;;;;;;;;OAmBG;IACH,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,EAAE,QAAQ,GAAE,MAAmB,GAAG,GAAG;IAIzG;;;;;;;;;;;;;;;OAeG;IACH,mBAAmB,CAAC,CAAC,EACnB,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,OAAO,CAAC,CAAC,CAAC,EAC7B,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,GAC3C,OAAO,CAAC,CAAC,CAAC;IAIb;;;OAGG;IACH,YAAY,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI;IAIhC;;;;OAIG;IACH,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAIvD;;;OAGG;IACH,OAAO,CAAC,IAAI,CAAC,EAAE,GAAG,GAAG,IAAI;IAIzB;;OAEG;IACG,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;CAGhC"}
|