@payloops/observability 0.0.6 → 0.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/README.md CHANGED
@@ -5,7 +5,7 @@ Shared observability package for PayLoops services. Provides OpenTelemetry traci
5
5
  ## Installation
6
6
 
7
7
  ```bash
8
- pnpm add @payloops/observability
8
+ npm install @payloops/observability
9
9
  ```
10
10
 
11
11
  ## Features
package/dist/index.d.ts CHANGED
@@ -18,6 +18,8 @@ declare function shutdownTelemetry(): Promise<void>;
18
18
 
19
19
  /**
20
20
  * Base logger with trace context mixin
21
+ * In development: pretty print to console + OTLP
22
+ * In production: JSON to stdout + OTLP
21
23
  */
22
24
  declare const logger: pino.Logger<never, boolean>;
23
25
  /**
package/dist/index.js CHANGED
@@ -2,6 +2,7 @@
2
2
  import { NodeSDK } from "@opentelemetry/sdk-node";
3
3
  import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
4
4
  import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-http";
5
+ import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-http";
5
6
  import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node";
6
7
 
7
8
  // node_modules/@opentelemetry/resources/build/esm/Resource.js
@@ -229,18 +230,31 @@ var Resource = (
229
230
  // src/lib/otel.ts
230
231
  import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } from "@opentelemetry/semantic-conventions";
231
232
  import { PeriodicExportingMetricReader } from "@opentelemetry/sdk-metrics";
233
+ import {
234
+ LoggerProvider,
235
+ BatchLogRecordProcessor
236
+ } from "@opentelemetry/sdk-logs";
237
+ import { logs } from "@opentelemetry/api-logs";
232
238
  var sdk = null;
239
+ var loggerProvider = null;
233
240
  function initTelemetry(config, serviceVersion = "0.0.1") {
234
241
  if (sdk) return sdk;
235
242
  const cfg = typeof config === "string" ? { serviceName: config, serviceVersion } : config;
236
243
  const otlpEndpoint = cfg.otlpEndpoint || process.env.OTEL_EXPORTER_OTLP_ENDPOINT || "http://localhost:4318";
237
244
  const environment = cfg.environment || process.env.NODE_ENV || "development";
245
+ const resource = new Resource({
246
+ [ATTR_SERVICE_NAME]: cfg.serviceName,
247
+ [ATTR_SERVICE_VERSION]: cfg.serviceVersion || "0.0.1",
248
+ "deployment.environment": environment
249
+ });
250
+ const logExporter = new OTLPLogExporter({
251
+ url: `${otlpEndpoint}/v1/logs`
252
+ });
253
+ loggerProvider = new LoggerProvider({ resource });
254
+ loggerProvider.addLogRecordProcessor(new BatchLogRecordProcessor(logExporter));
255
+ logs.setGlobalLoggerProvider(loggerProvider);
238
256
  sdk = new NodeSDK({
239
- resource: new Resource({
240
- [ATTR_SERVICE_NAME]: cfg.serviceName,
241
- [ATTR_SERVICE_VERSION]: cfg.serviceVersion || "0.0.1",
242
- "deployment.environment": environment
243
- }),
257
+ resource,
244
258
  traceExporter: new OTLPTraceExporter({
245
259
  url: `${otlpEndpoint}/v1/traces`
246
260
  }),
@@ -260,7 +274,7 @@ function initTelemetry(config, serviceVersion = "0.0.1") {
260
274
  });
261
275
  sdk.start();
262
276
  process.on("SIGTERM", () => {
263
- sdk?.shutdown().then(() => console.log("Telemetry shut down")).catch((err) => console.error("Telemetry shutdown error", err));
277
+ Promise.all([sdk?.shutdown(), loggerProvider?.shutdown()]).then(() => console.log("Telemetry shut down")).catch((err) => console.error("Telemetry shutdown error", err));
264
278
  });
265
279
  return sdk;
266
280
  }
@@ -272,6 +286,7 @@ function shutdownTelemetry() {
272
286
  // src/lib/logger.ts
273
287
  import pino from "pino";
274
288
  import { trace } from "@opentelemetry/api";
289
+ import { logs as logs2, SeverityNumber as SeverityNumber2 } from "@opentelemetry/api-logs";
275
290
 
276
291
  // src/lib/context.ts
277
292
  import { AsyncLocalStorage } from "async_hooks";
@@ -318,6 +333,20 @@ function createContextFromMemo(memo) {
318
333
  // src/lib/logger.ts
319
334
  var NODE_ENV = process.env.NODE_ENV || "development";
320
335
  var SERVICE_NAME = process.env.OTEL_SERVICE_NAME || "loop";
336
+ var pinoLevelToOtelSeverity = {
337
+ 10: SeverityNumber2.TRACE,
338
+ // trace
339
+ 20: SeverityNumber2.DEBUG,
340
+ // debug
341
+ 30: SeverityNumber2.INFO,
342
+ // info
343
+ 40: SeverityNumber2.WARN,
344
+ // warn
345
+ 50: SeverityNumber2.ERROR,
346
+ // error
347
+ 60: SeverityNumber2.FATAL
348
+ // fatal
349
+ };
321
350
  var traceMixin = () => {
322
351
  const mixinData = {};
323
352
  const span = trace.getActiveSpan();
@@ -335,16 +364,74 @@ var traceMixin = () => {
335
364
  }
336
365
  return mixinData;
337
366
  };
338
- var logger = pino({
339
- level: NODE_ENV === "production" ? "info" : "debug",
340
- mixin: traceMixin,
341
- base: {
342
- service: SERVICE_NAME,
343
- env: NODE_ENV
367
+ function emitToOtel(logRecord) {
368
+ try {
369
+ const otelLogger = logs2.getLogger(SERVICE_NAME);
370
+ const span = trace.getActiveSpan();
371
+ const spanContext = span?.spanContext();
372
+ otelLogger.emit({
373
+ severityNumber: pinoLevelToOtelSeverity[logRecord.level] || SeverityNumber2.INFO,
374
+ severityText: pino.levels.labels[logRecord.level] || "INFO",
375
+ body: logRecord.msg,
376
+ attributes: {
377
+ ...logRecord,
378
+ // Remove fields that are part of the log record structure
379
+ msg: void 0,
380
+ level: void 0,
381
+ time: void 0
382
+ },
383
+ timestamp: logRecord.time ? new Date(logRecord.time).getTime() * 1e6 : Date.now() * 1e6,
384
+ // nanoseconds
385
+ ...spanContext && {
386
+ spanId: spanContext.spanId,
387
+ traceId: spanContext.traceId,
388
+ traceFlags: spanContext.traceFlags
389
+ }
390
+ });
391
+ } catch {
392
+ }
393
+ }
394
+ var otelHooks = {
395
+ logMethod(inputArgs, method, level) {
396
+ method.apply(this, inputArgs);
397
+ const [objOrMsg, msgOrUndefined] = inputArgs;
398
+ const logRecord = {
399
+ level,
400
+ time: (/* @__PURE__ */ new Date()).toISOString(),
401
+ service: SERVICE_NAME,
402
+ env: NODE_ENV
403
+ };
404
+ if (typeof objOrMsg === "object" && objOrMsg !== null) {
405
+ Object.assign(logRecord, objOrMsg);
406
+ logRecord.msg = msgOrUndefined || "";
407
+ } else {
408
+ logRecord.msg = objOrMsg;
409
+ }
410
+ Object.assign(logRecord, traceMixin());
411
+ emitToOtel(logRecord);
412
+ }
413
+ };
414
+ var logger = pino(
415
+ {
416
+ level: NODE_ENV === "production" ? "info" : "debug",
417
+ mixin: traceMixin,
418
+ base: {
419
+ service: SERVICE_NAME,
420
+ env: NODE_ENV
421
+ },
422
+ timestamp: pino.stdTimeFunctions.isoTime,
423
+ hooks: otelHooks,
424
+ // In development, use pino-pretty for console output
425
+ ...NODE_ENV !== "production" && {
426
+ transport: {
427
+ target: "pino-pretty",
428
+ options: { colorize: true }
429
+ }
430
+ }
344
431
  },
345
- timestamp: pino.stdTimeFunctions.isoTime,
346
- transport: NODE_ENV !== "production" ? { target: "pino-pretty", options: { colorize: true } } : void 0
347
- });
432
+ // In production, also write JSON to stdout (in addition to OTLP via hooks)
433
+ NODE_ENV === "production" ? pino.destination(1) : void 0
434
+ );
348
435
  function createActivityLogger(activityName, correlationId) {
349
436
  return logger.child({
350
437
  activity: activityName,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@payloops/observability",
3
- "version": "0.0.6",
3
+ "version": "0.0.8",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -27,9 +27,12 @@
27
27
  },
28
28
  "dependencies": {
29
29
  "@opentelemetry/api": "^1.9.0",
30
+ "@opentelemetry/api-logs": "^0.57.0",
30
31
  "@opentelemetry/auto-instrumentations-node": "^0.53.0",
32
+ "@opentelemetry/exporter-logs-otlp-http": "^0.57.0",
31
33
  "@opentelemetry/exporter-metrics-otlp-http": "^0.57.0",
32
34
  "@opentelemetry/exporter-trace-otlp-http": "^0.57.0",
35
+ "@opentelemetry/sdk-logs": "^0.57.0",
33
36
  "@opentelemetry/sdk-metrics": "^1.30.1",
34
37
  "@opentelemetry/sdk-node": "^0.57.0",
35
38
  "@opentelemetry/semantic-conventions": "^1.28.0",