feat: 拆分 worker 架构并增强 assistant 安全保护
- 新增 daemon/channel/assistant/function/scheduler/logging worker 架构和 JetStream 消息协议 - 修复邮件通知误入 assistant、迁移 checksum、日志 subject 和工具调用无限循环风险 - 新增 assistant 工具轮次熔断、工具结果截断和提示词软收敛规则 - 补充邮件推送关键字、可信发件人配置、升级日志和验证测试
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
[package]
|
||||
name = "ias-logging"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
ias-common = { path = "../ias-common" }
|
||||
anyhow = "1"
|
||||
serde_json = "1"
|
||||
sqlx = { version = "0.9.0", features = [
|
||||
"runtime-tokio",
|
||||
"tls-rustls",
|
||||
"postgres",
|
||||
"chrono",
|
||||
] }
|
||||
tokio = { version = "1.0", features = ["rt-multi-thread", "macros", "time"] }
|
||||
@@ -0,0 +1,87 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use ias_common::mq::{
|
||||
ConsumerSpec, JetStreamBus, JetStreamConfig, LogEventPayload, StreamKind, StreamNames,
|
||||
};
|
||||
use sqlx::PgPool;
|
||||
|
||||
pub async fn run_worker(pool: PgPool) -> anyhow::Result<()> {
|
||||
let config = JetStreamConfig::from_env();
|
||||
loop {
|
||||
match run_once(pool.clone(), config.clone()).await {
|
||||
Ok(()) => return Ok(()),
|
||||
Err(error) => {
|
||||
eprintln!("Logging Worker 连接或消费失败: {error:#}");
|
||||
tokio::time::sleep(Duration::from_secs(5)).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn run_once(pool: PgPool, config: JetStreamConfig) -> anyhow::Result<()> {
|
||||
let bus = JetStreamBus::connect(config.clone()).await?;
|
||||
bus.ensure_streams().await?;
|
||||
let names = StreamNames::new(config.stream_prefix.clone());
|
||||
let spec = ConsumerSpec {
|
||||
stream: names.name(StreamKind::Logs),
|
||||
durable_name: "logging-worker".to_string(),
|
||||
filter_subject: "logs.>".to_string(),
|
||||
ack_wait: config.ack_wait(),
|
||||
max_deliver: config.max_deliver,
|
||||
};
|
||||
bus.consume_envelopes::<LogEventPayload, _, _>(spec, move |envelope| {
|
||||
let pool = pool.clone();
|
||||
async move {
|
||||
let payload = envelope.payload;
|
||||
let metadata = serde_json::json!({
|
||||
"trace_id": payload.trace_id,
|
||||
"message_id": payload.message_id,
|
||||
"command_id": payload.command_id,
|
||||
"call_id": payload.call_id,
|
||||
"fire_id": payload.fire_id,
|
||||
"fields": payload.fields,
|
||||
});
|
||||
sqlx::query(
|
||||
r#"
|
||||
INSERT INTO ias_operation_logs (level, module, message, metadata)
|
||||
VALUES ($1, $2, $3, $4)
|
||||
"#,
|
||||
)
|
||||
.bind(payload.level)
|
||||
.bind(payload.source)
|
||||
.bind(payload.message)
|
||||
.bind(metadata)
|
||||
.execute(&pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use ias_common::mq::{LogEventPayload, MessageEnvelope, log_subject};
|
||||
|
||||
#[test]
|
||||
fn log_event_serialization_preserves_trace_id_without_full_body_requirement() {
|
||||
let payload = LogEventPayload::new(
|
||||
"info",
|
||||
"worker.test",
|
||||
"状态变化",
|
||||
"trace-1",
|
||||
serde_json::json!({"message_id": "msg-1"}),
|
||||
);
|
||||
let subject = log_subject("worker.test");
|
||||
let envelope = MessageEnvelope::new(
|
||||
subject.as_str(),
|
||||
payload.event_id.clone(),
|
||||
payload.trace_id.clone(),
|
||||
payload,
|
||||
);
|
||||
let encoded = serde_json::to_string(&envelope).unwrap();
|
||||
|
||||
assert!(encoded.contains("trace-1"));
|
||||
assert!(encoded.contains("worker.test"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user