@vymalo/opencode-otel 0.12.0 → 0.14.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/dist/config.d.ts +29 -17
- package/dist/config.js +210 -197
- package/dist/config.js.map +1 -1
- package/dist/deferred.d.ts +22 -0
- package/dist/deferred.js +29 -0
- package/dist/deferred.js.map +1 -0
- package/dist/export-logging.d.ts +28 -0
- package/dist/export-logging.js +74 -0
- package/dist/export-logging.js.map +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/instruments.d.ts +22 -22
- package/dist/instruments.js +95 -94
- package/dist/instruments.js.map +1 -1
- package/dist/lib.d.ts +5 -1
- package/dist/lib.js +6 -1
- package/dist/lib.js.map +1 -1
- package/dist/logging.d.ts +6 -6
- package/dist/logging.js +59 -62
- package/dist/logging.js.map +1 -1
- package/dist/opencode.d.ts +16 -14
- package/dist/opencode.js +165 -122
- package/dist/opencode.js.map +1 -1
- package/dist/propagation.d.ts +13 -13
- package/dist/propagation.js +50 -48
- package/dist/propagation.js.map +1 -1
- package/dist/providers.d.ts +46 -35
- package/dist/providers.js +217 -158
- package/dist/providers.js.map +1 -1
- package/dist/recorder.d.ts +150 -69
- package/dist/recorder.js +788 -539
- package/dist/recorder.js.map +1 -1
- package/dist/token-source.d.ts +59 -0
- package/dist/token-source.js +125 -0
- package/dist/token-source.js.map +1 -0
- package/dist/types.d.ts +92 -64
- package/dist/types.js +1 -0
- package/dist/types.js.map +1 -1
- package/dist/vcs.d.ts +63 -0
- package/dist/vcs.js +188 -0
- package/dist/vcs.js.map +1 -0
- package/package.json +3 -3
package/dist/opencode.js
CHANGED
|
@@ -1,141 +1,184 @@
|
|
|
1
1
|
import { hostname } from "node:os";
|
|
2
2
|
import { resolveOtelConfig } from "./config.js";
|
|
3
|
+
import { deferredAttribute } from "./deferred.js";
|
|
3
4
|
import { createJsonConsoleLogger, DEFAULT_LOG_LEVEL, fromOpenCodeLogLevel, LOG_LEVEL_PRIORITY } from "./logging.js";
|
|
4
5
|
import { installTracePropagation } from "./propagation.js";
|
|
5
6
|
import { buildResource, createProviders, describeError } from "./providers.js";
|
|
6
7
|
import { TelemetryRecorder } from "./recorder.js";
|
|
8
|
+
import { readVcsInfo } from "./vcs.js";
|
|
7
9
|
const PLUGIN_SERVICE_NAME = "opencode-otel-plugin";
|
|
8
10
|
/**
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
* Pipe plugin logs through OpenCode's `client.app.log` so they show up in the
|
|
12
|
+
* host's structured log stream, with the JSON console as a reliable fallback.
|
|
13
|
+
* Mirrors the rest of the suite.
|
|
14
|
+
*
|
|
15
|
+
* Note this is the plugin's *own* diagnostic logging — entirely separate from
|
|
16
|
+
* the OTLP logs signal it exports.
|
|
17
|
+
*/
|
|
16
18
|
function createOpenCodeLogger(client, getMinLevel) {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
19
|
+
const fallback = createJsonConsoleLogger("debug");
|
|
20
|
+
const consoleAll = /^(1|true|yes|on)$/i.test(process.env.VYMALO_PLUGIN_CONSOLE_LOG ?? "");
|
|
21
|
+
const write = (level, event, fields) => {
|
|
22
|
+
if (LOG_LEVEL_PRIORITY[level] < LOG_LEVEL_PRIORITY[getMinLevel()]) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
if (consoleAll || level === "warn" || level === "error") {
|
|
26
|
+
fallback[level](event, fields);
|
|
27
|
+
}
|
|
28
|
+
const hostLevel = level === "trace" ? "debug" : level;
|
|
29
|
+
void client.app.log({ body: {
|
|
30
|
+
service: PLUGIN_SERVICE_NAME,
|
|
31
|
+
level: hostLevel,
|
|
32
|
+
message: event,
|
|
33
|
+
extra: fields
|
|
34
|
+
} }).catch(() => {
|
|
35
|
+
/* best-effort */
|
|
36
|
+
});
|
|
37
|
+
};
|
|
38
|
+
return {
|
|
39
|
+
trace: (event, fields) => write("trace", event, fields),
|
|
40
|
+
debug: (event, fields) => write("debug", event, fields),
|
|
41
|
+
info: (event, fields) => write("info", event, fields),
|
|
42
|
+
warn: (event, fields) => write("warn", event, fields),
|
|
43
|
+
error: (event, fields) => write("error", event, fields)
|
|
44
|
+
};
|
|
42
45
|
}
|
|
43
46
|
/**
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
function registerExitHandlers(providers, logger) {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
47
|
+
* Drain buffered telemetry on process exit. The plugin API has no dispose hook,
|
|
48
|
+
* so without this a short CLI invocation loses everything still in a batch
|
|
49
|
+
* processor. Handlers are registered once and never keep the loop alive.
|
|
50
|
+
*/
|
|
51
|
+
function registerExitHandlers(providers, logger, deferred) {
|
|
52
|
+
let done = false;
|
|
53
|
+
const drain = () => {
|
|
54
|
+
if (done) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
done = true;
|
|
58
|
+
// Settle any still-pending resource attribute first. Exporters await those
|
|
59
|
+
// promises, and their timers are `unref`'d — so on `beforeExit` the timer
|
|
60
|
+
// may never fire and the shutdown would hang, losing everything buffered.
|
|
61
|
+
for (const attribute of deferred) {
|
|
62
|
+
attribute.abandon();
|
|
63
|
+
}
|
|
64
|
+
void providers.shutdown().catch((error) => {
|
|
65
|
+
logger.warn("otel_shutdown_failed", { error: describeError(error) });
|
|
66
|
+
});
|
|
67
|
+
};
|
|
68
|
+
process.once("beforeExit", drain);
|
|
69
|
+
process.once("SIGINT", drain);
|
|
70
|
+
process.once("SIGTERM", drain);
|
|
62
71
|
}
|
|
63
72
|
export function createOtelPlugin(factoryOptions = {}) {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
73
|
+
return async (input, pluginOptions) => {
|
|
74
|
+
let currentLogLevel = DEFAULT_LOG_LEVEL;
|
|
75
|
+
const logger = factoryOptions.logger ?? createOpenCodeLogger(input.client, () => currentLogLevel);
|
|
76
|
+
const config = resolveOtelConfig(pluginOptions, factoryOptions.env ?? process.env);
|
|
77
|
+
if (!config.active) {
|
|
78
|
+
// No endpoint and no explicit exporter means the plugin was installed but
|
|
79
|
+
// never configured — cost nothing rather than half-initializing.
|
|
80
|
+
logger.info("otel_plugin_inactive", {
|
|
81
|
+
enabled: config.enabled,
|
|
82
|
+
reason: config.enabled ? "no_exporter_configured" : "disabled"
|
|
83
|
+
});
|
|
84
|
+
return { config: async (hostConfig) => {
|
|
85
|
+
currentLogLevel = fromOpenCodeLogLevel(hostConfig.logLevel) ?? DEFAULT_LOG_LEVEL;
|
|
86
|
+
} };
|
|
87
|
+
}
|
|
88
|
+
// `service.version` and the git branch only ever reach a plugin as events
|
|
89
|
+
// (`installation.updated` / `vcs.branch.updated`), which arrive after the
|
|
90
|
+
// resource is built. Deferred attributes bridge that, with a bounded wait
|
|
91
|
+
// so a host that never emits them cannot stall the first export.
|
|
92
|
+
const version = deferredAttribute(factoryOptions.deferredTimeoutMs);
|
|
93
|
+
const branch = deferredAttribute(factoryOptions.deferredTimeoutMs);
|
|
94
|
+
if (factoryOptions.hostInfo?.version) {
|
|
95
|
+
version.settle(factoryOptions.hostInfo.version);
|
|
96
|
+
}
|
|
97
|
+
// Read the checkout straight off disk. This is both richer and more
|
|
98
|
+
// reliable than waiting for `vcs.branch.updated`: it arrives before the
|
|
99
|
+
// first export instead of racing the deferral window, and it carries the
|
|
100
|
+
// remote and revision, which no event reports at all. The event stays as a
|
|
101
|
+
// fallback — `settle` keeps the first value, so whichever lands first wins.
|
|
102
|
+
const vcs = config.collectVcs ? await readVcsInfo(input.worktree ?? input.directory).catch(() => ({})) : {};
|
|
103
|
+
if (vcs.ref) {
|
|
104
|
+
branch.settle(vcs.ref);
|
|
105
|
+
}
|
|
106
|
+
const resource = buildResource(config, {
|
|
107
|
+
version: version.value,
|
|
108
|
+
hostname: factoryOptions.hostInfo?.hostname ?? safeHostname(),
|
|
109
|
+
projectName: input.project?.id,
|
|
110
|
+
directory: input.directory,
|
|
111
|
+
worktree: input.worktree,
|
|
112
|
+
branch: branch.value,
|
|
113
|
+
vcs
|
|
114
|
+
});
|
|
115
|
+
const providers = createProviders(config, resource, logger, factoryOptions.exporters);
|
|
116
|
+
const recorder = new TelemetryRecorder({
|
|
117
|
+
providers,
|
|
118
|
+
config,
|
|
119
|
+
logger,
|
|
120
|
+
now: factoryOptions.now,
|
|
121
|
+
resourceSinks: {
|
|
122
|
+
version: (value) => version.settle(value),
|
|
123
|
+
branch: (value) => branch.settle(value)
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
if (factoryOptions.registerProcessHandlers !== false) {
|
|
127
|
+
registerExitHandlers(providers, logger, [version, branch]);
|
|
128
|
+
}
|
|
129
|
+
logger.info("otel_plugin_enabled", {
|
|
130
|
+
serviceName: config.serviceName,
|
|
131
|
+
exporters: config.exporters,
|
|
132
|
+
endpoint: config.endpoint,
|
|
133
|
+
includeSessionId: config.includeSessionId,
|
|
134
|
+
filteredTools: [...config.filteredTools]
|
|
135
|
+
});
|
|
136
|
+
return {
|
|
137
|
+
config: async (hostConfig) => {
|
|
138
|
+
currentLogLevel = fromOpenCodeLogLevel(hostConfig.logLevel) ?? DEFAULT_LOG_LEVEL;
|
|
139
|
+
if (config.propagateTraceContext) {
|
|
140
|
+
const wrapped = installTracePropagation(hostConfig, {
|
|
141
|
+
getContext: () => recorder.currentChatContext(),
|
|
142
|
+
logger
|
|
143
|
+
});
|
|
144
|
+
logger.debug("otel_trace_propagation_ready", { providerCount: wrapped });
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
event: async ({ event }) => {
|
|
148
|
+
recorder.onEvent(event);
|
|
149
|
+
},
|
|
150
|
+
"chat.message": async (chatInput, chatOutput) => {
|
|
151
|
+
recorder.onChatMessage(chatInput, chatOutput);
|
|
152
|
+
},
|
|
153
|
+
"tool.execute.before": async (toolInput) => {
|
|
154
|
+
recorder.onToolBefore(toolInput);
|
|
155
|
+
},
|
|
156
|
+
"tool.execute.after": async (toolInput, toolOutput) => {
|
|
157
|
+
recorder.onToolAfter(toolInput, toolOutput);
|
|
158
|
+
},
|
|
159
|
+
"chat.params": async (paramsInput, paramsOutput) => {
|
|
160
|
+
recorder.onChatParams(paramsInput, paramsOutput);
|
|
161
|
+
},
|
|
162
|
+
"permission.ask": async (permissionInput, permissionOutput) => {
|
|
163
|
+
recorder.onPermissionAsk(permissionInput, permissionOutput);
|
|
164
|
+
},
|
|
165
|
+
"experimental.text.complete": async (textInput, textOutput) => {
|
|
166
|
+
recorder.onTextComplete(textInput, textOutput);
|
|
167
|
+
},
|
|
168
|
+
"experimental.compaction.autocontinue": async (compactionInput, compactionOutput) => {
|
|
169
|
+
recorder.onCompactionAutocontinue(compactionInput, compactionOutput);
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
};
|
|
130
173
|
}
|
|
131
174
|
function safeHostname() {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
}
|
|
175
|
+
try {
|
|
176
|
+
return hostname() || undefined;
|
|
177
|
+
} catch {
|
|
178
|
+
return undefined;
|
|
179
|
+
}
|
|
138
180
|
}
|
|
139
181
|
export const OpencodeOtelPlugin = createOtelPlugin();
|
|
140
182
|
export default OpencodeOtelPlugin;
|
|
183
|
+
|
|
141
184
|
//# sourceMappingURL=opencode.js.map
|
package/dist/opencode.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"
|
|
1
|
+
{"mappings":"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","names":[],"sources":["../src/opencode.ts"],"version":3,"file":"opencode.js","sourceRoot":""}
|
package/dist/propagation.d.ts
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
import type { Context } from "@opentelemetry/api";
|
|
2
2
|
import type { Logger } from "./logging.js";
|
|
3
3
|
export interface ProviderConfigLike {
|
|
4
|
-
|
|
4
|
+
options?: Record<string, unknown>;
|
|
5
5
|
}
|
|
6
6
|
export interface PropagationConfigInput {
|
|
7
|
-
|
|
7
|
+
provider?: Record<string, ProviderConfigLike | undefined>;
|
|
8
8
|
}
|
|
9
9
|
/**
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
10
|
+
* Wrap every provider's `options.fetch` so outgoing model requests carry W3C
|
|
11
|
+
* trace context, joining an OpenCode session and its gateway-side spans into
|
|
12
|
+
* one trace.
|
|
13
|
+
*
|
|
14
|
+
* This is the same interception seam `@vymalo/opencode-ratelimit` uses — and it
|
|
15
|
+
* composes the same way: the existing fetch is captured at install time and
|
|
16
|
+
* delegated to, so stacking the two plugins in either order works.
|
|
17
|
+
*/
|
|
18
18
|
export declare function installTracePropagation(input: PropagationConfigInput, deps: {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
getContext: () => Context | undefined;
|
|
20
|
+
logger: Logger;
|
|
21
|
+
fetchImpl?: typeof fetch;
|
|
22
22
|
}): number;
|
package/dist/propagation.js
CHANGED
|
@@ -2,54 +2,56 @@ import { defaultTextMapSetter } from "@opentelemetry/api";
|
|
|
2
2
|
import { W3CTraceContextPropagator } from "@opentelemetry/core";
|
|
3
3
|
const propagator = new W3CTraceContextPropagator();
|
|
4
4
|
/**
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
5
|
+
* Wrap every provider's `options.fetch` so outgoing model requests carry W3C
|
|
6
|
+
* trace context, joining an OpenCode session and its gateway-side spans into
|
|
7
|
+
* one trace.
|
|
8
|
+
*
|
|
9
|
+
* This is the same interception seam `@vymalo/opencode-ratelimit` uses — and it
|
|
10
|
+
* composes the same way: the existing fetch is captured at install time and
|
|
11
|
+
* delegated to, so stacking the two plugins in either order works.
|
|
12
|
+
*/
|
|
13
13
|
export function installTracePropagation(input, deps) {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
14
|
+
const providers = input.provider;
|
|
15
|
+
if (!providers) {
|
|
16
|
+
return 0;
|
|
17
|
+
}
|
|
18
|
+
let wrapped = 0;
|
|
19
|
+
for (const [providerId, providerConfig] of Object.entries(providers)) {
|
|
20
|
+
if (!providerConfig) {
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
const options = providerConfig.options ??= {};
|
|
24
|
+
const delegate = typeof options.fetch === "function" ? options.fetch : deps.fetchImpl ?? globalThis.fetch;
|
|
25
|
+
if (typeof delegate !== "function") {
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
options.fetch = async (input_, init) => {
|
|
29
|
+
const context = deps.getContext();
|
|
30
|
+
if (!context) {
|
|
31
|
+
return delegate(input_, init);
|
|
32
|
+
}
|
|
33
|
+
const carrier = {};
|
|
34
|
+
propagator.inject(context, carrier, defaultTextMapSetter);
|
|
35
|
+
if (Object.keys(carrier).length === 0) {
|
|
36
|
+
return delegate(input_, init);
|
|
37
|
+
}
|
|
38
|
+
const headers = new Headers(init?.headers ?? {});
|
|
39
|
+
// Never clobber an upstream traceparent — if something already set one,
|
|
40
|
+
// it knows more about the request than we do.
|
|
41
|
+
for (const [key, value] of Object.entries(carrier)) {
|
|
42
|
+
if (!headers.has(key)) {
|
|
43
|
+
headers.set(key, value);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return delegate(input_, {
|
|
47
|
+
...init,
|
|
48
|
+
headers
|
|
49
|
+
});
|
|
50
|
+
};
|
|
51
|
+
wrapped += 1;
|
|
52
|
+
deps.logger.trace("otel_trace_propagation_installed", { providerId });
|
|
53
|
+
}
|
|
54
|
+
return wrapped;
|
|
54
55
|
}
|
|
56
|
+
|
|
55
57
|
//# sourceMappingURL=propagation.js.map
|
package/dist/propagation.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"
|
|
1
|
+
{"mappings":"AACA,SAAS,4BAA4B;AACrC,SAAS,iCAAiC;AAY1C,MAAM,aAAa,IAAI,0BAA0B;;;;;;;;;;AAWjD,OAAO,SAAS,wBACd,OACA,MACQ;CACR,MAAM,YAAY,MAAM;CACxB,IAAI,CAAC,WAAW;EACd,OAAO;CACT;CAEA,IAAI,UAAU;CACd,KAAK,MAAM,CAAC,YAAY,mBAAmB,OAAO,QAAQ,SAAS,GAAG;EACpE,IAAI,CAAC,gBAAgB;GACnB;EACF;EACA,MAAM,UAAW,eAAe,YAAY,CAAC;EAC7C,MAAM,WACJ,OAAO,QAAQ,UAAU,aACpB,QAAQ,QACR,KAAK,aAAa,WAAW;EACpC,IAAI,OAAO,aAAa,YAAY;GAClC;EACF;EAEA,QAAQ,QAAQ,OACd,QACA,SACsB;GACtB,MAAM,UAAU,KAAK,WAAW;GAChC,IAAI,CAAC,SAAS;IACZ,OAAO,SAAS,QAAQ,IAAI;GAC9B;GACA,MAAM,UAAkC,CAAC;GACzC,WAAW,OAAO,SAAS,SAAS,oBAAoB;GACxD,IAAI,OAAO,KAAK,OAAO,CAAC,CAAC,WAAW,GAAG;IACrC,OAAO,SAAS,QAAQ,IAAI;GAC9B;GACA,MAAM,UAAU,IAAI,QAAQ,MAAM,WAAW,CAAC,CAAC;;;GAG/C,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,OAAO,GAAG;IAClD,IAAI,CAAC,QAAQ,IAAI,GAAG,GAAG;KACrB,QAAQ,IAAI,KAAK,KAAK;IACxB;GACF;GACA,OAAO,SAAS,QAAQ;IAAE,GAAG;IAAM;GAAQ,CAAC;EAC9C;EACA,WAAW;EACX,KAAK,OAAO,MAAM,oCAAoC,EAAE,WAAW,CAAC;CACtE;CAEA,OAAO;AACT","names":[],"sources":["../src/propagation.ts"],"version":3,"file":"propagation.js","sourceRoot":""}
|
package/dist/providers.d.ts
CHANGED
|
@@ -1,55 +1,66 @@
|
|
|
1
1
|
import type { Logger as OtelLogger } from "@opentelemetry/api-logs";
|
|
2
|
-
import { type Resource } from "@opentelemetry/resources";
|
|
2
|
+
import { type MaybePromise, type Resource } from "@opentelemetry/resources";
|
|
3
3
|
import { type LogRecordExporter } from "@opentelemetry/sdk-logs";
|
|
4
4
|
import { type PushMetricExporter } from "@opentelemetry/sdk-metrics";
|
|
5
5
|
import { type SpanExporter } from "@opentelemetry/sdk-trace";
|
|
6
6
|
import type { Tracer } from "@opentelemetry/api";
|
|
7
7
|
import type { Meter } from "@opentelemetry/api";
|
|
8
8
|
import type { Logger } from "./logging.js";
|
|
9
|
+
import { type TokenSource } from "./token-source.js";
|
|
10
|
+
import type { VcsInfo } from "./vcs.js";
|
|
9
11
|
import type { ResolvedOtelConfig } from "./types.js";
|
|
10
12
|
/**
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
* The three provider handles the recorder needs, plus lifecycle. `undefined`
|
|
14
|
+
* for a signal whose exporter is `none` — the recorder no-ops on it rather
|
|
15
|
+
* than branching on config everywhere.
|
|
16
|
+
*/
|
|
15
17
|
export interface TelemetryProviders {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
18
|
+
tracer?: Tracer;
|
|
19
|
+
meter?: Meter;
|
|
20
|
+
otelLogger?: OtelLogger;
|
|
21
|
+
/** Push everything buffered. Called on `session.idle` and on process exit. */
|
|
22
|
+
forceFlush(): Promise<void>;
|
|
23
|
+
/** Flush and tear down. */
|
|
24
|
+
shutdown(): Promise<void>;
|
|
23
25
|
}
|
|
24
26
|
/**
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
27
|
+
* Exporter factories, injectable so tests can substitute in-memory exporters
|
|
28
|
+
* without reaching the network or constructing real OTLP clients.
|
|
29
|
+
*/
|
|
28
30
|
export interface ExporterFactories {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
31
|
+
trace?: (config: ResolvedOtelConfig, tokenSource?: TokenSource) => SpanExporter | undefined;
|
|
32
|
+
metric?: (config: ResolvedOtelConfig, tokenSource?: TokenSource) => PushMetricExporter | undefined;
|
|
33
|
+
log?: (config: ResolvedOtelConfig, tokenSource?: TokenSource) => LogRecordExporter | undefined;
|
|
32
34
|
}
|
|
33
35
|
/**
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
36
|
+
* Build the resource every signal is stamped with.
|
|
37
|
+
*
|
|
38
|
+
* Deliberately identifies the **machine and the project**, never the developer:
|
|
39
|
+
* no git author email, no account id. An operator who wants per-person
|
|
40
|
+
* attribution adds it explicitly via `resourceAttributes` /
|
|
41
|
+
* `OTEL_RESOURCE_ATTRIBUTES`, which keeps that choice visible in config.
|
|
42
|
+
*/
|
|
41
43
|
export declare function buildResource(config: ResolvedOtelConfig, context: {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
44
|
+
/**
|
|
45
|
+
* May be a promise: OpenCode reports the host version as an
|
|
46
|
+
* `installation.updated` *event*, after the resource is already built. The
|
|
47
|
+
* OTel resource API awaits promise-valued attributes before the first
|
|
48
|
+
* export — see `deferred.ts` for why the deferral is always bounded.
|
|
49
|
+
*/
|
|
50
|
+
version?: MaybePromise<string | undefined>;
|
|
51
|
+
hostname?: string;
|
|
52
|
+
projectName?: string;
|
|
53
|
+
directory?: string;
|
|
54
|
+
worktree?: string;
|
|
55
|
+
/** May be a promise, for the same reason as `version` (`vcs.branch.updated`). */
|
|
56
|
+
branch?: MaybePromise<string | undefined>;
|
|
57
|
+
/** Repository metadata read off disk — see `vcs.ts`. */
|
|
58
|
+
vcs?: VcsInfo;
|
|
48
59
|
}): Resource;
|
|
49
60
|
/**
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
61
|
+
* Construct the enabled providers. Each signal is independent: a failure to
|
|
62
|
+
* build one leaves the others running, because partial telemetry is strictly
|
|
63
|
+
* better than an exception escaping into the host's plugin loader.
|
|
64
|
+
*/
|
|
54
65
|
export declare function createProviders(config: ResolvedOtelConfig, resource: Resource, logger: Logger, factories?: ExporterFactories): TelemetryProviders;
|
|
55
66
|
export declare function describeError(error: unknown): string;
|