Skip to content

Commit

Permalink
Move access log types in their file, avoid alloc in display
Browse files Browse the repository at this point in the history
Signed-off-by: Eloi DEMOLIS <[email protected]>
  • Loading branch information
Wonshtrum authored and Keksoj committed Feb 2, 2024
1 parent 79fb602 commit c42001d
Show file tree
Hide file tree
Showing 4 changed files with 300 additions and 301 deletions.
207 changes: 207 additions & 0 deletions command/src/access_logs.rs
Original file line number Diff line number Diff line change
@@ -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<T> 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<T> DuplicateOwnership for &[T] {
type Target = Vec<T>;
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<Duration>);

#[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<MethodRecord>,
authority: Option<&'a str>,
path: Option<&'a str>,
status: Option<u16>,
reason: Option<&'a str>,
},
Tcp {
context: Option<&'a str>,
},
}

/// used to aggregate tags in a session
#[derive(Debug)]
pub struct CachedTags {
pub tags: BTreeMap<String, String>,
pub concatenated: String,
}

impl CachedTags {
pub fn new(tags: BTreeMap<String, String>) -> Self {
let concatenated = tags
.iter()
.map(|(k, v)| format!("{k}={v}"))
.collect::<Vec<_>>()
.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<SocketAddr>,
pub backend_address: &'a Option<SocketAddr>,
pub protocol: &'a str,
pub endpoint: &'a EndpointRecord<'a>,
pub tags: &'a Option<&'a CachedTags>,
pub client_rtt: &'a Option<Duration>,
pub server_rtt: &'a Option<Duration>,
pub user_agent: &'a Option<String>,
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<BinaryAccessLog> {
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(),
})
}
}
1 change: 1 addition & 0 deletions command/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading

0 comments on commit c42001d

Please sign in to comment.