-
Notifications
You must be signed in to change notification settings - Fork 197
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Move access log types in their file, avoid alloc in display
Signed-off-by: Eloi DEMOLIS <[email protected]>
- Loading branch information
Showing
4 changed files
with
300 additions
and
301 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(), | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.