Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add JSON log serialization #6

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ optional = true
version = "1.4.1"
default-features = false

[dependencies.cfg-if]
version = "1"
default-features = false

[dev-dependencies.fern]
version = "0.6.2"
default-features = false
Expand All @@ -75,6 +79,7 @@ ureq = ["dep:ureq"]
reqwest-async = ["dep:reqwest", "async-tokio"]
async-tokio = ["tokio", "tokio/rt"]
json = ["dep:serde_json"]
json-log-fmt = ["json"]
structured_logging = ["log/kv_unstable_std"]

[package.metadata.docs.rs]
Expand Down
74 changes: 73 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ pub mod reqwest;
#[cfg(feature = "ureq")]
pub mod ureq;

use cfg_if::cfg_if;
#[cfg(feature = "structured_logging")]
use log::kv::{Source, Visitor};
use log::{Log, Metadata, Record};
Expand Down Expand Up @@ -95,6 +96,7 @@ pub struct Fenrir {
include_framework: bool,
log_stream: RwLock<Vec<Stream>>,
flush_threshold: usize,
max_message_size: Option<usize>,
}

impl Fenrir {
Expand All @@ -119,6 +121,7 @@ impl Fenrir {
include_framework: false,
runtime: None,
flush_threshold: 100,
max_message_size: None,
}
}
}
Expand Down Expand Up @@ -170,6 +173,30 @@ impl Log for Fenrir {
);
}

let serialized_event = {
cfg_if! {
if #[cfg(feature = "json-log-fmt")] {
serde_json::to_string(&SerializedEvent {
file: record.file(),
line: record.line(),
module: record.module_path(),
level: record.level().as_str(),
target: record.target(),
message: record.args().to_string(),
})
.expect("JSON serialization failed (should not happen)")
} else {
record.args().to_string()
}
}
};

if let Some(max_message_size) = self.max_message_size {
if serialized_event.len() > max_message_size {
return;
}
}

// create the logging stream we want to send to loki
let stream_object = Stream {
stream: labels,
Expand All @@ -179,7 +206,7 @@ impl Log for Fenrir {
.unwrap()
.as_nanos()
.to_string(),
record.args().to_string(),
serialized_event,
]],
};
// push the stream object to the log stream
Expand Down Expand Up @@ -250,6 +277,10 @@ pub struct FenrirBuilder {
/// Defaults to 100.
/// Must be greater than 0.
flush_threshold: usize,
/// Skip a log message if its serialized representation is larger than this value in bytes.
/// Defaults to None, which means no limit.
/// If set, must be greater than 0.
max_message_size: Option<usize>,
}

impl FenrirBuilder {
Expand Down Expand Up @@ -444,6 +475,28 @@ impl FenrirBuilder {
self
}

/// Configure the maximum size of a single message, in bytes, before it is dropped.
/// This is useful for avoiding network issues when sending large messages on slow or unreliable networks.
/// Defaults to None, which means that no limit is enforced.
///
/// # Panics
/// This method will panic if the supplied value is 0.
///
/// # Example
/// ```
/// use fenrir_rs::Fenrir;
///
/// let builder = Fenrir::builder()
/// .max_message_size(Some(1048576));
/// ```
pub fn max_message_size(mut self, size: Option<usize>) -> FenrirBuilder {
if size == Some(0) {
panic!("Max message size must be greater than 0");
}
self.max_message_size = size;
self
}

/// Create a new `Fenrir` instance with the parameters supplied to this struct before calling this method.
///
/// Before creating a new instance, the supplied parameters are validated (in contrast to [`FenrirBuilder::build`]
Expand Down Expand Up @@ -551,6 +604,7 @@ impl FenrirBuilder {
additional_tags: self.additional_tags,
log_stream: RwLock::new(Vec::with_capacity(self.flush_threshold)),
flush_threshold: self.flush_threshold,
max_message_size: self.max_message_size,
}
}
}
Expand Down Expand Up @@ -612,6 +666,24 @@ pub(crate) struct Stream {
pub(crate) values: Vec<Vec<String>>,
}

/// The data structure used for encoding a single log message before sending it to Loki
#[derive(Serialize)]
#[cfg(feature = "json-log-fmt")]
pub(crate) struct SerializedEvent<'a> {
/// The file name of the log message source
pub(crate) file: Option<&'a str>,
/// The line number of the log message source
pub(crate) line: Option<u32>,
/// The module name of the log message source
pub(crate) module: Option<&'a str>,
/// The log level of the log message
pub(crate) level: &'static str,
/// The target of the log message
pub(crate) target: &'a str,
/// The actual log message
pub(crate) message: String,
}

/// The base data structure Loki expects when receiving logging messages.
#[derive(Serialize)]
pub(crate) struct Streams<'a> {
Expand Down