OpenClaw Gateway 插件开发实战:从零构建企业级 AI 插件
一、为什么需要 Gateway 插件?
OpenClaw Gateway 是 Agent 架构中的”交通枢纽”——它负责请求路由、鉴权、限流、日志等横切关注点。但在实际生产中,企业往往需要:
- 自定义认证:对接内部 OAuth2/LDAP 而非默认的 API Key
- 数据转换:在 Agent 调用外部 API 前后做格式适配
- 审计日志:将每一次 Agent 交互写入公司合规系统
- 动态限流:基于团队配额、API 成本做精细化管控
这些需求无法通过配置「开箱即用」,而是需要插件化扩展。OpenClaw Gateway 的插件系统为此而生。
本文将带你完整走通一个 Elasticsearch 审计日志插件的开发全流程。
二、插件系统架构
2.1 生命周期钩子
OpenClaw Gateway 插件围绕一系列钩子(Hooks)展开,常见的有:
| 钩子 | 触发时机 | 典型用途 |
|---|---|---|
onRequest | 请求进入 Gateway 时(鉴权前) | 自定义认证、请求改写 |
onBeforeAgent | 请求路由到 Agent 前 | 注入上下文、加载记忆 |
onAfterAgent | Agent 返回响应后 | 响应转换、结果缓存 |
onResponse | 响应返回客户端前 | 审计日志、数据脱敏 |
onError | 任意环节抛出异常 | 错误记录、自定义错误响应 |
2.2 插件注册流程
请求 → Gateway → [onRequest] → [onBeforeAgent] → Agent
↓
客户端 ← [onResponse] ← [onAfterAgent] ←──────────────响应
三、实战:构建审计日志插件
3.1 项目结构
openclaw-audit-plugin/
├── package.json
├── src/
│ ├── index.ts # 插件入口
│ ├── es-client.ts # Elasticsearch 客户端
│ └── types.ts # 类型定义
└── README.md
3.2 插件入口文件
// src/index.ts
import { GatewayPlugin, PluginContext } from '@openclaw/gateway';
interface AuditPluginConfig {
esNodes: string[];
indexPrefix: string;
flushIntervalMs: number;
}
export default class AuditPlugin implements GatewayPlugin {
name = 'audit-log';
private config: AuditPluginConfig;
private buffer: AuditEntry[] = [];
private flushTimer: NodeJS.Timeout | null = null;
constructor(config: AuditPluginConfig) {
this.config = {
esNodes: config.esNodes,
indexPrefix: config.indexPrefix || 'openclaw-audit',
flushIntervalMs: config.flushIntervalMs || 5_000,
};
this.initEsClient();
}
private async flush(): Promise<void> {
if (this.buffer.length === 0) return;
const batch = this.buffer.splice(0);
try {
await this.bulkInsert(batch);
} catch (err) {
console.error('[AuditPlugin] flush failed:', err);
this.buffer.unshift(...batch);
}
}
async onStart(ctx: PluginContext): Promise<void> {
this.flushTimer = setInterval(() => this.flush(), this.config.flushIntervalMs);
ctx.logger.info('AuditPlugin initialized');
}
async onStop(): Promise<void> {
if (this.flushTimer) clearInterval(this.flushTimer);
await this.flush();
}
async onRequest(ctx: PluginContext): Promise<void> {
this.buffer.push({
timestamp: new Date().toISOString(),
phase: 'request',
requestId: ctx.request.id,
method: ctx.request.method,
path: ctx.request.path,
headers: this.sanitizeHeaders(ctx.request.headers),
});
}
async onAfterAgent(ctx: PluginContext): Promise<void> {
this.buffer.push({
timestamp: new Date().toISOString(),
phase: 'agent_response',
requestId: ctx.request.id,
agentName: ctx.agent?.name,
tokenUsage: ctx.usage,
durationMs: Date.now() - ctx.request.startTime,
});
}
async onError(ctx: PluginContext, error: Error): Promise<void> {
this.buffer.push({
timestamp: new Date().toISOString(),
phase: 'error',
requestId: ctx.request.id,
errorMessage: error.message,
stack: error.stack,
});
}
private sanitizeHeaders(headers: Record<string, string>): Record<string, string> {
const sensitive = ['authorization', 'x-api-key', 'cookie'];
const sanitized = { ...headers };
for (const key of sensitive) {
if (sanitized[key]) sanitized[key] = '***';
}
return sanitized;
}
}
3.3 插件注册配置
# openclaw.yaml
plugins:
- name: audit-log
package: "./openclaw-audit-plugin"
enabled: true
config:
esNodes:
- "http://es01:9200"
- "http://es02:9200"
indexPrefix: "openclaw-audit"
flushIntervalMs: 3000
3.4 启用插件
openclaw gateway start --config openclaw.yaml --plugins audit-log
# 或通过环境变量
OPENCLAW_PLUGINS=audit-log openclaw gateway start
四、插件开发最佳实践
4.1 错误隔离
插件抛出的异常不应导致 Gateway 主进程崩溃:
// 建议:用 try-catch 包裹插件回调
safeInvoke(plugin, 'onRequest', ctx);
// 或 Gateway 内部统一 catch
for (const hook of hooks) {
try {
await hook(ctx);
} catch (err) {
logger.error('Plugin failed:', err);
}
}
4.2 性能考量
- DB/网络操作必须异步,避免阻塞事件循环
onRequest每次请求都触发,避免在此处执行重操作- 参考 Buffer + Flush 模式,减少 IO 次数
- 为外部请求设置超时,默认 5s
4.3 配置验证
async onStart(ctx: PluginContext): Promise<void> {
if (!this.config.esNodes?.length) {
throw new Error('esNodes is required');
}
const health = await this.esClient.ping();
if (!health) throw new Error('cannot connect to ES');
}
4.4 内存安全
const MAX_BUFFER_SIZE = 10_000;
private pushEntry(entry: AuditEntry): void {
if (this.buffer.length >= MAX_BUFFER_SIZE) {
this.flush().catch(() => {
this.buffer.splice(0, Math.floor(MAX_BUFFER_SIZE * 0.1));
});
}
this.buffer.push(entry);
}
五、测试你的插件
5.1 单元测试
import AuditPlugin from '../src';
describe('AuditPlugin', () => {
let plugin: AuditPlugin;
beforeEach(() => {
plugin = new AuditPlugin({ esNodes: ['http://localhost:9200'], indexPrefix: 'test-audit' });
});
it('should buffer audit entries on request', async () => {
const mockCtx = {
request: { id: 'req-001', method: 'GET', path: '/api/chat', headers: { authorization: 'Bearer xxx' } },
} as any;
await plugin.onRequest(mockCtx);
expect(plugin['buffer']).toHaveLength(1);
expect(plugin['buffer'][0].headers.authorization).toBe('***');
});
});
5.2 集成测试
import { GatewayTestHarness } from '@openclaw/gateway/testing';
it('should log full request lifecycle', async () => {
const harness = new GatewayTestHarness({
plugins: [{ name: 'audit-log', package: '../dist/index.js', config: { flushIntervalMs: 100 } }],
});
await harness.start();
await harness.sendRequest({ method: 'POST', path: '/v1/chat', body: 'Hello' });
await new Promise(r => setTimeout(r, 200));
const logs = await harness.queryES('test-audit-*');
expect(logs.length).toBeGreaterThanOrEqual(2);
await harness.stop();
});
六、高级插件模式
6.1 链式插件
plugins:
- name: auth # 1. 先认证
- name: rate-limit # 2. 再限流
- name: audit-log # 3. 再审计
- name: response-transform # 4. 最后转换响应
6.2 动态热加载
openclaw gateway dev --plugin-watch
6.3 插件间数据共享
// auth 插件设置用户信息
async onRequest(ctx) {
const user = await this.authenticate(ctx.request);
ctx.pluginData.set('user', user);
}
// audit 插件读取
async onAfterAgent(ctx) {
const user = ctx.pluginData.get('user');
this.buffer.push({ user, ... });
}
七、调试与排障
| 症状 | 原因 | 解决 |
|---|---|---|
| 插件未加载 | package.json 中 main 字段错误 | 检查入口文件导出 |
| 内存持续增长 | 缓冲区未正确刷入 | 检查 onStop 和 flush 逻辑 |
| 请求卡死 | 插件中同步阻塞调用 | 确保所有 IO 都是 async |
| 插件间数据丢失 | 插件执行顺序错误 | 检查 yaml 中插件顺序 |
八、总结
通过本文的审计日志插件实战,你已掌握五大生命周期钩子的触发时机与用途、Buffer + Flush 高性能批量写入模式、错误隔离与配置验证等最佳实践,以及热加载、链式编排、数据共享等高级模式。
下一步可以探索的方向:集成 Prometheus 指标暴露插件、实现基于 Redis 的分布式限流插件、构建自定义词汇过滤或内容审核插件。
技术栈速查:开发语言 TypeScript / JavaScript,SDK @openclaw/gateway,注册方式 openclaw.yaml + –plugins,测试工具 @openclaw/gateway/testing,日志 ctx.logger(结构化),配置 constructor(config) + onStart 验证。