Skip to content

Commit d39be44

Browse files
committed
Address PR review feedback on Graylog logging
- Extract GraylogOptions into its own struct, independent of TracingOptions - Remove .vscode/launch.json (to be added in a separate PR) - Remove version/build additional_field calls from Graylog logger - Switch let-else to if-let in init_graylog - Warn when Graylog URL has no port rather than silently defaulting - Add reconnect loop so dropped connections recover automatically - Improve connection error messages to distinguish DNS failure from TCP errors
1 parent d6accbe commit d39be44

3 files changed

Lines changed: 71 additions & 66 deletions

File tree

src/cli/mod.rs

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,10 @@ pub mod client;
2929
pub struct Cli {
3030
#[clap(flatten, next_help_heading = "Logging/Debug")]
3131
verbose: Verbosity,
32-
#[clap(flatten, next_help_heading = "Tracing and Logging")]
32+
#[clap(flatten, next_help_heading = "Tracing")]
3333
tracing: TracingOptions,
34+
#[clap(flatten, next_help_heading = "Graylog")]
35+
pub graylog: GraylogOptions,
3436
#[clap(subcommand)]
3537
pub(crate) command: Command,
3638
}
@@ -43,12 +45,16 @@ pub struct TracingOptions {
4345
/// The minimum level of tracing events to send
4446
#[clap(long, default_value_t = Level::INFO, env = "NUMTRACKER_TRACING_LEVEL")]
4547
tracing_level: Level,
48+
}
49+
50+
#[derive(Debug, Parser)]
51+
pub struct GraylogOptions {
4652
/// The URL of the Graylog instance
4753
#[clap(long = "graylog", env = "NUMTRACKER_GRAYLOG")]
48-
graylog_url: Option<Url>,
54+
pub graylog_url: Option<Url>,
4955
/// The minimum level of logging events to send
5056
#[clap(long, default_value_t = Level::INFO, env = "NUMTRACKER_GRAYLOG_LOG_LEVEL")]
51-
logging_level: Level,
57+
pub logging_level: Level,
5258
}
5359

5460
#[derive(Debug, Subcommand)]
@@ -163,15 +169,9 @@ impl TracingOptions {
163169
pub(crate) fn tracing_url(&self) -> Option<Url> {
164170
self.tracing_url.clone()
165171
}
166-
pub(crate) fn level(&self) -> Level {
172+
pub(crate) fn level(&self) -> Level {
167173
self.tracing_level
168174
}
169-
pub(crate) fn graylog_url(&self) -> Option<Url> {
170-
self.graylog_url.clone()
171-
}
172-
pub(crate) fn logging_level(&self) -> Level {
173-
self.logging_level
174-
}
175175
}
176176

177177
#[cfg(test)]
@@ -351,10 +351,10 @@ mod tests {
351351
let cli = Cli::try_parse_from([APP, "--graylog", "tcp://graylog.example.com:12201", "serve"])
352352
.unwrap();
353353
assert_eq!(
354-
cli.tracing().graylog_url(),
354+
cli.graylog.graylog_url,
355355
Some("tcp://graylog.example.com:12201".parse().unwrap())
356356
);
357-
assert_eq!(cli.tracing().logging_level(), Level::INFO);
357+
assert_eq!(cli.graylog.logging_level, Level::INFO);
358358

359359
let cli = Cli::try_parse_from([
360360
APP,
@@ -366,10 +366,10 @@ mod tests {
366366
])
367367
.unwrap();
368368
assert_eq!(
369-
cli.tracing().graylog_url(),
369+
cli.graylog.graylog_url,
370370
Some("tcp://graylog.example.com:12201".parse().unwrap())
371371
);
372-
assert_eq!(cli.tracing().logging_level(), Level::WARN);
372+
assert_eq!(cli.graylog.logging_level, Level::WARN);
373373
}
374374

375375
#[test]

src/logging.rs

Lines changed: 56 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,7 @@ use tracing_subscriber::util::SubscriberInitExt as _;
2929
use tracing_subscriber::{EnvFilter, Layer};
3030
use url::Url;
3131

32-
use crate::build_info::GIT_COMMIT_HASH;
33-
use crate::cli::TracingOptions;
32+
use crate::cli::{GraylogOptions, TracingOptions};
3433

3534
#[derive(Debug, thiserror::Error)]
3635
pub enum LoggingError {
@@ -87,57 +86,62 @@ where
8786
}
8887
}
8988

90-
fn init_graylog<S>(endpoint: Option<Url>,level: Level,) -> Result<Option<impl Layer<S>>, LoggingError>
89+
fn init_graylog<S>(opts: &GraylogOptions) -> Result<Option<impl Layer<S>>, LoggingError>
9190
where
9291
S: Subscriber + for<'s> LookupSpan<'s>,
9392
{
94-
let Some(endpoint) = endpoint else {
95-
return Ok(None);
96-
};
97-
98-
let address = format!(
99-
"{}:{}",
100-
endpoint.host_str().unwrap_or("localhost"),
101-
endpoint.port().unwrap_or(12201),
102-
);
103-
104-
match Logger::builder()
105-
.additional_field("version", env!("CARGO_PKG_VERSION"))
106-
.additional_field("build", GIT_COMMIT_HASH.unwrap_or("unknown"))
107-
.connect_tcp(address)
108-
{
109-
Ok((logger, mut handle)) => {
110-
tokio::spawn(async move {
111-
let errors = handle.connect().await;
112-
if errors.0.is_empty() {
113-
error!("Failed to connect to graylog - address lookup failed");
114-
} else {
115-
warn!(
116-
"Connection to graylog {:?} closed: {:?}",
117-
handle.address(),
118-
errors
119-
);
120-
}
121-
});
122-
Ok(Some(
123-
logger
124-
.with_filter(LevelFilter::from_level(level))
125-
.with_filter(FilterFn::new(|m| {
126-
m.target().starts_with(env!("CARGO_PKG_NAME"))
127-
})),
128-
))
129-
}
130-
Err(e) => {
131-
eprintln!("Couldn't create graylog logger: {e}");
132-
Ok(None)
93+
if let Some(endpoint) = &opts.graylog_url {
94+
let level = opts.logging_level;
95+
let port = endpoint.port().unwrap_or_else(|| {
96+
warn!("No port specified for Graylog endpoint, using default 12201");
97+
12201
98+
});
99+
let address = format!(
100+
"{}:{}",
101+
endpoint.host_str().unwrap_or("localhost"),
102+
port,
103+
);
104+
105+
match Logger::builder().connect_tcp(address) {
106+
Ok((logger, mut handle)) => {
107+
tokio::spawn(async move {
108+
loop {
109+
let errors = handle.connect().await;
110+
if errors.0.is_empty() {
111+
error!(
112+
"Graylog DNS lookup failed for {:?} - no addresses resolved",
113+
handle.address()
114+
);
115+
break;
116+
} else {
117+
for (addr, err) in &errors.0 {
118+
warn!("Graylog connection to {addr} failed: {err} - reconnecting");
119+
}
120+
}
121+
}
122+
});
123+
Ok(Some(
124+
logger
125+
.with_filter(LevelFilter::from_level(level))
126+
.with_filter(FilterFn::new(|m| {
127+
m.target().starts_with(env!("CARGO_PKG_NAME"))
128+
})),
129+
))
130+
}
131+
Err(e) => {
132+
eprintln!("Couldn't create graylog logger: {e}");
133+
Ok(None)
134+
}
133135
}
136+
} else {
137+
Ok(None)
134138
}
135139
}
136140

137-
pub fn init(logging: Option<Level>, tracing: &TracingOptions) -> Result<(), LoggingError> {
141+
pub fn init(logging: Option<Level>, tracing: &TracingOptions, graylog: &GraylogOptions) -> Result<(), LoggingError> {
138142
let log_layer = init_stdout(logging);
139143
let trace_layer = init_tracing(tracing.tracing_url(), tracing.level())?;
140-
let graylog_layer = init_graylog(tracing.graylog_url(), tracing.logging_level())?;
144+
let graylog_layer = init_graylog(graylog)?;
141145
// Whatever level is set for logging/tracing, ignore the noise from the low-level libraries
142146
let filter = EnvFilter::new("trace") // let everything through
143147
.add_directive("h2=info".parse().expect("Static string is valid")) // except http,
@@ -161,23 +165,24 @@ mod tests {
161165
use tracing_subscriber::Registry;
162166

163167
use super::init_graylog;
168+
use crate::cli::GraylogOptions;
164169

165170
#[test]
166171
fn no_graylog_endpoint_returns_none() {
167-
// No URL configured means no layer should be added
168-
let result = init_graylog::<Registry>(None, Level::INFO);
172+
let opts = GraylogOptions { graylog_url: None, logging_level: Level::INFO };
173+
let result = init_graylog::<Registry>(&opts);
169174
assert!(matches!(result, Ok(None)));
170175
}
171176

172177
#[tokio::test]
173178
async fn graylog_with_endpoint_returns_layer() {
174-
// Bind a random local port to accept the TCP connection
175179
let listener = TcpListener::bind("127.0.0.1:0").expect("bind local port");
176180
let port = listener.local_addr().expect("local addr").port();
177-
let url = format!("tcp://127.0.0.1:{port}")
178-
.parse()
179-
.expect("valid url");
180-
let result = init_graylog::<Registry>(Some(url), Level::INFO);
181+
let opts = GraylogOptions {
182+
graylog_url: Some(format!("tcp://127.0.0.1:{port}").parse().expect("valid url")),
183+
logging_level: Level::INFO,
184+
};
185+
let result = init_graylog::<Registry>(&opts);
181186
assert!(matches!(result, Ok(Some(_))));
182187
}
183188
}

src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ mod template;
3030
#[tokio::main]
3131
async fn main() -> Result<(), Box<dyn Error>> {
3232
let args = Cli::init();
33-
let _ = logging::init(args.log_level(), args.tracing());
33+
let _ = logging::init(args.log_level(), args.tracing(), &args.graylog);
3434
match args.command {
3535
Command::Serve(opts) => graphql::serve_graphql(opts).await,
3636
#[cfg(not(feature = "client"))]

0 commit comments

Comments
 (0)