From c42001dd2758316131dbc8845dfdadb767200e44 Mon Sep 17 00:00:00 2001 From: Eloi DEMOLIS Date: Thu, 1 Feb 2024 16:39:40 +0100 Subject: [PATCH] Move access log types in their file, avoid alloc in display Signed-off-by: Eloi DEMOLIS --- command/src/access_logs.rs | 207 ++++++++++++++++++++++++ command/src/lib.rs | 1 + command/src/logging.rs | 299 ++++++----------------------------- command/src/proto/display.rs | 94 +++++------ 4 files changed, 300 insertions(+), 301 deletions(-) create mode 100644 command/src/access_logs.rs diff --git a/command/src/access_logs.rs b/command/src/access_logs.rs new file mode 100644 index 000000000..86ff2fae9 --- /dev/null +++ b/command/src/access_logs.rs @@ -0,0 +1,207 @@ +use std::{collections::BTreeMap, mem::ManuallyDrop, net::SocketAddr}; + +use rusty_ulid::Ulid; +use time::Duration; + +use crate::proto::command::{ + BinaryAccessLog, BinaryEndpoint, HttpMethod, RequestUlid, TcpEndpoint, +}; + +/// This uses unsafe to creates a "fake" owner of the underlying data. +/// Beware that for the compiler it is as legitimate as the original owner. +/// So you have to elide one of them (with std::mem::forget or ManuallyDrop) +/// before it is drop to avoid a double free. +/// +/// This trait works on &T and Option<&T> types +trait DuplicateOwnership { + type Target; + /// Don't forget to use std::mem::forget or ManuallyDrop over one of your owners + unsafe fn duplicate(self) -> Self::Target; +} + +impl DuplicateOwnership for &T { + type Target = T; + unsafe fn duplicate(self) -> T { + std::ptr::read(self as *const T) + } +} +impl<'a, T> DuplicateOwnership for Option<&'a T> +where + T: ?Sized, + &'a T: DuplicateOwnership + 'a, +{ + type Target = Option<<&'a T as DuplicateOwnership>::Target>; + unsafe fn duplicate(self) -> Self::Target { + self.map(|t| t.duplicate()) + } +} +impl DuplicateOwnership for &str { + type Target = String; + unsafe fn duplicate(self) -> Self::Target { + String::from_raw_parts(self.as_ptr() as *mut _, self.len(), self.len()) + } +} +impl DuplicateOwnership for &[T] { + type Target = Vec; + unsafe fn duplicate(self) -> Self::Target { + Vec::from_raw_parts(self.as_ptr() as *mut _, self.len(), self.len()) + } +} + +pub fn prepare_user_agent(user_agent: &str) -> String { + let mut user_agent = user_agent.replace(' ', "_"); + let mut ua_bytes = std::mem::take(&mut user_agent).into_bytes(); + if let Some(last) = ua_bytes.last_mut() { + if *last == b',' { + *last = b'!' + } + } + unsafe { String::from_utf8_unchecked(ua_bytes) } +} + +pub struct LogDuration(pub Option); + +#[derive(Debug)] +pub struct LogContext<'a> { + pub request_id: Ulid, + pub cluster_id: Option<&'a str>, + pub backend_id: Option<&'a str>, +} + +#[derive(Clone, Copy)] +pub enum MethodRecord { + Get, + Post, + Head, + Options, + Put, + Delete, + Trace, + Connect, + Other, +} + +pub enum EndpointRecord<'a> { + Http { + method: Option, + authority: Option<&'a str>, + path: Option<&'a str>, + status: Option, + reason: Option<&'a str>, + }, + Tcp { + context: Option<&'a str>, + }, +} + +/// used to aggregate tags in a session +#[derive(Debug)] +pub struct CachedTags { + pub tags: BTreeMap, + pub concatenated: String, +} + +impl CachedTags { + pub fn new(tags: BTreeMap) -> Self { + let concatenated = tags + .iter() + .map(|(k, v)| format!("{k}={v}")) + .collect::>() + .join(", "); + Self { tags, concatenated } + } +} + +/// Intermediate representation of an access log agnostic of the final format. +/// Every field is a reference to avoid capturing ownership (as a logger should). +pub struct RequestRecord<'a> { + pub error: &'a Option<&'a str>, + pub context: &'a LogContext<'a>, + pub session_address: &'a Option, + pub backend_address: &'a Option, + pub protocol: &'a str, + pub endpoint: &'a EndpointRecord<'a>, + pub tags: &'a Option<&'a CachedTags>, + pub client_rtt: &'a Option, + pub server_rtt: &'a Option, + pub user_agent: &'a Option, + pub service_time: &'a Duration, + pub response_time: &'a Duration, + pub bytes_in: &'a usize, + pub bytes_out: &'a usize, + // pub now: Rfc3339Time, + // pub precise_time: i128, + // pub tag: &'a str, +} + +// TODO: Add tag and time fields to Protobuf BinaryAccessLog +impl RequestRecord<'_> { + /// Converts the RequestRecord in its protobuf representation. + /// Prost needs ownership over all the fields but we don't want to take it from the user + /// or clone them, so we use the unsafe DuplicateOwnership. + pub unsafe fn into_binary_access_log( + self, + time: i128, + tag: &str, + ) -> ManuallyDrop { + let (first, second) = self.context.request_id.into(); + let request_id = RequestUlid { first, second }; + + let endpoint = match self.endpoint { + EndpointRecord::Http { + method, + authority, + path, + status, + reason, + } => crate::proto::command::binary_endpoint::Inner::Http( + crate::proto::command::HttpEndpoint { + method: method.map(|m| match m { + MethodRecord::Get => HttpMethod::Get, + MethodRecord::Post => HttpMethod::Post, + MethodRecord::Head => HttpMethod::Head, + MethodRecord::Options => HttpMethod::Options, + MethodRecord::Put => HttpMethod::Put, + MethodRecord::Delete => HttpMethod::Delete, + MethodRecord::Trace => HttpMethod::Trace, + MethodRecord::Connect => HttpMethod::Connect, + MethodRecord::Other => HttpMethod::Other, + } as i32), + authority: authority.duplicate().duplicate(), + path: path.duplicate().duplicate(), + status: status.map(|s| s as u32), + reason: reason.duplicate().duplicate(), + }, + ), + EndpointRecord::Tcp { context } => { + crate::proto::command::binary_endpoint::Inner::Tcp(TcpEndpoint { + context: context.duplicate().duplicate(), + }) + } + }; + + ManuallyDrop::new(BinaryAccessLog { + backend_address: self.backend_address.map(|a| a.to_string()), + backend_id: self.context.backend_id.duplicate(), + bytes_in: *self.bytes_in as u64, + bytes_out: *self.bytes_out as u64, + client_rtt: self.client_rtt.map(|t| t.whole_microseconds() as u64), + cluster_id: self.context.cluster_id.duplicate(), + endpoint: BinaryEndpoint { + inner: Some(endpoint), + }, + error: self.error.duplicate().duplicate(), + protocol: self.protocol.duplicate(), + request_id, + response_time: self.response_time.whole_microseconds() as u64, + server_rtt: self.server_rtt.map(|t| t.whole_microseconds() as u64), + service_time: self.service_time.whole_microseconds() as u64, + session_address: self.session_address.map(|a| a.to_string()), + tags: self + .tags + .map(|tags| tags.tags.duplicate()) + .unwrap_or_default(), + user_agent: self.user_agent.duplicate(), + }) + } +} diff --git a/command/src/lib.rs b/command/src/lib.rs index d7422ec4e..1f7aebda6 100644 --- a/command/src/lib.rs +++ b/command/src/lib.rs @@ -6,6 +6,7 @@ extern crate serde; #[macro_use] /// custom made logging macros pub mod logging; +pub mod access_logs; /// Custom buffer used for parsing within the Sōzu codebase. pub mod buffer; /// TLS certificates diff --git a/command/src/logging.rs b/command/src/logging.rs index fee9312ef..921c48076 100644 --- a/command/src/logging.rs +++ b/command/src/logging.rs @@ -1,12 +1,10 @@ use std::{ cell::RefCell, cmp::{self, Ord}, - collections::BTreeMap, env, fmt::Arguments, fs::{File, OpenOptions}, io::{stdout, Error as IoError, ErrorKind as IoErrorKind, Stdout, Write}, - mem::ManuallyDrop, net::{SocketAddr, TcpStream, ToSocketAddrs, UdpSocket}, path::Path, str::FromStr, @@ -16,13 +14,8 @@ use libc; use mio::net::UnixDatagram; use prost::{encoding::encoded_len_varint, Message}; use rand::{distributions::Alphanumeric, thread_rng, Rng}; -use rusty_ulid::Ulid; -use time::Duration; -use crate::{ - config::Config, - proto::command::{BinaryAccessLog, BinaryEndpoint, HttpMethod, RequestUlid, TcpEndpoint}, -}; +use crate::{access_logs::RequestRecord, bind_format_args, config::Config}; thread_local! { pub static LOGGER: RefCell = RefCell::new(Logger::new()); @@ -104,8 +97,8 @@ impl Logger { access_log_format: Option, ) { let directives = parse_logging_spec(spec); - LOGGER.with(|l| { - let logger = &mut (*l.borrow_mut()); + LOGGER.with(|logger| { + let mut logger = logger.borrow_mut(); if !logger.initialized { logger.set_directives(directives); logger.backend = backend; @@ -157,12 +150,15 @@ impl InnerLogger { } LoggerBackend::Tcp(socket) => socket.write_fmt(args), LoggerBackend::File(file) => file.write_fmt(args), - LoggerBackend::Unix(s) => self.buffer.fmt(args, |bytes| s.send(bytes)), - LoggerBackend::Udp(s, addr) => self.buffer.fmt(args, |bytes| s.send_to(bytes, *addr)), + LoggerBackend::Unix(socket) => self.buffer.fmt(args, |bytes| socket.send(bytes)), + LoggerBackend::Udp(sock, addr) => self.buffer.fmt(args, |b| sock.send_to(b, *addr)), }; if let Err(e) = io_result { - println!("cannot write access log to {}: {e:?}", self.backend); + println!( + "Cannot write access log to {}: {e:?}", + self.backend.as_ref() + ); } } @@ -172,7 +168,7 @@ impl InnerLogger { let io_result = match self.access_log_format { AccessLogFormat::Binary => { - let binary_log = unsafe { log.into_binary_access_log(now, precise_time, tag) }; + let binary_log = unsafe { log.into_binary_access_log(precise_time, tag) }; let log_length = binary_log.encoded_len(); let total_length = log_length + encoded_len_varint(log_length as u64); self.buffer.clear(); @@ -197,32 +193,23 @@ impl InnerLogger { .map(|_| ()) } } - AccessLogFormat::Ascii => match backend { - LoggerBackend::Stdout(stdout) => { - let _ = stdout.write_fmt(format_args!( - "{now} {precise_time} {pid} {tag} {level_tag} {log}" - )); - return; + AccessLogFormat::Ascii => bind_format_args! { + let args = ("{now} {precise_time} {pid} {tag} {level_tag} {log}"); + match backend { + LoggerBackend::Stdout(stdout) => { + let _ = stdout.write_fmt(args); + return Ok(()); + } + LoggerBackend::Tcp(socket) => socket.write_fmt(args), + LoggerBackend::File(file) => file.write_fmt(args), + LoggerBackend::Unix(socket) => self.buffer.fmt(args, |b| socket.send(b)), + LoggerBackend::Udp(sock, addr) => self.buffer.fmt(args, |b| sock.send_to(b, *addr)), } - LoggerBackend::Tcp(socket) => socket.write_fmt(format_args!( - "{now} {precise_time} {pid} {tag} {level_tag} {log}" - )), - LoggerBackend::File(file) => file.write_fmt(format_args!( - "{now} {precise_time} {pid} {tag} {level_tag} {log}" - )), - LoggerBackend::Unix(socket) => self.buffer.fmt( - format_args!("{now} {precise_time} {pid} {tag} {level_tag} {log}"), - |bytes| socket.send(bytes), - ), - LoggerBackend::Udp(socket, address) => self.buffer.fmt( - format_args!("{now} {precise_time} {pid} {tag} {level_tag} {log}"), - |bytes| socket.send_to(bytes, *address), - ), }, }; if let Err(e) = io_result { - println!("cannot write access log to {}: {:?}", backend, e); + println!("Cannot write access log to {}: {:?}", backend.as_ref(), e); } } @@ -234,12 +221,15 @@ impl InnerLogger { } LoggerBackend::Tcp(socket) => socket.write_fmt(args), LoggerBackend::File(file) => file.write_fmt(args), - LoggerBackend::Unix(s) => self.buffer.fmt(args, |bytes| s.send(bytes)), - LoggerBackend::Udp(s, addr) => self.buffer.fmt(args, |bytes| s.send_to(bytes, *addr)), + LoggerBackend::Unix(socket) => self.buffer.fmt(args, |b| socket.send(b)), + LoggerBackend::Udp(sock, addr) => self.buffer.fmt(args, |b| sock.send_to(b, *addr)), }; if let Err(e) = io_result { - println!("cannot write access log to {}: {e:?}", self.backend); + println!( + "Cannot write access log to {}: {e:?}", + self.backend.as_ref() + ); } } @@ -428,13 +418,11 @@ impl Ord for LogLevelFilter { impl FromStr for LogLevelFilter { type Err = (); fn from_str(level: &str) -> Result { - ok_or( - LOG_LEVEL_NAMES - .iter() - .position(|&name| name.eq_ignore_ascii_case(level)) - .map(|p| LogLevelFilter::from_usize(p).unwrap()), - (), - ) + LOG_LEVEL_NAMES + .iter() + .position(|&name| name.eq_ignore_ascii_case(level)) + .map(|p| LogLevelFilter::from_usize(p).unwrap()) + .ok_or(()) } } @@ -465,194 +453,6 @@ impl LogLevelFilter { } } -pub struct LogDuration(pub Option); - -#[derive(Debug)] -pub struct LogContext<'a> { - pub request_id: Ulid, - pub cluster_id: Option<&'a str>, - pub backend_id: Option<&'a str>, -} - -/// used to aggregate tags in a session -#[derive(Debug)] -pub struct CachedTags { - pub tags: BTreeMap, - pub concatenated: String, -} - -impl CachedTags { - pub fn new(tags: BTreeMap) -> Self { - let concatenated = tags - .iter() - .map(|(k, v)| format!("{k}={v}")) - .collect::>() - .join(", "); - Self { tags, concatenated } - } -} - -#[derive(Clone, Copy)] -pub enum MethodRecord { - Get, - Post, - Head, - Options, - Put, - Delete, - Trace, - Connect, - Other, -} - -pub enum EndpointRecord<'a> { - Http { - method: Option, - authority: Option<&'a str>, - path: Option<&'a str>, - status: Option, - reason: Option<&'a str>, - }, - Tcp { - context: Option<&'a str>, - }, -} - -/// Intermediate representation of an access log agnostic of the final format. -/// Every field is a reference to avoid capturing ownership (as a logger should). -pub struct RequestRecord<'a> { - pub error: &'a Option<&'a str>, - pub context: &'a LogContext<'a>, - pub session_address: &'a Option, - pub backend_address: &'a Option, - pub protocol: &'a str, - pub endpoint: &'a EndpointRecord<'a>, - pub tags: &'a Option<&'a CachedTags>, - pub client_rtt: &'a Option, - pub server_rtt: &'a Option, - pub user_agent: &'a Option, - pub service_time: &'a Duration, - pub response_time: &'a Duration, - pub bytes_in: &'a usize, - pub bytes_out: &'a usize, - // pub now: Rfc3339Time, - // pub precise_time: i128, - // pub tag: &'a str, -} - -impl RequestRecord<'_> { - /// Converts the RequestRecord in its protobuf representation. - /// Prost needs ownership over all the fields but we don't want to take it from the user - /// or clone them, we use the DuplicateOwnership. - unsafe fn into_binary_access_log( - self, - now: Rfc3339Time, - precise_time: i128, - tag: &str, - ) -> ManuallyDrop { - let (first, second) = self.context.request_id.into(); - let request_id = RequestUlid { first, second }; - - let endpoint = match self.endpoint { - EndpointRecord::Http { - method, - authority, - path, - status, - reason, - } => crate::proto::command::binary_endpoint::Inner::Http( - crate::proto::command::HttpEndpoint { - method: method.map(|m| match m { - MethodRecord::Get => HttpMethod::Get, - MethodRecord::Post => HttpMethod::Post, - MethodRecord::Head => HttpMethod::Head, - MethodRecord::Options => HttpMethod::Options, - MethodRecord::Put => HttpMethod::Put, - MethodRecord::Delete => HttpMethod::Delete, - MethodRecord::Trace => HttpMethod::Trace, - MethodRecord::Connect => HttpMethod::Connect, - MethodRecord::Other => HttpMethod::Other, - } as i32), - authority: authority.duplicate().duplicate(), - path: path.duplicate().duplicate(), - status: status.map(|s| s as u32), - reason: reason.duplicate().duplicate(), - }, - ), - EndpointRecord::Tcp { context } => { - crate::proto::command::binary_endpoint::Inner::Tcp(TcpEndpoint { - context: context.duplicate().duplicate(), - }) - } - }; - - ManuallyDrop::new(BinaryAccessLog { - backend_address: self.backend_address.map(|a| a.to_string()), - backend_id: self.context.backend_id.duplicate(), - bytes_in: *self.bytes_in as u64, - bytes_out: *self.bytes_out as u64, - client_rtt: self.client_rtt.map(|t| t.whole_microseconds() as u64), - cluster_id: self.context.cluster_id.duplicate(), - endpoint: BinaryEndpoint { - inner: Some(endpoint), - }, - error: self.error.duplicate().duplicate(), - protocol: self.protocol.duplicate(), - request_id, - response_time: self.response_time.whole_microseconds() as u64, - server_rtt: self.server_rtt.map(|t| t.whole_microseconds() as u64), - service_time: self.service_time.whole_microseconds() as u64, - session_address: self.session_address.map(|a| a.to_string()), - tags: self - .tags - .map(|tags| tags.tags.duplicate()) - .unwrap_or_default(), - user_agent: self.user_agent.duplicate(), - }) - } -} - -/// This uses unsafe to creates a "fake" owner of the underlying data. -/// Beware that for the compiler it is as legitimate as the original owner. -/// So you have to elide one of them (with std::mem::forget or ManuallyDrop) -/// before it is drop to avoid a double free. -/// -/// This trait works on &T and Option<&T> types -trait DuplicateOwnership { - type Target; - /// Don't forget to use std::mem::forget or ManuallyDrop over one of your owners - unsafe fn duplicate(self) -> Self::Target; -} - -impl DuplicateOwnership for &T { - type Target = T; - unsafe fn duplicate(self) -> T { - std::ptr::read(self as *const T) - } -} -impl<'a, T> DuplicateOwnership for Option<&'a T> -where - T: ?Sized, - &'a T: DuplicateOwnership + 'a, -{ - type Target = Option<<&'a T as DuplicateOwnership>::Target>; - unsafe fn duplicate(self) -> Self::Target { - self.map(|t| t.duplicate()) - } -} -impl DuplicateOwnership for &str { - type Target = String; - unsafe fn duplicate(self) -> Self::Target { - String::from_raw_parts(self.as_ptr() as *mut _, self.len(), self.len()) - } -} -impl DuplicateOwnership for &[T] { - type Target = Vec; - unsafe fn duplicate(self) -> Self::Target { - Vec::from_raw_parts(self.as_ptr() as *mut _, self.len(), self.len()) - } -} - /// Metadata about a log message. #[derive(Debug)] pub struct Metadata { @@ -666,13 +466,6 @@ pub struct LogDirective { level: LogLevelFilter, } -fn ok_or(t: Option, e: E) -> Result { - match t { - Some(t) => Ok(t), - None => Err(e), - } -} - pub fn parse_logging_spec(spec: &str) -> Vec { let mut dirs = Vec::new(); @@ -831,6 +624,13 @@ pub fn target_to_backend(target: &str) -> LoggerBackend { } } +#[macro_export] +macro_rules! bind_format_args { + (let $args: ident = ($($f:tt)+); $($t:tt)*) => { + (|$args| { $($t)* })(format_args!($($f)+)) + }; +} + /// write a log with the custom logger (used in other macros, do not use directly) #[macro_export] macro_rules! log { @@ -843,13 +643,13 @@ macro_rules! log { log!(@ $target, $lvl, $format, $level_tag, [$($ref_args,)* reg_arg] $(, $other_args)*); }}; (@ $target:expr, $lvl:expr, $format:expr, $level_tag:expr, [$($final_args:ident),*]) => {{ - $crate::logging::LOGGER.with(|l| { - let mut l = l.borrow_mut(); - if !l.enabled($crate::logging::Metadata { + $crate::logging::LOGGER.with(|logger| { + let mut logger = logger.borrow_mut(); + if !logger.enabled($crate::logging::Metadata { level: $lvl, target: module_path!(), }) { return; } - let (pid, tag, inner) = l.split(); + let (pid, tag, inner) = logger.split(); let (now, precise_time) = $crate::logging::now(); inner.log( format_args!( @@ -859,21 +659,22 @@ macro_rules! log { ); }) }}; + } /// log a failure concerning an HTTP or TCP request #[macro_export] macro_rules! log_access { ($lvl:expr, $level_tag:expr, $request_record:expr) => {{ - $crate::logging::LOGGER.with(|l| { - let mut l = l.borrow_mut(); - if !l.enabled($crate::logging::Metadata { + $crate::logging::LOGGER.with(|logger| { + let mut logger = logger.borrow_mut(); + if !logger.enabled($crate::logging::Metadata { level: $lvl, target: module_path!(), }) { return; } - let (pid, tag, inner) = l.split(); + let (pid, tag, inner) = logger.split(); inner.log_access(pid, tag, $level_tag, $request_record); }) }}; @@ -902,7 +703,7 @@ macro_rules! structured_access_log { #[macro_export] macro_rules! error_access { ($($request_record_fields:tt)*) => { - log_access!($crate::logging::LogLevel::Error, "ERRPR", structured_access_log!($($request_record_fields)*)); + log_access!($crate::logging::LogLevel::Error, "ERROR", structured_access_log!($($request_record_fields)*)); }; } diff --git a/command/src/proto/display.rs b/command/src/proto/display.rs index e909aa64a..4b527ca18 100644 --- a/command/src/proto/display.rs +++ b/command/src/proto/display.rs @@ -9,9 +9,10 @@ use time::format_description; use x509_parser::time::ASN1Time; use crate::{ - logging::{ - EndpointRecord, LogContext, LogDuration, LoggerBackend, MethodRecord, RequestRecord, + access_logs::{ + prepare_user_agent, EndpointRecord, LogContext, LogDuration, MethodRecord, RequestRecord, }, + logging::LoggerBackend, proto::{ command::{ filtered_metrics, request::RequestType, response_content::ContentType, @@ -1000,51 +1001,39 @@ pub trait AsStr { fn as_str_or(&self, default: &'static str) -> &str; } -impl AsStr for Option<&str> { +impl> AsStr for Option { fn as_str_or(&self, default: &'static str) -> &str { match self { None => default, - Some(s) => s, + Some(s) => s.as_ref(), } } } -impl Display for EndpointRecord<'_> { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { +impl AsRef for MethodRecord { + fn as_ref(&self) -> &str { match self { - Self::Http { - method, - authority, - path, - status, - .. - } => write!( - f, - "{} {} {} -> {}", - authority.as_string_or("-"), - method.as_string_or("-"), - path.as_string_or("-"), - status.as_string_or("-"), - ), - Self::Tcp { context } => { - write!(f, "{}", context.as_string_or("-")) - } + Self::Get => "GET", + Self::Post => "POST", + Self::Head => "HEAD", + Self::Options => "OPTIONS", + Self::Put => "PUT", + Self::Delete => "DELETE", + Self::Trace => "TRACE", + Self::Connect => "CONNECT", + Self::Other => "OTHER", } } } -impl Display for MethodRecord { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { +impl AsRef for LoggerBackend { + fn as_ref(&self) -> &str { match self { - Self::Get => write!(f, "GET"), - Self::Post => write!(f, "POST"), - Self::Head => write!(f, "HEAD"), - Self::Options => write!(f, "OPTIONS"), - Self::Put => write!(f, "PUT"), - Self::Delete => write!(f, "DELETE"), - Self::Trace => write!(f, "TRACE"), - Self::Connect => write!(f, "CONNECT"), - Self::Other => write!(f, "OTHER"), + LoggerBackend::Stdout(_) => "stdout", + LoggerBackend::Unix(_) => "UNIX socket", + LoggerBackend::Udp(_, _) => "UDP socket", + LoggerBackend::Tcp(_) => "TCP socket", + LoggerBackend::File(_) => "file", } } } @@ -1088,27 +1077,28 @@ impl Display for LogContext<'_> { } } -impl Display for LoggerBackend { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { +impl Display for EndpointRecord<'_> { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { match self { - LoggerBackend::Stdout(_) => write!(f, "stdout"), - LoggerBackend::Unix(_) => write!(f, "UNIX socket"), - LoggerBackend::Udp(_, _) => write!(f, "UDP socket"), - LoggerBackend::Tcp(_) => write!(f, "TCP socket"), - LoggerBackend::File(_) => write!(f, "file"), - } - } -} - -fn prepare_user_agent(user_agent: &str) -> String { - let mut user_agent = user_agent.replace(' ', "_"); - let mut ua_bytes = std::mem::take(&mut user_agent).into_bytes(); - if let Some(last) = ua_bytes.last_mut() { - if *last == b',' { - *last = b'!' + Self::Http { + method, + authority, + path, + status, + .. + } => write!( + f, + "{} {} {} -> {}", + authority.as_str_or("-"), + method.as_str_or("-"), + path.as_str_or("-"), + status.as_string_or("-"), + ), + Self::Tcp { context } => { + write!(f, "{}", context.as_str_or("-")) + } } } - unsafe { String::from_utf8_unchecked(ua_bytes) } } impl Display for RequestRecord<'_> {