- 传播 trace_id:BusToolCallEmitter/SubAgentEmitter/processor 全链路设置 - AgentEnd 配对:补发 5 个 cancel/defensive 路径,闭合 AgentStart 指标 - LLM 计时修正:attempt_start 移入 retry 循环,排除退避等待时间 - /metrics auth:非 loopback 部署时纳入 Bearer token 校验 - recorder 复用:OnceLock 缓存 PrometheusHandle,热重启后不再返回 503 - 结构化日志:新增 tracing_ctx + JSON 日志格式支持
136 lines
4.0 KiB
Rust
136 lines
4.0 KiB
Rust
use chrono::Utc;
|
||
use chrono_tz::Tz;
|
||
use std::path::PathBuf;
|
||
use tracing_appender::rolling::{RollingFileAppender, Rotation};
|
||
use tracing_subscriber::{
|
||
fmt, fmt::time::FormatTime, layer::SubscriberExt, util::SubscriberInitExt, EnvFilter, Layer,
|
||
};
|
||
|
||
use crate::config::LogFormat;
|
||
|
||
#[derive(Clone, Copy, Debug)]
|
||
struct ConfiguredTimestamp {
|
||
timezone: Tz,
|
||
}
|
||
|
||
impl FormatTime for ConfiguredTimestamp {
|
||
fn format_time(
|
||
&self,
|
||
writer: &mut tracing_subscriber::fmt::format::Writer<'_>,
|
||
) -> std::fmt::Result {
|
||
write!(
|
||
writer,
|
||
"{}",
|
||
Utc::now()
|
||
.with_timezone(&self.timezone)
|
||
.to_rfc3339_opts(chrono::SecondsFormat::Millis, true)
|
||
)
|
||
}
|
||
}
|
||
|
||
/// Get the default log directory path: ~/.picobot/logs
|
||
pub fn get_default_log_dir() -> PathBuf {
|
||
let home = crate::platform::picobot_home_dir();
|
||
home.join(".picobot").join("logs")
|
||
}
|
||
|
||
/// Get the default config file path: ~/.picobot/config.json
|
||
pub fn get_default_config_path() -> PathBuf {
|
||
let home = crate::platform::picobot_home_dir();
|
||
home.join(".picobot").join("config.json")
|
||
}
|
||
|
||
/// Initialize logging with file appender
|
||
/// Logs are written to ~/.picobot/logs/ with daily rotation
|
||
///
|
||
/// `log_format` 控制文件日志格式:Text(默认)或 Json(便于日志聚合)。
|
||
/// 控制台始终使用文本格式(便于人读)。
|
||
pub fn init_logging(timezone: Tz, log_format: LogFormat) {
|
||
use std::sync::Once;
|
||
static INIT: Once = Once::new();
|
||
|
||
let mut initialized = false;
|
||
INIT.call_once(|| {
|
||
initialized = true;
|
||
});
|
||
if !initialized {
|
||
// Already initialized (e.g. after gateway restart), skip
|
||
return;
|
||
}
|
||
|
||
let log_dir = get_default_log_dir();
|
||
|
||
// Create log directory if it doesn't exist
|
||
if !log_dir.exists() {
|
||
if let Err(e) = std::fs::create_dir_all(&log_dir) {
|
||
eprintln!(
|
||
"Warning: Failed to create log directory {}: {}",
|
||
log_dir.display(),
|
||
e
|
||
);
|
||
}
|
||
}
|
||
|
||
// Create file appender with daily rotation
|
||
let file_appender = RollingFileAppender::new(Rotation::DAILY, &log_dir, "picobot.log");
|
||
|
||
// Build subscriber with both console and file output
|
||
let env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info"));
|
||
|
||
// 文件层:根据 log_format 选择 text 或 json
|
||
let file_layer = match log_format {
|
||
LogFormat::Json => fmt::layer()
|
||
.with_writer(file_appender)
|
||
.with_timer(ConfiguredTimestamp { timezone })
|
||
.with_ansi(false)
|
||
.with_target(true)
|
||
.with_level(true)
|
||
.with_thread_ids(true)
|
||
.json()
|
||
.boxed(),
|
||
LogFormat::Text => fmt::layer()
|
||
.with_writer(file_appender)
|
||
.with_timer(ConfiguredTimestamp { timezone })
|
||
.with_ansi(false)
|
||
.with_target(true)
|
||
.with_level(true)
|
||
.with_thread_ids(true)
|
||
.boxed(),
|
||
};
|
||
|
||
// 控制台层:始终文本格式
|
||
let console_layer = fmt::layer()
|
||
.with_timer(ConfiguredTimestamp { timezone })
|
||
.with_target(true)
|
||
.with_level(true);
|
||
|
||
tracing_subscriber::registry()
|
||
.with(env_filter)
|
||
.with(console_layer)
|
||
.with(file_layer)
|
||
.init();
|
||
|
||
tracing::info!(
|
||
log_format = ?log_format,
|
||
log_dir = %log_dir.display(),
|
||
"Logging initialized"
|
||
);
|
||
}
|
||
|
||
/// Initialize logging without file output (console only)
|
||
pub fn init_logging_console_only(timezone: Tz) {
|
||
let env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info"));
|
||
|
||
let console_layer = fmt::layer()
|
||
.with_timer(ConfiguredTimestamp { timezone })
|
||
.with_target(true)
|
||
.with_level(true);
|
||
|
||
tracing_subscriber::registry()
|
||
.with(env_filter)
|
||
.with(console_layer)
|
||
.init();
|
||
|
||
tracing::info!("Logging initialized (console only)");
|
||
}
|