@senad-d/observme 0.1.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/.env.example +57 -0
- package/CHANGELOG.md +52 -0
- package/LICENSE +21 -0
- package/ObservMe-Production-Docs/00-README.md +79 -0
- package/ObservMe-Production-Docs/01-requirements-and-scope.md +207 -0
- package/ObservMe-Production-Docs/02-reference-architecture.md +306 -0
- package/ObservMe-Production-Docs/03-pi-event-and-session-model.md +266 -0
- package/ObservMe-Production-Docs/04-telemetry-semantic-conventions.md +722 -0
- package/ObservMe-Production-Docs/05-otel-pipeline-and-collector.md +355 -0
- package/ObservMe-Production-Docs/06-security-privacy-redaction.md +294 -0
- package/ObservMe-Production-Docs/07-extension-implementation-blueprint.md +447 -0
- package/ObservMe-Production-Docs/08-query-grafana-integration.md +276 -0
- package/ObservMe-Production-Docs/09-dashboards-alerts-slos.md +640 -0
- package/ObservMe-Production-Docs/10-testing-release-operations.md +319 -0
- package/ObservMe-Production-Docs/11-deployment-runbooks.md +203 -0
- package/ObservMe-Production-Docs/12-configuration-reference.md +337 -0
- package/ObservMe-Production-Docs/13-source-notes.md +33 -0
- package/ObservMe-Production-Docs/pi-session-format.md +427 -0
- package/README.md +356 -0
- package/SECURITY.md +45 -0
- package/dashboards/observme-agent-node-graphs.json +250 -0
- package/dashboards/observme-agents.json +1880 -0
- package/dashboards/observme-alerts.yaml +113 -0
- package/dashboards/observme-branches-compactions.json +1042 -0
- package/dashboards/observme-cost.json +1254 -0
- package/dashboards/observme-errors.json +1659 -0
- package/dashboards/observme-export-health.json +1802 -0
- package/dashboards/observme-latency.json +1494 -0
- package/dashboards/observme-llm-conversations.json +730 -0
- package/dashboards/observme-logs-llm.json +644 -0
- package/dashboards/observme-models.json +933 -0
- package/dashboards/observme-overview.json +2129 -0
- package/dashboards/observme-slo-health.json +737 -0
- package/dashboards/observme-slos.yaml +56 -0
- package/dashboards/observme-tools.json +902 -0
- package/dashboards/observme-trace-journey.json +1793 -0
- package/docs/STRUCTURE.md +49 -0
- package/docs/agent-subagent-observability-requirements.md +997 -0
- package/docs/compatibility-matrix.md +39 -0
- package/docs/configuration-tui-design-standard.md +767 -0
- package/docs/configuration.md +38 -0
- package/docs/review-validation.md +115 -0
- package/docs/validation-flow.md +117 -0
- package/examples/collector.yaml +123 -0
- package/examples/observme.yaml +131 -0
- package/img/demo.gif +0 -0
- package/img/icon.svg +47 -0
- package/package.json +103 -0
- package/src/commands/obs-agents-runtime.ts +150 -0
- package/src/commands/obs-agents.ts +491 -0
- package/src/commands/obs-args.ts +63 -0
- package/src/commands/obs-backfill.ts +1334 -0
- package/src/commands/obs-command-support.ts +43 -0
- package/src/commands/obs-cost.ts +228 -0
- package/src/commands/obs-diagnostics.ts +22 -0
- package/src/commands/obs-errors.ts +156 -0
- package/src/commands/obs-health.ts +301 -0
- package/src/commands/obs-link.ts +90 -0
- package/src/commands/obs-logs.ts +194 -0
- package/src/commands/obs-loki-summary.ts +184 -0
- package/src/commands/obs-session.ts +259 -0
- package/src/commands/obs-status.ts +359 -0
- package/src/commands/obs-tools.ts +274 -0
- package/src/commands/obs-trace.ts +411 -0
- package/src/commands/obs.ts +211 -0
- package/src/config/bootstrap-project-config.ts +300 -0
- package/src/config/defaults.ts +143 -0
- package/src/config/load-config.ts +631 -0
- package/src/config/project-paths.ts +61 -0
- package/src/config/schema.ts +405 -0
- package/src/config/validate.ts +456 -0
- package/src/constants.ts +4 -0
- package/src/diagnostics/sanitize.ts +6 -0
- package/src/extension.ts +38 -0
- package/src/otel/logs.ts +160 -0
- package/src/otel/metrics.ts +165 -0
- package/src/otel/otlp-endpoint.ts +10 -0
- package/src/otel/sdk.ts +114 -0
- package/src/otel/shutdown.ts +102 -0
- package/src/otel/traces.ts +166 -0
- package/src/pi/agent-lineage.ts +378 -0
- package/src/pi/agent-tree-tracker.ts +258 -0
- package/src/pi/event-handlers/agent-turn.ts +155 -0
- package/src/pi/event-handlers/lifecycle.ts +642 -0
- package/src/pi/event-handlers/llm.ts +115 -0
- package/src/pi/event-handlers/session-events.ts +159 -0
- package/src/pi/event-handlers/tool-bash.ts +275 -0
- package/src/pi/handler-internals.ts +2154 -0
- package/src/pi/handler-runtime.ts +633 -0
- package/src/pi/handler-types.ts +261 -0
- package/src/pi/handlers.ts +75 -0
- package/src/pi/subagent-spawn.ts +975 -0
- package/src/pi/subagent-types.ts +29 -0
- package/src/privacy/content-capture.ts +104 -0
- package/src/privacy/hash.ts +93 -0
- package/src/privacy/redact.ts +619 -0
- package/src/privacy/secret-patterns.ts +185 -0
- package/src/privacy/truncate.ts +69 -0
- package/src/query/grafana-readiness.ts +164 -0
- package/src/query/grafana-transport.ts +481 -0
- package/src/query/grafana.ts +371 -0
- package/src/query/loki.ts +332 -0
- package/src/query/prometheus.ts +388 -0
- package/src/query/tempo.ts +332 -0
- package/src/safety/sensitive-input.ts +208 -0
- package/src/semconv/attributes.ts +279 -0
- package/src/semconv/metrics.ts +146 -0
- package/src/semconv/spans.ts +19 -0
- package/src/semconv/values.ts +13 -0
- package/src/util/bounded-map.ts +97 -0
- package/tsconfig.json +15 -0
|
@@ -0,0 +1,633 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { performance } from "node:perf_hooks";
|
|
3
|
+
import type { Histogram, Span } from "@opentelemetry/api";
|
|
4
|
+
import { trace } from "@opentelemetry/api";
|
|
5
|
+
import type { ObservMeConfig } from "../config/schema.ts";
|
|
6
|
+
import { ObservMeLogSdk } from "../otel/logs.ts";
|
|
7
|
+
import { ObservMeMetricSdk } from "../otel/metrics.ts";
|
|
8
|
+
import { startOtelSdk } from "../otel/sdk.ts";
|
|
9
|
+
import { ObservMeTraceSdk } from "../otel/traces.ts";
|
|
10
|
+
import { inheritTenantSaltEnvironment } from "../privacy/hash.ts";
|
|
11
|
+
import { LOG_ATTRIBUTES, RESOURCE_ATTRIBUTES } from "../semconv/attributes.ts";
|
|
12
|
+
import {
|
|
13
|
+
LOG_EVENT_NAMES,
|
|
14
|
+
OBSERVME_COUNTER_METRIC_NAMES,
|
|
15
|
+
OBSERVME_GAUGE_METRIC_NAMES,
|
|
16
|
+
OBSERVME_HISTOGRAM_METRIC_NAMES,
|
|
17
|
+
OBSERVME_TOKEN_COST_COUNTER_METRIC_NAMES,
|
|
18
|
+
OFFICIAL_GENAI_METRIC_NAMES,
|
|
19
|
+
} from "../semconv/metrics.ts";
|
|
20
|
+
import { BoundedMap, type BoundedMapEviction } from "../util/bounded-map.ts";
|
|
21
|
+
import type { AgentLineageContext } from "./agent-lineage.ts";
|
|
22
|
+
import { buildResourceLineageAttributes } from "./agent-lineage.ts";
|
|
23
|
+
import type { AgentTreeNode } from "./agent-tree-tracker.ts";
|
|
24
|
+
import { AgentTreeTracker } from "./agent-tree-tracker.ts";
|
|
25
|
+
import {
|
|
26
|
+
emitLifecycleLog,
|
|
27
|
+
errorClass,
|
|
28
|
+
evictSpan,
|
|
29
|
+
evictSubagentSpawnState,
|
|
30
|
+
evictToolCallState,
|
|
31
|
+
evictWaitJoinState,
|
|
32
|
+
isRecord,
|
|
33
|
+
normalizeMetricValue,
|
|
34
|
+
readBoolean,
|
|
35
|
+
readString,
|
|
36
|
+
readUnknown,
|
|
37
|
+
recordTelemetryDrop,
|
|
38
|
+
stringifyAttributes,
|
|
39
|
+
type TelemetryDropTarget,
|
|
40
|
+
} from "./handler-internals.ts";
|
|
41
|
+
import type {
|
|
42
|
+
AttributeMap,
|
|
43
|
+
CompositeOtelSignalSdk,
|
|
44
|
+
Handler,
|
|
45
|
+
HandlerErrorRecorder,
|
|
46
|
+
HandlerRegistration,
|
|
47
|
+
HandlerSessionState,
|
|
48
|
+
ObservMeHandlerContext,
|
|
49
|
+
ObservMeMetrics,
|
|
50
|
+
ObservMePiApi,
|
|
51
|
+
ObservMeTelemetrySession,
|
|
52
|
+
SpanRegistry,
|
|
53
|
+
StartSessionTelemetryOptions,
|
|
54
|
+
TelemetryMeter,
|
|
55
|
+
ToolCallState,
|
|
56
|
+
TurnSequenceRegistry,
|
|
57
|
+
} from "./handler-types.ts";
|
|
58
|
+
import type { AgentWaitJoinState, SubagentSpawnState } from "./subagent-types.ts";
|
|
59
|
+
|
|
60
|
+
export const OBSERVME_SEMCONV_VERSION = "0.1.0";
|
|
61
|
+
|
|
62
|
+
const piApiCompatibilityErrorMessage =
|
|
63
|
+
"ObservMe/Pi API compatibility error: expected Pi ExtensionAPI with on(eventName, handler) before registering ObservMe handlers.";
|
|
64
|
+
const eventRegistrationOrder = [
|
|
65
|
+
"session_start",
|
|
66
|
+
"agent_start",
|
|
67
|
+
"turn_start",
|
|
68
|
+
"before_provider_request",
|
|
69
|
+
"after_provider_response",
|
|
70
|
+
"message_end",
|
|
71
|
+
"tool_execution_start",
|
|
72
|
+
"tool_call",
|
|
73
|
+
"tool_result",
|
|
74
|
+
"tool_execution_end",
|
|
75
|
+
"user_bash",
|
|
76
|
+
"bashExecution",
|
|
77
|
+
"model_select",
|
|
78
|
+
"model_change",
|
|
79
|
+
"thinking_level_select",
|
|
80
|
+
"thinking_level_change",
|
|
81
|
+
"session_before_tree",
|
|
82
|
+
"session_tree",
|
|
83
|
+
"session_compact",
|
|
84
|
+
"turn_end",
|
|
85
|
+
"agent_end",
|
|
86
|
+
"session_shutdown",
|
|
87
|
+
] as const;
|
|
88
|
+
const eventRegistrationIndexes = createEventRegistrationIndexes();
|
|
89
|
+
|
|
90
|
+
let defaultHandlerErrorRecorder: HandlerErrorRecorder = recordInternalErrorFallback;
|
|
91
|
+
|
|
92
|
+
export class HandlerRegistrar {
|
|
93
|
+
readonly #api: ObservMePiApi;
|
|
94
|
+
readonly #state: HandlerSessionState;
|
|
95
|
+
readonly #errorRecorder: HandlerErrorRecorder;
|
|
96
|
+
readonly #registrations: HandlerRegistration[] = [];
|
|
97
|
+
|
|
98
|
+
constructor(api: ObservMePiApi, state: HandlerSessionState, errorRecorder: HandlerErrorRecorder) {
|
|
99
|
+
this.#api = api;
|
|
100
|
+
this.#state = state;
|
|
101
|
+
this.#errorRecorder = errorRecorder;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
add(eventName: string, handler: Handler): void {
|
|
105
|
+
this.#registrations.push({ eventName, handler });
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
commit(): void {
|
|
109
|
+
this.#registrations.sort(compareHandlerRegistrations);
|
|
110
|
+
for (const registration of this.#registrations) {
|
|
111
|
+
registerObservedHandler(
|
|
112
|
+
this.#api,
|
|
113
|
+
registration.eventName,
|
|
114
|
+
registration.handler,
|
|
115
|
+
this.#state,
|
|
116
|
+
this.#errorRecorder,
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export class SerializedLifecycleQueue {
|
|
123
|
+
#previous = Promise.resolve();
|
|
124
|
+
|
|
125
|
+
wrap(fn: Handler): Handler {
|
|
126
|
+
return runSerializedLifecycleHandler.bind(undefined, this, fn);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
run(fn: Handler, event: unknown, ctx: ObservMeHandlerContext): Promise<void> {
|
|
130
|
+
// Pi docs guarantee /new, /resume, and /fork emit session_shutdown before session_start;
|
|
131
|
+
// ctx.reload() emits session_shutdown then session_start while the old command call frame can
|
|
132
|
+
// continue. ObservMe queues both lifecycle handlers together so async startup, shutdown,
|
|
133
|
+
// reload, and replacement state transitions cannot clear or replace shared runtime state out of order.
|
|
134
|
+
const current = this.#previous.then(invokeHandler.bind(undefined, fn, event, ctx));
|
|
135
|
+
this.#previous = current.catch(ignoreLifecycleQueueError);
|
|
136
|
+
return current;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export function resolveObservMePiApi(pi: unknown): ObservMePiApi {
|
|
141
|
+
if (!isRecord(pi)) throw new TypeError(piApiCompatibilityErrorMessage);
|
|
142
|
+
|
|
143
|
+
const on = pi.on;
|
|
144
|
+
if (typeof on !== "function") throw new TypeError(piApiCompatibilityErrorMessage);
|
|
145
|
+
|
|
146
|
+
return { on: callPiOn.bind(undefined, pi, on as ObservMePiApi["on"]) };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function setDefaultHandlerErrorRecorder(recorder: HandlerErrorRecorder): void {
|
|
150
|
+
defaultHandlerErrorRecorder = recorder;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export function safeHandler(
|
|
154
|
+
name: string,
|
|
155
|
+
fn: Handler,
|
|
156
|
+
recorder: HandlerErrorRecorder = defaultHandlerErrorRecorder,
|
|
157
|
+
): Handler {
|
|
158
|
+
return runSafeHandler.bind(undefined, name, fn, recorder);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export function createStatefulHandlerErrorRecorder(
|
|
162
|
+
state: HandlerSessionState,
|
|
163
|
+
fallback?: HandlerErrorRecorder,
|
|
164
|
+
): HandlerErrorRecorder {
|
|
165
|
+
return recordStatefulHandlerError.bind(undefined, state, fallback);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export async function startSessionTelemetry(options: StartSessionTelemetryOptions): Promise<ObservMeTelemetrySession> {
|
|
169
|
+
const config = withTelemetrySessionResourceAttributes(options.config, options.lineage);
|
|
170
|
+
const traceSdk = new ObservMeTraceSdk({ config });
|
|
171
|
+
const metricSdk = new ObservMeMetricSdk({ config });
|
|
172
|
+
const logSdk = new ObservMeLogSdk({ config });
|
|
173
|
+
const signalSdk = createCompositeOtelSignalSdk(traceSdk, metricSdk, logSdk);
|
|
174
|
+
const controller = await startOtelSdk({
|
|
175
|
+
config,
|
|
176
|
+
agent: options.lineage,
|
|
177
|
+
sdkFactory: returnCompositeSignalSdk.bind(undefined, signalSdk),
|
|
178
|
+
});
|
|
179
|
+
const tracer = traceSdk.tracer ?? trace.getTracer("@senad-d/observme");
|
|
180
|
+
const metrics = createObservMeMetrics(metricSdk.meter);
|
|
181
|
+
const sessionReference: HandlerSessionState = {};
|
|
182
|
+
const getTelemetryDropTarget = resolveSessionTelemetryDropTarget.bind(undefined, sessionReference, metrics);
|
|
183
|
+
const session: ObservMeTelemetrySession = {
|
|
184
|
+
config,
|
|
185
|
+
lineage: options.lineage,
|
|
186
|
+
controller,
|
|
187
|
+
tracer,
|
|
188
|
+
meter: metricSdk.meter,
|
|
189
|
+
logger: logSdk.logger,
|
|
190
|
+
metrics,
|
|
191
|
+
spans: createSpanRegistry(config, metrics, getTelemetryDropTarget),
|
|
192
|
+
agentTree: createAgentTreeTracker(config, options.lineage, metrics, getTelemetryDropTarget),
|
|
193
|
+
now: options.now ?? monotonicNowMs,
|
|
194
|
+
activeAgentRecorded: false,
|
|
195
|
+
agentRunSequence: 0,
|
|
196
|
+
llmRequestSequence: 0,
|
|
197
|
+
toolCallSequence: 0,
|
|
198
|
+
turnSequences: createTurnSequenceRegistry(config, metrics, getTelemetryDropTarget),
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
sessionReference.session = session;
|
|
202
|
+
return session;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export function createCompositeOtelSignalSdk(
|
|
206
|
+
traceSdk: ObservMeTraceSdk,
|
|
207
|
+
metricSdk: ObservMeMetricSdk,
|
|
208
|
+
logSdk: ObservMeLogSdk,
|
|
209
|
+
): CompositeOtelSignalSdk {
|
|
210
|
+
return new ObservMeCompositeOtelSignalSdk(traceSdk, metricSdk, logSdk);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export function createObservMeMetrics(meter: TelemetryMeter): ObservMeMetrics {
|
|
214
|
+
return {
|
|
215
|
+
handlerErrors: meter.createCounter(OBSERVME_COUNTER_METRIC_NAMES.HANDLER_ERRORS_TOTAL),
|
|
216
|
+
telemetryDropped: meter.createCounter(OBSERVME_COUNTER_METRIC_NAMES.TELEMETRY_DROPPED_TOTAL),
|
|
217
|
+
exportErrors: meter.createCounter(OBSERVME_COUNTER_METRIC_NAMES.EXPORT_ERRORS_TOTAL),
|
|
218
|
+
sessionsStarted: meter.createCounter(OBSERVME_COUNTER_METRIC_NAMES.SESSIONS_STARTED_TOTAL),
|
|
219
|
+
sessionsShutdown: meter.createCounter(OBSERVME_COUNTER_METRIC_NAMES.SESSIONS_SHUTDOWN_TOTAL),
|
|
220
|
+
workflowsStarted: meter.createCounter(OBSERVME_COUNTER_METRIC_NAMES.WORKFLOWS_STARTED_TOTAL),
|
|
221
|
+
workflowsCompleted: meter.createCounter(OBSERVME_COUNTER_METRIC_NAMES.WORKFLOWS_COMPLETED_TOTAL),
|
|
222
|
+
workflowErrors: meter.createCounter(OBSERVME_COUNTER_METRIC_NAMES.WORKFLOW_ERRORS_TOTAL),
|
|
223
|
+
agentRuns: meter.createCounter(OBSERVME_COUNTER_METRIC_NAMES.AGENT_RUNS_TOTAL),
|
|
224
|
+
agentRunErrors: meter.createCounter(OBSERVME_COUNTER_METRIC_NAMES.AGENT_RUN_ERRORS_TOTAL),
|
|
225
|
+
turnsStarted: meter.createCounter(OBSERVME_COUNTER_METRIC_NAMES.TURNS_STARTED_TOTAL),
|
|
226
|
+
turnsCompleted: meter.createCounter(OBSERVME_COUNTER_METRIC_NAMES.TURNS_COMPLETED_TOTAL),
|
|
227
|
+
llmRequests: meter.createCounter(OBSERVME_COUNTER_METRIC_NAMES.LLM_REQUESTS_TOTAL),
|
|
228
|
+
llmErrors: meter.createCounter(OBSERVME_COUNTER_METRIC_NAMES.LLM_ERRORS_TOTAL),
|
|
229
|
+
llmInputTokens: meter.createCounter(OBSERVME_TOKEN_COST_COUNTER_METRIC_NAMES.LLM_INPUT_TOKENS_TOTAL),
|
|
230
|
+
llmOutputTokens: meter.createCounter(OBSERVME_TOKEN_COST_COUNTER_METRIC_NAMES.LLM_OUTPUT_TOKENS_TOTAL),
|
|
231
|
+
llmCacheReadTokens: meter.createCounter(OBSERVME_TOKEN_COST_COUNTER_METRIC_NAMES.LLM_CACHE_READ_TOKENS_TOTAL),
|
|
232
|
+
llmCacheWriteTokens: meter.createCounter(OBSERVME_TOKEN_COST_COUNTER_METRIC_NAMES.LLM_CACHE_WRITE_TOKENS_TOTAL),
|
|
233
|
+
llmCacheWrite1hTokens: meter.createCounter(OBSERVME_TOKEN_COST_COUNTER_METRIC_NAMES.LLM_CACHE_WRITE_1H_TOKENS_TOTAL),
|
|
234
|
+
llmReasoningTokens: meter.createCounter(OBSERVME_TOKEN_COST_COUNTER_METRIC_NAMES.LLM_REASONING_TOKENS_TOTAL),
|
|
235
|
+
llmTotalTokens: meter.createCounter(OBSERVME_TOKEN_COST_COUNTER_METRIC_NAMES.LLM_TOTAL_TOKENS_TOTAL),
|
|
236
|
+
llmCostUsd: meter.createCounter(OBSERVME_TOKEN_COST_COUNTER_METRIC_NAMES.LLM_COST_USD_TOTAL),
|
|
237
|
+
toolCalls: meter.createCounter(OBSERVME_COUNTER_METRIC_NAMES.TOOL_CALLS_TOTAL),
|
|
238
|
+
toolFailures: meter.createCounter(OBSERVME_COUNTER_METRIC_NAMES.TOOL_FAILURES_TOTAL),
|
|
239
|
+
bashExecutions: meter.createCounter(OBSERVME_COUNTER_METRIC_NAMES.BASH_EXECUTIONS_TOTAL),
|
|
240
|
+
bashFailures: meter.createCounter(OBSERVME_COUNTER_METRIC_NAMES.BASH_FAILURES_TOTAL),
|
|
241
|
+
modelChanges: meter.createCounter(OBSERVME_COUNTER_METRIC_NAMES.MODEL_CHANGES_TOTAL),
|
|
242
|
+
thinkingLevelChanges: meter.createCounter(OBSERVME_COUNTER_METRIC_NAMES.THINKING_LEVEL_CHANGES_TOTAL),
|
|
243
|
+
compactions: meter.createCounter(OBSERVME_COUNTER_METRIC_NAMES.COMPACTIONS_TOTAL),
|
|
244
|
+
branches: meter.createCounter(OBSERVME_COUNTER_METRIC_NAMES.BRANCHES_TOTAL),
|
|
245
|
+
subagentsSpawned: meter.createCounter(OBSERVME_COUNTER_METRIC_NAMES.SUBAGENTS_SPAWNED_TOTAL),
|
|
246
|
+
subagentSpawnFailures: meter.createCounter(OBSERVME_COUNTER_METRIC_NAMES.SUBAGENT_SPAWN_FAILURES_TOTAL),
|
|
247
|
+
orphanAgents: meter.createCounter(OBSERVME_COUNTER_METRIC_NAMES.ORPHAN_AGENTS_TOTAL),
|
|
248
|
+
traceContextPropagationFailures: meter.createCounter(OBSERVME_COUNTER_METRIC_NAMES.TRACE_CONTEXT_PROPAGATION_FAILURES_TOTAL),
|
|
249
|
+
childAgentFailures: meter.createCounter(OBSERVME_COUNTER_METRIC_NAMES.CHILD_AGENT_FAILURES_TOTAL),
|
|
250
|
+
parentRecoveredFromChildFailure: meter.createCounter(OBSERVME_COUNTER_METRIC_NAMES.PARENT_RECOVERED_FROM_CHILD_FAILURE_TOTAL),
|
|
251
|
+
redactionFailures: meter.createCounter(OBSERVME_COUNTER_METRIC_NAMES.REDACTION_FAILURES_TOTAL),
|
|
252
|
+
eventsObserved: meter.createCounter(OBSERVME_COUNTER_METRIC_NAMES.EVENTS_OBSERVED_TOTAL),
|
|
253
|
+
activeSpans: meter.createUpDownCounter(OBSERVME_GAUGE_METRIC_NAMES.ACTIVE_SPANS),
|
|
254
|
+
activeAgents: meter.createUpDownCounter(OBSERVME_GAUGE_METRIC_NAMES.ACTIVE_AGENTS),
|
|
255
|
+
workflowDurationMs: createHistogram(meter, OBSERVME_HISTOGRAM_METRIC_NAMES.WORKFLOW_DURATION_MS),
|
|
256
|
+
agentRunDurationMs: createHistogram(meter, OBSERVME_HISTOGRAM_METRIC_NAMES.AGENT_RUN_DURATION_MS),
|
|
257
|
+
agentLifetimeDurationMs: createHistogram(meter, OBSERVME_HISTOGRAM_METRIC_NAMES.AGENT_LIFETIME_DURATION_MS),
|
|
258
|
+
subagentSpawnDurationMs: createHistogram(meter, OBSERVME_HISTOGRAM_METRIC_NAMES.SUBAGENT_SPAWN_DURATION_MS),
|
|
259
|
+
agentFanoutCount: createHistogram(meter, OBSERVME_HISTOGRAM_METRIC_NAMES.AGENT_FANOUT_COUNT),
|
|
260
|
+
agentTreeDepth: createHistogram(meter, OBSERVME_HISTOGRAM_METRIC_NAMES.AGENT_TREE_DEPTH),
|
|
261
|
+
agentTreeWidth: createHistogram(meter, OBSERVME_HISTOGRAM_METRIC_NAMES.AGENT_TREE_WIDTH),
|
|
262
|
+
agentWaitDurationMs: createHistogram(meter, OBSERVME_HISTOGRAM_METRIC_NAMES.AGENT_WAIT_DURATION_MS),
|
|
263
|
+
agentJoinDurationMs: createHistogram(meter, OBSERVME_HISTOGRAM_METRIC_NAMES.AGENT_JOIN_DURATION_MS),
|
|
264
|
+
turnDurationMs: createHistogram(meter, OBSERVME_HISTOGRAM_METRIC_NAMES.TURN_DURATION_MS),
|
|
265
|
+
llmRequestDurationMs: createHistogram(meter, OBSERVME_HISTOGRAM_METRIC_NAMES.LLM_REQUEST_DURATION_MS),
|
|
266
|
+
toolDurationMs: createHistogram(meter, OBSERVME_HISTOGRAM_METRIC_NAMES.TOOL_DURATION_MS),
|
|
267
|
+
bashDurationMs: createHistogram(meter, OBSERVME_HISTOGRAM_METRIC_NAMES.BASH_DURATION_MS),
|
|
268
|
+
compactionTokensBefore: createHistogram(meter, OBSERVME_HISTOGRAM_METRIC_NAMES.COMPACTION_TOKENS_BEFORE),
|
|
269
|
+
promptSizeChars: createHistogram(meter, OBSERVME_HISTOGRAM_METRIC_NAMES.PROMPT_SIZE_CHARS),
|
|
270
|
+
responseSizeChars: createHistogram(meter, OBSERVME_HISTOGRAM_METRIC_NAMES.RESPONSE_SIZE_CHARS),
|
|
271
|
+
toolResultSizeChars: createHistogram(meter, OBSERVME_HISTOGRAM_METRIC_NAMES.TOOL_RESULT_SIZE_CHARS),
|
|
272
|
+
handlerDurationMs: createHistogram(meter, OBSERVME_HISTOGRAM_METRIC_NAMES.HANDLER_DURATION_MS),
|
|
273
|
+
genAiClientTokenUsage: createHistogram(meter, OFFICIAL_GENAI_METRIC_NAMES.CLIENT_TOKEN_USAGE),
|
|
274
|
+
genAiClientOperationDuration: createHistogram(meter, OFFICIAL_GENAI_METRIC_NAMES.CLIENT_OPERATION_DURATION),
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
export function createHistogram(meter: TelemetryMeter, name: string): Histogram {
|
|
279
|
+
const maybeMeter = meter as Partial<TelemetryMeter>;
|
|
280
|
+
const noopHistogram = { record: recordNoopHistogram };
|
|
281
|
+
return typeof maybeMeter.createHistogram === "function" ? maybeMeter.createHistogram(name) : noopHistogram;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
export function createTurnSequenceRegistry(
|
|
285
|
+
config: ObservMeConfig,
|
|
286
|
+
metrics: ObservMeMetrics,
|
|
287
|
+
getTelemetryDropTarget?: () => TelemetryDropTarget,
|
|
288
|
+
): BoundedMap<string, number> {
|
|
289
|
+
return new BoundedMap({
|
|
290
|
+
maxSize: config.limits.maxActiveAgentRuns,
|
|
291
|
+
onEvict: recordTurnSequenceEviction.bind(undefined, metrics, getTelemetryDropTarget),
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
export function createAgentTreeTracker(
|
|
296
|
+
config: ObservMeConfig,
|
|
297
|
+
lineage: AgentLineageContext,
|
|
298
|
+
metrics: ObservMeMetrics,
|
|
299
|
+
getTelemetryDropTarget?: () => TelemetryDropTarget,
|
|
300
|
+
): AgentTreeTracker {
|
|
301
|
+
const tracker = new AgentTreeTracker({
|
|
302
|
+
maxAgents: Math.max(2, config.limits.maxActiveSubagentSpawns + 1),
|
|
303
|
+
onEvict: recordAgentTreeEviction.bind(undefined, metrics, getTelemetryDropTarget),
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
tracker.registerAgent(lineage);
|
|
307
|
+
return tracker;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
export function createSpanRegistry(
|
|
311
|
+
config: ObservMeConfig,
|
|
312
|
+
metrics: ObservMeMetrics,
|
|
313
|
+
getTelemetryDropTarget?: () => TelemetryDropTarget,
|
|
314
|
+
): SpanRegistry {
|
|
315
|
+
return {
|
|
316
|
+
activeAgentRuns: new BoundedMap({
|
|
317
|
+
maxSize: config.limits.maxActiveAgentRuns,
|
|
318
|
+
onEvict: handleAgentRunEviction.bind(undefined, metrics, getTelemetryDropTarget),
|
|
319
|
+
}),
|
|
320
|
+
activeTurns: new BoundedMap({
|
|
321
|
+
maxSize: config.limits.maxActiveTurns,
|
|
322
|
+
onEvict: handleSpanEviction.bind(undefined, metrics, getTelemetryDropTarget),
|
|
323
|
+
}),
|
|
324
|
+
activeLlmRequests: new BoundedMap({
|
|
325
|
+
maxSize: config.limits.maxActiveLlmRequests,
|
|
326
|
+
onEvict: handleSpanEviction.bind(undefined, metrics, getTelemetryDropTarget),
|
|
327
|
+
}),
|
|
328
|
+
activeToolCalls: new BoundedMap({
|
|
329
|
+
maxSize: config.limits.maxActiveToolCalls,
|
|
330
|
+
onEvict: handleToolCallEviction.bind(undefined, metrics, getTelemetryDropTarget),
|
|
331
|
+
}),
|
|
332
|
+
activeSubagentSpawns: new BoundedMap({
|
|
333
|
+
maxSize: config.limits.maxActiveSubagentSpawns,
|
|
334
|
+
onEvict: handleSubagentSpawnEviction.bind(undefined, metrics, getTelemetryDropTarget),
|
|
335
|
+
}),
|
|
336
|
+
activeAgentWaits: new BoundedMap({
|
|
337
|
+
maxSize: config.limits.maxActiveAgentWaits,
|
|
338
|
+
onEvict: handleWaitJoinEviction.bind(undefined, metrics, getTelemetryDropTarget),
|
|
339
|
+
}),
|
|
340
|
+
activeAgentJoins: new BoundedMap({
|
|
341
|
+
maxSize: config.limits.maxActiveAgentJoins,
|
|
342
|
+
onEvict: handleWaitJoinEviction.bind(undefined, metrics, getTelemetryDropTarget),
|
|
343
|
+
}),
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
export function isRootWorkflow(lineage: AgentLineageContext): boolean {
|
|
348
|
+
return lineage.role === "root" || lineage.role === "orchestrator";
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
export function workflowFailed(event: unknown): boolean {
|
|
352
|
+
const status = readString(event, "status") ?? readString(event, "outcome");
|
|
353
|
+
return readBoolean(event, "failed") === true || Boolean(readUnknown(event, "error")) || status === "failed" || status === "error";
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
export function monotonicNowMs(): number {
|
|
357
|
+
return performance.now();
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
export function withTelemetrySessionResourceAttributes(
|
|
361
|
+
config: ObservMeConfig,
|
|
362
|
+
lineage: AgentLineageContext,
|
|
363
|
+
instanceId = randomUUID(),
|
|
364
|
+
): ObservMeConfig {
|
|
365
|
+
const merged = structuredClone(config);
|
|
366
|
+
merged.resource.attributes = {
|
|
367
|
+
...merged.resource.attributes,
|
|
368
|
+
...buildTelemetryInstanceResourceAttributes(instanceId),
|
|
369
|
+
...stringifyAttributes(buildResourceLineageAttributes(lineage)),
|
|
370
|
+
};
|
|
371
|
+
return inheritTenantSaltEnvironment(merged, config);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
export function buildTelemetryInstanceResourceAttributes(instanceId: string): Record<string, string> {
|
|
375
|
+
return {
|
|
376
|
+
[RESOURCE_ATTRIBUTES.SERVICE_INSTANCE_ID]: instanceId,
|
|
377
|
+
[RESOURCE_ATTRIBUTES.OBSERVME_INSTANCE_ID]: instanceId,
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function createEventRegistrationIndexes(): Map<string, number> {
|
|
382
|
+
const indexes = new Map<string, number>();
|
|
383
|
+
for (const [index, name] of eventRegistrationOrder.entries()) indexes.set(name, index);
|
|
384
|
+
return indexes;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
function recordNoopHistogram(_value: number, _attributes?: Record<string, unknown>): void {
|
|
388
|
+
return undefined;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function compareHandlerRegistrations(left: HandlerRegistration, right: HandlerRegistration): number {
|
|
392
|
+
return registrationIndex(left.eventName) - registrationIndex(right.eventName);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function registrationIndex(eventName: string): number {
|
|
396
|
+
return eventRegistrationIndexes.get(eventName) ?? eventRegistrationOrder.length;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function registerObservedHandler(
|
|
400
|
+
api: ObservMePiApi,
|
|
401
|
+
name: string,
|
|
402
|
+
handler: Handler,
|
|
403
|
+
state: HandlerSessionState,
|
|
404
|
+
errorRecorder: HandlerErrorRecorder,
|
|
405
|
+
): void {
|
|
406
|
+
api.on(name, safeHandler(name, observeHandler(name, handler, state), errorRecorder));
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
function observeHandler(name: string, fn: Handler, state: HandlerSessionState): Handler {
|
|
410
|
+
return runObservedHandler.bind(undefined, name, fn, state);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
async function runObservedHandler(
|
|
414
|
+
name: string,
|
|
415
|
+
fn: Handler,
|
|
416
|
+
state: HandlerSessionState,
|
|
417
|
+
event: unknown,
|
|
418
|
+
ctx: ObservMeHandlerContext,
|
|
419
|
+
): Promise<void> {
|
|
420
|
+
const startedAtMs = Date.now();
|
|
421
|
+
const sessionBefore = state.session;
|
|
422
|
+
let status = "ok";
|
|
423
|
+
|
|
424
|
+
try {
|
|
425
|
+
await fn(event, ctx);
|
|
426
|
+
} catch (error) {
|
|
427
|
+
status = "error";
|
|
428
|
+
throw error;
|
|
429
|
+
} finally {
|
|
430
|
+
recordHandlerObservation(resolveObservationSession(name, sessionBefore, state.session), name, startedAtMs, status);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
function resolveObservationSession(
|
|
435
|
+
name: string,
|
|
436
|
+
sessionBefore: ObservMeTelemetrySession | undefined,
|
|
437
|
+
sessionAfter: ObservMeTelemetrySession | undefined,
|
|
438
|
+
): ObservMeTelemetrySession | undefined {
|
|
439
|
+
if (name === "session_start") return sessionAfter ?? sessionBefore;
|
|
440
|
+
return sessionBefore ?? sessionAfter;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
function recordHandlerObservation(
|
|
444
|
+
session: ObservMeTelemetrySession | undefined,
|
|
445
|
+
name: string,
|
|
446
|
+
startedAtMs: number,
|
|
447
|
+
status: string,
|
|
448
|
+
): void {
|
|
449
|
+
if (!session) return;
|
|
450
|
+
|
|
451
|
+
const operation = normalizeMetricValue(name, "handler");
|
|
452
|
+
session.metrics.eventsObserved.add(1, { operation });
|
|
453
|
+
session.metrics.handlerDurationMs.record(Math.max(0, Date.now() - startedAtMs), { operation, status });
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
async function runSafeHandler(
|
|
457
|
+
name: string,
|
|
458
|
+
fn: Handler,
|
|
459
|
+
recorder: HandlerErrorRecorder,
|
|
460
|
+
event: unknown,
|
|
461
|
+
ctx: ObservMeHandlerContext,
|
|
462
|
+
): Promise<void> {
|
|
463
|
+
try {
|
|
464
|
+
await fn(event, ctx);
|
|
465
|
+
} catch (error) {
|
|
466
|
+
recorder(name, error);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
async function runSerializedLifecycleHandler(
|
|
471
|
+
queue: SerializedLifecycleQueue,
|
|
472
|
+
fn: Handler,
|
|
473
|
+
event: unknown,
|
|
474
|
+
ctx: ObservMeHandlerContext,
|
|
475
|
+
): Promise<void> {
|
|
476
|
+
await queue.run(fn, event, ctx);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
async function invokeHandler(fn: Handler, event: unknown, ctx: ObservMeHandlerContext): Promise<void> {
|
|
480
|
+
await fn(event, ctx);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
function ignoreLifecycleQueueError(): undefined {
|
|
484
|
+
return undefined;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
function callPiOn(pi: Record<string, unknown>, on: ObservMePiApi["on"], eventName: string, handler: Handler): void {
|
|
488
|
+
on.call(pi, eventName, handler);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
function returnCompositeSignalSdk(signalSdk: CompositeOtelSignalSdk): CompositeOtelSignalSdk {
|
|
492
|
+
return signalSdk;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
function resolveSessionTelemetryDropTarget(
|
|
496
|
+
state: HandlerSessionState,
|
|
497
|
+
metrics: ObservMeMetrics,
|
|
498
|
+
): TelemetryDropTarget {
|
|
499
|
+
return state.session ?? metrics;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
function recordTurnSequenceEviction(
|
|
503
|
+
metrics: ObservMeMetrics,
|
|
504
|
+
getTelemetryDropTarget: (() => TelemetryDropTarget) | undefined,
|
|
505
|
+
_eviction: BoundedMapEviction<string, number>,
|
|
506
|
+
): void {
|
|
507
|
+
recordTelemetryDrop(resolveTelemetryDropTarget(metrics, getTelemetryDropTarget), "turn_sequence_full", {
|
|
508
|
+
operation: "turn_sequence",
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
function recordAgentTreeEviction(
|
|
513
|
+
metrics: ObservMeMetrics,
|
|
514
|
+
getTelemetryDropTarget: (() => TelemetryDropTarget) | undefined,
|
|
515
|
+
_node: AgentTreeNode,
|
|
516
|
+
): void {
|
|
517
|
+
recordTelemetryDrop(resolveTelemetryDropTarget(metrics, getTelemetryDropTarget), "agent_tree_full", {
|
|
518
|
+
operation: "agent_tree",
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
function handleAgentRunEviction(
|
|
523
|
+
metrics: ObservMeMetrics,
|
|
524
|
+
getTelemetryDropTarget: (() => TelemetryDropTarget) | undefined,
|
|
525
|
+
eviction: BoundedMapEviction<string, Span>,
|
|
526
|
+
): void {
|
|
527
|
+
evictAgentRunState(
|
|
528
|
+
eviction.key,
|
|
529
|
+
eviction.value,
|
|
530
|
+
resolveTelemetryDropTarget(metrics, getTelemetryDropTarget),
|
|
531
|
+
);
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
function handleSpanEviction(
|
|
535
|
+
metrics: ObservMeMetrics,
|
|
536
|
+
getTelemetryDropTarget: (() => TelemetryDropTarget) | undefined,
|
|
537
|
+
eviction: BoundedMapEviction<string, Span>,
|
|
538
|
+
): void {
|
|
539
|
+
evictSpan(eviction.value, resolveTelemetryDropTarget(metrics, getTelemetryDropTarget));
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
function handleToolCallEviction(
|
|
543
|
+
metrics: ObservMeMetrics,
|
|
544
|
+
getTelemetryDropTarget: (() => TelemetryDropTarget) | undefined,
|
|
545
|
+
eviction: BoundedMapEviction<string, ToolCallState>,
|
|
546
|
+
): void {
|
|
547
|
+
evictToolCallState(eviction.value, resolveTelemetryDropTarget(metrics, getTelemetryDropTarget));
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
function handleSubagentSpawnEviction(
|
|
551
|
+
metrics: ObservMeMetrics,
|
|
552
|
+
getTelemetryDropTarget: (() => TelemetryDropTarget) | undefined,
|
|
553
|
+
eviction: BoundedMapEviction<string, SubagentSpawnState>,
|
|
554
|
+
): void {
|
|
555
|
+
evictSubagentSpawnState(eviction.value, resolveTelemetryDropTarget(metrics, getTelemetryDropTarget));
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
function handleWaitJoinEviction(
|
|
559
|
+
metrics: ObservMeMetrics,
|
|
560
|
+
getTelemetryDropTarget: (() => TelemetryDropTarget) | undefined,
|
|
561
|
+
eviction: BoundedMapEviction<string, AgentWaitJoinState>,
|
|
562
|
+
): void {
|
|
563
|
+
evictWaitJoinState(eviction.value, resolveTelemetryDropTarget(metrics, getTelemetryDropTarget));
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
function evictAgentRunState(runId: string, span: Span, target: TelemetryDropTarget): void {
|
|
567
|
+
evictSpan(span, target);
|
|
568
|
+
|
|
569
|
+
const turnSequences = readTurnSequenceRegistry(target);
|
|
570
|
+
if (!turnSequences?.delete(runId)) return;
|
|
571
|
+
recordTelemetryDrop(target, "turn_sequence_full", { operation: "turn_sequence" });
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
function readTurnSequenceRegistry(target: TelemetryDropTarget): TurnSequenceRegistry | undefined {
|
|
575
|
+
if (!("turnSequences" in target)) return undefined;
|
|
576
|
+
return target.turnSequences as TurnSequenceRegistry;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
function resolveTelemetryDropTarget(
|
|
580
|
+
metrics: ObservMeMetrics,
|
|
581
|
+
getTelemetryDropTarget: (() => TelemetryDropTarget) | undefined,
|
|
582
|
+
): TelemetryDropTarget {
|
|
583
|
+
return getTelemetryDropTarget?.() ?? metrics;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
function recordStatefulHandlerError(
|
|
587
|
+
state: HandlerSessionState,
|
|
588
|
+
fallback: HandlerErrorRecorder | undefined,
|
|
589
|
+
name: string,
|
|
590
|
+
error: unknown,
|
|
591
|
+
): void {
|
|
592
|
+
const session = state.session;
|
|
593
|
+
session?.metrics.handlerErrors.add(1, { operation: normalizeMetricValue(name, "handler") });
|
|
594
|
+
if (session) emitLifecycleLog(session.logger, LOG_EVENT_NAMES.HANDLER_FAILED, handlerErrorAttributes(name, error), "ERROR");
|
|
595
|
+
if (!session) fallback?.(name, error);
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
function handlerErrorAttributes(name: string, error: unknown): AttributeMap {
|
|
599
|
+
return {
|
|
600
|
+
handler: name,
|
|
601
|
+
[LOG_ATTRIBUTES.ERROR_TYPE]: errorClass(error),
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
function recordInternalErrorFallback(_name: string, _error: unknown): void {
|
|
606
|
+
return undefined;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
class ObservMeCompositeOtelSignalSdk implements CompositeOtelSignalSdk {
|
|
610
|
+
readonly traceSdk: ObservMeTraceSdk;
|
|
611
|
+
readonly metricSdk: ObservMeMetricSdk;
|
|
612
|
+
readonly logSdk: ObservMeLogSdk;
|
|
613
|
+
|
|
614
|
+
constructor(traceSdk: ObservMeTraceSdk, metricSdk: ObservMeMetricSdk, logSdk: ObservMeLogSdk) {
|
|
615
|
+
this.traceSdk = traceSdk;
|
|
616
|
+
this.metricSdk = metricSdk;
|
|
617
|
+
this.logSdk = logSdk;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
start(): void {
|
|
621
|
+
this.traceSdk.start();
|
|
622
|
+
this.metricSdk.start();
|
|
623
|
+
this.logSdk.start();
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
async forceFlush(): Promise<void> {
|
|
627
|
+
await Promise.all([this.traceSdk.forceFlush(), this.metricSdk.forceFlush(), this.logSdk.forceFlush()]);
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
async shutdown(): Promise<void> {
|
|
631
|
+
await Promise.all([this.traceSdk.shutdown(), this.metricSdk.shutdown(), this.logSdk.shutdown()]);
|
|
632
|
+
}
|
|
633
|
+
}
|