feat: 添加结构化日志与审计
Some checks failed
Secrets CLI - Build & Release / 版本 & Release (push) Successful in 3s
Secrets CLI - Build & Release / 质量检查 (fmt / clippy / test) (push) Successful in 1m17s
Secrets CLI - Build & Release / 通知 (push) Successful in 6s
Secrets CLI - Build & Release / 发布草稿 Release (push) Has been cancelled
Secrets CLI - Build & Release / Build (aarch64-apple-darwin) (push) Has started running
Secrets CLI - Build & Release / Build (x86_64-pc-windows-msvc) (push) Has been cancelled
Secrets CLI - Build & Release / Build (x86_64-unknown-linux-musl) (push) Has been cancelled

- tracing + tracing-subscriber,全局 --verbose/-v 与 RUST_LOG 控制
- 新增 audit_log 表,add/update/delete 成功后自动写入审计记录
- 新增 src/audit.rs,审计失败仅 warn 不中断主流程
- 更新 README/AGENTS.md,补充 verbose、audit_log 说明
- .vscode/tasks.json 增加 verbose/update/audit 测试任务

Made-with: Cursor
This commit is contained in:
voson
2026-03-18 16:30:42 +08:00
parent 9620ff1923
commit 535683b15c
12 changed files with 370 additions and 25 deletions

View File

@@ -1,5 +1,5 @@
use anyhow::Result;
use serde_json::{Map, Value};
use serde_json::{Map, Value, json};
use sqlx::PgPool;
use std::fs;
@@ -43,6 +43,8 @@ pub async fn run(
let metadata = build_json(meta_entries)?;
let encrypted = build_json(secret_entries)?;
tracing::debug!(namespace, kind, name, "upserting record");
sqlx::query(
r#"
INSERT INTO secrets (namespace, kind, name, tags, metadata, encrypted, updated_at)
@@ -64,23 +66,38 @@ pub async fn run(
.execute(pool)
.await?;
let meta_keys: Vec<&str> = meta_entries
.iter()
.filter_map(|s| s.split_once('=').map(|(k, _)| k))
.collect();
let secret_keys: Vec<&str> = secret_entries
.iter()
.filter_map(|s| s.split_once('=').map(|(k, _)| k))
.collect();
crate::audit::log(
pool,
"add",
namespace,
kind,
name,
json!({
"tags": tags,
"meta_keys": meta_keys,
"secret_keys": secret_keys,
}),
)
.await;
println!("Added: [{}/{}] {}", namespace, kind, name);
if !tags.is_empty() {
println!(" tags: {}", tags.join(", "));
}
if !meta_entries.is_empty() {
let keys: Vec<&str> = meta_entries
.iter()
.filter_map(|s| s.split_once('=').map(|(k, _)| k))
.collect();
println!(" metadata: {}", keys.join(", "));
println!(" metadata: {}", meta_keys.join(", "));
}
if !secret_entries.is_empty() {
let keys: Vec<&str> = secret_entries
.iter()
.filter_map(|s| s.split_once('=').map(|(k, _)| k))
.collect();
println!(" secrets: {}", keys.join(", "));
println!(" secrets: {}", secret_keys.join(", "));
}
Ok(())

View File

@@ -1,7 +1,10 @@
use anyhow::Result;
use serde_json::json;
use sqlx::PgPool;
pub async fn run(pool: &PgPool, namespace: &str, kind: &str, name: &str) -> Result<()> {
tracing::debug!(namespace, kind, name, "deleting record");
let result =
sqlx::query("DELETE FROM secrets WHERE namespace = $1 AND kind = $2 AND name = $3")
.bind(namespace)
@@ -11,8 +14,10 @@ pub async fn run(pool: &PgPool, namespace: &str, kind: &str, name: &str) -> Resu
.await?;
if result.rows_affected() == 0 {
tracing::warn!(namespace, kind, name, "record not found for deletion");
println!("Not found: [{}/{}] {}", namespace, kind, name);
} else {
crate::audit::log(pool, "delete", namespace, kind, name, json!({})).await;
println!("Deleted: [{}/{}] {}", namespace, kind, name);
}
Ok(())

View File

@@ -44,6 +44,8 @@ pub async fn run(
where_clause
);
tracing::debug!(sql, "executing search query");
let mut q = sqlx::query_as::<_, Secret>(&sql);
if let Some(v) = namespace {
q = q.bind(v);

View File

@@ -1,5 +1,5 @@
use anyhow::Result;
use serde_json::{Map, Value};
use serde_json::{Map, Value, json};
use sqlx::{FromRow, PgPool};
use uuid::Uuid;
@@ -85,6 +85,13 @@ pub async fn run(pool: &PgPool, args: UpdateArgs<'_>) -> Result<()> {
}
let encrypted = Value::Object(enc_map);
tracing::debug!(
namespace = args.namespace,
kind = args.kind,
name = args.name,
"updating record"
);
sqlx::query(
r#"
UPDATE secrets
@@ -99,6 +106,34 @@ pub async fn run(pool: &PgPool, args: UpdateArgs<'_>) -> Result<()> {
.execute(pool)
.await?;
let meta_keys: Vec<&str> = args
.meta_entries
.iter()
.filter_map(|s| s.split_once('=').map(|(k, _)| k))
.collect();
let secret_keys: Vec<&str> = args
.secret_entries
.iter()
.filter_map(|s| s.split_once('=').map(|(k, _)| k))
.collect();
crate::audit::log(
pool,
"update",
args.namespace,
args.kind,
args.name,
json!({
"add_tags": args.add_tags,
"remove_tags": args.remove_tags,
"meta_keys": meta_keys,
"remove_meta": args.remove_meta,
"secret_keys": secret_keys,
"remove_secrets": args.remove_secrets,
}),
)
.await;
println!("Updated: [{}/{}] {}", args.namespace, args.kind, args.name);
if !args.add_tags.is_empty() {