@qlibs/apm 0.1.2 → 0.3.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/README.md +113 -6
- package/dist/apm.config.js +3 -3
- package/dist/filters/apm-exception.filter.js +1 -1
- package/dist/http/apm-logging.interceptor.js +2 -2
- package/dist/logger/apm-logger.service.d.ts +1 -0
- package/dist/logger/apm-logger.service.js +3 -0
- package/dist/logger/otlp-log.transport.js +20 -8
- package/dist/logger/winston.instance.js +9 -1
- package/dist/tracing.js +36 -15
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -70,17 +70,124 @@ dependency of this package.
|
|
|
70
70
|
|
|
71
71
|
| Variable | Default | Purpose |
|
|
72
72
|
|---|---|---|
|
|
73
|
-
| `APM_ENABLED` | `true` |
|
|
73
|
+
| `APM_ENABLED` | `true` | `false` disables tracing. Log transports are controlled only by `APM_LOG_TRANSPORT`. |
|
|
74
74
|
| `APM_SERVICE_NAME` | `ats-be` | Service name shown in traces/logs — set per app, e.g. `my-app-api`. |
|
|
75
|
-
| `APM_ENVIRONMENT` | `NODE_ENV` | `deployment.environment` resource attribute. |
|
|
75
|
+
| `APM_ENVIRONMENT` | `NODE_ENV`, else `production` | `deployment.environment` resource attribute. Also drives the log level/format defaults below. Unset `NODE_ENV` is treated as `production` (never as local). |
|
|
76
76
|
| `OTEL_EXPORTER_OTLP_ENDPOINT` | _(empty)_ | Base OTLP endpoint. Empty = tracing disabled. |
|
|
77
77
|
| `OTEL_EXPORTER_OTLP_PROTOCOL` | `http/protobuf` | `http/protobuf` (port 4318 typically) or `grpc` (port 4317). Must match what your collector listens on. |
|
|
78
78
|
| `OTEL_EXPORTER_OTLP_HEADERS` | _(empty)_ | Comma-separated `key=value` pairs, e.g. ingestion keys / auth tokens. |
|
|
79
|
-
| `LOG_LEVEL` | `info` (`debug`
|
|
80
|
-
| `APM_LOG_FORMAT` | `pretty`
|
|
79
|
+
| `LOG_LEVEL` | `info` (`debug` when env is `local`) | Winston log level (`error`, `warn`, `info`, `http`, `verbose`, `debug`). At `info`, Nest `verbose`/`debug` logs are dropped — set `verbose` or `debug` to see them. HTTP access logs and Nest `fatal` (logged as `error` with `fatal: true`) are always visible at `info`. On the console, `error`/`warn` go to stderr and everything else to stdout. |
|
|
80
|
+
| `APM_LOG_FORMAT` | `pretty` for `local`/`development`, `json` otherwise | `pretty` = colorized console; `json` = structured stdout. |
|
|
81
81
|
| `APM_LOG_TRANSPORT` | `stdout` | Comma list of `stdout`, `elasticsearch`, `otlp`. Multiple can be active at once. |
|
|
82
|
-
| `ELASTICSEARCH_NODE` / `_USERNAME` / `_PASSWORD` / `_INDEX` | _(empty)_ | Only used when `APM_LOG_TRANSPORT` includes `elasticsearch`. |
|
|
83
|
-
| `APM_HTTP_IGNORE_PATHS` | `/health,/favicon.ico` | Comma list of path prefixes excluded from the HTTP access log. |
|
|
82
|
+
| `ELASTICSEARCH_NODE` / `_USERNAME` / `_PASSWORD` / `_INDEX` | _(empty)_ | Only used when `APM_LOG_TRANSPORT` includes `elasticsearch`. While ES is unreachable at most 1000 log lines are buffered (the rest are dropped) and each is retried 5 times. Shipping via stdout + Filebeat/Fluent Bit is still preferred in production. |
|
|
83
|
+
| `APM_HTTP_IGNORE_PATHS` | `/health,/favicon.ico` | Comma list of path prefixes excluded from the HTTP access log and from tracing. |
|
|
84
|
+
| `OTEL_NODE_ENABLED_INSTRUMENTATIONS` | `http,undici,express,nestjs-core,pg,ioredis,redis,amqplib` | Which auto-instrumentations to load (names without the `@opentelemetry/instrumentation-` prefix). Add e.g. `mongodb`, `kafkajs` if your app needs them. |
|
|
85
|
+
| `OTEL_TRACES_SAMPLER` / `OTEL_TRACES_SAMPLER_ARG` | `parentbased_always_on` | Standard OTel sampling. For high-traffic services use `parentbased_traceidratio` with e.g. `0.1` (10%). |
|
|
86
|
+
|
|
87
|
+
## Example configurations
|
|
88
|
+
|
|
89
|
+
`LOG_LEVEL` is a threshold: it keeps that level and everything more severe
|
|
90
|
+
(`error` < `warn` < `info` < `http` < `verbose` < `debug` < `silly`). Nest's
|
|
91
|
+
`logger.log()` is `info`, `logger.fatal()` is `error`. Use lowercase values
|
|
92
|
+
exactly as listed — an unknown value (e.g. `WARN`, `log`, `trace`) currently
|
|
93
|
+
drops **all** logs, errors included.
|
|
94
|
+
|
|
95
|
+
What each setting affects:
|
|
96
|
+
|
|
97
|
+
| | stdout (pm2 / docker logs) | Logs in SigNoz | Traces in SigNoz |
|
|
98
|
+
|---|---|---|---|
|
|
99
|
+
| `LOG_LEVEL` | ✅ | ✅ | ❌ |
|
|
100
|
+
| `APM_LOG_TRANSPORT` | ✅ | ✅ (needs `otlp`) | ❌ |
|
|
101
|
+
| `OTEL_TRACES_SAMPLER` | ❌ | ❌ | ✅ |
|
|
102
|
+
|
|
103
|
+
Errors are still marked on the trace span by `recordException()` regardless of
|
|
104
|
+
`LOG_LEVEL`. After changing env vars, restart the app (pm2:
|
|
105
|
+
`pm2 restart <app> --update-env`).
|
|
106
|
+
|
|
107
|
+
**1. Production, minimal — only warn + error in pm2/docker, traces to SigNoz**
|
|
108
|
+
```env
|
|
109
|
+
NODE_ENV=production
|
|
110
|
+
APM_SERVICE_NAME=my-app-api
|
|
111
|
+
LOG_LEVEL=warn
|
|
112
|
+
APM_LOG_TRANSPORT=stdout
|
|
113
|
+
OTEL_EXPORTER_OTLP_ENDPOINT=http://signoz-otel-collector:4318
|
|
114
|
+
```
|
|
115
|
+
Note: at `warn` the HTTP access log (`GET /api/... 200`, level `info`) is
|
|
116
|
+
dropped too.
|
|
117
|
+
|
|
118
|
+
**2. Production, standard — info + access log in pm2/docker, logs and traces to SigNoz**
|
|
119
|
+
```env
|
|
120
|
+
NODE_ENV=production
|
|
121
|
+
APM_SERVICE_NAME=my-app-api
|
|
122
|
+
LOG_LEVEL=info
|
|
123
|
+
APM_LOG_TRANSPORT=stdout,otlp
|
|
124
|
+
OTEL_EXPORTER_OTLP_ENDPOINT=http://signoz-otel-collector:4318
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
**3. Production, high traffic — trace only 10% of requests**
|
|
128
|
+
```env
|
|
129
|
+
NODE_ENV=production
|
|
130
|
+
APM_SERVICE_NAME=my-app-api
|
|
131
|
+
LOG_LEVEL=warn
|
|
132
|
+
APM_LOG_TRANSPORT=stdout,otlp
|
|
133
|
+
OTEL_EXPORTER_OTLP_ENDPOINT=http://signoz-otel-collector:4318
|
|
134
|
+
OTEL_TRACES_SAMPLER=parentbased_traceidratio
|
|
135
|
+
OTEL_TRACES_SAMPLER_ARG=0.1
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
**4. Staging, investigating a bug — everything**
|
|
139
|
+
```env
|
|
140
|
+
NODE_ENV=staging
|
|
141
|
+
APM_SERVICE_NAME=my-app-api
|
|
142
|
+
LOG_LEVEL=debug
|
|
143
|
+
APM_LOG_TRANSPORT=stdout,otlp
|
|
144
|
+
OTEL_EXPORTER_OTLP_ENDPOINT=http://signoz-otel-collector:4318
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
**5. Local development, no SigNoz**
|
|
148
|
+
```env
|
|
149
|
+
NODE_ENV=local
|
|
150
|
+
APM_SERVICE_NAME=my-app-api
|
|
151
|
+
# LOG_LEVEL defaults to debug, format to colorized pretty output
|
|
152
|
+
# OTEL_EXPORTER_OTLP_ENDPOINT left empty -> tracing off
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
**6. Logs to pm2/docker only, no tracing at all**
|
|
156
|
+
```env
|
|
157
|
+
NODE_ENV=production
|
|
158
|
+
APM_SERVICE_NAME=my-app-api
|
|
159
|
+
LOG_LEVEL=warn
|
|
160
|
+
APM_LOG_TRANSPORT=stdout
|
|
161
|
+
# OTEL_EXPORTER_OTLP_ENDPOINT not set -> nothing is sent to SigNoz
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
**7. Collector listening on gRPC (port 4317)**
|
|
165
|
+
```env
|
|
166
|
+
OTEL_EXPORTER_OTLP_ENDPOINT=http://signoz-otel-collector:4317
|
|
167
|
+
OTEL_EXPORTER_OTLP_PROTOCOL=grpc
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
**8. Logs to Elasticsearch / Kibana**
|
|
171
|
+
```env
|
|
172
|
+
NODE_ENV=production
|
|
173
|
+
APM_SERVICE_NAME=my-app-api
|
|
174
|
+
LOG_LEVEL=warn
|
|
175
|
+
APM_LOG_TRANSPORT=stdout,elasticsearch
|
|
176
|
+
ELASTICSEARCH_NODE=https://es.internal:9200
|
|
177
|
+
ELASTICSEARCH_USERNAME=elastic
|
|
178
|
+
ELASTICSEARCH_PASSWORD=changeme
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
To stop shipping logs remotely, remove `otlp` / `elasticsearch` from
|
|
182
|
+
`APM_LOG_TRANSPORT` — `APM_ENABLED=false` currently only turns tracing off.
|
|
183
|
+
|
|
184
|
+
## Shutdown
|
|
185
|
+
|
|
186
|
+
On `SIGTERM`/`SIGINT` the tracing SDK flushes pending spans (max 5s) but does
|
|
187
|
+
**not** call `process.exit()` — your app's own graceful shutdown (e.g.
|
|
188
|
+
`app.enableShutdownHooks()` in Nest) stays in control. If no other signal
|
|
189
|
+
handler is registered, the signal is re-raised after the flush so the process
|
|
190
|
+
still terminates normally.
|
|
84
191
|
|
|
85
192
|
## Pointing at a specific backend
|
|
86
193
|
|
package/dist/apm.config.js
CHANGED
|
@@ -29,7 +29,8 @@ function getApmConfig() {
|
|
|
29
29
|
if (cachedConfig)
|
|
30
30
|
return cachedConfig;
|
|
31
31
|
const env = process.env;
|
|
32
|
-
const nodeEnv = env.NODE_ENV || '
|
|
32
|
+
const nodeEnv = env.APM_ENVIRONMENT || env.NODE_ENV || 'production';
|
|
33
|
+
const isLocal = nodeEnv === 'local' || nodeEnv === 'development';
|
|
33
34
|
const otlpEndpoint = env.OTEL_EXPORTER_OTLP_ENDPOINT || undefined;
|
|
34
35
|
const transports = parseList(env.APM_LOG_TRANSPORT);
|
|
35
36
|
cachedConfig = {
|
|
@@ -45,8 +46,7 @@ function getApmConfig() {
|
|
|
45
46
|
},
|
|
46
47
|
logging: {
|
|
47
48
|
level: env.LOG_LEVEL || (nodeEnv === 'local' ? 'debug' : 'info'),
|
|
48
|
-
format: env.APM_LOG_FORMAT ||
|
|
49
|
-
(nodeEnv === 'local' || nodeEnv === 'development' ? 'pretty' : 'json'),
|
|
49
|
+
format: env.APM_LOG_FORMAT || (isLocal ? 'pretty' : 'json'),
|
|
50
50
|
transports: transports.length ? transports : ['stdout'],
|
|
51
51
|
elasticsearch: {
|
|
52
52
|
node: env.ELASTICSEARCH_NODE || undefined,
|
|
@@ -14,7 +14,7 @@ let ApmExceptionFilter = class ApmExceptionFilter {
|
|
|
14
14
|
(0, record_exception_1.recordException)(err, 'ApmExceptionFilter');
|
|
15
15
|
const ctx = host.switchToHttp();
|
|
16
16
|
const response = ctx.getResponse();
|
|
17
|
-
if (!(response === null || response === void 0 ? void 0 : response.status))
|
|
17
|
+
if (!(response === null || response === void 0 ? void 0 : response.status) || response.headersSent)
|
|
18
18
|
return;
|
|
19
19
|
const status = err instanceof common_1.HttpException ? err.getStatus() : 500;
|
|
20
20
|
const body = err instanceof common_1.HttpException
|
|
@@ -30,7 +30,7 @@ let ApmLoggingInterceptor = class ApmLoggingInterceptor {
|
|
|
30
30
|
return next.handle().pipe((0, rxjs_1.tap)({
|
|
31
31
|
next: () => {
|
|
32
32
|
const response = context.switchToHttp().getResponse();
|
|
33
|
-
winston_instance_1.winstonLogger.
|
|
33
|
+
winston_instance_1.winstonLogger.info(`${method} ${path} ${response.statusCode}`, {
|
|
34
34
|
context: 'HTTP',
|
|
35
35
|
method,
|
|
36
36
|
path,
|
|
@@ -41,7 +41,7 @@ let ApmLoggingInterceptor = class ApmLoggingInterceptor {
|
|
|
41
41
|
});
|
|
42
42
|
},
|
|
43
43
|
error: (err) => {
|
|
44
|
-
winston_instance_1.winstonLogger.
|
|
44
|
+
winston_instance_1.winstonLogger.info(`${method} ${path} failed`, {
|
|
45
45
|
context: 'HTTP',
|
|
46
46
|
method,
|
|
47
47
|
path,
|
|
@@ -4,6 +4,7 @@ export declare class ApmLoggerService implements LoggerService {
|
|
|
4
4
|
error(message: any, trace?: string, context?: string): void;
|
|
5
5
|
warn(message: any, context?: string): void;
|
|
6
6
|
debug(message: any, context?: string): void;
|
|
7
|
+
fatal(message: any, context?: string): void;
|
|
7
8
|
verbose(message: any, context?: string): void;
|
|
8
9
|
private stringify;
|
|
9
10
|
}
|
|
@@ -22,6 +22,9 @@ let ApmLoggerService = class ApmLoggerService {
|
|
|
22
22
|
debug(message, context) {
|
|
23
23
|
winston_instance_1.winstonLogger.debug(this.stringify(message), { context });
|
|
24
24
|
}
|
|
25
|
+
fatal(message, context) {
|
|
26
|
+
winston_instance_1.winstonLogger.error(this.stringify(message), { context, fatal: true });
|
|
27
|
+
}
|
|
25
28
|
verbose(message, context) {
|
|
26
29
|
winston_instance_1.winstonLogger.verbose(this.stringify(message), { context });
|
|
27
30
|
}
|
|
@@ -28,25 +28,33 @@ const SEVERITY_MAP = {
|
|
|
28
28
|
silly: { number: 1, text: 'TRACE' },
|
|
29
29
|
};
|
|
30
30
|
let otelLogger;
|
|
31
|
+
let emitFailureReported = false;
|
|
31
32
|
function getOtelLogger() {
|
|
32
33
|
if (otelLogger)
|
|
33
34
|
return otelLogger;
|
|
34
35
|
const config = (0, apm_config_1.getApmConfig)();
|
|
35
36
|
const { LoggerProvider, BatchLogRecordProcessor, } = require('@opentelemetry/sdk-logs');
|
|
36
|
-
const { OTLPLogExporter } = require('@opentelemetry/exporter-logs-otlp-http');
|
|
37
37
|
const { resourceFromAttributes } = require('@opentelemetry/resources');
|
|
38
38
|
const { ATTR_SERVICE_NAME } = require('@opentelemetry/semantic-conventions');
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
39
|
+
let exporter;
|
|
40
|
+
if (config.tracing.protocol === 'grpc') {
|
|
41
|
+
const { OTLPLogExporter, } = require('@opentelemetry/exporter-logs-otlp-grpc');
|
|
42
|
+
exporter = new OTLPLogExporter({ url: config.tracing.otlpEndpoint });
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
const { OTLPLogExporter, } = require('@opentelemetry/exporter-logs-otlp-http');
|
|
46
|
+
const base = (config.tracing.otlpEndpoint || '').replace(/\/$/, '');
|
|
47
|
+
exporter = new OTLPLogExporter({
|
|
48
|
+
url: `${base}/v1/logs`,
|
|
49
|
+
headers: config.tracing.headers,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
44
52
|
const provider = new LoggerProvider({
|
|
45
53
|
resource: resourceFromAttributes({
|
|
46
54
|
[ATTR_SERVICE_NAME]: config.serviceName,
|
|
47
55
|
}),
|
|
56
|
+
processors: [new BatchLogRecordProcessor({ exporter })],
|
|
48
57
|
});
|
|
49
|
-
provider.addLogRecordProcessor(new BatchLogRecordProcessor(exporter));
|
|
50
58
|
otelLogger = provider.getLogger(config.serviceName);
|
|
51
59
|
return otelLogger;
|
|
52
60
|
}
|
|
@@ -64,7 +72,11 @@ class OtlpLogTransport extends winston_transport_1.default {
|
|
|
64
72
|
context: api_1.context.active(),
|
|
65
73
|
});
|
|
66
74
|
}
|
|
67
|
-
catch (
|
|
75
|
+
catch (err) {
|
|
76
|
+
if (!emitFailureReported) {
|
|
77
|
+
emitFailureReported = true;
|
|
78
|
+
console.error('[apm] otlp log transport failed, logs are not being exported:', err instanceof Error ? err.message : err);
|
|
79
|
+
}
|
|
68
80
|
}
|
|
69
81
|
callback();
|
|
70
82
|
}
|
|
@@ -61,7 +61,10 @@ function buildConsoleTransport() {
|
|
|
61
61
|
return `${timestamp} ${level}${ctx} ${message}${trace}${extra}`;
|
|
62
62
|
}))
|
|
63
63
|
: winston.format.combine((0, trace_context_format_1.traceContextFormat)(), winston.format.timestamp(), winston.format.json());
|
|
64
|
-
return new winston.transports.Console({
|
|
64
|
+
return new winston.transports.Console({
|
|
65
|
+
format,
|
|
66
|
+
stderrLevels: ['error', 'warn'],
|
|
67
|
+
});
|
|
65
68
|
}
|
|
66
69
|
function buildElasticsearchTransport() {
|
|
67
70
|
const config = (0, apm_config_1.getApmConfig)();
|
|
@@ -73,6 +76,8 @@ function buildElasticsearchTransport() {
|
|
|
73
76
|
return new ElasticsearchTransport({
|
|
74
77
|
level: config.logging.level,
|
|
75
78
|
indexPrefix: config.logging.elasticsearch.index,
|
|
79
|
+
bufferLimit: 1000,
|
|
80
|
+
retryLimit: 5,
|
|
76
81
|
clientOpts: {
|
|
77
82
|
node: config.logging.elasticsearch.node,
|
|
78
83
|
auth: config.logging.elasticsearch.username &&
|
|
@@ -122,3 +127,6 @@ exports.winstonLogger = winston.createLogger({
|
|
|
122
127
|
defaultMeta: { service: config.serviceName, environment: config.environment },
|
|
123
128
|
transports: buildTransports(),
|
|
124
129
|
});
|
|
130
|
+
exports.winstonLogger.on('error', (err) => {
|
|
131
|
+
console.error('[apm] log transport error:', err instanceof Error ? err.message : err);
|
|
132
|
+
});
|
package/dist/tracing.js
CHANGED
|
@@ -3,17 +3,16 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
const apm_config_1 = require("./apm.config");
|
|
4
4
|
const config = (0, apm_config_1.getApmConfig)();
|
|
5
5
|
if (config.enabled && config.tracing.enabled && config.tracing.otlpEndpoint) {
|
|
6
|
+
if (!process.env.OTEL_NODE_ENABLED_INSTRUMENTATIONS) {
|
|
7
|
+
process.env.OTEL_NODE_ENABLED_INSTRUMENTATIONS =
|
|
8
|
+
'http,undici,express,nestjs-core,pg,ioredis,redis,amqplib';
|
|
9
|
+
}
|
|
6
10
|
const { NodeSDK } = require('@opentelemetry/sdk-node');
|
|
7
11
|
const { getNodeAutoInstrumentations, } = require('@opentelemetry/auto-instrumentations-node');
|
|
8
|
-
const { OTLPTraceExporter, } = require('@opentelemetry/exporter-trace-otlp-http');
|
|
9
12
|
const { resourceFromAttributes } = require('@opentelemetry/resources');
|
|
10
13
|
const { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION, } = require('@opentelemetry/semantic-conventions');
|
|
11
|
-
const traceExporter =
|
|
12
|
-
|
|
13
|
-
? config.tracing.otlpEndpoint
|
|
14
|
-
: `${config.tracing.otlpEndpoint.replace(/\/$/, '')}/v1/traces`,
|
|
15
|
-
headers: config.tracing.headers,
|
|
16
|
-
});
|
|
14
|
+
const traceExporter = createTraceExporter();
|
|
15
|
+
const ignorePaths = config.http.ignorePaths;
|
|
17
16
|
const sdk = new NodeSDK({
|
|
18
17
|
resource: resourceFromAttributes({
|
|
19
18
|
[ATTR_SERVICE_NAME]: config.serviceName,
|
|
@@ -24,20 +23,42 @@ if (config.enabled && config.tracing.enabled && config.tracing.otlpEndpoint) {
|
|
|
24
23
|
instrumentations: [
|
|
25
24
|
getNodeAutoInstrumentations({
|
|
26
25
|
'@opentelemetry/instrumentation-fs': { enabled: false },
|
|
26
|
+
'@opentelemetry/instrumentation-http': {
|
|
27
|
+
ignoreIncomingRequestHook: (req) => ignorePaths.some((p) => { var _a; return (_a = req.url) === null || _a === void 0 ? void 0 : _a.startsWith(p); }),
|
|
28
|
+
},
|
|
27
29
|
}),
|
|
28
30
|
],
|
|
29
31
|
});
|
|
30
32
|
sdk.start();
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
.
|
|
35
|
-
|
|
33
|
+
const SHUTDOWN_TIMEOUT_MS = 5000;
|
|
34
|
+
const onSignal = (signal) => {
|
|
35
|
+
Promise.race([
|
|
36
|
+
sdk.shutdown(),
|
|
37
|
+
new Promise((resolve) => setTimeout(resolve, SHUTDOWN_TIMEOUT_MS).unref()),
|
|
38
|
+
])
|
|
39
|
+
.catch((err) => console.error('[apm] error shutting down OTel SDK', err))
|
|
40
|
+
.finally(() => {
|
|
41
|
+
if (process.listenerCount(signal) === 0) {
|
|
42
|
+
process.kill(process.pid, signal);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
36
45
|
};
|
|
37
|
-
process.
|
|
38
|
-
process.
|
|
39
|
-
console.info(`[apm] tracing enabled -> ${config.tracing.otlpEndpoint} (service: ${config.serviceName})`);
|
|
46
|
+
process.once('SIGTERM', onSignal);
|
|
47
|
+
process.once('SIGINT', onSignal);
|
|
48
|
+
console.info(`[apm] tracing enabled -> ${config.tracing.otlpEndpoint} (${config.tracing.protocol}, service: ${config.serviceName})`);
|
|
40
49
|
}
|
|
41
50
|
else {
|
|
42
51
|
console.info('[apm] tracing disabled (set OTEL_EXPORTER_OTLP_ENDPOINT to enable)');
|
|
43
52
|
}
|
|
53
|
+
function createTraceExporter() {
|
|
54
|
+
const { otlpEndpoint, protocol, headers } = config.tracing;
|
|
55
|
+
if (protocol === 'grpc') {
|
|
56
|
+
const { OTLPTraceExporter, } = require('@opentelemetry/exporter-trace-otlp-grpc');
|
|
57
|
+
return new OTLPTraceExporter({ url: otlpEndpoint });
|
|
58
|
+
}
|
|
59
|
+
const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-http');
|
|
60
|
+
return new OTLPTraceExporter({
|
|
61
|
+
url: `${otlpEndpoint.replace(/\/$/, '')}/v1/traces`,
|
|
62
|
+
headers,
|
|
63
|
+
});
|
|
64
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qlibs/apm",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Vendor-neutral APM for NestJS: OpenTelemetry tracing (OTLP) + structured logging + error monitoring. Works with SigNoz, Elastic/Kibana, Jaeger, or any OTel collector.",
|
|
5
5
|
"author": "QBIT Developer",
|
|
6
6
|
"license": "MIT",
|
|
@@ -28,7 +28,9 @@
|
|
|
28
28
|
"@opentelemetry/api": "^1.9.1",
|
|
29
29
|
"@opentelemetry/api-logs": "^0.221.0",
|
|
30
30
|
"@opentelemetry/auto-instrumentations-node": "^0.79.0",
|
|
31
|
+
"@opentelemetry/exporter-logs-otlp-grpc": "^0.221.0",
|
|
31
32
|
"@opentelemetry/exporter-logs-otlp-http": "^0.221.0",
|
|
33
|
+
"@opentelemetry/exporter-trace-otlp-grpc": "^0.221.0",
|
|
32
34
|
"@opentelemetry/exporter-trace-otlp-http": "^0.221.0",
|
|
33
35
|
"@opentelemetry/resources": "^2.10.0",
|
|
34
36
|
"@opentelemetry/sdk-logs": "^0.221.0",
|