use crate::Sensitivity;
use serde::{Deserialize, Serialize};
pub const MAX_LOG_TEXT_BYTES: usize = 4 * 1024;
pub const MAX_LOG_FIELDS: usize = 32;
pub const MAX_LOG_FIELD_KEY_BYTES: usize = 128;
pub const MAX_LOG_FIELD_VALUE_BYTES: usize = 4 * 1024;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LogEventError {
TextTooLong,
TooManyFields,
FieldKeyTooLong,
FieldValueTooLong,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum Severity {
Trace,
Debug,
Info,
Warn,
Error,
Critical,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct Verbosity(u8);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct VerbosityError;
impl Verbosity {
pub const V1: Self = Self(1);
pub const V2: Self = Self(2);
pub const V3: Self = Self(3);
pub const V4: Self = Self(4);
pub const V5: Self = Self(5);
pub const V6: Self = Self(6);
pub const V7: Self = Self(7);
pub const V8: Self = Self(8);
pub const V9: Self = Self(9);
pub const fn new(value: u8) -> Option<Self> {
if value >= 1 && value <= 9 {
Some(Self(value))
} else {
None
}
}
pub const fn value(self) -> u8 {
self.0
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct LogField {
pub key: String,
pub value: String,
pub path: bool,
pub sensitive: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct LogEvent {
pub timestamp_ms: u64,
pub severity: Severity,
pub verbosity: Verbosity,
pub message: String,
pub component: String,
pub module: Option<String>,
pub operation: Option<String>,
pub sensitivity: Sensitivity,
pub application_id: Option<String>,
pub node_id: Option<String>,
pub tenant_id: Option<String>,
pub trace_id: Option<String>,
pub request_id: Option<String>,
pub fields: Vec<LogField>,
}
impl LogEvent {
pub fn new(
timestamp_ms: u64,
severity: Severity,
verbosity: Verbosity,
component: impl Into<String>,
message: impl Into<String>,
) -> Self {
Self {
timestamp_ms,
severity,
verbosity,
message: message.into(),
component: component.into(),
module: None,
operation: None,
sensitivity: Sensitivity::Safe,
application_id: None,
node_id: None,
tenant_id: None,
trace_id: None,
request_id: None,
fields: Vec::new(),
}
}
#[must_use]
pub fn field(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.fields.push(LogField {
key: key.into(),
value: value.into(),
path: false,
sensitive: false,
});
self
}
#[must_use]
pub fn path(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.fields.push(LogField {
key: key.into(),
value: value.into(),
path: true,
sensitive: false,
});
self
}
#[must_use]
pub fn secret(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.fields.push(LogField {
key: key.into(),
value: value.into(),
path: false,
sensitive: true,
});
self
}
#[must_use]
pub fn module(mut self, module: impl Into<String>) -> Self {
self.module = Some(module.into());
self
}
#[must_use]
pub fn operation(mut self, operation: impl Into<String>) -> Self {
self.operation = Some(operation.into());
self
}
#[must_use]
pub fn sensitivity(mut self, sensitivity: Sensitivity) -> Self {
self.sensitivity = sensitivity;
self
}
pub fn validate(&self) -> Result<(), LogEventError> {
for value in [
&self.message,
&self.component,
self.module.as_deref().unwrap_or_default(),
self.operation.as_deref().unwrap_or_default(),
self.application_id.as_deref().unwrap_or_default(),
self.node_id.as_deref().unwrap_or_default(),
self.tenant_id.as_deref().unwrap_or_default(),
self.trace_id.as_deref().unwrap_or_default(),
self.request_id.as_deref().unwrap_or_default(),
] {
if value.len() > MAX_LOG_TEXT_BYTES {
return Err(LogEventError::TextTooLong);
}
}
if self.fields.len() > MAX_LOG_FIELDS {
return Err(LogEventError::TooManyFields);
}
for field in &self.fields {
if field.key.len() > MAX_LOG_FIELD_KEY_BYTES {
return Err(LogEventError::FieldKeyTooLong);
}
if field.value.len() > MAX_LOG_FIELD_VALUE_BYTES {
return Err(LogEventError::FieldValueTooLong);
}
}
Ok(())
}
pub fn retained_bytes(&self) -> usize {
let optional = [
&self.module,
&self.operation,
&self.application_id,
&self.node_id,
&self.tenant_id,
&self.trace_id,
&self.request_id,
]
.into_iter()
.flatten()
.map(String::capacity)
.sum::<usize>();
let fields = self
.fields
.iter()
.map(|field| field.key.capacity().saturating_add(field.value.capacity()))
.sum::<usize>();
std::mem::size_of::<Self>()
.saturating_add(self.message.capacity())
.saturating_add(self.component.capacity())
.saturating_add(
self.fields
.capacity()
.saturating_mul(std::mem::size_of::<LogField>()),
)
.saturating_add(optional)
.saturating_add(fields)
}
}