Add admin commands to get and reset tracing filters

This commit is contained in:
Lambda 2025-02-15 00:12:38 +00:00
parent 5eab758bd2
commit 99924e5779
4 changed files with 121 additions and 19 deletions

View file

@ -261,9 +261,10 @@ This will be the first release of Grapevine since it was forked from Conduit
([!46](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/46))
10. Recognize the `!admin` prefix to invoke admin commands.
([!45](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/45))
11. Add the `set-tracing-filter` admin command to change log/metrics/flame
11. Add the `tracing-filter` admin command to view and change log/metrics/flame
filters dynamically at runtime.
([!49](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/49))
([!49](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/49),
[!164](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/164))
12. Add more configuration options.
([!49](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/49))
* `observability.traces.filter`: The `tracing` filter to use for

View file

@ -15,7 +15,7 @@ use tracing_subscriber::EnvFilter;
/// Use [`FromStr`] or [`Deserialize`] to construct this type, then [`From`] or
/// [`Into`] to convert it into an [`EnvFilter`] when needed.
#[derive(Debug, Clone)]
pub(crate) struct EnvFilterClone(String);
pub(crate) struct EnvFilterClone(pub(crate) String);
impl FromStr for EnvFilterClone {
type Err = <EnvFilter as FromStr>::Err;

View file

@ -21,6 +21,7 @@ use opentelemetry_sdk::{
Resource,
};
use strum::{AsRefStr, IntoStaticStr};
use thiserror::Error;
use tokio::time::Instant;
use tracing::{subscriber::SetGlobalDefaultError, Span};
use tracing_flame::{FlameLayer, FlushGuard};
@ -78,9 +79,67 @@ impl<L, S> ReloadHandle<L> for reload::Handle<L, S> {
}
}
/// A type-erased [reload handle][reload::Handle] for an [`EnvFilter`].
pub(crate) type FilterReloadHandle =
Box<dyn ReloadHandle<EnvFilter> + Send + Sync>;
/// Error returned from [`FilterReloadHandle::set_filter()`]
#[allow(clippy::missing_docs_in_private_items)]
#[derive(Debug, Error)]
pub(crate) enum SetFilterError {
#[error("invalid filter string")]
InvalidFilter(#[from] tracing_subscriber::filter::ParseError),
#[error("failed to reload filter layer")]
Reload(#[from] reload::Error),
}
/// A wrapper around a tracing filter [reload handle][reload::Handle] that
/// remembers the filter string that was last set.
pub(crate) struct FilterReloadHandle {
/// The actual [`reload::Handle`] that can be used to modify the filter
/// [`Layer`]
inner: Box<dyn ReloadHandle<EnvFilter> + Send + Sync>,
/// Filter string that was last applied to `inner`
current_filter: String,
/// Filter string that was initially loaded from the configuration
initial_filter: String,
}
impl FilterReloadHandle {
/// Creates a new [`FilterReloadHandle`] from a filter string, returning the
/// filter layer itself and the handle that can be used to modify it.
pub(crate) fn new<S: tracing::Subscriber>(
filter: EnvFilterClone,
) -> (impl tracing_subscriber::layer::Filter<S>, Self) {
let (layer, handle) = reload::Layer::new(EnvFilter::from(&filter));
let handle = Self {
inner: Box::new(handle),
current_filter: filter.0.clone(),
initial_filter: filter.0,
};
(layer, handle)
}
/// Sets the filter string for the linked filter layer. Can fail if the
/// filter string is invalid or when the link to the layer has been
/// broken.
pub(crate) fn set_filter(
&mut self,
filter: String,
) -> Result<(), SetFilterError> {
self.inner.reload(filter.parse()?)?;
self.current_filter = filter;
Ok(())
}
/// Returns the filter string that the underlying filter layer is currently
/// configured for.
pub(crate) fn get_filter(&self) -> &str {
&self.current_filter
}
/// Returns the filter string that the underlying filter layer was
/// initialized with.
pub(crate) fn get_initial_filter(&self) -> &str {
&self.initial_filter
}
}
/// Collection of [`FilterReloadHandle`]s, allowing the filters for tracing
/// backends to be changed dynamically. Handles may be [`None`] if the backend
@ -153,9 +212,9 @@ where
return Ok((None, None, None));
}
let (filter, handle) = reload::Layer::new(EnvFilter::from(filter));
let (filter, handle) = FilterReloadHandle::new(filter.clone());
let (layer, data) = init()?;
Ok((Some(layer.with_filter(filter)), Some(Box::new(handle)), Some(data)))
Ok((Some(layer.with_filter(filter)), Some(handle), Some(data)))
}
/// Initialize observability

View file

@ -1,6 +1,6 @@
use std::{collections::BTreeMap, fmt::Write, sync::Arc, time::Instant};
use clap::{Parser, ValueEnum};
use clap::{Parser, Subcommand, ValueEnum};
use regex::Regex;
use ruma::{
api::appservice::Registration,
@ -205,10 +205,41 @@ enum AdminCommand {
VerifyJson,
/// Dynamically change a tracing backend's filter string
SetTracingFilter {
TracingFilter {
#[command(subcommand)]
cmd: TracingFilterCommand,
},
}
#[derive(Debug, Subcommand)]
enum TracingFilterCommand {
Get {
backend: TracingBackend,
},
Set {
backend: TracingBackend,
filter: String,
},
Reset {
backend: TracingBackend,
},
}
impl TracingFilterCommand {
fn backend(&self) -> &TracingBackend {
match self {
TracingFilterCommand::Get {
backend,
}
| TracingFilterCommand::Set {
backend,
..
}
| TracingFilterCommand::Reset {
backend,
} => backend,
}
}
}
#[derive(Debug)]
@ -1167,9 +1198,8 @@ impl Service {
)
}
}
AdminCommand::SetTracingFilter {
backend,
filter,
AdminCommand::TracingFilter {
cmd,
} => {
let Some(handles) = &services().globals.reload_handles else {
return Ok(RoomMessageEventContent::text_plain(
@ -1177,7 +1207,7 @@ impl Service {
));
};
let mut handles = handles.write().await;
let handle = match backend {
let handle = match cmd.backend() {
TracingBackend::Log => &mut handles.log,
TracingBackend::Flame => &mut handles.flame,
TracingBackend::Traces => &mut handles.traces,
@ -1187,15 +1217,27 @@ impl Service {
"Backend is disabled",
));
};
let filter = match filter.parse() {
Ok(filter) => filter,
Err(e) => {
let filter = match cmd {
TracingFilterCommand::Set {
filter,
..
} => filter,
TracingFilterCommand::Reset {
..
} => handle.get_initial_filter().to_owned(),
TracingFilterCommand::Get {
..
} => {
return Ok(RoomMessageEventContent::text_plain(
format!("Invalid filter string: {e}"),
format!(
"Current filter string: {}",
handle.get_filter()
),
));
}
};
if let Err(e) = handle.reload(filter) {
if let Err(e) = handle.set_filter(filter) {
return Ok(RoomMessageEventContent::text_plain(format!(
"Failed to reload filter: {e}"
)));