Skip to content

Commit

Permalink
feat(gateway): add support for TCP logging and JSON log format
Browse files Browse the repository at this point in the history
  • Loading branch information
PhotonQuantum committed Oct 13, 2023
1 parent d5004a1 commit 1fd24a0
Show file tree
Hide file tree
Showing 15 changed files with 433 additions and 73 deletions.
45 changes: 36 additions & 9 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion rsync-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@ redis = ["dep:redis", "clean-path", "scan_fmt", "bincode"]
percent-encoding = ["dep:percent-encoding", "url-escape"]

[dependencies]
backtrace = "0.3"
bincode = { version = "=2.0.0-rc.3", optional = true }
blake2 = { version = "0.10", optional = true }
clean-path = { version = "0.2", optional = true }
color-eyre = "0.6"
doku = { version = "0.21", optional = true }
eyre = "0.6"
futures = "0.3"
itertools = { version = "0.11", optional = true }
Expand All @@ -29,9 +31,11 @@ proptest-derive = { version = "0.4", optional = true }
rand = "0.8"
redis = { git = "https://github.com/PhotonQuantum/redis-rs", features = ["aio", "tokio-comp"], optional = true }
scan_fmt = { version = "0.2", optional = true }
serde = { version = "1.0", optional = true }
sqlx = { version = "0.7", features = ["bigdecimal", "runtime-tokio-rustls", "postgres", "chrono", "macros"], optional = true }
stubborn-io = "0.3"
tokio = { version = "1.33", features = ["full"] }
tracing = "0.1"
tracing-error = "0.2"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
tracing-subscriber = { version = "0.3", features = ["env-filter", "json"] }
url-escape = { version = "0.1", optional = true }
1 change: 1 addition & 0 deletions rsync-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
clippy::future_not_send
)]

pub mod logging;
pub mod metadata;
#[cfg(feature = "pg")]
pub mod pg;
Expand Down
22 changes: 22 additions & 0 deletions rsync-core/src/logging/either.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
use tracing_subscriber::fmt::writer::EitherWriter;
use tracing_subscriber::fmt::MakeWriter;

pub enum EitherMakeWriter<A, B> {
Left(A),
Right(B),
}

impl<'a, A, B> MakeWriter<'a> for EitherMakeWriter<A, B>
where
A: MakeWriter<'a>,
B: MakeWriter<'a>,
{
type Writer = EitherWriter<A::Writer, B::Writer>;

fn make_writer(&'a self) -> Self::Writer {
match self {
Self::Left(w) => EitherWriter::A(w.make_writer()),
Self::Right(w) => EitherWriter::B(w.make_writer()),
}
}
}
142 changes: 142 additions & 0 deletions rsync-core/src/logging/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
use std::panic::PanicInfo;
use std::{env, panic};

use tracing::{error, Subscriber};
use tracing_error::ErrorLayer;
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::registry::LookupSpan;
use tracing_subscriber::util::SubscriberInitExt;
use tracing_subscriber::{reload, EnvFilter, Layer};

pub use opts::{LogFormat, LogTarget};

mod either;
mod opts;
mod tcp;

fn tracing_panic_hook(panic_info: &PanicInfo) {
let payload = panic_info.payload();

let payload = payload.downcast_ref::<&str>().map_or_else(
|| payload.downcast_ref::<String>().map(String::as_str),
|s| Some(&**s),
);

let location = panic_info.location().map(ToString::to_string);
let backtrace = backtrace::Backtrace::new();

error!(
panic.payload = payload,
panic.location = location,
panic.backtrace = ?backtrace,
"A panic occurred",
);
}

pub struct LoggerHandle {
f_set_target_format: Box<dyn Fn(LogTarget, LogFormat)>,
}

impl LoggerHandle {
/// Replace log target.
///
/// # Panics
/// Panics if tracing registry is poisoned or gone.
pub fn set_target_format(&mut self, target: LogTarget, format: LogFormat) {
(self.f_set_target_format)(target, format);
}
}

fn boxed_subscriber_layer<S>(
target: LogTarget,
format: LogFormat,
) -> Box<dyn Layer<S> + Send + Sync>
where
S: Subscriber + for<'a> LookupSpan<'a>,
{
match format {
LogFormat::Human => {
Box::new(tracing_subscriber::fmt::layer().with_writer(target.into_make_writer()))
}
LogFormat::JSON => Box::new(
tracing_subscriber::fmt::layer()
.json()
.with_writer(target.into_make_writer()),
),
}
}

/// Init logger with `tracing_subscriber`, setup eyre trace helper, metrics helper, and panic handler.
///
/// This must be called after eyre setup or panic handler will not work.
///
/// # Panics
/// Panics if tracing registry is poisoned or gone during initialization, which is unlikely.
#[allow(clippy::must_use_candidate)]
pub fn init_logger(target: LogTarget, format: LogFormat) -> LoggerHandle {
// First load with stderr to catch errors in tcp connection
let (subscriber, reload_handle) =
reload::Layer::new(boxed_subscriber_layer(LogTarget::Stderr, format));

let set_target_format = move |target: LogTarget, format: LogFormat| {
reload_handle
.modify(|subscriber| {
*subscriber = boxed_subscriber_layer(target, format);
})
.expect("failed to replace subscriber");
};

let builder = tracing_subscriber::Registry::default()
.with(EnvFilter::from_default_env())
.with(ErrorLayer::default())
.with(subscriber);
#[cfg(feature = "metrics-tracing-context")]
{
builder
.with(metrics_tracing_context::MetricsLayer::new())
.init();
}
#[cfg(not(feature = "metrics-tracing-context"))]
{
builder.init();
}

// Then try to replace with tcp.
set_target_format(target, format);

let prev_hook = panic::take_hook();
panic::set_hook(Box::new(move |panic_info| {
tracing_panic_hook(panic_info);
prev_hook(panic_info);
}));

LoggerHandle {
f_set_target_format: Box::new(set_target_format),
}
}

#[cfg(feature = "tests")]
pub fn test_init_logger() {
drop(
tracing_subscriber::Registry::default()
.with(tracing::level_filters::LevelFilter::DEBUG)
.with(ErrorLayer::default())
.with(tracing_subscriber::fmt::layer().with_writer(std::io::stderr))
.try_init(),
);
}

/// Initialize color-eyre error handling, with `NO_COLOR` support.
///
/// # Errors
/// Returns an error if `color-eyre` has already been initialized.
pub fn init_color_eyre() -> eyre::Result<()> {
if env::var("NO_COLOR").is_ok() {
color_eyre::config::HookBuilder::new()
.theme(color_eyre::config::Theme::new())
.install()?;
} else {
color_eyre::install()?;
}
Ok(())
}
Loading

0 comments on commit 1fd24a0

Please sign in to comment.