@contrail/telemetry 2.0.8-alpha-formatting.1 → 2.0.8

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 CHANGED
@@ -7,6 +7,12 @@ 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
+
10
16
  ## [2.0.4] - 2026-04-16
11
17
 
12
18
  ### Added
@@ -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;
@@ -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,6 +28,7 @@ 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; } });
@@ -57,11 +58,6 @@ if (globalRecord[TELEMETRY_SINGLETON_KEY]) {
57
58
  else {
58
59
  globalRecord[TELEMETRY_SINGLETON_KEY] = true;
59
60
  }
60
- // Capture the real console.log before app-framework's hijackConsole() replaces it.
61
- // This module loads first (as a dependency), so this is always the original.
62
- // Using it in the stdout stream avoids the infinite loop (hijacked console → pino → console → …)
63
- // while preserving single-event CloudWatch output (process.stdout.write splits on newlines).
64
- const originalConsoleLog = console.log;
65
61
  // --- Environment & Resource Setup ---
66
62
  const semantic_conventions_4 = require("../semantic-conventions");
67
63
  const LAMBDA_ENV_OTEL_ATTRIBUTES = getLambdaEnvAttributes();
@@ -87,6 +83,7 @@ const otelLoggerProvider = new sdk_logs_1.LoggerProvider({
87
83
  });
88
84
  const otelLogger = otelLoggerProvider.getLogger(serviceName);
89
85
  // --- Pino Stream Destinations ---
86
+ const stdout_writer_1 = require("./stdout-writer");
90
87
  function isPinoPrettyInstalled() {
91
88
  try {
92
89
  require.resolve('pino-pretty');
@@ -129,7 +126,7 @@ function createStdoutStream() {
129
126
  catch {
130
127
  line = chunk.toString().replace(/\n$/, '');
131
128
  }
132
- originalConsoleLog(line);
129
+ stdout_writer_1.stdoutWriter.log(line);
133
130
  callback();
134
131
  },
135
132
  }),
@@ -168,6 +165,7 @@ function createOtelStream() {
168
165
  // environment, even if user code accidentally sets a conflicting key.
169
166
  attributes: {
170
167
  ...safeAttributes,
168
+ ...(message != null && { [semantic_conventions_2.ATTR_LOG_BODY]: message }),
171
169
  ...lambdaEnvAttributes,
172
170
  ...spanAttributes,
173
171
  },
@@ -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
  */
@@ -0,0 +1,6 @@
1
+ export declare const stdoutWriter: {
2
+ log: {
3
+ (...data: any[]): void;
4
+ (message?: any, ...optionalParams: any[]): void;
5
+ };
6
+ };
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.stdoutWriter = void 0;
4
+ // Capture the original console.log before app-framework's hijackConsole() replaces it.
5
+ // Lambda treats each console.log() call as a single CloudWatch log event (even with
6
+ // embedded newlines), unlike process.stdout.write() which splits on \n. Using the
7
+ // original lets us pretty-print objects without them scattering across log events.
8
+ //
9
+ // Guard: if telemetry is imported after hijackConsole(), the captured function would be
10
+ // the hijacked one, creating an infinite loop. Detect by checking for [native code] —
11
+ // falls back to process.stdout.write if console.log has been replaced by anything.
12
+ const _capturedConsoleLog = console.log;
13
+ const isNative = Function.prototype.toString.call(_capturedConsoleLog).includes('[native code]');
14
+ // Wrapped in an object so tests can jest.spyOn(stdoutWriter, 'log') regardless
15
+ // of Jest's console patching strategy (BufferedConsole vs CustomConsole).
16
+ exports.stdoutWriter = {
17
+ log: isNative ? _capturedConsoleLog : (...args) => process.stdout.write(args.map(String).join(' ') + '\n'),
18
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contrail/telemetry",
3
- "version": "2.0.8-alpha-formatting.1",
3
+ "version": "2.0.8",
4
4
  "description": "Telemetry and monitoring utilities for contrail services",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",