@modainteract/moda-interact-shared 0.3.0 → 0.5.0
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 +183 -16
- package/dist/chunk-5JNEZEKV.js +374 -0
- package/dist/chunk-5JNEZEKV.js.map +1 -0
- package/dist/chunk-A3EK6N46.js +73 -0
- package/dist/chunk-A3EK6N46.js.map +1 -0
- package/dist/logging/node.js +8 -363
- package/dist/logging/node.js.map +1 -1
- package/dist/observability/bullmq.d.ts +10 -0
- package/dist/observability/bullmq.js +14 -0
- package/dist/observability/bullmq.js.map +1 -0
- package/dist/observability/genai.d.ts +17 -0
- package/dist/observability/genai.js +121 -0
- package/dist/observability/genai.js.map +1 -0
- package/dist/observability/index.d.ts +10 -0
- package/dist/observability/index.js +9 -0
- package/dist/observability/index.js.map +1 -0
- package/dist/observability/node.d.ts +29 -0
- package/dist/observability/node.js +352 -0
- package/dist/observability/node.js.map +1 -0
- package/package.json +29 -1
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import {
|
|
2
|
+
withObservedSpan
|
|
3
|
+
} from "../chunk-A3EK6N46.js";
|
|
4
|
+
|
|
5
|
+
// src/observability/genai.ts
|
|
6
|
+
import { metrics } from "@opentelemetry/api";
|
|
7
|
+
var MAX_ATTRIBUTE_LENGTH = 80;
|
|
8
|
+
var meter = metrics.getMeter(
|
|
9
|
+
"@modainteract/moda-interact-shared/observability/genai"
|
|
10
|
+
);
|
|
11
|
+
var agentDuration = meter.createHistogram(
|
|
12
|
+
"moda.agent.invocation.duration_ms",
|
|
13
|
+
{ unit: "ms" }
|
|
14
|
+
);
|
|
15
|
+
var agentOperations = meter.createCounter("moda.agent.invocation.operations");
|
|
16
|
+
var toolDuration = meter.createHistogram("moda.agent.tool.duration_ms", {
|
|
17
|
+
unit: "ms"
|
|
18
|
+
});
|
|
19
|
+
var toolOperations = meter.createCounter("moda.agent.tool.operations");
|
|
20
|
+
var turnDuration = meter.createHistogram(
|
|
21
|
+
"moda.conversation.turn.duration_ms",
|
|
22
|
+
{ unit: "ms" }
|
|
23
|
+
);
|
|
24
|
+
var turnOperations = meter.createCounter("moda.conversation.turn.operations");
|
|
25
|
+
async function observeConversationTurn(channel, work, options = {}) {
|
|
26
|
+
const observeSpan = () => withObservedSpan(
|
|
27
|
+
`conversation.turn ${channel}`,
|
|
28
|
+
{ "moda.messaging.channel": channel },
|
|
29
|
+
work,
|
|
30
|
+
options
|
|
31
|
+
);
|
|
32
|
+
return observeGenAIOperation(
|
|
33
|
+
turnDuration,
|
|
34
|
+
turnOperations,
|
|
35
|
+
{ channel },
|
|
36
|
+
observeSpan,
|
|
37
|
+
options
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
async function observeAgentInvocation(observation, work, options = {}) {
|
|
41
|
+
const agentName = boundedSpanValue(observation.agentName) ?? "unknown";
|
|
42
|
+
const observeSpan = () => withObservedSpan(
|
|
43
|
+
`invoke_agent ${agentName}`,
|
|
44
|
+
compact({
|
|
45
|
+
"gen_ai.operation.name": "invoke_agent",
|
|
46
|
+
"gen_ai.agent.name": agentName,
|
|
47
|
+
"gen_ai.provider.name": boundedSpanValue(observation.provider),
|
|
48
|
+
"gen_ai.request.model": boundedSpanValue(observation.model)
|
|
49
|
+
}),
|
|
50
|
+
work,
|
|
51
|
+
options
|
|
52
|
+
);
|
|
53
|
+
return observeGenAIOperation(
|
|
54
|
+
agentDuration,
|
|
55
|
+
agentOperations,
|
|
56
|
+
{},
|
|
57
|
+
observeSpan,
|
|
58
|
+
options
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
async function observeAgentTool(toolName, work, options = {}) {
|
|
62
|
+
const safeToolName = boundedSpanValue(toolName) ?? "unknown";
|
|
63
|
+
const observeSpan = () => withObservedSpan(
|
|
64
|
+
`execute_tool ${safeToolName}`,
|
|
65
|
+
{
|
|
66
|
+
"gen_ai.operation.name": "execute_tool",
|
|
67
|
+
"gen_ai.tool.name": safeToolName
|
|
68
|
+
},
|
|
69
|
+
work,
|
|
70
|
+
options
|
|
71
|
+
);
|
|
72
|
+
return observeGenAIOperation(
|
|
73
|
+
toolDuration,
|
|
74
|
+
toolOperations,
|
|
75
|
+
{},
|
|
76
|
+
observeSpan,
|
|
77
|
+
options
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
function observeGenAIOperation(duration, operations, attributes, work, options) {
|
|
81
|
+
return options.recordMetrics === false ? work() : observeOperation(duration, operations, attributes, work);
|
|
82
|
+
}
|
|
83
|
+
async function observeOperation(duration, operations, attributes, work) {
|
|
84
|
+
const started = performance.now();
|
|
85
|
+
let outcome = "error";
|
|
86
|
+
try {
|
|
87
|
+
const result = await work();
|
|
88
|
+
outcome = "success";
|
|
89
|
+
return result;
|
|
90
|
+
} finally {
|
|
91
|
+
recordMetrics(duration, operations, performance.now() - started, {
|
|
92
|
+
...attributes,
|
|
93
|
+
outcome
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function recordMetrics(duration, operations, elapsedMilliseconds, attributes) {
|
|
98
|
+
try {
|
|
99
|
+
duration.record(elapsedMilliseconds, attributes);
|
|
100
|
+
operations.add(1, attributes);
|
|
101
|
+
} catch {
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
function boundedSpanValue(value) {
|
|
105
|
+
if (value === void 0) return void 0;
|
|
106
|
+
const bounded = value.trim().slice(0, MAX_ATTRIBUTE_LENGTH);
|
|
107
|
+
return bounded || void 0;
|
|
108
|
+
}
|
|
109
|
+
function compact(attributes) {
|
|
110
|
+
return Object.fromEntries(
|
|
111
|
+
Object.entries(attributes).filter(
|
|
112
|
+
(entry) => entry[1] !== void 0
|
|
113
|
+
)
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
export {
|
|
117
|
+
observeAgentInvocation,
|
|
118
|
+
observeAgentTool,
|
|
119
|
+
observeConversationTurn
|
|
120
|
+
};
|
|
121
|
+
//# sourceMappingURL=genai.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/observability/genai.ts"],"sourcesContent":["import { metrics, type Attributes, type Counter, type Histogram } from \"@opentelemetry/api\";\nimport {\n withObservedSpan,\n type ObservedSpanOptions,\n} from \"./index.js\";\n\nexport type {\n ObservedSpanOptions,\n SpanExceptionMapper,\n} from \"./index.js\";\n\nconst MAX_ATTRIBUTE_LENGTH = 80;\nconst meter = metrics.getMeter(\n \"@modainteract/moda-interact-shared/observability/genai\",\n);\nconst agentDuration = meter.createHistogram(\n \"moda.agent.invocation.duration_ms\",\n { unit: \"ms\" },\n);\nconst agentOperations = meter.createCounter(\"moda.agent.invocation.operations\");\nconst toolDuration = meter.createHistogram(\"moda.agent.tool.duration_ms\", {\n unit: \"ms\",\n});\nconst toolOperations = meter.createCounter(\"moda.agent.tool.operations\");\nconst turnDuration = meter.createHistogram(\n \"moda.conversation.turn.duration_ms\",\n { unit: \"ms\" },\n);\nconst turnOperations = meter.createCounter(\"moda.conversation.turn.operations\");\n\ntype Outcome = \"success\" | \"error\";\n\nexport type AgentObservation = {\n agentName: string;\n provider?: string;\n model?: string;\n};\n\nexport type GenAIObservationOptions = ObservedSpanOptions & {\n recordMetrics?: boolean;\n};\n\nexport async function observeConversationTurn<T>(\n channel: \"whatsapp\" | \"other\",\n work: () => Promise<T>,\n options: GenAIObservationOptions = {},\n): Promise<T> {\n const observeSpan = () => withObservedSpan(\n `conversation.turn ${channel}`,\n { \"moda.messaging.channel\": channel },\n work,\n options,\n );\n\n return observeGenAIOperation(\n turnDuration,\n turnOperations,\n { channel },\n observeSpan,\n options,\n );\n}\n\nexport async function observeAgentInvocation<T>(\n observation: AgentObservation,\n work: () => Promise<T>,\n options: GenAIObservationOptions = {},\n): Promise<T> {\n const agentName = boundedSpanValue(observation.agentName) ?? \"unknown\";\n\n const observeSpan = () => withObservedSpan(\n `invoke_agent ${agentName}`,\n compact({\n \"gen_ai.operation.name\": \"invoke_agent\",\n \"gen_ai.agent.name\": agentName,\n \"gen_ai.provider.name\": boundedSpanValue(observation.provider),\n \"gen_ai.request.model\": boundedSpanValue(observation.model),\n }),\n work,\n options,\n );\n\n return observeGenAIOperation(\n agentDuration,\n agentOperations,\n {},\n observeSpan,\n options,\n );\n}\n\nexport async function observeAgentTool<T>(\n toolName: string,\n work: () => Promise<T>,\n options: GenAIObservationOptions = {},\n): Promise<T> {\n const safeToolName = boundedSpanValue(toolName) ?? \"unknown\";\n\n const observeSpan = () => withObservedSpan(\n `execute_tool ${safeToolName}`,\n {\n \"gen_ai.operation.name\": \"execute_tool\",\n \"gen_ai.tool.name\": safeToolName,\n },\n work,\n options,\n );\n\n return observeGenAIOperation(\n toolDuration,\n toolOperations,\n {},\n observeSpan,\n options,\n );\n}\n\nfunction observeGenAIOperation<T>(\n duration: Histogram,\n operations: Counter,\n attributes: Attributes,\n work: () => Promise<T>,\n options: GenAIObservationOptions,\n): Promise<T> {\n return options.recordMetrics === false\n ? work()\n : observeOperation(duration, operations, attributes, work);\n}\n\nasync function observeOperation<T>(\n duration: Histogram,\n operations: Counter,\n attributes: Attributes,\n work: () => Promise<T>,\n): Promise<T> {\n const started = performance.now();\n let outcome: Outcome = \"error\";\n\n try {\n const result = await work();\n outcome = \"success\";\n return result;\n } finally {\n recordMetrics(duration, operations, performance.now() - started, {\n ...attributes,\n outcome,\n });\n }\n}\n\nfunction recordMetrics(\n duration: Histogram,\n operations: Counter,\n elapsedMilliseconds: number,\n attributes: Attributes,\n): void {\n try {\n duration.record(elapsedMilliseconds, attributes);\n operations.add(1, attributes);\n } catch {\n // Telemetry must not replace an application result or error.\n }\n}\n\nfunction boundedSpanValue(value: string | undefined): string | undefined {\n if (value === undefined) return undefined;\n const bounded = value.trim().slice(0, MAX_ATTRIBUTE_LENGTH);\n return bounded || undefined;\n}\n\nfunction compact(\n attributes: Record<string, string | undefined>,\n): Record<string, string> {\n return Object.fromEntries(\n Object.entries(attributes).filter((entry): entry is [string, string] =>\n entry[1] !== undefined\n ),\n );\n}"],"mappings":";;;;;AAAA,SAAS,eAA8D;AAWvE,IAAM,uBAAuB;AAC7B,IAAM,QAAQ,QAAQ;AAAA,EACpB;AACF;AACA,IAAM,gBAAgB,MAAM;AAAA,EAC1B;AAAA,EACA,EAAE,MAAM,KAAK;AACf;AACA,IAAM,kBAAkB,MAAM,cAAc,kCAAkC;AAC9E,IAAM,eAAe,MAAM,gBAAgB,+BAA+B;AAAA,EACxE,MAAM;AACR,CAAC;AACD,IAAM,iBAAiB,MAAM,cAAc,4BAA4B;AACvE,IAAM,eAAe,MAAM;AAAA,EACzB;AAAA,EACA,EAAE,MAAM,KAAK;AACf;AACA,IAAM,iBAAiB,MAAM,cAAc,mCAAmC;AAc9E,eAAsB,wBACpB,SACA,MACA,UAAmC,CAAC,GACxB;AACZ,QAAM,cAAc,MAAM;AAAA,IACxB,qBAAqB,OAAO;AAAA,IAC5B,EAAE,0BAA0B,QAAQ;AAAA,IACpC;AAAA,IACA;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,EAAE,QAAQ;AAAA,IACV;AAAA,IACA;AAAA,EACF;AACF;AAEA,eAAsB,uBACpB,aACA,MACA,UAAmC,CAAC,GACxB;AACZ,QAAM,YAAY,iBAAiB,YAAY,SAAS,KAAK;AAE7D,QAAM,cAAc,MAAM;AAAA,IACxB,gBAAgB,SAAS;AAAA,IACzB,QAAQ;AAAA,MACN,yBAAyB;AAAA,MACzB,qBAAqB;AAAA,MACrB,wBAAwB,iBAAiB,YAAY,QAAQ;AAAA,MAC7D,wBAAwB,iBAAiB,YAAY,KAAK;AAAA,IAC5D,CAAC;AAAA,IACD;AAAA,IACA;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,CAAC;AAAA,IACD;AAAA,IACA;AAAA,EACF;AACF;AAEA,eAAsB,iBACpB,UACA,MACA,UAAmC,CAAC,GACxB;AACZ,QAAM,eAAe,iBAAiB,QAAQ,KAAK;AAEnD,QAAM,cAAc,MAAM;AAAA,IACxB,gBAAgB,YAAY;AAAA,IAC5B;AAAA,MACE,yBAAyB;AAAA,MACzB,oBAAoB;AAAA,IACtB;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,CAAC;AAAA,IACD;AAAA,IACA;AAAA,EACF;AACF;AAEA,SAAS,sBACP,UACA,YACA,YACA,MACA,SACY;AACZ,SAAO,QAAQ,kBAAkB,QAC7B,KAAK,IACL,iBAAiB,UAAU,YAAY,YAAY,IAAI;AAC7D;AAEA,eAAe,iBACb,UACA,YACA,YACA,MACY;AACZ,QAAM,UAAU,YAAY,IAAI;AAChC,MAAI,UAAmB;AAEvB,MAAI;AACF,UAAM,SAAS,MAAM,KAAK;AAC1B,cAAU;AACV,WAAO;AAAA,EACT,UAAE;AACA,kBAAc,UAAU,YAAY,YAAY,IAAI,IAAI,SAAS;AAAA,MAC/D,GAAG;AAAA,MACH;AAAA,IACF,CAAC;AAAA,EACH;AACF;AAEA,SAAS,cACP,UACA,YACA,qBACA,YACM;AACN,MAAI;AACF,aAAS,OAAO,qBAAqB,UAAU;AAC/C,eAAW,IAAI,GAAG,UAAU;AAAA,EAC9B,QAAQ;AAAA,EAER;AACF;AAEA,SAAS,iBAAiB,OAA+C;AACvE,MAAI,UAAU,OAAW,QAAO;AAChC,QAAM,UAAU,MAAM,KAAK,EAAE,MAAM,GAAG,oBAAoB;AAC1D,SAAO,WAAW;AACpB;AAEA,SAAS,QACP,YACwB;AACxB,SAAO,OAAO;AAAA,IACZ,OAAO,QAAQ,UAAU,EAAE;AAAA,MAAO,CAAC,UACjC,MAAM,CAAC,MAAM;AAAA,IACf;AAAA,EACF;AACF;","names":[]}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Exception, Attributes } from '@opentelemetry/api';
|
|
2
|
+
|
|
3
|
+
type SpanExceptionMapper = (error: unknown) => Exception | undefined;
|
|
4
|
+
type ObservedSpanOptions = {
|
|
5
|
+
mapException?: SpanExceptionMapper;
|
|
6
|
+
};
|
|
7
|
+
declare function getActiveTraceId(): string | undefined;
|
|
8
|
+
declare function withObservedSpan<T>(name: string, attributes: Attributes, work: () => Promise<T>, options?: ObservedSpanOptions): Promise<T>;
|
|
9
|
+
|
|
10
|
+
export { type ObservedSpanOptions, type SpanExceptionMapper, getActiveTraceId, withObservedSpan };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Sampler } from '@opentelemetry/sdk-trace-base';
|
|
2
|
+
|
|
3
|
+
type NodeInstrumentProfile = {
|
|
4
|
+
http?: boolean;
|
|
5
|
+
fetch?: boolean;
|
|
6
|
+
prisma?: boolean;
|
|
7
|
+
};
|
|
8
|
+
type NodeObservabilityOptions = {
|
|
9
|
+
serviceName: string;
|
|
10
|
+
environment?: string;
|
|
11
|
+
serviceNamespace?: string;
|
|
12
|
+
instrument?: NodeInstrumentProfile;
|
|
13
|
+
traceSampleRatio?: number;
|
|
14
|
+
forceEnable?: boolean;
|
|
15
|
+
};
|
|
16
|
+
type NodeObservabilityRuntime = {
|
|
17
|
+
enabled: boolean;
|
|
18
|
+
serviceName: string;
|
|
19
|
+
environment: string;
|
|
20
|
+
error?: string;
|
|
21
|
+
forceFlush(): Promise<void>;
|
|
22
|
+
shutdown(): Promise<void>;
|
|
23
|
+
};
|
|
24
|
+
declare function resolveDeploymentEnvironmentName(): string;
|
|
25
|
+
declare function initNodeObservability(options: NodeObservabilityOptions): NodeObservabilityRuntime;
|
|
26
|
+
declare function getNodeObservabilityRuntime(): NodeObservabilityRuntime | undefined;
|
|
27
|
+
declare function resolveSampler(explicitRatio: number | undefined, environment: string): Sampler;
|
|
28
|
+
|
|
29
|
+
export { type NodeInstrumentProfile, type NodeObservabilityOptions, type NodeObservabilityRuntime, getNodeObservabilityRuntime, initNodeObservability, resolveDeploymentEnvironmentName, resolveSampler };
|
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
import {
|
|
2
|
+
initNodeLokiLogging,
|
|
3
|
+
initNodeOpenTelemetryLogging
|
|
4
|
+
} from "../chunk-5JNEZEKV.js";
|
|
5
|
+
import "../chunk-XU4XX7SD.js";
|
|
6
|
+
|
|
7
|
+
// src/observability/node.ts
|
|
8
|
+
import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-http";
|
|
9
|
+
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
|
|
10
|
+
import { PrismaInstrumentation } from "@prisma/instrumentation";
|
|
11
|
+
import { HttpInstrumentation } from "@opentelemetry/instrumentation-http";
|
|
12
|
+
import { UndiciInstrumentation } from "@opentelemetry/instrumentation-undici";
|
|
13
|
+
import { resourceFromAttributes } from "@opentelemetry/resources";
|
|
14
|
+
import { PeriodicExportingMetricReader } from "@opentelemetry/sdk-metrics";
|
|
15
|
+
import { NodeSDK } from "@opentelemetry/sdk-node";
|
|
16
|
+
import {
|
|
17
|
+
AlwaysOffSampler,
|
|
18
|
+
AlwaysOnSampler,
|
|
19
|
+
BatchSpanProcessor,
|
|
20
|
+
ParentBasedSampler,
|
|
21
|
+
TraceIdRatioBasedSampler
|
|
22
|
+
} from "@opentelemetry/sdk-trace-base";
|
|
23
|
+
var DEFAULT_NAMESPACE = "moda-interact";
|
|
24
|
+
var configurationWarnings = /* @__PURE__ */ new Set();
|
|
25
|
+
var runtime;
|
|
26
|
+
function resolveDeploymentEnvironmentName() {
|
|
27
|
+
return nonEmpty(process.env.DEPLOYMENT_ENVIRONMENT_NAME) ?? nonEmpty(process.env.OTEL_DEPLOYMENT_ENVIRONMENT) ?? nonEmpty(process.env.NODE_ENV) ?? "development";
|
|
28
|
+
}
|
|
29
|
+
function initNodeObservability(options) {
|
|
30
|
+
if (runtime) {
|
|
31
|
+
return runtime;
|
|
32
|
+
}
|
|
33
|
+
const serviceName = nonEmpty(options?.serviceName) ?? "unknown-service";
|
|
34
|
+
const environment = nonEmpty(options?.environment) ?? resolveDeploymentEnvironmentName();
|
|
35
|
+
const serviceNamespace = nonEmpty(options?.serviceNamespace) ?? DEFAULT_NAMESPACE;
|
|
36
|
+
if (!nonEmpty(options?.serviceName)) {
|
|
37
|
+
warnConfigurationOnce(
|
|
38
|
+
"service-name",
|
|
39
|
+
"observability disabled: serviceName must be non-empty"
|
|
40
|
+
);
|
|
41
|
+
runtime = disabledRuntime(serviceName, environment);
|
|
42
|
+
return runtime;
|
|
43
|
+
}
|
|
44
|
+
const otelLogs = safeInitLogging(() => initNodeOpenTelemetryLogging({
|
|
45
|
+
serviceName,
|
|
46
|
+
environment,
|
|
47
|
+
serviceNamespace
|
|
48
|
+
}), "OpenTelemetry Logs");
|
|
49
|
+
const loki = safeInitLogging(() => initNodeLokiLogging({
|
|
50
|
+
serviceName,
|
|
51
|
+
environment,
|
|
52
|
+
serviceNamespace
|
|
53
|
+
}), "Loki");
|
|
54
|
+
if (!options.forceEnable && envBool("OTEL_SDK_DISABLED") === true) {
|
|
55
|
+
runtime = disabledRuntime(serviceName, environment, otelLogs, loki);
|
|
56
|
+
return runtime;
|
|
57
|
+
}
|
|
58
|
+
const traceEndpoint = signalEndpoint("TRACES", "traces");
|
|
59
|
+
const metricEndpoint = signalEndpoint("METRICS", "metrics");
|
|
60
|
+
if (!options.forceEnable && !traceEndpoint && !metricEndpoint) {
|
|
61
|
+
runtime = disabledRuntime(serviceName, environment, otelLogs, loki);
|
|
62
|
+
return runtime;
|
|
63
|
+
}
|
|
64
|
+
try {
|
|
65
|
+
const resource = resourceFromAttributes({
|
|
66
|
+
"service.namespace": serviceNamespace,
|
|
67
|
+
"service.name": serviceName,
|
|
68
|
+
"deployment.environment.name": environment
|
|
69
|
+
});
|
|
70
|
+
const profile = options.instrument ?? {};
|
|
71
|
+
const instrumentations = [
|
|
72
|
+
...profile.http ? [new HttpInstrumentation({
|
|
73
|
+
requestHook: (span, request) => {
|
|
74
|
+
const path = "path" in request ? request.path : request.url;
|
|
75
|
+
setSanitizedUrlAttributes(span, path);
|
|
76
|
+
if ("protocol" in request && "host" in request && typeof request.protocol === "string" && typeof request.host === "string") {
|
|
77
|
+
span.setAttribute(
|
|
78
|
+
"url.full",
|
|
79
|
+
`${request.protocol}//${request.host}${sanitizedPath(path ?? "/")}`
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
})] : [],
|
|
84
|
+
...profile.fetch ? [new UndiciInstrumentation({
|
|
85
|
+
requestHook: (span, request) => {
|
|
86
|
+
const path = sanitizedPath(request.path);
|
|
87
|
+
span.setAttribute("url.path", path);
|
|
88
|
+
span.setAttribute("url.full", `${request.origin}${path}`);
|
|
89
|
+
span.setAttribute("url.query", "");
|
|
90
|
+
}
|
|
91
|
+
})] : [],
|
|
92
|
+
...profile.prisma ? [new PrismaInstrumentation()] : []
|
|
93
|
+
];
|
|
94
|
+
const traceProcessor = createTraceProcessor(traceEndpoint);
|
|
95
|
+
const metricReader = createMetricReader(metricEndpoint);
|
|
96
|
+
if (!traceProcessor && !metricReader) {
|
|
97
|
+
runtime = {
|
|
98
|
+
...disabledRuntime(serviceName, environment, otelLogs, loki),
|
|
99
|
+
error: "observability-exporters-unavailable"
|
|
100
|
+
};
|
|
101
|
+
return runtime;
|
|
102
|
+
}
|
|
103
|
+
const sdk = new NodeSDK({
|
|
104
|
+
resource,
|
|
105
|
+
autoDetectResources: false,
|
|
106
|
+
sampler: resolveSampler(options.traceSampleRatio, environment),
|
|
107
|
+
spanProcessors: traceProcessor ? [traceProcessor] : [],
|
|
108
|
+
...metricReader ? { metricReader } : {},
|
|
109
|
+
instrumentations
|
|
110
|
+
});
|
|
111
|
+
sdk.start();
|
|
112
|
+
let shutdownPromise;
|
|
113
|
+
runtime = {
|
|
114
|
+
enabled: true,
|
|
115
|
+
serviceName,
|
|
116
|
+
environment,
|
|
117
|
+
forceFlush: async () => {
|
|
118
|
+
await Promise.allSettled([
|
|
119
|
+
traceProcessor?.forceFlush(),
|
|
120
|
+
metricReader?.forceFlush(),
|
|
121
|
+
otelLogs?.forceFlush(),
|
|
122
|
+
loki?.forceFlush()
|
|
123
|
+
]);
|
|
124
|
+
},
|
|
125
|
+
shutdown: () => {
|
|
126
|
+
shutdownPromise ??= settleAll([
|
|
127
|
+
Promise.resolve(sdk.shutdown()),
|
|
128
|
+
otelLogs?.shutdown(),
|
|
129
|
+
loki?.shutdown()
|
|
130
|
+
]);
|
|
131
|
+
return shutdownPromise;
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
return runtime;
|
|
135
|
+
} catch (error) {
|
|
136
|
+
warnConfigurationOnce(
|
|
137
|
+
"runtime-init",
|
|
138
|
+
"observability disabled: Node runtime initialization failed"
|
|
139
|
+
);
|
|
140
|
+
runtime = {
|
|
141
|
+
...disabledRuntime(serviceName, environment, otelLogs, loki),
|
|
142
|
+
error: boundedError(error)
|
|
143
|
+
};
|
|
144
|
+
return runtime;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
function getNodeObservabilityRuntime() {
|
|
148
|
+
return runtime;
|
|
149
|
+
}
|
|
150
|
+
function resolveSampler(explicitRatio, environment) {
|
|
151
|
+
const fallbackRatio = environment === "test" ? 1 : 0.1;
|
|
152
|
+
if (explicitRatio !== void 0) {
|
|
153
|
+
if (!Number.isFinite(explicitRatio) || explicitRatio < 0 || explicitRatio > 1) {
|
|
154
|
+
warnConfigurationOnce(
|
|
155
|
+
"explicit-sampler-ratio",
|
|
156
|
+
"invalid traceSampleRatio; using the Moda environment fallback"
|
|
157
|
+
);
|
|
158
|
+
return parentBasedRatio(fallbackRatio);
|
|
159
|
+
}
|
|
160
|
+
return parentBasedRatio(explicitRatio);
|
|
161
|
+
}
|
|
162
|
+
const requested = nonEmpty(process.env.OTEL_TRACES_SAMPLER)?.toLowerCase();
|
|
163
|
+
if (!requested) {
|
|
164
|
+
return parentBasedRatio(fallbackRatio);
|
|
165
|
+
}
|
|
166
|
+
switch (requested) {
|
|
167
|
+
case "always_on":
|
|
168
|
+
return new AlwaysOnSampler();
|
|
169
|
+
case "always_off":
|
|
170
|
+
return new AlwaysOffSampler();
|
|
171
|
+
case "traceidratio":
|
|
172
|
+
return new TraceIdRatioBasedSampler(samplerRatioArg(fallbackRatio));
|
|
173
|
+
case "parentbased_always_on":
|
|
174
|
+
return new ParentBasedSampler({ root: new AlwaysOnSampler() });
|
|
175
|
+
case "parentbased_always_off":
|
|
176
|
+
return new ParentBasedSampler({ root: new AlwaysOffSampler() });
|
|
177
|
+
case "parentbased_traceidratio":
|
|
178
|
+
return parentBasedRatio(samplerRatioArg(fallbackRatio));
|
|
179
|
+
default:
|
|
180
|
+
warnConfigurationOnce(
|
|
181
|
+
"unsupported-sampler",
|
|
182
|
+
"unsupported OTEL_TRACES_SAMPLER; using the Moda environment fallback"
|
|
183
|
+
);
|
|
184
|
+
return parentBasedRatio(fallbackRatio);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
function createTraceProcessor(endpoint) {
|
|
188
|
+
if (!endpoint) {
|
|
189
|
+
return void 0;
|
|
190
|
+
}
|
|
191
|
+
try {
|
|
192
|
+
return new BatchSpanProcessor(
|
|
193
|
+
new OTLPTraceExporter({
|
|
194
|
+
url: endpoint,
|
|
195
|
+
concurrencyLimit: positiveInt("OTEL_EXPORTER_OTLP_TRACES_CONCURRENCY_LIMIT", 2)
|
|
196
|
+
}),
|
|
197
|
+
{
|
|
198
|
+
maxQueueSize: positiveInt("OTEL_BSP_MAX_QUEUE_SIZE", 2048),
|
|
199
|
+
maxExportBatchSize: positiveInt("OTEL_BSP_MAX_EXPORT_BATCH_SIZE", 512),
|
|
200
|
+
scheduledDelayMillis: positiveInt("OTEL_BSP_SCHEDULE_DELAY", 5e3),
|
|
201
|
+
exportTimeoutMillis: positiveInt("OTEL_BSP_EXPORT_TIMEOUT", 3e4)
|
|
202
|
+
}
|
|
203
|
+
);
|
|
204
|
+
} catch {
|
|
205
|
+
warnConfigurationOnce(
|
|
206
|
+
"trace-exporter",
|
|
207
|
+
"trace exporter unavailable; trace export disabled"
|
|
208
|
+
);
|
|
209
|
+
return void 0;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
function createMetricReader(endpoint) {
|
|
213
|
+
if (!endpoint) {
|
|
214
|
+
return void 0;
|
|
215
|
+
}
|
|
216
|
+
try {
|
|
217
|
+
const interval = positiveInt("OTEL_METRIC_EXPORT_INTERVAL", 6e4);
|
|
218
|
+
const timeout = Math.min(
|
|
219
|
+
positiveInt("OTEL_METRIC_EXPORT_TIMEOUT", 3e4),
|
|
220
|
+
interval
|
|
221
|
+
);
|
|
222
|
+
return new PeriodicExportingMetricReader({
|
|
223
|
+
exporter: new OTLPMetricExporter({
|
|
224
|
+
url: endpoint,
|
|
225
|
+
concurrencyLimit: positiveInt("OTEL_EXPORTER_OTLP_METRICS_CONCURRENCY_LIMIT", 2)
|
|
226
|
+
}),
|
|
227
|
+
exportIntervalMillis: interval,
|
|
228
|
+
exportTimeoutMillis: timeout
|
|
229
|
+
});
|
|
230
|
+
} catch {
|
|
231
|
+
warnConfigurationOnce(
|
|
232
|
+
"metric-exporter",
|
|
233
|
+
"metric exporter unavailable; metric export disabled"
|
|
234
|
+
);
|
|
235
|
+
return void 0;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
function samplerRatioArg(fallback) {
|
|
239
|
+
const raw = nonEmpty(process.env.OTEL_TRACES_SAMPLER_ARG);
|
|
240
|
+
if (!raw) {
|
|
241
|
+
return fallback;
|
|
242
|
+
}
|
|
243
|
+
const value = Number(raw);
|
|
244
|
+
if (!Number.isFinite(value) || value < 0 || value > 1) {
|
|
245
|
+
warnConfigurationOnce(
|
|
246
|
+
"sampler-ratio-arg",
|
|
247
|
+
"invalid OTEL_TRACES_SAMPLER_ARG; using the Moda environment fallback"
|
|
248
|
+
);
|
|
249
|
+
return fallback;
|
|
250
|
+
}
|
|
251
|
+
return value;
|
|
252
|
+
}
|
|
253
|
+
function parentBasedRatio(ratio) {
|
|
254
|
+
return new ParentBasedSampler({ root: new TraceIdRatioBasedSampler(ratio) });
|
|
255
|
+
}
|
|
256
|
+
function disabledRuntime(serviceName, environment, otelLogs, loki) {
|
|
257
|
+
let shutdownPromise;
|
|
258
|
+
return {
|
|
259
|
+
enabled: false,
|
|
260
|
+
serviceName,
|
|
261
|
+
environment,
|
|
262
|
+
forceFlush: () => settleAll([
|
|
263
|
+
otelLogs?.forceFlush(),
|
|
264
|
+
loki?.forceFlush()
|
|
265
|
+
]),
|
|
266
|
+
shutdown: () => {
|
|
267
|
+
shutdownPromise ??= settleAll([
|
|
268
|
+
otelLogs?.shutdown(),
|
|
269
|
+
loki?.shutdown()
|
|
270
|
+
]);
|
|
271
|
+
return shutdownPromise;
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
function safeInitLogging(fn, destination) {
|
|
276
|
+
try {
|
|
277
|
+
return fn();
|
|
278
|
+
} catch {
|
|
279
|
+
warnConfigurationOnce(
|
|
280
|
+
`logging-${destination}`,
|
|
281
|
+
`${destination} initialization failed; destination disabled`
|
|
282
|
+
);
|
|
283
|
+
return void 0;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
async function settleAll(operations) {
|
|
287
|
+
await Promise.allSettled(operations);
|
|
288
|
+
}
|
|
289
|
+
function warnConfigurationOnce(key, message) {
|
|
290
|
+
if (configurationWarnings.has(key)) {
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
configurationWarnings.add(key);
|
|
294
|
+
try {
|
|
295
|
+
console.warn(`[moda-observability] ${message.slice(0, 240)}`);
|
|
296
|
+
} catch {
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
function boundedError(error) {
|
|
300
|
+
return error instanceof Error ? error.message.slice(0, 300) : "observability-init-failed";
|
|
301
|
+
}
|
|
302
|
+
function setSanitizedUrlAttributes(span, requestPath) {
|
|
303
|
+
if (!requestPath) {
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
span.setAttribute("url.path", sanitizedPath(requestPath));
|
|
307
|
+
span.setAttribute("url.query", "");
|
|
308
|
+
}
|
|
309
|
+
function sanitizedPath(value) {
|
|
310
|
+
return value.split(/[?#]/, 1)[0] || "/";
|
|
311
|
+
}
|
|
312
|
+
function nonEmpty(value) {
|
|
313
|
+
const normalized = value?.trim();
|
|
314
|
+
return normalized ? normalized : void 0;
|
|
315
|
+
}
|
|
316
|
+
function envBool(name) {
|
|
317
|
+
const value = nonEmpty(process.env[name])?.toLowerCase();
|
|
318
|
+
if (["true", "1", "yes", "on"].includes(value ?? "")) {
|
|
319
|
+
return true;
|
|
320
|
+
}
|
|
321
|
+
if (["false", "0", "no", "off"].includes(value ?? "")) {
|
|
322
|
+
return false;
|
|
323
|
+
}
|
|
324
|
+
return void 0;
|
|
325
|
+
}
|
|
326
|
+
function positiveInt(name, fallback) {
|
|
327
|
+
const raw = nonEmpty(process.env[name]);
|
|
328
|
+
if (!raw) {
|
|
329
|
+
return fallback;
|
|
330
|
+
}
|
|
331
|
+
const value = Number(raw);
|
|
332
|
+
return Number.isFinite(value) && value > 0 ? Math.floor(value) : fallback;
|
|
333
|
+
}
|
|
334
|
+
function signalEndpoint(signal, suffix) {
|
|
335
|
+
const exporter = nonEmpty(process.env[`OTEL_${signal}_EXPORTER`]);
|
|
336
|
+
if (exporter && exporter.toLowerCase() !== "otlp") {
|
|
337
|
+
return void 0;
|
|
338
|
+
}
|
|
339
|
+
const exact = nonEmpty(process.env[`OTEL_EXPORTER_OTLP_${signal}_ENDPOINT`]);
|
|
340
|
+
if (exact) {
|
|
341
|
+
return exact;
|
|
342
|
+
}
|
|
343
|
+
const base = nonEmpty(process.env.OTEL_EXPORTER_OTLP_ENDPOINT)?.replace(/\/+$/, "");
|
|
344
|
+
return base ? `${base}/v1/${suffix}` : void 0;
|
|
345
|
+
}
|
|
346
|
+
export {
|
|
347
|
+
getNodeObservabilityRuntime,
|
|
348
|
+
initNodeObservability,
|
|
349
|
+
resolveDeploymentEnvironmentName,
|
|
350
|
+
resolveSampler
|
|
351
|
+
};
|
|
352
|
+
//# sourceMappingURL=node.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/observability/node.ts"],"sourcesContent":["import { OTLPMetricExporter } from \"@opentelemetry/exporter-metrics-otlp-http\";\nimport { OTLPTraceExporter } from \"@opentelemetry/exporter-trace-otlp-http\";\nimport { PrismaInstrumentation } from \"@prisma/instrumentation\";\nimport { HttpInstrumentation } from \"@opentelemetry/instrumentation-http\";\nimport { UndiciInstrumentation } from \"@opentelemetry/instrumentation-undici\";\nimport type { Span } from \"@opentelemetry/api\";\nimport { resourceFromAttributes } from \"@opentelemetry/resources\";\nimport { PeriodicExportingMetricReader } from \"@opentelemetry/sdk-metrics\";\nimport { NodeSDK } from \"@opentelemetry/sdk-node\";\nimport {\n AlwaysOffSampler,\n AlwaysOnSampler,\n BatchSpanProcessor,\n ParentBasedSampler,\n TraceIdRatioBasedSampler,\n type Sampler,\n} from \"@opentelemetry/sdk-trace-base\";\nimport {\n initNodeLokiLogging,\n initNodeOpenTelemetryLogging,\n type NodeLokiLoggingRuntime,\n type NodeOpenTelemetryLoggingRuntime,\n} from \"../logging/node.js\";\n\nconst DEFAULT_NAMESPACE = \"moda-interact\";\nconst configurationWarnings = new Set<string>();\n\nexport type NodeInstrumentProfile = {\n http?: boolean;\n fetch?: boolean;\n prisma?: boolean;\n};\n\nexport type NodeObservabilityOptions = {\n serviceName: string;\n environment?: string;\n serviceNamespace?: string;\n instrument?: NodeInstrumentProfile;\n traceSampleRatio?: number;\n forceEnable?: boolean;\n};\n\nexport type NodeObservabilityRuntime = {\n enabled: boolean;\n serviceName: string;\n environment: string;\n error?: string;\n forceFlush(): Promise<void>;\n shutdown(): Promise<void>;\n};\n\nlet runtime: NodeObservabilityRuntime | undefined;\n\nexport function resolveDeploymentEnvironmentName(): string {\n return nonEmpty(process.env.DEPLOYMENT_ENVIRONMENT_NAME)\n ?? nonEmpty(process.env.OTEL_DEPLOYMENT_ENVIRONMENT)\n ?? nonEmpty(process.env.NODE_ENV)\n ?? \"development\";\n}\n\nexport function initNodeObservability(\n options: NodeObservabilityOptions,\n): NodeObservabilityRuntime {\n if (runtime) {\n return runtime;\n }\n\n const serviceName = nonEmpty(options?.serviceName) ?? \"unknown-service\";\n const environment = nonEmpty(options?.environment)\n ?? resolveDeploymentEnvironmentName();\n const serviceNamespace = nonEmpty(options?.serviceNamespace)\n ?? DEFAULT_NAMESPACE;\n\n if (!nonEmpty(options?.serviceName)) {\n warnConfigurationOnce(\n \"service-name\",\n \"observability disabled: serviceName must be non-empty\",\n );\n runtime = disabledRuntime(serviceName, environment);\n return runtime;\n }\n\n const otelLogs = safeInitLogging(() => initNodeOpenTelemetryLogging({\n serviceName,\n environment,\n serviceNamespace,\n }), \"OpenTelemetry Logs\");\n const loki = safeInitLogging(() => initNodeLokiLogging({\n serviceName,\n environment,\n serviceNamespace,\n }), \"Loki\");\n\n if (!options.forceEnable && envBool(\"OTEL_SDK_DISABLED\") === true) {\n runtime = disabledRuntime(serviceName, environment, otelLogs, loki);\n return runtime;\n }\n\n const traceEndpoint = signalEndpoint(\"TRACES\", \"traces\");\n const metricEndpoint = signalEndpoint(\"METRICS\", \"metrics\");\n\n if (!options.forceEnable && !traceEndpoint && !metricEndpoint) {\n runtime = disabledRuntime(serviceName, environment, otelLogs, loki);\n return runtime;\n }\n\n try {\n const resource = resourceFromAttributes({\n \"service.namespace\": serviceNamespace,\n \"service.name\": serviceName,\n \"deployment.environment.name\": environment,\n });\n const profile = options.instrument ?? {};\n const instrumentations = [\n ...(profile.http ? [new HttpInstrumentation({\n requestHook: (span, request) => {\n const path = \"path\" in request ? request.path : request.url;\n setSanitizedUrlAttributes(span, path);\n if (\n \"protocol\" in request\n && \"host\" in request\n && typeof request.protocol === \"string\"\n && typeof request.host === \"string\"\n ) {\n span.setAttribute(\n \"url.full\",\n `${request.protocol}//${request.host}${sanitizedPath(path ?? \"/\")}`,\n );\n }\n },\n })] : []),\n ...(profile.fetch ? [new UndiciInstrumentation({\n requestHook: (span, request) => {\n const path = sanitizedPath(request.path);\n span.setAttribute(\"url.path\", path);\n span.setAttribute(\"url.full\", `${request.origin}${path}`);\n span.setAttribute(\"url.query\", \"\");\n },\n })] : []),\n ...(profile.prisma ? [new PrismaInstrumentation()] : []),\n ];\n\n const traceProcessor = createTraceProcessor(traceEndpoint);\n const metricReader = createMetricReader(metricEndpoint);\n\n if (!traceProcessor && !metricReader) {\n runtime = {\n ...disabledRuntime(serviceName, environment, otelLogs, loki),\n error: \"observability-exporters-unavailable\",\n };\n return runtime;\n }\n\n const sdk = new NodeSDK({\n resource,\n autoDetectResources: false,\n sampler: resolveSampler(options.traceSampleRatio, environment),\n spanProcessors: traceProcessor ? [traceProcessor] : [],\n ...(metricReader ? { metricReader } : {}),\n instrumentations,\n });\n\n sdk.start();\n\n let shutdownPromise: Promise<void> | undefined;\n runtime = {\n enabled: true,\n serviceName,\n environment,\n forceFlush: async () => {\n await Promise.allSettled([\n traceProcessor?.forceFlush(),\n metricReader?.forceFlush(),\n otelLogs?.forceFlush(),\n loki?.forceFlush(),\n ]);\n },\n shutdown: () => {\n shutdownPromise ??= settleAll([\n Promise.resolve(sdk.shutdown()),\n otelLogs?.shutdown(),\n loki?.shutdown(),\n ]);\n return shutdownPromise;\n },\n };\n return runtime;\n } catch (error) {\n warnConfigurationOnce(\n \"runtime-init\",\n \"observability disabled: Node runtime initialization failed\",\n );\n runtime = {\n ...disabledRuntime(serviceName, environment, otelLogs, loki),\n error: boundedError(error),\n };\n return runtime;\n }\n}\n\nexport function getNodeObservabilityRuntime():\n | NodeObservabilityRuntime\n | undefined {\n return runtime;\n}\n\nexport function resolveSampler(\n explicitRatio: number | undefined,\n environment: string,\n): Sampler {\n const fallbackRatio = environment === \"test\" ? 1 : 0.1;\n\n if (explicitRatio !== undefined) {\n if (!Number.isFinite(explicitRatio) || explicitRatio < 0 || explicitRatio > 1) {\n warnConfigurationOnce(\n \"explicit-sampler-ratio\",\n \"invalid traceSampleRatio; using the Moda environment fallback\",\n );\n return parentBasedRatio(fallbackRatio);\n }\n return parentBasedRatio(explicitRatio);\n }\n\n const requested = nonEmpty(process.env.OTEL_TRACES_SAMPLER)?.toLowerCase();\n if (!requested) {\n return parentBasedRatio(fallbackRatio);\n }\n\n switch (requested) {\n case \"always_on\":\n return new AlwaysOnSampler();\n case \"always_off\":\n return new AlwaysOffSampler();\n case \"traceidratio\":\n return new TraceIdRatioBasedSampler(samplerRatioArg(fallbackRatio));\n case \"parentbased_always_on\":\n return new ParentBasedSampler({ root: new AlwaysOnSampler() });\n case \"parentbased_always_off\":\n return new ParentBasedSampler({ root: new AlwaysOffSampler() });\n case \"parentbased_traceidratio\":\n return parentBasedRatio(samplerRatioArg(fallbackRatio));\n default:\n warnConfigurationOnce(\n \"unsupported-sampler\",\n \"unsupported OTEL_TRACES_SAMPLER; using the Moda environment fallback\",\n );\n return parentBasedRatio(fallbackRatio);\n }\n}\n\nfunction createTraceProcessor(\n endpoint: string | undefined,\n): BatchSpanProcessor | undefined {\n if (!endpoint) {\n return undefined;\n }\n\n try {\n return new BatchSpanProcessor(\n new OTLPTraceExporter({\n url: endpoint,\n concurrencyLimit: positiveInt(\"OTEL_EXPORTER_OTLP_TRACES_CONCURRENCY_LIMIT\", 2),\n }),\n {\n maxQueueSize: positiveInt(\"OTEL_BSP_MAX_QUEUE_SIZE\", 2048),\n maxExportBatchSize: positiveInt(\"OTEL_BSP_MAX_EXPORT_BATCH_SIZE\", 512),\n scheduledDelayMillis: positiveInt(\"OTEL_BSP_SCHEDULE_DELAY\", 5_000),\n exportTimeoutMillis: positiveInt(\"OTEL_BSP_EXPORT_TIMEOUT\", 30_000),\n },\n );\n } catch {\n warnConfigurationOnce(\n \"trace-exporter\",\n \"trace exporter unavailable; trace export disabled\",\n );\n return undefined;\n }\n}\n\nfunction createMetricReader(\n endpoint: string | undefined,\n): PeriodicExportingMetricReader | undefined {\n if (!endpoint) {\n return undefined;\n }\n\n try {\n const interval = positiveInt(\"OTEL_METRIC_EXPORT_INTERVAL\", 60_000);\n const timeout = Math.min(\n positiveInt(\"OTEL_METRIC_EXPORT_TIMEOUT\", 30_000),\n interval,\n );\n\n return new PeriodicExportingMetricReader({\n exporter: new OTLPMetricExporter({\n url: endpoint,\n concurrencyLimit: positiveInt(\"OTEL_EXPORTER_OTLP_METRICS_CONCURRENCY_LIMIT\", 2),\n }),\n exportIntervalMillis: interval,\n exportTimeoutMillis: timeout,\n });\n } catch {\n warnConfigurationOnce(\n \"metric-exporter\",\n \"metric exporter unavailable; metric export disabled\",\n );\n return undefined;\n }\n}\n\nfunction samplerRatioArg(fallback: number): number {\n const raw = nonEmpty(process.env.OTEL_TRACES_SAMPLER_ARG);\n if (!raw) {\n return fallback;\n }\n\n const value = Number(raw);\n if (!Number.isFinite(value) || value < 0 || value > 1) {\n warnConfigurationOnce(\n \"sampler-ratio-arg\",\n \"invalid OTEL_TRACES_SAMPLER_ARG; using the Moda environment fallback\",\n );\n return fallback;\n }\n return value;\n}\n\nfunction parentBasedRatio(ratio: number): ParentBasedSampler {\n return new ParentBasedSampler({ root: new TraceIdRatioBasedSampler(ratio) });\n}\n\nfunction disabledRuntime(\n serviceName: string,\n environment: string,\n otelLogs?: NodeOpenTelemetryLoggingRuntime,\n loki?: NodeLokiLoggingRuntime,\n): NodeObservabilityRuntime {\n let shutdownPromise: Promise<void> | undefined;\n\n return {\n enabled: false,\n serviceName,\n environment,\n forceFlush: () => settleAll([\n otelLogs?.forceFlush(),\n loki?.forceFlush(),\n ]),\n shutdown: () => {\n shutdownPromise ??= settleAll([\n otelLogs?.shutdown(),\n loki?.shutdown(),\n ]);\n return shutdownPromise;\n },\n };\n}\n\nfunction safeInitLogging<T>(fn: () => T, destination: string): T | undefined {\n try {\n return fn();\n } catch {\n warnConfigurationOnce(\n `logging-${destination}`,\n `${destination} initialization failed; destination disabled`,\n );\n return undefined;\n }\n}\n\nasync function settleAll(\n operations: Array<Promise<unknown> | undefined>,\n): Promise<void> {\n await Promise.allSettled(operations);\n}\n\nfunction warnConfigurationOnce(key: string, message: string): void {\n if (configurationWarnings.has(key)) {\n return;\n }\n configurationWarnings.add(key);\n\n try {\n console.warn(`[moda-observability] ${message.slice(0, 240)}`);\n } catch {\n // Configuration diagnostics must not affect application startup.\n }\n}\n\nfunction boundedError(error: unknown): string {\n return error instanceof Error\n ? error.message.slice(0, 300)\n : \"observability-init-failed\";\n}\n\nfunction setSanitizedUrlAttributes(\n span: Span,\n requestPath: string | undefined,\n): void {\n if (!requestPath) {\n return;\n }\n span.setAttribute(\"url.path\", sanitizedPath(requestPath));\n span.setAttribute(\"url.query\", \"\");\n}\n\nfunction sanitizedPath(value: string): string {\n return value.split(/[?#]/, 1)[0] || \"/\";\n}\n\nfunction nonEmpty(value: string | undefined): string | undefined {\n const normalized = value?.trim();\n return normalized ? normalized : undefined;\n}\n\nfunction envBool(name: string): boolean | undefined {\n const value = nonEmpty(process.env[name])?.toLowerCase();\n if ([\"true\", \"1\", \"yes\", \"on\"].includes(value ?? \"\")) {\n return true;\n }\n if ([\"false\", \"0\", \"no\", \"off\"].includes(value ?? \"\")) {\n return false;\n }\n return undefined;\n}\n\nfunction positiveInt(name: string, fallback: number): number {\n const raw = nonEmpty(process.env[name]);\n if (!raw) {\n return fallback;\n }\n const value = Number(raw);\n return Number.isFinite(value) && value > 0 ? Math.floor(value) : fallback;\n}\n\nfunction signalEndpoint(\n signal: \"TRACES\" | \"METRICS\",\n suffix: string,\n): string | undefined {\n const exporter = nonEmpty(process.env[`OTEL_${signal}_EXPORTER`]);\n if (exporter && exporter.toLowerCase() !== \"otlp\") {\n return undefined;\n }\n\n const exact = nonEmpty(process.env[`OTEL_EXPORTER_OTLP_${signal}_ENDPOINT`]);\n if (exact) {\n return exact;\n }\n\n const base = nonEmpty(process.env.OTEL_EXPORTER_OTLP_ENDPOINT)\n ?.replace(/\\/+$/, \"\");\n return base ? `${base}/v1/${suffix}` : undefined;\n}"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@modainteract/moda-interact-shared",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -27,6 +27,22 @@
|
|
|
27
27
|
"./logging/node": {
|
|
28
28
|
"types": "./dist/logging/node.d.ts",
|
|
29
29
|
"import": "./dist/logging/node.js"
|
|
30
|
+
},
|
|
31
|
+
"./observability": {
|
|
32
|
+
"types": "./dist/observability/index.d.ts",
|
|
33
|
+
"import": "./dist/observability/index.js"
|
|
34
|
+
},
|
|
35
|
+
"./observability/node": {
|
|
36
|
+
"types": "./dist/observability/node.d.ts",
|
|
37
|
+
"import": "./dist/observability/node.js"
|
|
38
|
+
},
|
|
39
|
+
"./observability/bullmq": {
|
|
40
|
+
"types": "./dist/observability/bullmq.d.ts",
|
|
41
|
+
"import": "./dist/observability/bullmq.js"
|
|
42
|
+
},
|
|
43
|
+
"./observability/genai": {
|
|
44
|
+
"types": "./dist/observability/genai.d.ts",
|
|
45
|
+
"import": "./dist/observability/genai.js"
|
|
30
46
|
}
|
|
31
47
|
},
|
|
32
48
|
"files": [
|
|
@@ -41,15 +57,27 @@
|
|
|
41
57
|
"dependencies": {
|
|
42
58
|
"@opentelemetry/api": "^1.9.0",
|
|
43
59
|
"@opentelemetry/api-logs": "0.221.0",
|
|
60
|
+
"@opentelemetry/context-async-hooks": "^2.10.0",
|
|
44
61
|
"@opentelemetry/exporter-logs-otlp-http": "0.221.0",
|
|
62
|
+
"@opentelemetry/exporter-metrics-otlp-http": "0.221.0",
|
|
63
|
+
"@opentelemetry/exporter-trace-otlp-http": "0.221.0",
|
|
64
|
+
"@opentelemetry/instrumentation-http": "0.221.0",
|
|
65
|
+
"@opentelemetry/instrumentation-undici": "0.31.0",
|
|
45
66
|
"@opentelemetry/resources": "^2.10.0",
|
|
46
67
|
"@opentelemetry/sdk-logs": "0.221.0",
|
|
68
|
+
"@opentelemetry/sdk-metrics": "^2.10.0",
|
|
69
|
+
"@opentelemetry/sdk-node": "0.221.0",
|
|
70
|
+
"@opentelemetry/sdk-trace-base": "^2.10.0",
|
|
71
|
+
"@prisma/instrumentation": "^6.19.3",
|
|
72
|
+
"bullmq-otel": "^2.0.1",
|
|
47
73
|
"winston": "3.19.0",
|
|
48
74
|
"winston-loki": "6.1.7",
|
|
49
75
|
"zod": "^4.0.0"
|
|
50
76
|
},
|
|
51
77
|
"devDependencies": {
|
|
52
78
|
"@types/node": "^26.4.0",
|
|
79
|
+
"bullmq": "^6.3.1",
|
|
80
|
+
"ioredis": "^6.0.0",
|
|
53
81
|
"tsup": "^8.0.0",
|
|
54
82
|
"tsx": "^4.23.12",
|
|
55
83
|
"typescript": "^5.0.0"
|