@contrail/telemetry 2.0.4-beta.0 → 2.0.5-alpha-log-body.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/CHANGELOG.md +12 -0
- package/lib/index.d.ts +0 -1
- package/lib/index.js +0 -1
- package/lib/logger/index.d.ts +1 -1
- package/lib/logger/index.js +28 -1
- package/lib/logger/semantic-conventions.d.ts +5 -0
- package/lib/logger/semantic-conventions.js +6 -1
- package/package.json +1 -3
- package/lib/metrics/index.d.ts +0 -4
- package/lib/metrics/index.js +0 -40
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,18 @@ Versioning follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [2.0.5] - 2026-04-17
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **`contrail.log.body` attribute** — Every OTel log record now carries a `contrail.log.body` custom attribute that duplicates the log body. This enables grouping and filtering by log message in observability UIs (e.g. Dash0) that only expose custom attributes, not the standard `body` field.
|
|
15
|
+
|
|
16
|
+
## [2.0.4] - 2026-04-16
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- **Duplicate module detection** — logs a `stderr` warning at cold start if two copies of `@contrail/telemetry` are loaded, so silent log loss and broken context propagation are immediately visible.
|
|
21
|
+
|
|
10
22
|
## [2.0.3] - 2026-04-15
|
|
11
23
|
|
|
12
24
|
### Changed
|
package/lib/index.d.ts
CHANGED
package/lib/index.js
CHANGED
|
@@ -15,5 +15,4 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
17
|
__exportStar(require("./logger"), exports);
|
|
18
|
-
__exportStar(require("./metrics"), exports);
|
|
19
18
|
__exportStar(require("./semantic-conventions"), exports);
|
package/lib/logger/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import pino from 'pino';
|
|
2
2
|
export { parseOtelResourceAttributes } from './parse-otel-resource-attributes';
|
|
3
3
|
export { PINO_LEVEL_TO_OTEL_SEVERITY, PINO_LEVEL_TO_NAME } from './logger-config';
|
|
4
|
-
export { ATTR_LOG_MESSAGE, ATTR_LOG_PAYLOAD } from './semantic-conventions';
|
|
4
|
+
export { ATTR_LOG_MESSAGE, ATTR_LOG_BODY, ATTR_LOG_PAYLOAD } from './semantic-conventions';
|
|
5
5
|
export { loggerStorage, withLogAttributes } from './log-context';
|
|
6
6
|
export declare const baseLogger: pino.Logger<never, boolean>;
|
|
7
7
|
export declare const logger: pino.Logger;
|
package/lib/logger/index.js
CHANGED
|
@@ -4,7 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
var _a, _b, _c, _d, _e, _f, _g;
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
exports.logger = exports.baseLogger = exports.withLogAttributes = exports.loggerStorage = exports.ATTR_LOG_PAYLOAD = exports.ATTR_LOG_MESSAGE = exports.PINO_LEVEL_TO_NAME = exports.PINO_LEVEL_TO_OTEL_SEVERITY = exports.parseOtelResourceAttributes = void 0;
|
|
7
|
+
exports.logger = exports.baseLogger = exports.withLogAttributes = exports.loggerStorage = exports.ATTR_LOG_PAYLOAD = exports.ATTR_LOG_BODY = exports.ATTR_LOG_MESSAGE = exports.PINO_LEVEL_TO_NAME = exports.PINO_LEVEL_TO_OTEL_SEVERITY = exports.parseOtelResourceAttributes = void 0;
|
|
8
8
|
exports.addStream = addStream;
|
|
9
9
|
exports.flushLogs = flushLogs;
|
|
10
10
|
const pino_1 = __importDefault(require("pino"));
|
|
@@ -28,10 +28,36 @@ Object.defineProperty(exports, "PINO_LEVEL_TO_OTEL_SEVERITY", { enumerable: true
|
|
|
28
28
|
Object.defineProperty(exports, "PINO_LEVEL_TO_NAME", { enumerable: true, get: function () { return logger_config_2.PINO_LEVEL_TO_NAME; } });
|
|
29
29
|
var semantic_conventions_3 = require("./semantic-conventions");
|
|
30
30
|
Object.defineProperty(exports, "ATTR_LOG_MESSAGE", { enumerable: true, get: function () { return semantic_conventions_3.ATTR_LOG_MESSAGE; } });
|
|
31
|
+
Object.defineProperty(exports, "ATTR_LOG_BODY", { enumerable: true, get: function () { return semantic_conventions_3.ATTR_LOG_BODY; } });
|
|
31
32
|
Object.defineProperty(exports, "ATTR_LOG_PAYLOAD", { enumerable: true, get: function () { return semantic_conventions_3.ATTR_LOG_PAYLOAD; } });
|
|
32
33
|
var log_context_1 = require("./log-context");
|
|
33
34
|
Object.defineProperty(exports, "loggerStorage", { enumerable: true, get: function () { return log_context_1.loggerStorage; } });
|
|
34
35
|
Object.defineProperty(exports, "withLogAttributes", { enumerable: true, get: function () { return log_context_1.withLogAttributes; } });
|
|
36
|
+
// --- Singleton Guard ---
|
|
37
|
+
// The logger uses AsyncLocalStorage for request context propagation. If two copies
|
|
38
|
+
// of this module are loaded (e.g. due to a version mismatch causing npm to install
|
|
39
|
+
// a nested duplicate), each copy has its own AsyncLocalStorage instance and its own
|
|
40
|
+
// OTel LoggerProvider. Context set in one copy won't be visible in the other, and
|
|
41
|
+
// flushLogs() will only flush the provider it closes over — silently dropping logs.
|
|
42
|
+
//
|
|
43
|
+
// Symbol.for uses the global symbol registry, which is shared across all module
|
|
44
|
+
// instances, so this check fires correctly even when two copies are loaded.
|
|
45
|
+
//
|
|
46
|
+
// Local dev note: running `npm i` inside a lib package directory causes npm v7+ to
|
|
47
|
+
// auto-install peer deps locally. If that package is then symlinked into a service,
|
|
48
|
+
// the nested node_modules travels with it and re-creates the duplicate. Published
|
|
49
|
+
// tarballs don't ship node_modules, so this is local-only.
|
|
50
|
+
const TELEMETRY_SINGLETON_KEY = Symbol.for('@contrail/telemetry/logger-initialized');
|
|
51
|
+
const globalRecord = globalThis;
|
|
52
|
+
if (globalRecord[TELEMETRY_SINGLETON_KEY]) {
|
|
53
|
+
process.stderr.write('[WARN] @contrail/telemetry loaded twice — duplicate module instance detected. ' +
|
|
54
|
+
'flushLogs() will only flush one provider and log context may not propagate correctly. ' +
|
|
55
|
+
'Ensure @contrail/telemetry is listed as a peerDependency in any library that re-exports ' +
|
|
56
|
+
'it, and that the host service lists it in its own package.json.\n');
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
globalRecord[TELEMETRY_SINGLETON_KEY] = true;
|
|
60
|
+
}
|
|
35
61
|
// --- Environment & Resource Setup ---
|
|
36
62
|
const semantic_conventions_4 = require("../semantic-conventions");
|
|
37
63
|
const LAMBDA_ENV_OTEL_ATTRIBUTES = getLambdaEnvAttributes();
|
|
@@ -138,6 +164,7 @@ function createOtelStream() {
|
|
|
138
164
|
// environment, even if user code accidentally sets a conflicting key.
|
|
139
165
|
attributes: {
|
|
140
166
|
...safeAttributes,
|
|
167
|
+
[semantic_conventions_2.ATTR_LOG_BODY]: message,
|
|
141
168
|
...lambdaEnvAttributes,
|
|
142
169
|
...spanAttributes,
|
|
143
170
|
},
|
|
@@ -5,6 +5,11 @@
|
|
|
5
5
|
* Attribute key for the log message body.
|
|
6
6
|
*/
|
|
7
7
|
export declare const ATTR_LOG_MESSAGE: "contrail.message";
|
|
8
|
+
/**
|
|
9
|
+
* Custom attribute that duplicates the log body so it is available for
|
|
10
|
+
* grouping / filtering in observability UIs that only expose custom attributes.
|
|
11
|
+
*/
|
|
12
|
+
export declare const ATTR_LOG_BODY: "contrail.log.body";
|
|
8
13
|
/**
|
|
9
14
|
* Attribute key for user-provided log payload objects.
|
|
10
15
|
*/
|
|
@@ -3,11 +3,16 @@
|
|
|
3
3
|
* Log-specific semantic conventions.
|
|
4
4
|
*/
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.ATTR_LOG_PAYLOAD = exports.ATTR_LOG_MESSAGE = void 0;
|
|
6
|
+
exports.ATTR_LOG_PAYLOAD = exports.ATTR_LOG_BODY = exports.ATTR_LOG_MESSAGE = void 0;
|
|
7
7
|
/**
|
|
8
8
|
* Attribute key for the log message body.
|
|
9
9
|
*/
|
|
10
10
|
exports.ATTR_LOG_MESSAGE = 'contrail.message';
|
|
11
|
+
/**
|
|
12
|
+
* Custom attribute that duplicates the log body so it is available for
|
|
13
|
+
* grouping / filtering in observability UIs that only expose custom attributes.
|
|
14
|
+
*/
|
|
15
|
+
exports.ATTR_LOG_BODY = 'contrail.log.body';
|
|
11
16
|
/**
|
|
12
17
|
* Attribute key for user-provided log payload objects.
|
|
13
18
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contrail/telemetry",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.5-alpha-log-body.0",
|
|
4
4
|
"description": "Telemetry and monitoring utilities for contrail services",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"types": "lib/index.d.ts",
|
|
@@ -20,10 +20,8 @@
|
|
|
20
20
|
"@opentelemetry/api-logs": "^0.211.0",
|
|
21
21
|
"@opentelemetry/core": "^2.5.0",
|
|
22
22
|
"@opentelemetry/exporter-logs-otlp-http": "^0.211.0",
|
|
23
|
-
"@opentelemetry/exporter-metrics-otlp-http": "^0.211.0",
|
|
24
23
|
"@opentelemetry/resources": "^2.5.0",
|
|
25
24
|
"@opentelemetry/sdk-logs": "^0.211.0",
|
|
26
|
-
"@opentelemetry/sdk-metrics": "^2.5.0",
|
|
27
25
|
"@opentelemetry/sdk-trace-base": "^2.5.0",
|
|
28
26
|
"@opentelemetry/semantic-conventions": "^1.39.0",
|
|
29
27
|
"pino": "^10.1.0"
|
package/lib/metrics/index.d.ts
DELETED
package/lib/metrics/index.js
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getMeter = getMeter;
|
|
4
|
-
exports.flushMetrics = flushMetrics;
|
|
5
|
-
const sdk_metrics_1 = require("@opentelemetry/sdk-metrics");
|
|
6
|
-
const exporter_metrics_otlp_http_1 = require("@opentelemetry/exporter-metrics-otlp-http");
|
|
7
|
-
const resources_1 = require("@opentelemetry/resources");
|
|
8
|
-
const parse_otel_resource_attributes_1 = require("../logger/parse-otel-resource-attributes");
|
|
9
|
-
const otlpEndpoint = process.env.OTEL_EXPORTER_OTLP_ENDPOINT;
|
|
10
|
-
const shouldExportToOtlp = Boolean(otlpEndpoint);
|
|
11
|
-
const resource = (0, resources_1.resourceFromAttributes)((0, parse_otel_resource_attributes_1.parseOtelResourceAttributes)());
|
|
12
|
-
const readers = shouldExportToOtlp
|
|
13
|
-
? [
|
|
14
|
-
new sdk_metrics_1.PeriodicExportingMetricReader({
|
|
15
|
-
exporter: new exporter_metrics_otlp_http_1.OTLPMetricExporter(),
|
|
16
|
-
exportIntervalMillis: 30000,
|
|
17
|
-
}),
|
|
18
|
-
]
|
|
19
|
-
: [];
|
|
20
|
-
const meterProvider = new sdk_metrics_1.MeterProvider({ resource, readers });
|
|
21
|
-
function getMeter(name) {
|
|
22
|
-
return meterProvider.getMeter(name);
|
|
23
|
-
}
|
|
24
|
-
async function flushMetrics(options) {
|
|
25
|
-
var _a;
|
|
26
|
-
const timeoutMs = (_a = options === null || options === void 0 ? void 0 : options.timeoutMs) !== null && _a !== void 0 ? _a : 5000;
|
|
27
|
-
try {
|
|
28
|
-
await Promise.race([
|
|
29
|
-
meterProvider.forceFlush(),
|
|
30
|
-
new Promise((_, reject) => setTimeout(() => reject(new Error(`Metrics flush timed out after ${timeoutMs}ms`)), timeoutMs)),
|
|
31
|
-
]);
|
|
32
|
-
}
|
|
33
|
-
catch (error) {
|
|
34
|
-
process.stderr.write(`[OTel Metrics Flush Failed] ${JSON.stringify({
|
|
35
|
-
error: error instanceof Error ? error.message : String(error),
|
|
36
|
-
timeoutMs,
|
|
37
|
-
timestamp: new Date().toISOString(),
|
|
38
|
-
})}\n`);
|
|
39
|
-
}
|
|
40
|
-
}
|