add dynamic log config

This commit is contained in:
Adil Hafeez 2026-04-01 15:40:40 -07:00
parent f019f05738
commit f04c0b7cdd
9 changed files with 634 additions and 5 deletions

View file

@ -11,7 +11,7 @@ use brightstaff::router::orchestrator::OrchestratorService;
use brightstaff::state::memory::MemoryConversationalStorage;
use brightstaff::state::postgresql::PostgreSQLConversationStorage;
use brightstaff::state::StateStorage;
use brightstaff::tracing::init_tracer;
use brightstaff::tracing::{get_log_level, init_tracer, set_log_level};
use bytes::Bytes;
use common::configuration::{
Agent, Configuration, FilterPipeline, ListenerType, ResolvedFilterChain,
@ -384,6 +384,59 @@ async fn init_state_storage(
Ok(Some(storage))
}
// ---------------------------------------------------------------------------
// Admin handlers
// ---------------------------------------------------------------------------
use http_body_util::BodyExt;
fn json_response(
status: StatusCode,
body: &str,
) -> Result<Response<BoxBody<Bytes, hyper::Error>>, hyper::Error> {
let bytes = Bytes::from(body.to_string());
let body = http_body_util::Full::new(bytes)
.map_err(|never| match never {})
.boxed();
let mut resp = Response::new(body);
*resp.status_mut() = status;
resp.headers_mut()
.insert("Content-Type", HeaderValue::from_static("application/json"));
Ok(resp)
}
async fn handle_get_log_level() -> Result<Response<BoxBody<Bytes, hyper::Error>>, hyper::Error> {
match get_log_level() {
Some(level) => json_response(StatusCode::OK, &format!("{{\"level\":\"{level}\"}}")),
None => json_response(
StatusCode::INTERNAL_SERVER_ERROR,
"{\"error\":\"tracer not initialized\"}",
),
}
}
async fn handle_set_log_level(
req: Request<Incoming>,
) -> Result<Response<BoxBody<Bytes, hyper::Error>>, hyper::Error> {
let body = req.collect().await.map_err(|_| ()).unwrap();
let new_level = String::from_utf8_lossy(&body.to_bytes()).trim().to_string();
if new_level.is_empty() {
return json_response(
StatusCode::BAD_REQUEST,
"{\"error\":\"body must contain a log level filter, e.g. 'debug'\"}",
);
}
match set_log_level(&new_level) {
Ok(()) => {
info!(level = %new_level, "log level updated");
json_response(StatusCode::OK, &format!("{{\"level\":\"{new_level}\"}}"))
}
Err(e) => json_response(StatusCode::BAD_REQUEST, &format!("{{\"error\":\"{e}\"}}")),
}
}
// ---------------------------------------------------------------------------
// Request routing
// ---------------------------------------------------------------------------
@ -426,6 +479,13 @@ async fn route(
}
}
// --- Admin routes ---
match (req.method(), path.as_str()) {
(&Method::GET, "/admin/log-level") => return handle_get_log_level().await,
(&Method::PUT, "/admin/log-level") => return handle_set_log_level(req).await,
_ => {}
}
// --- Standard routes ---
match (req.method(), path.as_str()) {
(&Method::POST, CHAT_COMPLETIONS_PATH | MESSAGES_PATH | OPENAI_RESPONSES_API_PATH) => {

View file

@ -8,12 +8,38 @@ use tracing::{Event, Subscriber};
use tracing_subscriber::fmt::{format, time::FormatTime, FmtContext, FormatEvent, FormatFields};
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::registry::LookupSpan;
use tracing_subscriber::reload;
use tracing_subscriber::util::SubscriberInitExt;
use tracing_subscriber::EnvFilter;
use super::ServiceNameOverrideExporter;
use common::configuration::Tracing;
type ReloadHandle = reload::Handle<EnvFilter, tracing_subscriber::Registry>;
static LOG_LEVEL_HANDLE: OnceLock<ReloadHandle> = OnceLock::new();
/// Dynamically change the log level filter at runtime.
///
/// Accepts any valid `RUST_LOG` / `EnvFilter` syntax, e.g. `"debug"`,
/// `"brightstaff=trace,info"`.
pub fn set_log_level(new_filter: &str) -> Result<(), String> {
let handle = LOG_LEVEL_HANDLE
.get()
.ok_or_else(|| "tracer not initialized".to_string())?;
let filter = EnvFilter::try_new(new_filter)
.map_err(|e| format!("invalid filter '{new_filter}': {e}"))?;
handle
.reload(filter)
.map_err(|e| format!("failed to reload filter: {e}"))
}
/// Returns the current log level filter string, if the tracer is initialized.
pub fn get_log_level() -> Option<String> {
let handle = LOG_LEVEL_HANDLE.get()?;
handle.with_current(|f| f.to_string()).ok()
}
struct BracketedTime;
impl FormatTime for BracketedTime {
@ -118,9 +144,10 @@ pub fn init_tracer(tracing_config: Option<&Tracing>) -> &'static SdkTracerProvid
let telemetry_layer =
tracing_opentelemetry::layer().with_tracer(provider.tracer("brightstaff"));
// Combine the OpenTelemetry layer with fmt layer using the registry
let env_filter =
EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info"));
let (filter_layer, reload_handle) = reload::Layer::new(env_filter);
LOG_LEVEL_HANDLE.set(reload_handle).ok();
// Create fmt layer with span field formatting enabled (no ANSI to keep fields parseable)
let fmt_layer = tracing_subscriber::fmt::layer()
@ -129,8 +156,8 @@ pub fn init_tracer(tracing_config: Option<&Tracing>) -> &'static SdkTracerProvid
.with_ansi(false);
let subscriber = tracing_subscriber::registry()
.with(filter_layer)
.with(telemetry_layer)
.with(env_filter)
.with(fmt_layer);
tracing::subscriber::set_global_default(subscriber)
@ -144,6 +171,8 @@ pub fn init_tracer(tracing_config: Option<&Tracing>) -> &'static SdkTracerProvid
let env_filter =
EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info"));
let (filter_layer, reload_handle) = reload::Layer::new(env_filter);
LOG_LEVEL_HANDLE.set(reload_handle).ok();
// Create fmt layer with span field formatting enabled (no ANSI to keep fields parseable)
let fmt_layer = tracing_subscriber::fmt::layer()
@ -152,7 +181,7 @@ pub fn init_tracer(tracing_config: Option<&Tracing>) -> &'static SdkTracerProvid
.with_ansi(false);
tracing_subscriber::registry()
.with(env_filter)
.with(filter_layer)
.with(fmt_layer)
.init();

View file

@ -7,7 +7,7 @@ pub use constants::{
error, http, llm, operation_component, routing, signals, OperationNameBuilder,
};
pub use custom_attributes::collect_custom_trace_attributes;
pub use init::init_tracer;
pub use init::{get_log_level, init_tracer, set_log_level};
pub use service_name_exporter::{ServiceNameOverrideExporter, SERVICE_NAME_OVERRIDE_KEY};
use opentelemetry::trace::get_active_span;