@foam-ai/node 0.1.0-alpha.5 → 0.1.0-alpha.6

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/dist/exporters.js CHANGED
@@ -1,81 +1,158 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.FoamMetricExporter = exports.FoamLogExporter = exports.FoamTraceExporter = void 0;
4
+ const core_1 = require("@opentelemetry/core");
4
5
  const exporter_logs_otlp_http_1 = require("@opentelemetry/exporter-logs-otlp-http");
5
6
  const exporter_metrics_otlp_http_1 = require("@opentelemetry/exporter-metrics-otlp-http");
6
7
  const exporter_trace_otlp_http_1 = require("@opentelemetry/exporter-trace-otlp-http");
7
8
  const resources_1 = require("@opentelemetry/resources");
9
+ const before_send_js_1 = require("./before-send.js");
8
10
  const constants_js_1 = require("./constants.js");
9
11
  const endpoint_js_1 = require("./endpoint.js");
12
+ const redaction_js_1 = require("./redaction.js");
13
+ function redactionConfig(options) {
14
+ return options.redaction ?? (0, redaction_js_1.getActiveRedactionConfig)();
15
+ }
16
+ function copySpan(span, name, attributes, events, links, overlay) {
17
+ return {
18
+ name,
19
+ kind: span.kind,
20
+ spanContext: span.spanContext.bind(span),
21
+ parentSpanContext: span.parentSpanContext,
22
+ startTime: span.startTime,
23
+ endTime: span.endTime,
24
+ status: span.status,
25
+ attributes,
26
+ links,
27
+ events,
28
+ duration: span.duration,
29
+ ended: span.ended,
30
+ resource: overlay ? span.resource.merge(overlay) : span.resource,
31
+ instrumentationScope: span.instrumentationScope,
32
+ droppedAttributesCount: span.droppedAttributesCount,
33
+ droppedEventsCount: span.droppedEventsCount,
34
+ droppedLinksCount: span.droppedLinksCount,
35
+ };
36
+ }
10
37
  class FoamTraceExporter extends exporter_trace_otlp_http_1.OTLPTraceExporter {
11
38
  stamp;
12
- constructor(token, stamp) {
39
+ options;
40
+ constructor(token, stamp, options = {}) {
13
41
  super({
14
42
  url: `${endpoint_js_1.endpoint}${constants_js_1.FOAM_OTLP_TRACES_PATH}`,
15
43
  headers: { Authorization: `Bearer ${token}` },
16
44
  });
17
45
  this.stamp = stamp;
46
+ this.options = options;
18
47
  }
19
48
  export(spans, callback) {
20
- const { stamp } = this;
21
- if (!stamp) {
22
- super.export(spans, callback);
49
+ const config = redactionConfig(this.options);
50
+ const overlay = this.stamp ? (0, resources_1.resourceFromAttributes)(this.stamp) : undefined;
51
+ const output = [];
52
+ for (const span of spans) {
53
+ let name = span.name;
54
+ let sourceAttributes = span.attributes;
55
+ if (this.options.beforeSend) {
56
+ const result = (0, before_send_js_1.invokeBeforeSend)(this.options.beforeSend, {
57
+ type: "span",
58
+ name,
59
+ attributes: sourceAttributes,
60
+ });
61
+ if (result === null)
62
+ continue;
63
+ if (result?.type === "span") {
64
+ name = result.name;
65
+ sourceAttributes = result.attributes;
66
+ }
67
+ }
68
+ const attributes = (0, redaction_js_1.redactAttributesCopy)(sourceAttributes, config);
69
+ const events = (span.events ?? []).map((event) => event.attributes
70
+ ? {
71
+ ...event,
72
+ attributes: (0, redaction_js_1.redactAttributesCopy)(event.attributes, config),
73
+ }
74
+ : event);
75
+ const links = (span.links ?? []).map((link) => link.attributes
76
+ ? {
77
+ ...link,
78
+ attributes: (0, redaction_js_1.redactAttributesCopy)(link.attributes, config),
79
+ }
80
+ : link);
81
+ output.push(copySpan(span, name, attributes, events, links, overlay));
82
+ }
83
+ if (output.length === 0) {
84
+ callback({ code: core_1.ExportResultCode.SUCCESS });
23
85
  return;
24
86
  }
25
- const overlay = (0, resources_1.resourceFromAttributes)(stamp);
26
- const copies = spans.map((span) => ({
27
- name: span.name,
28
- kind: span.kind,
29
- spanContext: span.spanContext.bind(span),
30
- parentSpanContext: span.parentSpanContext,
31
- startTime: span.startTime,
32
- endTime: span.endTime,
33
- status: span.status,
34
- attributes: span.attributes,
35
- links: span.links,
36
- events: span.events,
37
- duration: span.duration,
38
- ended: span.ended,
39
- resource: span.resource.merge(overlay),
40
- instrumentationScope: span.instrumentationScope,
41
- droppedAttributesCount: span.droppedAttributesCount,
42
- droppedEventsCount: span.droppedEventsCount,
43
- droppedLinksCount: span.droppedLinksCount,
44
- }));
45
- super.export(copies, callback);
87
+ super.export(output, callback);
46
88
  }
47
89
  }
48
90
  exports.FoamTraceExporter = FoamTraceExporter;
91
+ function copyLog(record, body, severityText, attributes, overlay) {
92
+ return {
93
+ hrTime: record.hrTime,
94
+ hrTimeObserved: record.hrTimeObserved,
95
+ spanContext: record.spanContext,
96
+ severityText,
97
+ severityNumber: record.severityNumber,
98
+ body,
99
+ eventName: record.eventName,
100
+ resource: overlay ? record.resource.merge(overlay) : record.resource,
101
+ instrumentationScope: record.instrumentationScope,
102
+ attributes,
103
+ droppedAttributesCount: record.droppedAttributesCount,
104
+ };
105
+ }
106
+ function redactBody(body, config) {
107
+ if (body === undefined)
108
+ return undefined;
109
+ if (typeof body === "string")
110
+ return (0, redaction_js_1.redactString)(body, config);
111
+ return (0, redaction_js_1.redactDeep)(body, config);
112
+ }
49
113
  class FoamLogExporter extends exporter_logs_otlp_http_1.OTLPLogExporter {
50
114
  stamp;
51
- constructor(token, stamp) {
115
+ options;
116
+ constructor(token, stamp, options = {}) {
52
117
  super({
53
118
  url: `${endpoint_js_1.endpoint}${constants_js_1.FOAM_OTLP_LOGS_PATH}`,
54
119
  headers: { Authorization: `Bearer ${token}` },
55
120
  });
56
121
  this.stamp = stamp;
122
+ this.options = options;
57
123
  }
58
124
  export(records, callback) {
59
- const { stamp } = this;
60
- if (!stamp) {
61
- super.export(records, callback);
125
+ const config = redactionConfig(this.options);
126
+ const overlay = this.stamp ? (0, resources_1.resourceFromAttributes)(this.stamp) : undefined;
127
+ const output = [];
128
+ for (const record of records) {
129
+ let sourceBody = record.body;
130
+ let severityText = record.severityText;
131
+ let sourceAttributes = record.attributes;
132
+ if (this.options.beforeSend) {
133
+ const result = (0, before_send_js_1.invokeBeforeSend)(this.options.beforeSend, {
134
+ type: "log",
135
+ body: sourceBody,
136
+ severityText,
137
+ attributes: sourceAttributes,
138
+ });
139
+ if (result === null)
140
+ continue;
141
+ if (result?.type === "log") {
142
+ sourceBody = result.body;
143
+ severityText = result.severityText;
144
+ sourceAttributes = result.attributes;
145
+ }
146
+ }
147
+ const body = redactBody(sourceBody, config);
148
+ const attributes = (0, redaction_js_1.redactAttributesCopy)(sourceAttributes, config);
149
+ output.push(copyLog(record, body, severityText, attributes, overlay));
150
+ }
151
+ if (output.length === 0) {
152
+ callback({ code: core_1.ExportResultCode.SUCCESS });
62
153
  return;
63
154
  }
64
- const overlay = (0, resources_1.resourceFromAttributes)(stamp);
65
- const copies = records.map((record) => ({
66
- hrTime: record.hrTime,
67
- hrTimeObserved: record.hrTimeObserved,
68
- spanContext: record.spanContext,
69
- severityText: record.severityText,
70
- severityNumber: record.severityNumber,
71
- body: record.body,
72
- eventName: record.eventName,
73
- resource: record.resource.merge(overlay),
74
- instrumentationScope: record.instrumentationScope,
75
- attributes: record.attributes,
76
- droppedAttributesCount: record.droppedAttributesCount,
77
- }));
78
- super.export(copies, callback);
155
+ super.export(output, callback);
79
156
  }
80
157
  }
81
158
  exports.FoamLogExporter = FoamLogExporter;
@@ -89,14 +166,13 @@ class FoamMetricExporter extends exporter_metrics_otlp_http_1.OTLPMetricExporter
89
166
  this.stamp = stamp;
90
167
  }
91
168
  export(resourceMetrics, callback) {
92
- const { stamp } = this;
93
- if (!stamp) {
169
+ if (!this.stamp) {
94
170
  super.export(resourceMetrics, callback);
95
171
  return;
96
172
  }
97
173
  super.export({
98
174
  ...resourceMetrics,
99
- resource: resourceMetrics.resource.merge((0, resources_1.resourceFromAttributes)(stamp)),
175
+ resource: resourceMetrics.resource.merge((0, resources_1.resourceFromAttributes)(this.stamp)),
100
176
  }, callback);
101
177
  }
102
178
  }
package/dist/index.d.ts CHANGED
@@ -1,6 +1,9 @@
1
- export { init } from "./init.js";
1
+ export { init, type InitOptions } from "./init.js";
2
+ export type { BeforeSendEvent, BeforeSendHook, } from "./before-send.js";
3
+ export type { RedactOptions } from "./redaction.js";
2
4
  export { getState } from "./state.js";
3
- export { createFoamIngestLogRecordProcessor, createFoamIngestMetricReader, createFoamIngestSpanProcessor, } from "./ingest.js";
5
+ export { setEndpoint } from "./endpoint.js";
6
+ export { createFoamIngestLogRecordProcessor, createFoamIngestMetricReader, createFoamIngestSpanProcessor, type FoamIngestOptions, } from "./ingest.js";
4
7
  export { extractTraceContext, getBaggage, injectTraceContext, setBaggage, } from "./propagation.js";
5
8
  export { addUpDownCounter, incrementCounter, recordHistogram, setMetric, } from "./metrics.js";
6
9
  export { log, SeverityNumber } from "./logs.js";
package/dist/index.js CHANGED
@@ -1,10 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.recordException = exports.SeverityNumber = exports.log = exports.setMetric = exports.recordHistogram = exports.incrementCounter = exports.addUpDownCounter = exports.setBaggage = exports.injectTraceContext = exports.getBaggage = exports.extractTraceContext = exports.createFoamIngestSpanProcessor = exports.createFoamIngestMetricReader = exports.createFoamIngestLogRecordProcessor = exports.getState = exports.init = void 0;
3
+ exports.recordException = exports.SeverityNumber = exports.log = exports.setMetric = exports.recordHistogram = exports.incrementCounter = exports.addUpDownCounter = exports.setBaggage = exports.injectTraceContext = exports.getBaggage = exports.extractTraceContext = exports.createFoamIngestSpanProcessor = exports.createFoamIngestMetricReader = exports.createFoamIngestLogRecordProcessor = exports.setEndpoint = exports.getState = exports.init = void 0;
4
4
  var init_js_1 = require("./init.js");
5
5
  Object.defineProperty(exports, "init", { enumerable: true, get: function () { return init_js_1.init; } });
6
6
  var state_js_1 = require("./state.js");
7
7
  Object.defineProperty(exports, "getState", { enumerable: true, get: function () { return state_js_1.getState; } });
8
+ var endpoint_js_1 = require("./endpoint.js");
9
+ Object.defineProperty(exports, "setEndpoint", { enumerable: true, get: function () { return endpoint_js_1.setEndpoint; } });
8
10
  var ingest_js_1 = require("./ingest.js");
9
11
  Object.defineProperty(exports, "createFoamIngestLogRecordProcessor", { enumerable: true, get: function () { return ingest_js_1.createFoamIngestLogRecordProcessor; } });
10
12
  Object.defineProperty(exports, "createFoamIngestMetricReader", { enumerable: true, get: function () { return ingest_js_1.createFoamIngestMetricReader; } });
package/dist/ingest.d.ts CHANGED
@@ -1,6 +1,12 @@
1
1
  import { type LogRecordProcessor } from "@opentelemetry/sdk-logs";
2
2
  import { PeriodicExportingMetricReader } from "@opentelemetry/sdk-metrics";
3
3
  import { type SpanProcessor } from "@opentelemetry/sdk-trace-base";
4
- export declare function createFoamIngestSpanProcessor(name: string, environment: string, token: string): SpanProcessor;
5
- export declare function createFoamIngestLogRecordProcessor(name: string, environment: string, token: string): LogRecordProcessor;
4
+ import { type BeforeSendHook } from "./before-send.js";
5
+ import { type RedactOptions } from "./redaction.js";
6
+ export interface FoamIngestOptions {
7
+ readonly redact?: RedactOptions;
8
+ readonly beforeSend?: BeforeSendHook;
9
+ }
10
+ export declare function createFoamIngestSpanProcessor(name: string, environment: string, token: string, options?: FoamIngestOptions): SpanProcessor;
11
+ export declare function createFoamIngestLogRecordProcessor(name: string, environment: string, token: string, options?: FoamIngestOptions): LogRecordProcessor;
6
12
  export declare function createFoamIngestMetricReader(name: string, environment: string, token: string): PeriodicExportingMetricReader;
package/dist/ingest.js CHANGED
@@ -8,11 +8,19 @@ const sdk_logs_1 = require("@opentelemetry/sdk-logs");
8
8
  const sdk_metrics_1 = require("@opentelemetry/sdk-metrics");
9
9
  const sdk_trace_base_1 = require("@opentelemetry/sdk-trace-base");
10
10
  const api_logs_1 = require("@opentelemetry/api-logs");
11
+ const before_send_js_1 = require("./before-send.js");
11
12
  const constants_js_1 = require("./constants.js");
12
13
  const report_js_1 = require("./report.js");
13
14
  const exporters_js_1 = require("./exporters.js");
14
15
  const logs_js_1 = require("./logs.js");
16
+ const redaction_js_1 = require("./redaction.js");
15
17
  const state_js_1 = require("./state.js");
18
+ function prepareOptions(options) {
19
+ return {
20
+ redaction: (0, redaction_js_1.resolveRedactionConfig)(options.redact),
21
+ beforeSend: (0, before_send_js_1.resolveBeforeSend)(options.beforeSend),
22
+ };
23
+ }
16
24
  function prepareStamp(name, environment, token) {
17
25
  if (!name?.trim() || !environment?.trim() || !token) {
18
26
  throw new Error(`${constants_js_1.FOAM_IDENTIFIER_NAME}: name, environment, and token are required`);
@@ -21,7 +29,7 @@ function prepareStamp(name, environment, token) {
21
29
  stamp: {
22
30
  [semantic_conventions_1.ATTR_SERVICE_NAME]: name,
23
31
  [semantic_conventions_1.ATTR_DEPLOYMENT_ENVIRONMENT_NAME]: environment,
24
- [constants_js_1.FOAM_INGEST_HOST]: constants_js_1.FOAM_INGEST_HOST,
32
+ [constants_js_1.ATTR_FOAM_INGEST_HOST]: constants_js_1.FOAM_INGEST_HOST,
25
33
  },
26
34
  };
27
35
  }
@@ -36,18 +44,19 @@ function reportIngestSignal(signal, name, environment, token) {
36
44
  message: `ingest ${signal} processor attached`,
37
45
  });
38
46
  }
39
- function createFoamIngestSpanProcessor(name, environment, token) {
47
+ function createFoamIngestSpanProcessor(name, environment, token, options = {}) {
40
48
  const { stamp } = prepareStamp(name, environment, token);
41
- const processor = new sdk_trace_base_1.BatchSpanProcessor(new exporters_js_1.FoamTraceExporter(token, stamp));
49
+ const processor = new sdk_trace_base_1.BatchSpanProcessor(new exporters_js_1.FoamTraceExporter(token, stamp, prepareOptions(options)));
42
50
  reportIngestSignal(state_js_1.Signals.traces, name, environment, token);
43
51
  return processor;
44
52
  }
45
- function createFoamIngestLogRecordProcessor(name, environment, token) {
53
+ function createFoamIngestLogRecordProcessor(name, environment, token, options = {}) {
46
54
  const { stamp } = prepareStamp(name, environment, token);
55
+ const prepared = prepareOptions(options);
47
56
  const processor = new sdk_logs_1.BatchLogRecordProcessor({
48
- exporter: new exporters_js_1.FoamLogExporter(token, stamp),
57
+ exporter: new exporters_js_1.FoamLogExporter(token, stamp, prepared),
49
58
  });
50
- (0, logs_js_1.ensureFoamLoggerProvider)(name, environment, token, stamp);
59
+ (0, logs_js_1.ensureFoamLoggerProvider)(name, environment, token, stamp, prepared);
51
60
  reportIngestSignal(state_js_1.Signals.logs, name, environment, token);
52
61
  return processor;
53
62
  }
package/dist/init.d.ts CHANGED
@@ -3,6 +3,8 @@ import { type Instrumentation } from "@opentelemetry/instrumentation";
3
3
  import { type LogRecordProcessor } from "@opentelemetry/sdk-logs";
4
4
  import { type MetricReader } from "@opentelemetry/sdk-metrics";
5
5
  import { type SpanProcessor } from "@opentelemetry/sdk-trace-base";
6
+ import { type BeforeSendHook } from "./before-send.js";
7
+ import { type RedactOptions } from "./redaction.js";
6
8
  export interface InitOptions {
7
9
  name: string;
8
10
  environment: string;
@@ -12,6 +14,8 @@ export interface InitOptions {
12
14
  disableLogSending?: boolean;
13
15
  networkCapture?: "off" | "basic" | "advanced";
14
16
  enabled?: boolean;
17
+ redact?: RedactOptions;
18
+ beforeSend?: BeforeSendHook;
15
19
  additionalInstrumentations?: readonly Instrumentation[];
16
20
  additionalSpanProcessors?: readonly SpanProcessor[];
17
21
  additionalLogRecordProcessors?: readonly LogRecordProcessor[];
package/dist/init.js CHANGED
@@ -13,11 +13,15 @@ const sdk_trace_base_1 = require("@opentelemetry/sdk-trace-base");
13
13
  const report_js_1 = require("./report.js");
14
14
  const exporters_js_1 = require("./exporters.js");
15
15
  const instrumentations_js_1 = require("./instrumentations.js");
16
+ const before_send_js_1 = require("./before-send.js");
16
17
  const logs_js_1 = require("./logs.js");
18
+ const redaction_js_1 = require("./redaction.js");
17
19
  const state_js_1 = require("./state.js");
18
- // TODO(pcga11): conflicts, redact and beforeSend handling pending.
20
+ // TODO(pcga11): conflicts handling pending.
19
21
  function init(options) {
20
22
  const { sampleRate = 1, disableLogSending = false, networkCapture = "basic", enabled = true, } = options;
23
+ const redactionConfig = (0, redaction_js_1.resolveRedactionConfig)(options.redact);
24
+ const beforeSend = (0, before_send_js_1.resolveBeforeSend)(options.beforeSend);
21
25
  // pcga11: Set params before any early return so every state is reported, including failed or disabled attempts.
22
26
  (0, state_js_1.setParams)({ name: options.name, environment: options.environment });
23
27
  // pcga11: We keep this option because companies often want to turn the integration off in specific envs
@@ -54,11 +58,20 @@ function init(options) {
54
58
  });
55
59
  return;
56
60
  }
61
+ if (!Number.isFinite(sampleRate) || sampleRate < 0 || sampleRate > 1) {
62
+ (0, report_js_1.report)({
63
+ token: options.token,
64
+ severity: api_logs_1.SeverityNumber.ERROR,
65
+ message: 'Parameter sampleRate must be a finite number between 0 and 1',
66
+ });
67
+ return;
68
+ }
57
69
  // pcga11: Hoisted so the catch block can shut down providers whose batch/periodic
58
70
  // export timers started in their constructors, even when init fails midway.
59
71
  let tracerProvider;
60
72
  let meterProvider;
61
73
  try {
74
+ (0, redaction_js_1.setActiveRedactionConfig)(redactionConfig);
62
75
  const resource = (0, resource_js_1.createFoamResource)(options.name, options.environment, options.version, options.additionalResourceAttributes);
63
76
  // pcga11: The context manager is what makes the "current" context (active span, baggage)
64
77
  // follow execution across await/callback boundaries, via Node's AsyncLocalStorage. Without
@@ -83,7 +96,10 @@ function init(options) {
83
96
  root: new sdk_trace_base_1.TraceIdRatioBasedSampler(sampleRate),
84
97
  }),
85
98
  spanProcessors: [
86
- new sdk_trace_base_1.BatchSpanProcessor(new exporters_js_1.FoamTraceExporter(options.token)),
99
+ new sdk_trace_base_1.BatchSpanProcessor(new exporters_js_1.FoamTraceExporter(options.token, undefined, {
100
+ redaction: redactionConfig,
101
+ beforeSend,
102
+ })),
87
103
  ...(options.additionalSpanProcessors ?? []),
88
104
  ],
89
105
  });
@@ -100,7 +116,10 @@ function init(options) {
100
116
  resource,
101
117
  processors: [
102
118
  new sdk_logs_1.BatchLogRecordProcessor({
103
- exporter: new exporters_js_1.FoamLogExporter(options.token),
119
+ exporter: new exporters_js_1.FoamLogExporter(options.token, undefined, {
120
+ redaction: redactionConfig,
121
+ beforeSend,
122
+ }),
104
123
  }),
105
124
  ...(options.additionalLogRecordProcessors ?? []),
106
125
  ],
package/dist/logs.d.ts CHANGED
@@ -1,9 +1,10 @@
1
1
  import type { Attributes } from "@opentelemetry/api";
2
2
  import { SeverityNumber } from "@opentelemetry/api-logs";
3
3
  import { LoggerProvider } from "@opentelemetry/sdk-logs";
4
+ import { type FoamExporterOptions } from "./exporters.js";
4
5
  export declare function setFoamLoggerProvider(provider: LoggerProvider): void;
5
6
  export declare function getFoamLoggerProvider(): LoggerProvider | undefined;
6
7
  export declare function clearFoamLoggerProvider(): void;
7
- export declare function ensureFoamLoggerProvider(name: string, environment: string, token: string, stamp?: Attributes): LoggerProvider;
8
+ export declare function ensureFoamLoggerProvider(name: string, environment: string, token: string, stamp?: Attributes, options?: FoamExporterOptions): LoggerProvider;
8
9
  export declare const log: (body: string, severityNumber?: SeverityNumber, attributes?: Attributes) => void;
9
10
  export { SeverityNumber };
package/dist/logs.js CHANGED
@@ -25,7 +25,7 @@ function getFoamLoggerProvider() {
25
25
  function clearFoamLoggerProvider() {
26
26
  foamLoggerProvider = undefined;
27
27
  }
28
- function ensureFoamLoggerProvider(name, environment, token, stamp) {
28
+ function ensureFoamLoggerProvider(name, environment, token, stamp, options) {
29
29
  const existing = foamLoggerProvider;
30
30
  if (existing) {
31
31
  return existing;
@@ -34,7 +34,7 @@ function ensureFoamLoggerProvider(name, environment, token, stamp) {
34
34
  resource: (0, resource_js_1.createFoamResource)(name, environment),
35
35
  processors: [
36
36
  new sdk_logs_1.BatchLogRecordProcessor({
37
- exporter: new exporters_js_1.FoamLogExporter(token, stamp),
37
+ exporter: new exporters_js_1.FoamLogExporter(token, stamp, options),
38
38
  }),
39
39
  ],
40
40
  });
@@ -1,4 +1,37 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
2
35
  Object.defineProperty(exports, "__esModule", { value: true });
3
36
  exports.BodyCollector = void 0;
4
37
  exports.setHeaderAttributes = setHeaderAttributes;
@@ -6,7 +39,7 @@ const api_1 = require("@opentelemetry/api");
6
39
  const api_logs_1 = require("@opentelemetry/api-logs");
7
40
  const semantic_conventions_1 = require("@opentelemetry/semantic-conventions");
8
41
  const node_crypto_1 = require("node:crypto");
9
- const node_zlib_1 = require("node:zlib");
42
+ const zlib = __importStar(require("node:zlib"));
10
43
  const constants_js_1 = require("../constants.js");
11
44
  const utils_js_1 = require("../utils.js");
12
45
  const redact_js_1 = require("./redact.js");
@@ -78,31 +111,45 @@ function bodyByteLength(value, encoding) {
78
111
  function decodeBody(bytes, contentEncoding) {
79
112
  if (contentEncoding === undefined)
80
113
  return bytes;
81
- const encoding = headerValues(contentEncoding)[0]?.toLowerCase().trim();
82
- if (!encoding || encoding === "identity")
114
+ const encodings = headerValues(contentEncoding)
115
+ .flatMap((value) => value.split(","))
116
+ .map((value) => value.trim().toLowerCase())
117
+ .filter((value) => value && value !== "identity");
118
+ if (encodings.length === 0)
83
119
  return bytes;
84
- // maxOutputLength turns a decompression bomb into a caught throw instead
85
- // of an unrecoverable synchronous OOM allocation.
86
120
  const limits = { maxOutputLength: constants_js_1.NETWORK_BODY_MAX_DECODE_BYTES };
121
+ let decoded = bytes;
87
122
  try {
88
- if (encoding === "gzip" || encoding === "x-gzip") {
89
- return (0, node_zlib_1.gunzipSync)(bytes, limits);
90
- }
91
- if (encoding === "deflate") {
92
- try {
93
- return (0, node_zlib_1.inflateSync)(bytes, limits);
123
+ for (const encoding of encodings.reverse()) {
124
+ if (encoding === "gzip" || encoding === "x-gzip") {
125
+ decoded = zlib.gunzipSync(decoded, limits);
126
+ }
127
+ else if (encoding === "deflate") {
128
+ try {
129
+ decoded = zlib.inflateSync(decoded, limits);
130
+ }
131
+ catch {
132
+ decoded = zlib.unzipSync(decoded, limits);
133
+ }
94
134
  }
95
- catch {
96
- return (0, node_zlib_1.unzipSync)(bytes, limits);
135
+ else if (encoding === "br") {
136
+ decoded = zlib.brotliDecompressSync(decoded, limits);
137
+ }
138
+ else if (encoding === "zstd") {
139
+ const decompress = zlib.zstdDecompressSync;
140
+ if (!decompress)
141
+ return undefined;
142
+ decoded = decompress(decoded, limits);
143
+ }
144
+ else {
145
+ return undefined;
97
146
  }
98
147
  }
99
- if (encoding === "br")
100
- return (0, node_zlib_1.brotliDecompressSync)(bytes, limits);
148
+ return decoded;
101
149
  }
102
150
  catch {
103
151
  return undefined;
104
152
  }
105
- return undefined;
106
153
  }
107
154
  function textEncoding(contentType) {
108
155
  const raw = headerValues(contentType)[0];
@@ -113,20 +160,18 @@ function textEncoding(contentType) {
113
160
  const charset = parts
114
161
  .find((part) => part.toLowerCase().startsWith("charset="))
115
162
  ?.slice("charset=".length)
163
+ .trim()
164
+ .replace(/^["']|["']$/g, "")
116
165
  .toLowerCase();
117
- const isText = media.startsWith("text/") ||
118
- media.endsWith("/json") ||
119
- media.endsWith("+json") ||
120
- media.endsWith("/xml") ||
121
- media.endsWith("+xml") ||
122
- media.endsWith("/yaml") ||
123
- media.endsWith("+yaml") ||
124
- media === "application/x-www-form-urlencoded" ||
125
- Boolean(charset);
166
+ const isText = (0, redact_js_1.isTextualBodyMediaType)(media) || Boolean(charset);
126
167
  if (!isText)
127
168
  return undefined;
128
169
  if (charset && Buffer.isEncoding(charset))
129
170
  return charset;
171
+ if (charset === "iso-8859-1" || charset === "windows-1252")
172
+ return "latin1";
173
+ if (media.startsWith("multipart/"))
174
+ return "latin1";
130
175
  return "utf8";
131
176
  }
132
177
  // Last index such that `buf.subarray(0, end)` is complete UTF-8, capped at `max`.
@@ -283,6 +328,7 @@ class BodyCollector {
283
328
  // the span; binary uses the same `http.*.body.content` name on the event.
284
329
  let binaryContent;
285
330
  let chunkSource = wire;
331
+ let chunkSourceIsDecodedText = false;
286
332
  if (wire !== undefined) {
287
333
  const decoded = decodeBody(wire, headers["content-encoding"]);
288
334
  if (decoded === undefined) {
@@ -298,6 +344,7 @@ class BodyCollector {
298
344
  // redacted, so the (possibly compressed) wire bytes are replaced
299
345
  // with the redacted text.
300
346
  chunkSource = Buffer.from(redacted.text, encoding);
347
+ chunkSourceIsDecodedText = true;
301
348
  }
302
349
  }
303
350
  else {
@@ -319,10 +366,18 @@ class BodyCollector {
319
366
  [constants_js_1.ATTR_FOAM_HTTP_BODY_TRUNCATED]: this.truncated,
320
367
  [constants_js_1.ATTR_FOAM_HTTP_BODY_OUTCOME]: outcome,
321
368
  [this.attr.size]: this.observedBytes,
322
- [constants_js_1.ATTR_FOAM_HTTP_BODY_CAPTURED_BYTES]: this.capturedBytes,
369
+ [constants_js_1.ATTR_FOAM_HTTP_BODY_CAPTURED_BYTES]: chunkSourceIsDecodedText
370
+ ? (chunkSource?.byteLength ?? 0)
371
+ : this.capturedBytes,
323
372
  [constants_js_1.ATTR_FOAM_HTTP_BODY_CHUNK_COUNT]: chunks.length,
324
373
  };
325
374
  for (const name of ["content-type", "content-encoding"]) {
375
+ if (name === "content-encoding" &&
376
+ chunkSourceIsDecodedText &&
377
+ headers[name] !== undefined) {
378
+ common[this.attr.header(name)] = ["identity"];
379
+ continue;
380
+ }
326
381
  if (headers[name] !== undefined) {
327
382
  common[this.attr.header(name)] = headerValues(headers[name]);
328
383
  }
@@ -3,4 +3,7 @@ export interface RedactionResult {
3
3
  readonly changed: boolean;
4
4
  }
5
5
  export declare function isSensitiveBodyKey(key: string): boolean;
6
+ export declare function isNdjsonMediaType(media: string): boolean;
7
+ export declare function isJsonSequenceMediaType(media: string): boolean;
8
+ export declare function isTextualBodyMediaType(media: string): boolean;
6
9
  export declare function redactBodyText(text: string, contentType: unknown): RedactionResult;