-
Notifications
You must be signed in to change notification settings - Fork 29
feat: adding a log file support #200
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
base: main
Are you sure you want to change the base?
Conversation
✅ Docs preview readyThe preview is ready to be viewed. View the preview File Changes 0 new, 1 changed, 0 removed
Build ID: 580029315949ed2b60d33392 URL: https://www.apollographql.com/docs/deploy-preview/580029315949ed2b60d33392 |
Moved logging.rs to src/ and combined with logging setup functions that were added |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good! I left a few nits.
@@ -1,19 +1,18 @@ | |||
use std::path::PathBuf; | |||
|
|||
use crate::runtime::logging::init::setup_logging; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Re-export the setup_logging
method under the logging
module and rename it to setup
so that it doesn't embed the name of the module into it. Also merge it with the existing use
on line 14.
Then you could use it like:
// On line 14
use runtime::{logging, IdOrDefault};
// Wherever you used `setup_logging`
logging::setup(...);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, refer to the comment on the logging struct. This should probably be a member function of logging.
//! | ||
//! This module is only used by the main binary and provides helper code | ||
//! related to runtime configuration. | ||
|
||
mod config; | ||
mod graphos; | ||
mod introspection; | ||
mod logging; | ||
pub(crate) mod logging; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: This can probably be not public if the setup function is a member on the logging struct.
use super::{ | ||
OperationSource, SchemaSource, graphos::GraphOSConfig, introspection::Introspection, | ||
overrides::Overrides, | ||
}; | ||
use crate::runtime::logging::Logging; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: These can be merged by just using super::logging:Logging
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, any reason why the super imports were moved here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm I was altering things but I don't remember moving any imports myself. Maybe it was RustRover or clippy after I made some change (which was removed later).
//! | ||
//! This module is only used by the main binary and provides logging config structures and setup | ||
//! helper functions | ||
pub(crate) mod init; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: This can be private.
pub fn setup_logging(config: &Config) -> Result<Option<WorkerGuard>, anyhow::Error> { | ||
let mut env_filter = EnvFilter::from_default_env().add_directive(config.logging.level.into()); | ||
|
||
if config.logging.level == Level::INFO { | ||
env_filter = env_filter | ||
.add_directive("rmcp=warn".parse()?) | ||
.add_directive("tantivy=warn".parse()?); | ||
} | ||
|
||
if let Some(path) = &config.logging.path { | ||
setup_file_logging(path, env_filter, config.logging.rotation.clone()) | ||
} else { | ||
setup_stderr_logging(env_filter) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This method should probably be a member function on Logging
. You can either have an impl Logging
block in this file or merge it with the parent module.
fn ensure_log_dir_exists(dir: PathBuf) -> std::io::Result<()> { | ||
std::fs::create_dir_all(dir) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: This can probably be deleted.
match ensure_log_dir_exists(log_path.clone()) { | ||
Ok(..) => {} | ||
Err(_err) => { | ||
eprintln!("Could not build log path - falling back to stderr"); | ||
return setup_stderr_logging(env_filter); | ||
} | ||
} | ||
|
||
let (non_blocking_writer, guard) = match RollingFileAppender::builder() | ||
.rotation(log_rotation.into()) | ||
.filename_prefix("apollo_mcp_server") | ||
.filename_suffix("log") | ||
.build(log_path) | ||
{ | ||
Ok(appender) => tracing_appender::non_blocking(appender), | ||
Err(_error) => { | ||
eprintln!("Log file setup failed - falling back to stderr"); | ||
return setup_stderr_logging(env_filter); | ||
} | ||
}; | ||
|
||
tracing_subscriber::registry() | ||
.with(env_filter) | ||
.with( | ||
tracing_subscriber::fmt::layer() | ||
.with_writer(non_blocking_writer) | ||
.with_ansi(false) | ||
.with_target(false), | ||
) | ||
.init(); | ||
|
||
Ok(Some(guard)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: This is mostly to show that this could be simplified slightly by functional chaining, but feel free to ignore.
match ensure_log_dir_exists(log_path.clone()) { | |
Ok(..) => {} | |
Err(_err) => { | |
eprintln!("Could not build log path - falling back to stderr"); | |
return setup_stderr_logging(env_filter); | |
} | |
} | |
let (non_blocking_writer, guard) = match RollingFileAppender::builder() | |
.rotation(log_rotation.into()) | |
.filename_prefix("apollo_mcp_server") | |
.filename_suffix("log") | |
.build(log_path) | |
{ | |
Ok(appender) => tracing_appender::non_blocking(appender), | |
Err(_error) => { | |
eprintln!("Log file setup failed - falling back to stderr"); | |
return setup_stderr_logging(env_filter); | |
} | |
}; | |
tracing_subscriber::registry() | |
.with(env_filter) | |
.with( | |
tracing_subscriber::fmt::layer() | |
.with_writer(non_blocking_writer) | |
.with_ansi(false) | |
.with_target(false), | |
) | |
.init(); | |
Ok(Some(guard)) | |
let (writer, guard) = std::fs::create_dir_all(log_path) | |
.and_then(RollingFileAppender::builder() | |
.rotation(log_rotation.into()) | |
.filename_prefix("apollo_mcp_server") | |
.filename_suffix("log") | |
.build()) | |
.and_then(|(non_blocking, guard)| (non_blocking, Some(guard))) | |
.unwrap_or_else(|| { | |
eprintln!("Log file setup failed - falling back to stderr"); | |
(std::io::stderr, None) | |
}) | |
tracing_subscriber::registry() | |
.with(env_filter) | |
.with( | |
tracing_subscriber::fmt::layer() | |
.with_writer(writer) | |
.with_ansi(true) | |
.with_target(false), | |
) | |
.init(); | |
Ok(guard) |
} | ||
|
||
/// Sets up stderr logging | ||
fn setup_stderr_logging(env_filter: EnvFilter) -> Result<Option<WorkerGuard>, anyhow::Error> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: this is probably not needed.
#[rstest] | ||
#[case(LogRotationKind::Minutely, Rotation::MINUTELY)] | ||
#[case(LogRotationKind::Hourly, Rotation::HOURLY)] | ||
#[case(LogRotationKind::Daily, Rotation::DAILY)] | ||
#[case(LogRotationKind::Never, Rotation::NEVER)] | ||
fn it_maps_to_rotation_correctly( | ||
#[case] log_rotation_kind: LogRotationKind, | ||
#[case] expected: Rotation, | ||
) { | ||
let actual: Rotation = log_rotation_kind.into(); | ||
assert_eq!(expected, actual); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: This test seems weird to me since the From
impl is just a straight mapping, but feel free to leave it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added it mainly for coverage and to break in case someone changes something with the mapping. The test will then at least alert you that you broke some expected mapping between the two.
Adding support for logging to a file via config option
logging.path
. When provided the server will attempt to build the log path and then initialize a RollingFileAppender log writer. If any of this failed or if not provided, logging defaults to stderr.Note: Decided to default to stderr (when not provided) since the server will only be able to log out to specific places which it has permissions for. Since this isn't not easily known, the easiest thing is to defualt to stderr and let the user setup file logging, pointing to a location that it can log to.