Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(daemon): support dynamically setting the logging level at runtime #105

Merged
merged 4 commits into from
Sep 13, 2024

Conversation

sminez
Copy link
Collaborator

@sminez sminez commented Sep 13, 2024

This lets us do the following:

$ prompting-client.logging-level --filter=debug

and have the daemon update its logging filter without needing to restart the service. By default we will now log at info level.

Copy link
Member

@olivercalder olivercalder left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Look pretty good, thanks for working on this!

I'm rather confused about what valid log levels are, and where log levels vs filters should be used. In some places (e.g. set_filter and log_filter), it seems like these terms are used interchangeably, which confuses me a bit more :)

What I'd really like would be to have an enum of valid log levels, ideally passed as a set of options displayed via clap. That, with a bit more clarity about level vs filter, and it looks good to me.

It seems that EnvFilter and reload, among others, are defined in external crates, so some comments or documentation about what they require would be nice too, I think. Perhaps where the filter is constructed based on level, since it seems arbitrary if one doesn't know that that's just the format which is expected by the other crate.

Comment on lines 5 to 12
/// Set the logging level for a running instance of the prompting client daemon
#[derive(Debug, Parser)]
#[clap(about, long_about = None)]
struct Args {
/// The filter to use for setting the logging level
#[clap(short, long, value_name = "FILTER")]
level: String,
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are there a list of supported options for the log level? Or how does the filtering work?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's not a single list, no. The API docs here cover what is possible which really is a lot, which is why I've not restricted this to a fixed list of levels like trace, debug, info etc as an enum. That would prevent us using more granular targeting of logs in situations where we really want it.

Comment on lines 213 to 226
async fn set_logging_level(
&self,
level: Request<String>,
) -> Result<Response<SetLoggingLevelResponse>, Status> {
let current = log_filter(&level.into_inner());

match self.reload_handle.set_filter(&current) {
Ok(_) => Ok(Response::new(SetLoggingLevelResponse { current })),
Err(e) => Err(Status::new(
Code::InvalidArgument,
format!("unable to set logging level: {e}"),
)),
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here level is passed into log_filter as filter, but I don't think of levels and filters as really the same thing. What can level be set to, and is it validated anywhere?

Comment on lines +25 to +30
pub const SOCKET_ENV_VAR: &str = "PROMPTING_CLIENT_SOCKET";
pub const DEFAULT_LOG_LEVEL: &str = "info";

pub fn log_filter(filter: &str) -> String {
format!("{filter},hyper=error,h2=error")
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See here.

I would prefer if there were an enum of support log levels, and if the clap arg were defined so the default is explicit.

@@ -417,6 +423,36 @@ pub mod app_armor_prompting_client {
);
self.inner.unary(req, path, codec).await
}
pub async fn set_logging_level(
&mut self,
request: impl tonic::IntoRequest<::prost::alloc::string::String>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the logging level is contained in the request?

let listener = UnixListener::bind(&socket_path).expect("to be able to bind to our socket");

(AppArmorPromptingServer::new(service), listener)
}

pub struct Service<R>
pub trait SetLogLevel: Send + Sync + 'static {
fn set_filter(&self, level: &str) -> crate::Result<()>;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here as well, we define set_filter to take a level, but

L: From<EnvFilter> + Send + Sync + 'static,
S: 'static,
{
fn set_filter(&self, filter: &str) -> crate::Result<()> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

then set_filter is implemented to take a filter.

Comment on lines 81 to 85
let f = filter
.parse::<EnvFilter>()
.map_err(|_| Error::UnableToUpdateLogLevel {
reason: format!("{filter:?} is not a valid logging filter"),
})?;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like EnvFilter is defined in another crate (not in this repo) so I definitely think there should be some documentation (both in the code and in the CLI args) about what valid log levels are.

reason: format!("{filter:?} is not a valid logging filter"),
})?;

self.reload(f).map_err(|e| Error::UnableToUpdateLogLevel {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reload also isn't defined in this repo either? Seems like some tonic magic or something

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's a feature of tracing_subscriber, yeah. All of this is just a way of plumbing a valid logging filter through to this call to reload

Copy link
Collaborator

@matthew-hagemann matthew-hagemann left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎉

@sminez
Copy link
Collaborator Author

sminez commented Sep 13, 2024

@olivercalder I see what you mean about being inconsistent around levels vs filters. I'll update things to consistently use the term filter everywhere but documenting the full functionality of what is supported here (or restricting things to a subset) is probably not what we want to go for. I'll update the clap docs so that they cover the common use cases of just setting a logging level for the daemon code itself. The issue is that a very large number of possible filters are possible and I don't really want to link to Rust API documentation in the CLI.

Copy link
Member

@olivercalder olivercalder left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great, thanks for the changes! Some filenames still use "log level" terminology, and could be updated to use "filter" instead, but not a huge deal.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the filename could be updated to match as well

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same

Comment on lines +1 to +9
//! This is a helper command for setting our logging filter within the daemon while it is running.
//! Doing this involves having to send a GRPC message to the daemon which inolves more plumbing
//! than we'd ideally like but it allows us to update the filter without having to restart the
//! daemon.
//!
//! The docs on the command itself cover how to set simple logging levels but the tracing framework
//! we are using actually supports a rich syntax for how these filters are written. As and when we
//! need to use this in anger, the docs can be found here:
//! https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, this is great!

Comment on lines +14 to +26
/// Set the logging level for a running instance of the prompting client daemon.
///
/// Simple usage of this command involves specifying a level based filter for what logs are written
/// to the system journal by the prompting-client daemon. The supported levels are (in order of
/// least to most verbose):
///
/// - error
/// - warn
/// - info
/// - debug
/// - trace
///
/// The default level is "info".
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perfect!

@sminez sminez merged commit abed3ac into main Sep 13, 2024
9 checks passed
@sminez sminez deleted the set-logging-level branch September 13, 2024 16:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants