@43world/llm-logger-openclaw-plugin 0.0.3

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/DESIGN.md ADDED
@@ -0,0 +1,352 @@
1
+ # llm-logger-openclaw-plugin 需求与设计文档
2
+
3
+ ## 1. 目标
4
+
5
+ 开发一个 OpenClaw 原生插件 `llm-logger-openclaw-plugin`。
6
+
7
+ 插件安装并启用后,应当能够把 OpenClaw 对话过程中触发的底层 LLM 调用信息写入指定日志文件,包括:
8
+
9
+ - LLM 调用前的请求参数
10
+ - LLM 调用后的返回响应数据
11
+ - 与该次调用相关的基础上下文信息
12
+
13
+ 日志应尽量覆盖 OpenClaw 对话主链路中的所有模型调用,而不是只记录最终回复。
14
+
15
+ ## 2. 用户需求拆解
16
+
17
+ 用户的原始需求可以拆成以下可验证目标:
18
+
19
+ 1. 插件是一个独立项目,路径为 `/root/projects/llm-logger-openclaw-plugin`。
20
+ 2. 插件遵循 OpenClaw 插件规范,可被 `openclaw plugins install` / `openclaw plugins enable` 使用。
21
+ 3. 插件启用后,不需要修改 OpenClaw 核心源码。
22
+ 4. 插件可以把日志写到用户指定文件。
23
+ 5. 日志内容至少包含:
24
+ - provider
25
+ - model
26
+ - 调用发生时间
27
+ - 请求 payload
28
+ - 响应 payload
29
+ - OpenClaw 会话上下文标识
30
+ 6. 日志格式应当便于后续机器分析,优先使用 JSON Lines。
31
+
32
+ ## 3. 对 OpenClaw 源码的调研结论
33
+
34
+ ### 3.1 已有插件 hook
35
+
36
+ OpenClaw 已经提供 typed hooks:
37
+
38
+ - `llm_input`
39
+ - `llm_output`
40
+
41
+ 但这两个 hook 只能拿到“规范化后的输入/输出摘要”,例如:
42
+
43
+ - `prompt`
44
+ - `historyMessages`
45
+ - `assistantTexts`
46
+ - `usage`
47
+
48
+ 它们不能直接拿到底层 provider 的原始请求体和原始响应体,因此单独依赖这两个 hook 不足以满足需求。
49
+
50
+ ### 3.2 插件服务能力
51
+
52
+ OpenClaw 插件支持 `registerService()`。服务会在网关启动后常驻运行,因此适合做运行时拦截和 monkey patch。
53
+
54
+ 这意味着插件可以在不修改核心源码的前提下,对运行时共享依赖增加日志能力。
55
+
56
+ ### 3.3 Agent 内部存在 `onPayload`
57
+
58
+ OpenClaw 依赖的 `@mariozechner/pi-agent-core` / `@mariozechner/pi-ai` 已支持 `onPayload` 回调。
59
+
60
+ 该回调可以拿到 provider 实际发送前的 payload,包括:
61
+
62
+ - HTTP provider 的请求对象
63
+ - OpenAI Responses WebSocket 路径下的 `response.create` payload
64
+
65
+ OpenClaw 核心目前没有把这个能力直接暴露为插件 hook,但可以通过对共享 `Agent` 原型打补丁统一接入。
66
+
67
+ ### 3.4 返回响应的采集路径
68
+
69
+ 底层响应存在两条主要链路:
70
+
71
+ 1. HTTP / fetch 链路
72
+ - 大多数 provider 走这条路径
73
+ - 可以通过包装 `globalThis.fetch` 记录请求与响应
74
+ 2. OpenAI Responses WebSocket 链路
75
+ - OpenClaw 对 `openai/openai-responses` 存在专门的 WebSocket 快速路径
76
+ - 这条路径不走 `fetch`
77
+ - 需要补充 `ws` 层的发送/接收日志
78
+
79
+ ## 4. 设计目标
80
+
81
+ ### 4.1 功能目标
82
+
83
+ - 记录每一次底层 LLM 请求 payload
84
+ - 记录对应的底层响应数据
85
+ - 记录 OpenClaw 视角的会话/模型上下文
86
+ - 支持配置日志文件路径
87
+ - 支持基础脱敏与截断,避免日志无限膨胀
88
+
89
+ ### 4.2 非目标
90
+
91
+ 以下内容不作为第一版目标:
92
+
93
+ - 不实现日志轮转
94
+ - 不实现远程日志上报
95
+ - 不实现 UI 面板
96
+ - 不保证覆盖 OpenClaw 之外的任意第三方插件自定义网络请求
97
+
98
+ ## 5. 总体方案
99
+
100
+ 插件由三部分组成:
101
+
102
+ 1. `typed hooks`
103
+ - 使用 `llm_input` / `llm_output`
104
+ - 记录 OpenClaw 语义层的调用边界和摘要信息
105
+ 2. `service runtime patch`
106
+ - 在插件服务启动时,对共享运行时打补丁
107
+ - 统一补进“底层请求/响应”日志能力
108
+ 3. `JSONL writer`
109
+ - 统一输出到指定日志文件
110
+ - 每条事件一行 JSON
111
+
112
+ ## 6. 核心技术方案
113
+
114
+ ### 6.1 对 `Agent.prototype` 打补丁
115
+
116
+ 目标:
117
+
118
+ - 在每次实际 LLM 调用前创建一段“调用上下文”
119
+ - 把 `onPayload` 回调接入到现有 `streamFn`
120
+ - 为后续 `fetch` / `ws` 拦截提供关联上下文
121
+
122
+ 实现方式:
123
+
124
+ 1. 解析 OpenClaw 当前运行时使用的 `@mariozechner/pi-agent-core` 实际路径。
125
+ 2. 加载同一个模块实例。
126
+ 3. patch `Agent.prototype._runLoop`。
127
+ 4. 在 `_runLoop` 执行期间临时包装 `this.streamFn`。
128
+ 5. 每当 `streamFn(model, context, options)` 被调用时:
129
+ - 生成一个插件内部 `callId`
130
+ - 建立 AsyncLocalStorage 上下文
131
+ - 包装 `options.onPayload`
132
+ - 记录 `provider_request_payload` 事件
133
+
134
+ 这样可以覆盖一次用户 prompt 内部的多次 LLM 调用,而不是只覆盖最外层一次。
135
+
136
+ ### 6.2 AsyncLocalStorage 关联上下文
137
+
138
+ 因为底层网络调用发生在异步链路中,需要一个轻量关联机制,把:
139
+
140
+ - sessionId
141
+ - provider
142
+ - model
143
+ - callId
144
+ - 调用序号
145
+
146
+ 传递到 `fetch` 和 `ws` 拦截层。
147
+
148
+ 方案:
149
+
150
+ - 使用 `AsyncLocalStorage<CallContext>`
151
+ - 每次 `streamFn` 调用时进入新的上下文
152
+ - 后续网络层日志自动读取当前上下文
153
+
154
+ ### 6.3 包装 `globalThis.fetch`
155
+
156
+ 目标:
157
+
158
+ - 记录 HTTP provider 的请求和响应
159
+
160
+ 记录内容:
161
+
162
+ - 请求:
163
+ - url
164
+ - method
165
+ - headers
166
+ - body 文本或可序列化摘要
167
+ - 响应:
168
+ - status
169
+ - headers
170
+ - body 文本截断内容
171
+
172
+ 实现要求:
173
+
174
+ - 只在当前存在 AsyncLocalStorage 调用上下文时记录,避免污染无关请求
175
+ - 对敏感 header 做脱敏
176
+ - 对 body 做最大字节截断
177
+ - 对响应使用 `clone()` 或副本流读取,不能破坏原始消费链
178
+
179
+ ### 6.4 包装 `ws` 模块
180
+
181
+ 目标:
182
+
183
+ - 记录 OpenAI Responses WebSocket 路径下的发送和接收数据
184
+
185
+ 实现方式:
186
+
187
+ 1. 解析 OpenClaw 当前运行时实际使用的 `ws` 模块路径
188
+ 2. patch 同一个 `WebSocket.prototype`
189
+ 3. 包装:
190
+ - `send()`
191
+ - `emit("message", ...)`
192
+
193
+ 筛选条件:
194
+
195
+ - 仅记录 `url` 命中 OpenAI Responses 路径的连接
196
+ - 优先记录文本帧
197
+
198
+ 记录内容:
199
+
200
+ - outbound frame
201
+ - inbound frame
202
+ - 当前 `callId`
203
+ - 当前 `sessionId/provider/model`
204
+
205
+ 说明:
206
+
207
+ WebSocket 返回的是事件流,不一定天然存在一个“单个完整响应体”。因此第一版会按帧记录;同时用 `llm_output` 事件补充最终输出摘要。
208
+
209
+ ## 7. 日志模型设计
210
+
211
+ 日志采用 JSONL,每行一条事件。
212
+
213
+ ### 7.1 通用字段
214
+
215
+ 所有事件尽量包含:
216
+
217
+ - `ts`: ISO 时间戳
218
+ - `plugin`: 固定为 `llm-logger-openclaw-plugin`
219
+ - `eventType`
220
+ - `callId`
221
+ - `sessionId`
222
+ - `provider`
223
+ - `model`
224
+
225
+ ### 7.2 事件类型
226
+
227
+ 第一版计划输出以下事件:
228
+
229
+ 1. `llm_input`
230
+ - OpenClaw 规范化输入摘要
231
+ 2. `provider_request_payload`
232
+ - `onPayload` 捕获到的 provider 请求对象
233
+ 3. `http_request`
234
+ - `fetch` 级别请求
235
+ 4. `http_response`
236
+ - `fetch` 级别响应
237
+ 5. `ws_send`
238
+ - WebSocket 发送帧
239
+ 6. `ws_message`
240
+ - WebSocket 接收帧
241
+ 7. `llm_output`
242
+ - OpenClaw 规范化输出摘要
243
+
244
+ ### 7.3 响应体截断策略
245
+
246
+ 为了避免日志文件无限膨胀:
247
+
248
+ - 默认对 request/response body 设置最大采集字节数
249
+ - 超限后写入:
250
+ - `truncated: true`
251
+ - `capturedBytes`
252
+ - `originalLength`(若可得)
253
+
254
+ ## 8. 配置设计
255
+
256
+ 插件配置放在:
257
+
258
+ - `plugins.entries.llm-logger-openclaw-plugin.config`
259
+
260
+ 第一版配置项:
261
+
262
+ - `enabled`: boolean,默认 `true`
263
+ - `logFile`: string,必填,日志文件路径
264
+ - `maxBodyBytes`: number,默认 `262144`
265
+ - `redactAuthorization`: boolean,默认 `true`
266
+ - `includeHooks`: boolean,默认 `true`
267
+ - `includeHttp`: boolean,默认 `true`
268
+ - `includeWebSocket`: boolean,默认 `true`
269
+
270
+ 备注:
271
+
272
+ - 插件启用由 OpenClaw 本身控制
273
+ - `enabled` 是插件内部软开关,便于临时停用
274
+
275
+ ## 9. 脱敏规则
276
+
277
+ 默认脱敏以下字段:
278
+
279
+ - `authorization`
280
+ - `api-key`
281
+ - `x-api-key`
282
+ - `proxy-authorization`
283
+ - JSON body 中常见字段:
284
+ - `apiKey`
285
+ - `token`
286
+ - `access_token`
287
+ - `refresh_token`
288
+ - `secret`
289
+ - `password`
290
+
291
+ 脱敏策略:
292
+
293
+ - header 统一替换为 `[REDACTED]`
294
+ - JSON 中命中敏感键的值替换为 `[REDACTED]`
295
+
296
+ ## 10. 已知限制
297
+
298
+ ### 10.1 `llm_input/llm_output` 不是逐次底层调用
299
+
300
+ 这两个 hook 更接近“外层 agent turn”,不是一次用户 prompt 内部所有 tool loop 的逐次底层请求。因此它们只能作为摘要和边界补充。
301
+
302
+ ### 10.2 WebSocket 返回按帧记录
303
+
304
+ OpenAI Responses WebSocket 路径第一版按帧记录,而不是强制拼装为单个完整响应对象。
305
+
306
+ ### 10.3 仅保证 OpenClaw 主对话链路
307
+
308
+ 插件第一版的保证范围是 OpenClaw 主对话链路中的 LLM 调用。对于完全脱离 Agent 调用上下文的自定义后台请求,不保证记录。
309
+
310
+ ## 11. 实施计划
311
+
312
+ ### 阶段 1
313
+
314
+ - 建立插件项目结构
315
+ - 提供 `openclaw.plugin.json`
316
+ - 提供基础配置 schema
317
+
318
+ ### 阶段 2
319
+
320
+ - 实现 JSONL writer
321
+ - 实现脱敏与截断工具
322
+
323
+ ### 阶段 3
324
+
325
+ - 实现 typed hooks:`llm_input` / `llm_output`
326
+ - 实现服务启动与停止
327
+
328
+ ### 阶段 4
329
+
330
+ - 实现 `Agent.prototype` patch
331
+ - 实现 `fetch` patch
332
+ - 实现 `ws` patch
333
+
334
+ ### 阶段 5
335
+
336
+ - 做本地静态验证
337
+ - 输出安装与配置说明
338
+
339
+ ## 12. 交付标准
340
+
341
+ 满足以下条件视为完成:
342
+
343
+ 1. 插件目录具备完整可加载结构
344
+ 2. OpenClaw 可以识别插件 manifest
345
+ 3. 插件启用后能写出 JSONL 日志
346
+ 4. 日志中可以看到:
347
+ - 一次规范化 `llm_input`
348
+ - 至少一次底层请求事件
349
+ - 至少一次底层响应事件
350
+ - 一次规范化 `llm_output`
351
+ 5. 日志文件路径可通过插件配置指定
352
+
package/README.md ADDED
@@ -0,0 +1,80 @@
1
+ # llm-logger-openclaw-plugin
2
+
3
+ OpenClaw 插件。
4
+
5
+ 启用后会把 OpenClaw 对话链路中的底层 LLM 调用请求参数和响应数据写入 JSONL 日志文件。
6
+
7
+ ## 功能
8
+
9
+ - 记录 provider 请求 payload
10
+ - 记录 HTTP 请求与响应
11
+ - 记录 OpenAI Responses WebSocket 收发帧
12
+ - 记录 OpenClaw 的 `llm_input` / `llm_output` 摘要事件
13
+
14
+ ## 安装
15
+
16
+ ```bash
17
+ openclaw plugins install -l /root/projects/llm-logger-openclaw-plugin
18
+ openclaw plugins enable llm-logger-openclaw-plugin
19
+ ```
20
+
21
+ ## 配置
22
+
23
+ 在 OpenClaw 配置中加入:
24
+
25
+ ```json
26
+ {
27
+ "plugins": {
28
+ "entries": {
29
+ "llm-logger-openclaw-plugin": {
30
+ "enabled": true,
31
+ "config": {
32
+ "logFile": "/tmp/openclaw-llm.jsonl",
33
+ "maxBodyBytes": 262144,
34
+ "redactAuthorization": true,
35
+ "includeHooks": true,
36
+ "includeHttp": true,
37
+ "includeWebSocket": true
38
+ }
39
+ }
40
+ }
41
+ }
42
+ }
43
+ ```
44
+
45
+ 如果不指定 `logFile`,默认按会话和日期写到:
46
+
47
+ ```text
48
+ <OPENCLAW_STATE_DIR>/logs/<session_id>/llm-logger-openclaw-plugin-YYYY-MM-DD.jsonl
49
+ ```
50
+
51
+ 其中:
52
+
53
+ - `<session_id>` 来自 OpenClaw 会话;若缺失会写到 `_unknown_session` 目录
54
+ - `YYYY-MM-DD` 为当天日期后缀,用于每日滚动记录
55
+
56
+ 如果指定了 `logFile`(例如 `/tmp/openclaw-llm.jsonl`),会将其作为基准路径与基准文件名,最终写入:
57
+
58
+ ```text
59
+ /tmp/<session_id>/openclaw-llm-YYYY-MM-DD.jsonl
60
+ ```
61
+
62
+ ## 日志格式
63
+
64
+ 日志为 JSON Lines。
65
+
66
+ 常见事件类型:
67
+
68
+ - `llm_input`
69
+ - `provider_request_payload`
70
+ - `http_request`
71
+ - `http_response`
72
+ - `ws_send`
73
+ - `ws_message`
74
+ - `llm_output`
75
+
76
+ ## 说明
77
+
78
+ - 日志默认会对常见认证字段做脱敏
79
+ - 请求/响应 body 默认按 `maxBodyBytes` 截断
80
+ - WebSocket 路径按帧记录,不强制拼装成单一完整响应
package/index.ts ADDED
@@ -0,0 +1,46 @@
1
+ import path from "node:path";
2
+ import { pluginConfigSchema, resolvePluginConfig } from "./src/config.js";
3
+ import { getPluginManager } from "./src/manager.js";
4
+ import packageJson from "./package.json" with { type: "json" };
5
+
6
+ const PLUGIN_ID = "llm-logger-openclaw-plugin";
7
+
8
+ export default {
9
+ id: PLUGIN_ID,
10
+ name: "LLM Logger OpenClaw Plugin",
11
+ version: packageJson.version,
12
+ description: "Logs OpenClaw LLM request payloads and responses to a JSONL file.",
13
+ configSchema: pluginConfigSchema,
14
+ register(api) {
15
+ const pluginConfig = resolvePluginConfig(api.pluginConfig);
16
+ const manager = getPluginManager();
17
+ manager.setRegistrationLogger(api.logger);
18
+ manager.setPluginConfig(pluginConfig);
19
+
20
+ const service = {
21
+ id: PLUGIN_ID,
22
+ async start(ctx) {
23
+ await manager.start({
24
+ pluginId: PLUGIN_ID,
25
+ stateDir: ctx.stateDir,
26
+ workspaceDir: ctx.workspaceDir,
27
+ defaultLogFile: path.join(ctx.stateDir, "logs", `${PLUGIN_ID}.jsonl`),
28
+ logger: ctx.logger,
29
+ });
30
+ },
31
+ async stop() {
32
+ await manager.stop();
33
+ },
34
+ };
35
+
36
+ api.registerService(service);
37
+
38
+ api.on("llm_input", (event, ctx) => {
39
+ manager.recordLlmInput(event, ctx);
40
+ });
41
+
42
+ api.on("llm_output", (event, ctx) => {
43
+ manager.recordLlmOutput(event, ctx);
44
+ });
45
+ },
46
+ };
@@ -0,0 +1,40 @@
1
+ {
2
+ "id": "llm-logger-openclaw-plugin",
3
+ "name": "LLM Logger OpenClaw Plugin",
4
+ "description": "Logs OpenClaw LLM request payloads and responses to a JSONL file.",
5
+ "version": "0.0.3",
6
+ "configSchema": {
7
+ "type": "object",
8
+ "additionalProperties": false,
9
+ "properties": {
10
+ "enabled": {
11
+ "type": "boolean",
12
+ "default": true
13
+ },
14
+ "logFile": {
15
+ "type": "string"
16
+ },
17
+ "maxBodyBytes": {
18
+ "type": "integer",
19
+ "minimum": 1024,
20
+ "default": 262144
21
+ },
22
+ "redactAuthorization": {
23
+ "type": "boolean",
24
+ "default": true
25
+ },
26
+ "includeHooks": {
27
+ "type": "boolean",
28
+ "default": true
29
+ },
30
+ "includeHttp": {
31
+ "type": "boolean",
32
+ "default": true
33
+ },
34
+ "includeWebSocket": {
35
+ "type": "boolean",
36
+ "default": true
37
+ }
38
+ }
39
+ }
40
+ }
package/package.json ADDED
@@ -0,0 +1,14 @@
1
+ {
2
+ "name": "@43world/llm-logger-openclaw-plugin",
3
+ "version": "0.0.3",
4
+ "description": "OpenClaw plugin that logs LLM request payloads and responses to JSONL.",
5
+ "type": "module",
6
+ "openclaw": {
7
+ "extensions": [
8
+ "./index.ts"
9
+ ]
10
+ },
11
+ "peerDependencies": {
12
+ "openclaw": "*"
13
+ }
14
+ }
package/src/config.ts ADDED
@@ -0,0 +1,142 @@
1
+ const DEFAULT_MAX_BODY_BYTES = 262_144;
2
+
3
+ export type PluginConfig = {
4
+ enabled: boolean;
5
+ logFile?: string;
6
+ maxBodyBytes: number;
7
+ redactAuthorization: boolean;
8
+ includeHooks: boolean;
9
+ includeHttp: boolean;
10
+ includeWebSocket: boolean;
11
+ };
12
+
13
+ type Issue = {
14
+ path: Array<string | number>;
15
+ message: string;
16
+ };
17
+
18
+ type SafeParseResult =
19
+ | { success: true; data: PluginConfig | undefined }
20
+ | { success: false; error: { issues: Issue[] } };
21
+
22
+ export type ResolvedPluginConfig = PluginConfig & {
23
+ logFile: string;
24
+ };
25
+
26
+ function error(message: string): SafeParseResult {
27
+ return {
28
+ success: false,
29
+ error: {
30
+ issues: [{ path: [], message }],
31
+ },
32
+ };
33
+ }
34
+
35
+ function normalizeBoolean(value: unknown, fallback: boolean): boolean {
36
+ return typeof value === "boolean" ? value : fallback;
37
+ }
38
+
39
+ function normalizeString(value: unknown): string | undefined {
40
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined;
41
+ }
42
+
43
+ function normalizeMaxBodyBytes(value: unknown): number {
44
+ if (typeof value === "number" && Number.isFinite(value) && value >= 1024) {
45
+ return Math.floor(value);
46
+ }
47
+ if (typeof value === "string") {
48
+ const parsed = Number.parseInt(value, 10);
49
+ if (Number.isFinite(parsed) && parsed >= 1024) {
50
+ return parsed;
51
+ }
52
+ }
53
+ return DEFAULT_MAX_BODY_BYTES;
54
+ }
55
+
56
+ export function resolvePluginConfig(value: unknown): PluginConfig {
57
+ const parsed = pluginConfigSchema.safeParse(value);
58
+ if (!parsed.success) {
59
+ return {
60
+ enabled: true,
61
+ maxBodyBytes: DEFAULT_MAX_BODY_BYTES,
62
+ redactAuthorization: true,
63
+ includeHooks: true,
64
+ includeHttp: true,
65
+ includeWebSocket: true,
66
+ };
67
+ }
68
+ return (
69
+ parsed.data ?? {
70
+ enabled: true,
71
+ maxBodyBytes: DEFAULT_MAX_BODY_BYTES,
72
+ redactAuthorization: true,
73
+ includeHooks: true,
74
+ includeHttp: true,
75
+ includeWebSocket: true,
76
+ }
77
+ );
78
+ }
79
+
80
+ export function finalizePluginConfig(
81
+ config: PluginConfig,
82
+ defaultLogFile: string,
83
+ ): ResolvedPluginConfig {
84
+ return {
85
+ ...config,
86
+ logFile: config.logFile?.trim() || defaultLogFile,
87
+ };
88
+ }
89
+
90
+ export const pluginConfigSchema = {
91
+ safeParse(value: unknown): SafeParseResult {
92
+ if (value === undefined) {
93
+ return { success: true, data: undefined };
94
+ }
95
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
96
+ return error("expected config object");
97
+ }
98
+
99
+ const record = value as Record<string, unknown>;
100
+ const unknownKeys = Object.keys(record).filter(
101
+ (key) =>
102
+ ![
103
+ "enabled",
104
+ "logFile",
105
+ "maxBodyBytes",
106
+ "redactAuthorization",
107
+ "includeHooks",
108
+ "includeHttp",
109
+ "includeWebSocket",
110
+ ].includes(key),
111
+ );
112
+ if (unknownKeys.length > 0) {
113
+ return error(`unknown config keys: ${unknownKeys.join(", ")}`);
114
+ }
115
+
116
+ return {
117
+ success: true,
118
+ data: {
119
+ enabled: normalizeBoolean(record.enabled, true),
120
+ logFile: normalizeString(record.logFile),
121
+ maxBodyBytes: normalizeMaxBodyBytes(record.maxBodyBytes),
122
+ redactAuthorization: normalizeBoolean(record.redactAuthorization, true),
123
+ includeHooks: normalizeBoolean(record.includeHooks, true),
124
+ includeHttp: normalizeBoolean(record.includeHttp, true),
125
+ includeWebSocket: normalizeBoolean(record.includeWebSocket, true),
126
+ },
127
+ };
128
+ },
129
+ jsonSchema: {
130
+ type: "object",
131
+ additionalProperties: false,
132
+ properties: {
133
+ enabled: { type: "boolean", default: true },
134
+ logFile: { type: "string" },
135
+ maxBodyBytes: { type: "integer", minimum: 1024, default: DEFAULT_MAX_BODY_BYTES },
136
+ redactAuthorization: { type: "boolean", default: true },
137
+ includeHooks: { type: "boolean", default: true },
138
+ includeHttp: { type: "boolean", default: true },
139
+ includeWebSocket: { type: "boolean", default: true },
140
+ },
141
+ },
142
+ };