Skip to content

Commit 15bfec8

Browse files
feat(main): opt-in prompt logging
1 parent f88839a commit 15bfec8

4 files changed

Lines changed: 20 additions & 1 deletion

File tree

‎README.md‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ An [opencode](https://opencode.ai) plugin that exports telemetry via OpenTelemet
5656
| `session.created` | Session started |
5757
| `session.idle` | Session went idle (includes total tokens, cost, messages) |
5858
| `session.error` | Session error |
59-
| `user_prompt` | User sent a message (includes `prompt_length`, `model`, `agent`) |
59+
| `user_prompt` | User sent a message (includes `prompt_length`, `model`, `agent`; also `prompt` when `OPENCODE_CAPTURE_PROMPT` is set) |
6060
| `api_request` | Completed assistant message (tokens, cost, duration) |
6161
| `api_error` | Failed assistant message (error summary, duration) |
6262
| `tool_result` | Tool completed or errored (duration, success, output size) |
@@ -99,6 +99,7 @@ The environment variables (set them in your shell profile — `~/.zshrc`, `~/.ba
9999
| `OPENCODE_METRIC_PREFIX` | `opencode.` | Prefix for all metric names (e.g. set to `claude_code.` for Claude Code dashboard compatibility) |
100100
| `OPENCODE_DISABLE_METRICS` | *(unset)* | Comma-separated list of metric name suffixes to disable (e.g. `cache.count,session.duration`) |
101101
| `OPENCODE_DISABLE_LOGS` | *(unset)* | Set to any non-empty value to suppress all OTLP log events while leaving metrics and traces unchanged |
102+
| `OPENCODE_CAPTURE_PROMPT` | *(unset)* | Set to any non-empty value to include the full prompt text in the `prompt` field of `user_prompt` log events. **Off by default — prompts may contain secrets or PII; enable only for trusted collectors.** |
102103
| `OPENCODE_DISABLE_TRACES` | *(unset)* | Comma-separated list of trace types to disable (`session`, `llm`, `tool`). Use `all`, `*`, `true`, or `1` to disable every trace type |
103104
| `OPENCODE_OTLP_HEADERS` | *(unset)* | Comma-separated `key=value` headers added to all OTLP exports. **Keep out of version control — may contain sensitive auth tokens.** |
104105
| `OPENCODE_OTLP_HEADERS_HELPER` | *(unset)* | Executable script/binary that returns dynamic OTLP headers as JSON after an auth failure. Helper headers override `OPENCODE_OTLP_HEADERS`. |

‎src/config.ts‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ const TRACE_DISABLE_ALL_VALUES = new Set(["all", "*", "true", "1"])
1313
export type PluginConfig = {
1414
enabled: boolean
1515
logsEnabled: boolean
16+
capturePromptInLogs: boolean
1617
endpoint: string
1718
protocol: "grpc" | "http/protobuf" | "http/json"
1819
metricsInterval: number
@@ -55,6 +56,7 @@ export function parseAttributePairs(raw: string | undefined): Record<string, str
5556
export type OtelPluginOptions = {
5657
enabled?: boolean
5758
logsEnabled?: boolean
59+
capturePromptInLogs?: boolean
5860
endpoint?: string
5961
protocol?: "grpc" | "http/protobuf" | "http/json"
6062
metricsInterval?: number
@@ -185,6 +187,7 @@ export function loadConfig(options: OtelPluginOptions = {}): PluginConfig {
185187
return {
186188
enabled: pickBoolean(resolvedOptions.enabled) ?? hasNonEmptyEnv("OPENCODE_ENABLE_TELEMETRY"),
187189
logsEnabled: pickBoolean(resolvedOptions.logsEnabled) ?? !hasNonEmptyEnv("OPENCODE_DISABLE_LOGS"),
190+
capturePromptInLogs: pickBoolean(resolvedOptions.capturePromptInLogs) ?? hasNonEmptyEnv("OPENCODE_CAPTURE_PROMPT_IN_LOGS"),
188191
endpoint: pickString(resolvedOptions.endpoint) ?? process.env["OPENCODE_OTLP_ENDPOINT"] ?? "http://localhost:4317",
189192
protocol,
190193
metricsInterval: pickPositiveInt(resolvedOptions.metricsInterval) ?? parseEnvInt("OPENCODE_OTLP_METRICS_INTERVAL", 60000),

‎src/index.ts‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,13 @@ export const OtelPlugin: Plugin = async ({ project, client, directory, worktree
133133
await log("info", "OTLP log events disabled")
134134
}
135135

136+
if (config.capturePromptInLogs) {
137+
await log(
138+
"info",
139+
"prompt-in-logs capture enabled - full prompt text emitted in the `prompt` attribute of user_prompt log events (spans always carry the prompt regardless)",
140+
)
141+
}
142+
136143
const ctx: HandlerContext = {
137144
log,
138145
emitLog,
@@ -269,6 +276,7 @@ export const OtelPlugin: Plugin = async ({ project, client, directory, worktree
269276
"session.id": input.sessionID,
270277
...agentAttrs(agent, agentType),
271278
prompt_length: promptLength,
279+
...(config.capturePromptInLogs ? { prompt: promptText } : {}),
272280
model: input.model
273281
? `${input.model.providerID}/${input.model.modelID}`
274282
: "unknown",

‎tests/config.test.ts‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ describe("loadConfig", () => {
7474
"OPENCODE_OTLP_METRICS_TEMPORALITY",
7575
"OPENCODE_DISABLE_METRICS",
7676
"OPENCODE_DISABLE_LOGS",
77+
"OPENCODE_CAPTURE_PROMPT_IN_LOGS",
7778
"OPENCODE_DISABLE_TRACES",
7879
"OTEL_EXPORTER_OTLP_HEADERS",
7980
"OTEL_RESOURCE_ATTRIBUTES",
@@ -86,6 +87,7 @@ describe("loadConfig", () => {
8687
const cfg = loadConfig()
8788
expect(cfg.enabled).toBe(false)
8889
expect(cfg.logsEnabled).toBe(true)
90+
expect(cfg.capturePromptInLogs).toBe(false)
8991
expect(cfg.endpoint).toBe("http://localhost:4317")
9092
expect(cfg.protocol).toBe("grpc")
9193
expect(cfg.metricsInterval).toBe(60000)
@@ -102,6 +104,11 @@ describe("loadConfig", () => {
102104
expect(loadConfig().logsEnabled).toBe(false)
103105
})
104106

107+
test("capturePromptInLogs is true when OPENCODE_CAPTURE_PROMPT_IN_LOGS is set", () => {
108+
process.env["OPENCODE_CAPTURE_PROMPT_IN_LOGS"] = "1"
109+
expect(loadConfig().capturePromptInLogs).toBe(true)
110+
})
111+
105112
test("reads custom endpoint", () => {
106113
process.env["OPENCODE_OTLP_ENDPOINT"] = "http://collector:4317"
107114
expect(loadConfig().endpoint).toBe("http://collector:4317")

0 commit comments

Comments
 (0)