@ryanzeng/nest-observe 0.1.4 → 0.1.5

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
@@ -1,4 +1,4 @@
1
- # @ryanzeng/nest-observe
1
+ ![header](https://capsule-render.vercel.app/api?type=waving&color=gradient&height=220&section=header&text=nest-observe&fontSize=48&fontColor=ffffff&animation=fadeIn)
2
2
 
3
3
  面向 NestJS 的零配置、Vendor Neutral Observability SDK。基于 OpenTelemetry,通过标准 OTLP 同时发送 Traces、Metrics 和 Logs,可连接 OpenObserve、Grafana、Jaeger、Datadog、Elastic 等兼容后端。
4
4
 
@@ -166,8 +166,10 @@ Traces:
166
166
  Logs:
167
167
 
168
168
  - 默认 Nest `Logger` 的 `verbose/debug/log/warn/error/fatal`
169
+ - 遵循 Nest `ConsoleLogger` 的重载解析语义,保留 `error(message, stack?, context?)`、附加消息和结构化参数
170
+ - `Error` 或显式 stack 会映射为 OTLP `exception.type`、`exception.message`、`exception.stacktrace` 属性
169
171
  - 尊重 Nest 11+ 的实例级日志级别过滤(`logLevels` / `setLogLevels` / `Logger.overrideLogger`),上传内容与控制台输出一致
170
- - 对象和数组消息会在脱敏后保持结构化 body,由观测后端或查询侧决定如何展开、展示或序列化
172
+ - 对象和数组消息会在脱敏后保持结构化 body;附加的结构化参数会展开为 OTLP attributes,同时保持首个 message 为 body
171
173
  - OTLP severity、`nestjs.context`、`trace_id`、`span_id`
172
174
  - `service.name`、`service.version`、`deployment.environment.name`
173
175
 
package/dist/index.d.mts CHANGED
@@ -1,10 +1,10 @@
1
1
  import { LogRecordExporter, LoggerProvider } from '@opentelemetry/sdk-logs';
2
2
  import { MetricReader, MeterProvider } from '@opentelemetry/sdk-metrics';
3
3
  import { SpanExporter, SpanProcessor, ReadableSpan } from '@opentelemetry/sdk-trace-base';
4
- import { Attributes, Meter, Tracer, Span, Context } from '@opentelemetry/api';
5
- import { Logger } from '@opentelemetry/api-logs';
4
+ import { LogAttributes, Logger } from '@opentelemetry/api-logs';
6
5
  import { InstrumentationConfig, InstrumentationNodeModuleDefinition } from '@opentelemetry/instrumentation';
7
6
  import { NestInstrumentation } from '@opentelemetry/instrumentation-nestjs-core';
7
+ import { Meter, Tracer, Span, Context } from '@opentelemetry/api';
8
8
  import { IncomingMessage, ServerResponse } from 'node:http';
9
9
  import { NestInterceptor, ExecutionContext, CallHandler, DynamicModule } from '@nestjs/common';
10
10
  import { Observable } from 'rxjs';
@@ -109,7 +109,7 @@ declare function Trace(nameOrOptions?: string | TraceOptions): TraceDecorator;
109
109
  interface StructuredLogRecord {
110
110
  body: unknown;
111
111
  severityText: 'TRACE' | 'DEBUG' | 'INFO' | 'WARN' | 'ERROR' | 'FATAL';
112
- attributes: Attributes;
112
+ attributes: LogAttributes;
113
113
  timestamp?: number;
114
114
  }
115
115
  interface StructuredLogEmitter {
package/dist/index.d.ts CHANGED
@@ -1,10 +1,10 @@
1
1
  import { LogRecordExporter, LoggerProvider } from '@opentelemetry/sdk-logs';
2
2
  import { MetricReader, MeterProvider } from '@opentelemetry/sdk-metrics';
3
3
  import { SpanExporter, SpanProcessor, ReadableSpan } from '@opentelemetry/sdk-trace-base';
4
- import { Attributes, Meter, Tracer, Span, Context } from '@opentelemetry/api';
5
- import { Logger } from '@opentelemetry/api-logs';
4
+ import { LogAttributes, Logger } from '@opentelemetry/api-logs';
6
5
  import { InstrumentationConfig, InstrumentationNodeModuleDefinition } from '@opentelemetry/instrumentation';
7
6
  import { NestInstrumentation } from '@opentelemetry/instrumentation-nestjs-core';
7
+ import { Meter, Tracer, Span, Context } from '@opentelemetry/api';
8
8
  import { IncomingMessage, ServerResponse } from 'node:http';
9
9
  import { NestInterceptor, ExecutionContext, CallHandler, DynamicModule } from '@nestjs/common';
10
10
  import { Observable } from 'rxjs';
@@ -109,7 +109,7 @@ declare function Trace(nameOrOptions?: string | TraceOptions): TraceDecorator;
109
109
  interface StructuredLogRecord {
110
110
  body: unknown;
111
111
  severityText: 'TRACE' | 'DEBUG' | 'INFO' | 'WARN' | 'ERROR' | 'FATAL';
112
- attributes: Attributes;
112
+ attributes: LogAttributes;
113
113
  timestamp?: number;
114
114
  }
115
115
  interface StructuredLogEmitter {
package/dist/index.js CHANGED
@@ -347,6 +347,71 @@ function bodyValue(message) {
347
347
  return redactText(message.stack ?? `${message.name}: ${message.message}`);
348
348
  return redact(message);
349
349
  }
350
+ function isPlainObject(value) {
351
+ if (value === null || typeof value !== "object") return false;
352
+ const prototype = Object.getPrototypeOf(value);
353
+ if (prototype === null) return true;
354
+ const constructor = Object.prototype.hasOwnProperty.call(prototype, "constructor") ? prototype.constructor : void 0;
355
+ return typeof constructor === "function" && constructor instanceof constructor && Function.prototype.toString.call(constructor) === Function.prototype.toString.call(Object);
356
+ }
357
+ function isStackFormat(value) {
358
+ return typeof value === "string" && /^(.)+\n\s+at .+:\d+:\d+/.test(value);
359
+ }
360
+ function fallbackMessages(logger, values) {
361
+ if (values.length <= 1) return { messages: values, context: logger.context };
362
+ const lastValue = values.at(-1);
363
+ const contextName = typeof lastValue === "string" ? lastValue : logger.context;
364
+ const remaining = typeof lastValue === "string" ? values.slice(0, -1) : values;
365
+ if (logger.options?.structuredParams === false) {
366
+ return { messages: remaining, context: contextName };
367
+ }
368
+ const messages = [remaining[0]];
369
+ const structuredParams = [];
370
+ for (const value of remaining.slice(1)) {
371
+ if (isPlainObject(value)) structuredParams.push(value);
372
+ else messages.push(value);
373
+ }
374
+ const params = structuredParams.length > 0 ? Object.assign({}, ...structuredParams) : void 0;
375
+ return { messages, context: contextName, ...params ? { params } : {} };
376
+ }
377
+ function fallbackError(logger, values) {
378
+ if (values.length === 2) {
379
+ if (isStackFormat(values[1])) {
380
+ return { messages: [values[0]], stack: values[1], context: logger.context };
381
+ }
382
+ return fallbackMessages(logger, values);
383
+ }
384
+ const trailingValue = values.at(-1);
385
+ if (isStackFormat(trailingValue)) {
386
+ return { ...fallbackMessages(logger, values.slice(0, -1)), stack: trailingValue };
387
+ }
388
+ const parsed = fallbackMessages(logger, values);
389
+ if (parsed.messages.length <= 1) return parsed;
390
+ const possibleStack = parsed.messages.at(-1);
391
+ if (typeof possibleStack !== "string" && possibleStack !== void 0) return parsed;
392
+ return {
393
+ ...parsed,
394
+ messages: parsed.messages.slice(0, -1),
395
+ ...possibleStack === void 0 ? {} : { stack: possibleStack }
396
+ };
397
+ }
398
+ function parseLogCall(logger, method, message, args) {
399
+ const values = [message, ...args];
400
+ const nestParser = method === "error" ? logger.getContextAndStackAndMessagesToPrint : logger.getContextAndMessagesToPrint;
401
+ if (typeof nestParser === "function") return nestParser.call(logger, values);
402
+ return method === "error" ? fallbackError(logger, values) : fallbackMessages(logger, values);
403
+ }
404
+ function exceptionAttributes(message, stack) {
405
+ const error = message instanceof Error ? message : void 0;
406
+ const stacktrace = stack ?? error?.stack;
407
+ return {
408
+ ...error ? {
409
+ "exception.type": error.name,
410
+ "exception.message": redactText(error.message)
411
+ } : {},
412
+ ...stacktrace ? { "exception.stacktrace": redactText(stacktrace) } : {}
413
+ };
414
+ }
350
415
  var NestLoggerInstrumentation = class {
351
416
  constructor(emitter, resourceAttributes = {}) {
352
417
  this.emitter = emitter;
@@ -373,17 +438,25 @@ var NestLoggerInstrumentation = class {
373
438
  return original.call(this, message, ...args);
374
439
  }
375
440
  const spanContext = import_api3.trace.getActiveSpan()?.spanContext();
376
- const contextName = this.context ?? (typeof args.at(-1) === "string" ? String(args.at(-1)) : void 0);
377
- const attributes = { ...resourceAttributes };
378
- if (contextName) attributes["nestjs.context"] = contextName;
379
- if (spanContext?.traceId) attributes.trace_id = spanContext.traceId;
380
- if (spanContext?.spanId) attributes.span_id = spanContext.spanId;
381
- emitter.emit({
382
- body: bodyValue(message),
383
- severityText: SEVERITY[method],
384
- attributes,
385
- timestamp: Date.now()
386
- });
441
+ const parsed = parseLogCall(this, method, message, args);
442
+ const timestamp = Date.now();
443
+ for (const parsedMessage of parsed.messages) {
444
+ const params = parsed.params ? redact(parsed.params) : {};
445
+ const attributes = {
446
+ ...params,
447
+ ...resourceAttributes,
448
+ ...exceptionAttributes(parsedMessage, parsed.stack)
449
+ };
450
+ if (parsed.context) attributes["nestjs.context"] = parsed.context;
451
+ if (spanContext?.traceId) attributes.trace_id = spanContext.traceId;
452
+ if (spanContext?.spanId) attributes.span_id = spanContext.spanId;
453
+ emitter.emit({
454
+ body: bodyValue(parsedMessage),
455
+ severityText: SEVERITY[method],
456
+ attributes,
457
+ timestamp
458
+ });
459
+ }
387
460
  } catch {
388
461
  }
389
462
  return original.call(this, message, ...args);