@veryfront/ext-observability-opentelemetry 0.1.1028 → 0.1.1030

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
@@ -2,7 +2,7 @@
2
2
 
3
3
  > **Category:** Observability | **Contracts:** `TracingExporter`, `NodeTelemetryProvider` | **Optional**
4
4
 
5
- Provides distributed tracing, OTLP metrics export, the OpenTelemetry metrics API bridge, and Node telemetry bootstrap for Veryfront via the [OpenTelemetry JS SDK](https://github.com/open-telemetry/opentelemetry-js). Exports trace spans and metrics over OTLP/HTTP to any OpenTelemetry-compatible collector.
5
+ Provides distributed tracing, OTLP log export, OTLP metrics export, the OpenTelemetry metrics API bridge, and Node telemetry bootstrap for Veryfront via the [OpenTelemetry JS SDK](https://github.com/open-telemetry/opentelemetry-js). Exports trace spans, log records, and metrics over OTLP/HTTP to any OpenTelemetry-compatible collector.
6
6
 
7
7
  ## Installation
8
8
 
@@ -20,18 +20,22 @@ export default defineConfig({
20
20
 
21
21
  The extension reads the standard OpenTelemetry env vars at setup time:
22
22
 
23
- | Variable | Required | Description |
24
- | ------------------------------------- | ---------------- | ----------------------------------------------------------------- |
25
- | Variable | Required | Description |
26
- | ------------------------------------ | ---------------- | ----------------------------------------------------------------- |
27
- | `OTEL_EXPORTER_OTLP_ENDPOINT` | Yes (for export) | Base collector URL, e.g. `http://localhost:4318` |
28
- | `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT` | No | Trace-specific OTLP HTTP URL |
29
- | `OTEL_EXPORTER_OTLP_METRICS_ENDPOINT` | No | Metric-specific OTLP HTTP URL |
30
- | `OTEL_EXPORTER_OTLP_HEADERS` | No | Comma-separated `key=value` pairs (commonly used for auth tokens) |
31
- | `OTEL_SERVICE_NAME` | No | Service name attached to telemetry |
32
- | `OTEL_TRACES_ENABLED` | No | Set to `true` to enable trace export |
33
- | `OTEL_METRICS_ENABLED` | No | Set to `true` to enable metric export |
34
- | `OTEL_METRIC_EXPORT_INTERVAL` | No | Metric export interval in milliseconds |
23
+ | Variable | Required | Description |
24
+ | --------------------------------------------------------- | ---------------- | ----------------------------------------------------------------- |
25
+ | `OTEL_EXPORTER_OTLP_ENDPOINT` | Yes (for export) | Base collector URL, e.g. `http://localhost:4318` |
26
+ | `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT` | No | Trace-specific OTLP HTTP URL |
27
+ | `OTEL_EXPORTER_OTLP_METRICS_ENDPOINT` | No | Metric-specific OTLP HTTP URL |
28
+ | `OTEL_EXPORTER_OTLP_LOGS_ENDPOINT` | No | Log-specific OTLP HTTP URL |
29
+ | `OTEL_EXPORTER_OTLP_HEADERS` | No | Comma-separated `key=value` pairs (commonly used for auth tokens) |
30
+ | `OTEL_EXPORTER_OTLP_TRACES_HEADERS` | No | Trace-specific headers merged over global headers |
31
+ | `OTEL_EXPORTER_OTLP_METRICS_HEADERS` | No | Metric-specific headers merged over global headers |
32
+ | `OTEL_EXPORTER_OTLP_LOGS_HEADERS` | No | Log-specific headers merged over global headers |
33
+ | `OTEL_SERVICE_NAME` | No | Service name attached to telemetry |
34
+ | `OTEL_TRACES_ENABLED` / `OTEL_TRACES_EXPORTER=otlp` | No | Enables trace export |
35
+ | `OTEL_METRICS_ENABLED` / `OTEL_METRICS_EXPORTER=otlp` | No | Enables metric export |
36
+ | `OTEL_LOGS_ENABLED` / `OTEL_LOGS_EXPORTER=otlp` | No | Enables log export |
37
+ | `OTEL_METRIC_EXPORT_INTERVAL` | No | Metric export interval in milliseconds |
38
+ | `OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE=delta` | No | Metrics temporality. Dedicated service defaults to `delta`. |
35
39
 
36
40
  Configuration is read from process `OTEL_*` environment variables. In shared Veryfront runtimes these are platform-owned host env vars. The extension does not accept `ctx.config.otel` exporter endpoint, header, service name, or enable-flag overrides because project config is tenant controlled in shared runtimes.
37
41
 
@@ -47,6 +51,14 @@ Exporter configuration is process-level. Dedicated runtimes can use project-spec
47
51
 
48
52
  Set `OTEL_METRICS_ENABLED=true` to export framework metrics through OTLP HTTP. The extension resolves `OTEL_EXPORTER_OTLP_METRICS_ENDPOINT` first, then `OTEL_EXPORTER_OTLP_ENDPOINT`. A base OTLP endpoint receives `/v1/metrics`.
49
53
 
54
+ Dedicated Node agent services create a startup counter named `veryfront.agent.telemetry.startups` when metrics export is enabled. The dedicated service defaults metric temporality to `delta`, which matches Datadog's OTLP metrics intake requirement.
55
+
56
+ ## Logs
57
+
58
+ Set `OTEL_LOGS_ENABLED=true` or `OTEL_LOGS_EXPORTER=otlp` to export structured Veryfront agent logs through OTLP HTTP. The extension resolves `OTEL_EXPORTER_OTLP_LOGS_ENDPOINT` first, then `OTEL_EXPORTER_OTLP_ENDPOINT`. A base OTLP endpoint receives `/v1/logs`.
59
+
60
+ Dedicated Node agent services bridge Veryfront's structured logger into OpenTelemetry logs after telemetry initialization. Log records include the active `trace_id` and `span_id` when available, so Datadog can correlate logs with traces.
61
+
50
62
  In shared Veryfront runtimes, these variables are platform-owned host env vars. Project env overlays must not control the shared runtime metrics exporter. Use a dedicated runtime for project-owned collector endpoints or credentials.
51
63
 
52
64
  ## Provided contracts
package/esm/index.d.ts CHANGED
@@ -19,17 +19,24 @@ export interface ResolvedOtlpExtensionConfig {
19
19
  serviceName: string;
20
20
  serviceVersion: string;
21
21
  headers: Record<string, string>;
22
+ tracesHeaders: Record<string, string>;
23
+ metricsHeaders: Record<string, string>;
24
+ logsHeaders: Record<string, string>;
22
25
  tracesEnabled: boolean;
23
26
  metricsEnabled: boolean;
27
+ logsEnabled: boolean;
24
28
  tracesUrl: string | undefined;
25
29
  metricsUrl: string | undefined;
30
+ logsUrl: string | undefined;
26
31
  metricsExportIntervalMillis: number;
32
+ metricsTemporalityPreference: "delta" | "cumulative" | "lowmemory";
27
33
  }
28
- export declare function resolveOtlpSignalUrl(endpoint: string | undefined, signal: "traces" | "metrics"): string | undefined;
34
+ export declare function resolveOtlpSignalUrl(endpoint: string | undefined, signal: "traces" | "metrics" | "logs"): string | undefined;
29
35
  export declare function resolveOtlpExtensionConfig(read?: EnvReader): ResolvedOtlpExtensionConfig;
30
36
  declare class OtlpTracingExporter implements TracingExporter {
31
37
  private sdkProvider;
32
38
  private meterProvider;
39
+ private logProvider;
33
40
  private metricsApi;
34
41
  private traceApi;
35
42
  start(_ctxConfig: Record<string, unknown>): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,KAAK,EACV,8BAA8B,EAC9B,qBAAqB,EACrB,QAAQ,EACR,eAAe,EAChB,MAAM,oCAAoC,CAAC;AAE5C;;;GAGG;AACH,UAAU,kBAAkB;IAC1B,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;CACpD;AAsBD,KAAK,UAAU,GAAG;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;CAAE,CAAC;AACpF,KAAK,QAAQ,GAAG;IAAE,aAAa,IAAI,OAAO,CAAC;IAAC,OAAO,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAA;CAAE,CAAC;AAuG7E,KAAK,SAAS,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;AAEtD,MAAM,WAAW,2BAA2B;IAC1C,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,aAAa,EAAE,OAAO,CAAC;IACvB,cAAc,EAAE,OAAO,CAAC;IACxB,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,2BAA2B,EAAE,MAAM,CAAC;CACrC;AAgCD,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,MAAM,EAAE,QAAQ,GAAG,SAAS,GAC3B,MAAM,GAAG,SAAS,CAKpB;AAED,wBAAgB,0BAA0B,CACxC,IAAI,GAAE,SAAmB,GACxB,2BAA2B,CAqB7B;AAED,cAAM,mBAAoB,YAAW,eAAe;IAClD,OAAO,CAAC,WAAW,CAAkC;IACrD,OAAO,CAAC,aAAa,CAAiC;IACtD,OAAO,CAAC,UAAU,CAA2B;IAC7C,OAAO,CAAC,QAAQ,CAAyB;IAEnC,KAAK,CAAC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IA2EzD,MAAM,CAAC,MAAM,EAAE,QAAQ,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAKzC,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAkB/B,WAAW,IAAI,kBAAkB;IAKjC,aAAa,IAAI,UAAU,GAAG,IAAI;IAIlC,WAAW,IAAI,QAAQ,GAAG,IAAI;CAG/B;AAED,cAAM,kCAAmC,YAAW,qBAAqB;IACvE,OAAO,CAAC,GAAG,CAA8C;IAEnD,UAAU,CAAC,OAAO,EAAE,8BAA8B,GAAG,OAAO,CAAC,OAAO,CAAC;IA6CrE,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;CAQhC;AAED;;;;;GAKG;AACH,QAAA,MAAM,gBAAgB,EAAE,gBAqCvB,CAAC;AAEF,eAAe,gBAAgB,CAAC;AAChC,OAAO,EAAE,kCAAkC,EAAE,mBAAmB,EAAE,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,KAAK,EACV,8BAA8B,EAE9B,qBAAqB,EACrB,QAAQ,EACR,eAAe,EAChB,MAAM,oCAAoC,CAAC;AAE5C;;;GAGG;AACH,UAAU,kBAAkB;IAC1B,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;CACpD;AA4BD,KAAK,UAAU,GAAG;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;CAAE,CAAC;AACpF,KAAK,QAAQ,GAAG;IAAE,aAAa,IAAI,OAAO,CAAC;IAAC,OAAO,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAA;CAAE,CAAC;AAgH7E,KAAK,SAAS,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;AAEtD,MAAM,WAAW,2BAA2B;IAC1C,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACvC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACpC,aAAa,EAAE,OAAO,CAAC;IACvB,cAAc,EAAE,OAAO,CAAC;IACxB,WAAW,EAAE,OAAO,CAAC;IACrB,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,2BAA2B,EAAE,MAAM,CAAC;IACpC,4BAA4B,EAAE,OAAO,GAAG,YAAY,GAAG,WAAW,CAAC;CACpE;AAoGD,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,MAAM,EAAE,QAAQ,GAAG,SAAS,GAAG,MAAM,GACpC,MAAM,GAAG,SAAS,CAKpB;AAqCD,wBAAgB,0BAA0B,CACxC,IAAI,GAAE,SAAmB,GACxB,2BAA2B,CA8C7B;AAED,cAAM,mBAAoB,YAAW,eAAe;IAClD,OAAO,CAAC,WAAW,CAAkC;IACrD,OAAO,CAAC,aAAa,CAAiC;IACtD,OAAO,CAAC,WAAW,CAAkC;IACrD,OAAO,CAAC,UAAU,CAA2B;IAC7C,OAAO,CAAC,QAAQ,CAAyB;IAEnC,KAAK,CAAC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IA4GzD,MAAM,CAAC,MAAM,EAAE,QAAQ,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAKzC,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IA0B/B,WAAW,IAAI,kBAAkB;IAKjC,aAAa,IAAI,UAAU,GAAG,IAAI;IAIlC,WAAW,IAAI,QAAQ,GAAG,IAAI;CAG/B;AAED,cAAM,kCAAmC,YAAW,qBAAqB;IACvE,OAAO,CAAC,GAAG,CAA8C;IAEnD,UAAU,CAAC,OAAO,EAAE,8BAA8B,GAAG,OAAO,CAAC,OAAO,CAAC;IA4HrE,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;CAQhC;AAED;;;;;GAKG;AACH,QAAA,MAAM,gBAAgB,EAAE,gBA8CvB,CAAC;AAEF,eAAe,gBAAgB,CAAC;AAChC,OAAO,EAAE,kCAAkC,EAAE,mBAAmB,EAAE,CAAC"}
package/esm/index.js CHANGED
@@ -58,12 +58,15 @@ const NOOP_TRACER_PROVIDER = {
58
58
  };
59
59
  async function loadOpenTelemetryRuntime() {
60
60
  try {
61
- const [api, autoInstrumentations, core, contextAsyncHooks, sdkNode, metricsExporter, sdkMetrics, sdkTraceBase, traceExporter, resources, semanticConventions,] = await Promise.all([
61
+ const [api, autoInstrumentations, apiLogs, core, contextAsyncHooks, logsExporter, sdkNode, sdkLogs, metricsExporter, sdkMetrics, sdkTraceBase, traceExporter, resources, semanticConventions,] = await Promise.all([
62
62
  import("@opentelemetry/api"),
63
63
  import("@opentelemetry/auto-instrumentations-node"),
64
+ import("@opentelemetry/api-logs"),
64
65
  import("@opentelemetry/core"),
65
66
  import("@opentelemetry/context-async-hooks"),
67
+ import("@opentelemetry/exporter-logs-otlp-http"),
66
68
  import("@opentelemetry/sdk-node"),
69
+ import("@opentelemetry/sdk-logs"),
67
70
  import("@opentelemetry/exporter-metrics-otlp-http"),
68
71
  import("@opentelemetry/sdk-metrics"),
69
72
  import("@opentelemetry/sdk-trace-base"),
@@ -74,9 +77,12 @@ async function loadOpenTelemetryRuntime() {
74
77
  return {
75
78
  api,
76
79
  autoInstrumentations,
80
+ apiLogs,
77
81
  core,
78
82
  contextAsyncHooks,
83
+ logsExporter,
79
84
  sdkNode,
85
+ sdkLogs,
80
86
  metricsExporter,
81
87
  sdkMetrics,
82
88
  sdkTraceBase,
@@ -118,6 +124,60 @@ function parseHeaders(headerInput) {
118
124
  }
119
125
  return result;
120
126
  }
127
+ function headersOrDefault(signalHeaders, defaultHeaders) {
128
+ return signalHeaders ?? defaultHeaders;
129
+ }
130
+ function metricTemporalityPreference(otel, preference) {
131
+ const enumValue = otel.metricsExporter.AggregationTemporalityPreference;
132
+ if (preference === "cumulative")
133
+ return enumValue.CUMULATIVE;
134
+ if (preference === "lowmemory")
135
+ return enumValue.LOWMEMORY;
136
+ return enumValue.DELTA;
137
+ }
138
+ function logSeverityNumber(otel, level) {
139
+ switch (level) {
140
+ case "debug":
141
+ return otel.apiLogs.SeverityNumber.DEBUG;
142
+ case "warn":
143
+ return otel.apiLogs.SeverityNumber.WARN;
144
+ case "error":
145
+ return otel.apiLogs.SeverityNumber.ERROR;
146
+ case "info":
147
+ default:
148
+ return otel.apiLogs.SeverityNumber.INFO;
149
+ }
150
+ }
151
+ function logAttributes(record) {
152
+ const attributes = {};
153
+ for (const [key, value] of Object.entries(record)) {
154
+ if (key === "timestamp" ||
155
+ key === "level" ||
156
+ key === "message" ||
157
+ key === "context" ||
158
+ key === "error") {
159
+ continue;
160
+ }
161
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
162
+ attributes[key] = value;
163
+ }
164
+ }
165
+ if (record.context) {
166
+ for (const [key, value] of Object.entries(record.context)) {
167
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
168
+ attributes[`context.${key}`] = value;
169
+ }
170
+ }
171
+ }
172
+ if (record.error && typeof record.error === "object") {
173
+ const error = record.error;
174
+ if (typeof error.name === "string")
175
+ attributes["error.type"] = error.name;
176
+ if (typeof error.message === "string")
177
+ attributes["error.message"] = error.message;
178
+ }
179
+ return attributes;
180
+ }
121
181
  export function resolveOtlpSignalUrl(endpoint, signal) {
122
182
  if (!endpoint)
123
183
  return undefined;
@@ -125,34 +185,73 @@ export function resolveOtlpSignalUrl(endpoint, signal) {
125
185
  const suffix = `/v1/${signal}`;
126
186
  return trimmed.endsWith(suffix) ? trimmed : `${trimmed}${suffix}`;
127
187
  }
188
+ function isTruthySignalFlag(value) {
189
+ if (value === undefined)
190
+ return undefined;
191
+ return value === "true" || value === "1";
192
+ }
193
+ function exporterIncludesOtlp(value) {
194
+ if (value === undefined)
195
+ return undefined;
196
+ if (value === "none")
197
+ return false;
198
+ return value.split(",").map((part) => part.trim()).includes("otlp");
199
+ }
200
+ function resolveSignalEnabled(enabledValue, exporterValue) {
201
+ const enabledFlag = isTruthySignalFlag(enabledValue);
202
+ if (enabledFlag !== undefined)
203
+ return enabledFlag;
204
+ return exporterIncludesOtlp(exporterValue) ?? false;
205
+ }
206
+ function mergeHeaders(base, override) {
207
+ return { ...base, ...override };
208
+ }
209
+ function resolveMetricsTemporalityPreference(value) {
210
+ const normalized = value?.toLowerCase();
211
+ if (normalized === "cumulative" || normalized === "lowmemory")
212
+ return normalized;
213
+ return "delta";
214
+ }
128
215
  export function resolveOtlpExtensionConfig(read = readEnv) {
129
216
  const endpoint = read("OTEL_EXPORTER_OTLP_ENDPOINT");
130
217
  const tracesEndpoint = read("OTEL_EXPORTER_OTLP_TRACES_ENDPOINT") ?? endpoint;
131
218
  const metricsEndpoint = read("OTEL_EXPORTER_OTLP_METRICS_ENDPOINT") ?? endpoint;
219
+ const logsEndpoint = read("OTEL_EXPORTER_OTLP_LOGS_ENDPOINT") ?? endpoint;
132
220
  const metricsExportIntervalMillis = Number.parseInt(read("OTEL_METRIC_EXPORT_INTERVAL") ?? "60000", 10);
221
+ const headers = parseHeaders(read("OTEL_EXPORTER_OTLP_HEADERS"));
222
+ const tracesHeaders = mergeHeaders(headers, parseHeaders(read("OTEL_EXPORTER_OTLP_TRACES_HEADERS")));
223
+ const metricsHeaders = mergeHeaders(headers, parseHeaders(read("OTEL_EXPORTER_OTLP_METRICS_HEADERS")));
224
+ const logsHeaders = mergeHeaders(headers, parseHeaders(read("OTEL_EXPORTER_OTLP_LOGS_HEADERS")));
133
225
  return {
134
226
  serviceName: read("OTEL_SERVICE_NAME") ?? "veryfront",
135
227
  serviceVersion: "0.1.0",
136
- headers: parseHeaders(read("OTEL_EXPORTER_OTLP_HEADERS")),
137
- tracesEnabled: read("OTEL_TRACES_ENABLED") === "true",
138
- metricsEnabled: read("OTEL_METRICS_ENABLED") === "true",
228
+ headers,
229
+ tracesHeaders,
230
+ metricsHeaders,
231
+ logsHeaders,
232
+ tracesEnabled: resolveSignalEnabled(read("OTEL_TRACES_ENABLED"), read("OTEL_TRACES_EXPORTER")),
233
+ metricsEnabled: resolveSignalEnabled(read("OTEL_METRICS_ENABLED"), read("OTEL_METRICS_EXPORTER")),
234
+ logsEnabled: resolveSignalEnabled(read("OTEL_LOGS_ENABLED"), read("OTEL_LOGS_EXPORTER")),
139
235
  tracesUrl: resolveOtlpSignalUrl(tracesEndpoint, "traces"),
140
236
  metricsUrl: resolveOtlpSignalUrl(metricsEndpoint, "metrics"),
237
+ logsUrl: resolveOtlpSignalUrl(logsEndpoint, "logs"),
141
238
  metricsExportIntervalMillis: Number.isFinite(metricsExportIntervalMillis)
142
239
  ? metricsExportIntervalMillis
143
240
  : 60_000,
241
+ metricsTemporalityPreference: resolveMetricsTemporalityPreference(read("OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE")),
144
242
  };
145
243
  }
146
244
  class OtlpTracingExporter {
147
245
  sdkProvider = null;
148
246
  meterProvider = null;
247
+ logProvider = null;
149
248
  metricsApi = null;
150
249
  traceApi = null;
151
250
  async start(_ctxConfig) {
152
251
  const cfg = resolveOtlpExtensionConfig(readEnv);
153
252
  // Honor OTEL_TRACES_ENABLED: when unset/false, skip exporter wiring so
154
253
  // deployments opting out never create OTLP traffic or set globals.
155
- if (!cfg.tracesEnabled && !cfg.metricsEnabled)
254
+ if (!cfg.tracesEnabled && !cfg.metricsEnabled && !cfg.logsEnabled)
156
255
  return;
157
256
  const otel = await loadOpenTelemetryRuntime();
158
257
  const resource = otel.resources.resourceFromAttributes({
@@ -165,7 +264,7 @@ class OtlpTracingExporter {
165
264
  }
166
265
  const exporter = new otel.traceExporter.OTLPTraceExporter({
167
266
  url: cfg.tracesUrl,
168
- headers: cfg.headers,
267
+ headers: cfg.tracesHeaders,
169
268
  });
170
269
  const provider = new otel.sdkTraceBase.BasicTracerProvider({
171
270
  resource,
@@ -192,7 +291,8 @@ class OtlpTracingExporter {
192
291
  const metricReader = new otel.sdkMetrics.PeriodicExportingMetricReader({
193
292
  exporter: new otel.metricsExporter.OTLPMetricExporter({
194
293
  url: cfg.metricsUrl,
195
- headers: cfg.headers,
294
+ headers: cfg.metricsHeaders,
295
+ temporalityPreference: metricTemporalityPreference(otel, cfg.metricsTemporalityPreference),
196
296
  }),
197
297
  exportIntervalMillis: cfg.metricsExportIntervalMillis,
198
298
  });
@@ -203,6 +303,29 @@ class OtlpTracingExporter {
203
303
  otel.api.metrics.setGlobalMeterProvider(this.meterProvider);
204
304
  this.metricsApi = otel.api.metrics;
205
305
  }
306
+ if (cfg.logsEnabled) {
307
+ if (!cfg.logsUrl) {
308
+ throw new Error("OTEL_LOGS_ENABLED=true requires OTEL_EXPORTER_OTLP_ENDPOINT or OTEL_EXPORTER_OTLP_LOGS_ENDPOINT");
309
+ }
310
+ this.logProvider = new otel.sdkLogs.LoggerProvider({
311
+ resource,
312
+ processors: [
313
+ new otel.sdkLogs.BatchLogRecordProcessor(new otel.logsExporter.OTLPLogExporter({
314
+ url: cfg.logsUrl,
315
+ headers: cfg.logsHeaders,
316
+ })),
317
+ ],
318
+ });
319
+ otel.apiLogs.logs.setGlobalLoggerProvider(this.logProvider);
320
+ otel.apiLogs.logs.getLogger(cfg.serviceName, cfg.serviceVersion).emit({
321
+ severityNumber: otel.apiLogs.SeverityNumber.INFO,
322
+ severityText: "INFO",
323
+ body: "OpenTelemetry log export initialized",
324
+ attributes: {
325
+ "service.name": cfg.serviceName,
326
+ },
327
+ });
328
+ }
206
329
  }
207
330
  // eslint-disable-next-line require-await
208
331
  async export(_spans) {
@@ -210,6 +333,14 @@ class OtlpTracingExporter {
210
333
  // Callers that want to push custom SpanData batches can extend this.
211
334
  }
212
335
  async shutdown() {
336
+ if (this.logProvider) {
337
+ try {
338
+ await this.logProvider.shutdown();
339
+ }
340
+ finally {
341
+ this.logProvider = null;
342
+ }
343
+ }
213
344
  if (this.meterProvider) {
214
345
  try {
215
346
  await this.meterProvider.shutdown();
@@ -242,24 +373,56 @@ class OtlpTracingExporter {
242
373
  class OpenTelemetryNodeTelemetryProvider {
243
374
  sdk = null;
244
375
  async initialize(options) {
376
+ const tracesEnabled = options.tracesEnabled ?? true;
377
+ const metricsEnabled = options.metricsEnabled ?? false;
378
+ const logsEnabled = options.logsEnabled ?? false;
379
+ if (!tracesEnabled && !metricsEnabled && !logsEnabled)
380
+ return false;
245
381
  const otel = await loadOpenTelemetryRuntime();
246
382
  const resource = otel.resources.resourceFromAttributes({
247
383
  "service.name": options.serviceName,
248
384
  "service.version": options.serviceVersion,
249
385
  "deployment.environment": options.deploymentEnvironment,
250
386
  });
251
- const traceExporter = new otel.traceExporter.OTLPTraceExporter({
252
- headers: options.exporterHeaders,
253
- });
387
+ const spanProcessors = tracesEnabled
388
+ ? [
389
+ new otel.sdkTraceBase.BatchSpanProcessor(new otel.traceExporter.OTLPTraceExporter({
390
+ url: options.tracesEndpoint,
391
+ headers: headersOrDefault(options.tracesHeaders, options.exporterHeaders),
392
+ }), {
393
+ maxExportBatchSize: 100,
394
+ scheduledDelayMillis: 500,
395
+ }),
396
+ ]
397
+ : [];
398
+ const metricReaders = metricsEnabled
399
+ ? [
400
+ new otel.sdkMetrics.PeriodicExportingMetricReader({
401
+ exporter: new otel.metricsExporter.OTLPMetricExporter({
402
+ url: options.metricsEndpoint,
403
+ headers: headersOrDefault(options.metricsHeaders, options.exporterHeaders),
404
+ temporalityPreference: metricTemporalityPreference(otel, options.metricsTemporalityPreference),
405
+ }),
406
+ exportIntervalMillis: options.metricsExportIntervalMillis ?? 60_000,
407
+ }),
408
+ ]
409
+ : [];
410
+ const logRecordProcessors = logsEnabled
411
+ ? [
412
+ new otel.sdkLogs.BatchLogRecordProcessor(new otel.logsExporter.OTLPLogExporter({
413
+ url: options.logsEndpoint,
414
+ headers: headersOrDefault(options.logsHeaders, options.exporterHeaders),
415
+ })),
416
+ ]
417
+ : [];
254
418
  const sdk = new otel.sdkNode.NodeSDK({
255
419
  resource,
256
420
  sampler: new otel.sdkTraceBase.ParentBasedSampler({
257
421
  root: new otel.sdkTraceBase.TraceIdRatioBasedSampler(options.samplingRatio),
258
422
  }),
259
- spanProcessor: new otel.sdkTraceBase.BatchSpanProcessor(traceExporter, {
260
- maxExportBatchSize: 100,
261
- scheduledDelayMillis: 500,
262
- }),
423
+ spanProcessors,
424
+ metricReaders,
425
+ logRecordProcessors,
263
426
  instrumentations: [
264
427
  otel.autoInstrumentations.getNodeAutoInstrumentations({
265
428
  "@opentelemetry/instrumentation-fs": { enabled: options.instrumentation.fs },
@@ -270,9 +433,42 @@ class OpenTelemetryNodeTelemetryProvider {
270
433
  });
271
434
  sdk.start();
272
435
  this.sdk = sdk;
436
+ if (metricsEnabled) {
437
+ otel.api.metrics
438
+ .getMeter(options.serviceName, options.serviceVersion)
439
+ .createCounter("veryfront.agent.telemetry.startups")
440
+ .add(1, {
441
+ "deployment.environment": options.deploymentEnvironment,
442
+ "service.name": options.serviceName,
443
+ });
444
+ }
445
+ if (logsEnabled) {
446
+ const otelLogger = otel.apiLogs.logs.getLogger(options.serviceName, options.serviceVersion);
447
+ options.registerLogRecordEmitter?.((record) => {
448
+ otelLogger.emit({
449
+ timestamp: record.timestamp ? new Date(record.timestamp) : new Date(),
450
+ severityNumber: logSeverityNumber(otel, record.level),
451
+ severityText: record.level?.toUpperCase() ?? "INFO",
452
+ body: record.message,
453
+ attributes: logAttributes(record),
454
+ });
455
+ });
456
+ otelLogger.emit({
457
+ severityNumber: otel.apiLogs.SeverityNumber.INFO,
458
+ severityText: "INFO",
459
+ body: "OpenTelemetry log export initialized",
460
+ attributes: {
461
+ "deployment.environment": options.deploymentEnvironment,
462
+ "service.name": options.serviceName,
463
+ },
464
+ });
465
+ }
273
466
  options.logger?.info("OpenTelemetry initialized", {
274
467
  serviceName: options.serviceName,
275
468
  samplingRatio: options.samplingRatio,
469
+ tracesEnabled,
470
+ metricsEnabled,
471
+ logsEnabled,
276
472
  });
277
473
  options.processTarget?.on("SIGTERM", async () => {
278
474
  await this.shutdown();
@@ -314,11 +510,20 @@ const extOpenTelemetry = () => {
314
510
  "OTEL_EXPORTER_OTLP_ENDPOINT",
315
511
  "OTEL_EXPORTER_OTLP_TRACES_ENDPOINT",
316
512
  "OTEL_EXPORTER_OTLP_METRICS_ENDPOINT",
513
+ "OTEL_EXPORTER_OTLP_LOGS_ENDPOINT",
317
514
  "OTEL_EXPORTER_OTLP_HEADERS",
515
+ "OTEL_EXPORTER_OTLP_TRACES_HEADERS",
516
+ "OTEL_EXPORTER_OTLP_METRICS_HEADERS",
517
+ "OTEL_EXPORTER_OTLP_LOGS_HEADERS",
318
518
  "OTEL_SERVICE_NAME",
319
519
  "OTEL_TRACES_ENABLED",
320
520
  "OTEL_METRICS_ENABLED",
521
+ "OTEL_LOGS_ENABLED",
522
+ "OTEL_TRACES_EXPORTER",
523
+ "OTEL_METRICS_EXPORTER",
524
+ "OTEL_LOGS_EXPORTER",
321
525
  "OTEL_METRIC_EXPORT_INTERVAL",
526
+ "OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE",
322
527
  ],
323
528
  },
324
529
  ],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@veryfront/ext-observability-opentelemetry",
3
- "version": "0.1.1028",
3
+ "version": "0.1.1030",
4
4
  "description": "Veryfront first-party extension package for ext-observability-opentelemetry",
5
5
  "keywords": [
6
6
  "veryfront",
@@ -53,11 +53,20 @@
53
53
  "OTEL_EXPORTER_OTLP_ENDPOINT",
54
54
  "OTEL_EXPORTER_OTLP_TRACES_ENDPOINT",
55
55
  "OTEL_EXPORTER_OTLP_METRICS_ENDPOINT",
56
+ "OTEL_EXPORTER_OTLP_LOGS_ENDPOINT",
56
57
  "OTEL_EXPORTER_OTLP_HEADERS",
58
+ "OTEL_EXPORTER_OTLP_TRACES_HEADERS",
59
+ "OTEL_EXPORTER_OTLP_METRICS_HEADERS",
60
+ "OTEL_EXPORTER_OTLP_LOGS_HEADERS",
57
61
  "OTEL_SERVICE_NAME",
58
62
  "OTEL_TRACES_ENABLED",
59
63
  "OTEL_METRICS_ENABLED",
60
- "OTEL_METRIC_EXPORT_INTERVAL"
64
+ "OTEL_LOGS_ENABLED",
65
+ "OTEL_TRACES_EXPORTER",
66
+ "OTEL_METRICS_EXPORTER",
67
+ "OTEL_LOGS_EXPORTER",
68
+ "OTEL_METRIC_EXPORT_INTERVAL",
69
+ "OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE"
61
70
  ]
62
71
  }
63
72
  ]
@@ -67,19 +76,22 @@
67
76
  "@deno/shim-crypto": "~0.3.1",
68
77
  "@deno/shim-timers": "~0.1.0",
69
78
  "@opentelemetry/api": "1.9.1",
79
+ "@opentelemetry/api-logs": "0.219.0",
70
80
  "@opentelemetry/auto-instrumentations-node": "0.77.0",
71
81
  "@opentelemetry/context-async-hooks": "2.8.0",
72
82
  "@opentelemetry/core": "2.8.0",
83
+ "@opentelemetry/exporter-logs-otlp-http": "0.219.0",
73
84
  "@opentelemetry/exporter-metrics-otlp-http": "0.219.0",
74
85
  "@opentelemetry/exporter-trace-otlp-http": "0.219.0",
75
86
  "@opentelemetry/resources": "2.8.0",
87
+ "@opentelemetry/sdk-logs": "0.219.0",
76
88
  "@opentelemetry/sdk-metrics": "2.8.0",
77
89
  "@opentelemetry/sdk-node": "0.219.0",
78
90
  "@opentelemetry/sdk-trace-base": "2.8.0",
79
91
  "@opentelemetry/semantic-conventions": "1.41.1"
80
92
  },
81
93
  "peerDependencies": {
82
- "veryfront": "^0.1.1028"
94
+ "veryfront": "^0.1.1030"
83
95
  },
84
96
  "type": "module",
85
97
  "types": "./esm/index.d.ts",