Skip to content
Merged
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
38 changes: 19 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,9 +109,9 @@ fn main() -> std::io::Result<()> {
.poller_interval(Duration::from_millis(1))
.start()?;

ferrilog::logi!("server started on port {}", 8080u32);
ferrilog::logw!("connection pool at {}% capacity", 85u32);
ferrilog::loge!("request failed with code {:#x}", 0xDEADu32);
ferrilog::info!("server started on port {}", 8080u32);
ferrilog::warn!("connection pool at {}% capacity", 85u32);
ferrilog::error!("request failed with code {:#x}", 0xDEADu32);

Ok(())
// _guard drops here: stops poller, flushes all pending messages
Expand All @@ -130,10 +130,10 @@ Output:

| Macro | Level | Example |
|-------|-------|---------|
| `logd!(...)` | Debug | `logd!("x={}", x)` |
| `logi!(...)` | Info | `logi!("started")` |
| `logw!(...)` | Warning | `logw!("slow query: {}ms", elapsed)` |
| `loge!(...)` | Error | `loge!("failed: {}", err)` |
| `debug!(...)` | Debug | `debug!("x={}", x)` |
| `info!(...)` | Info | `info!("started")` |
| `warn!(...)` | Warning | `warn!("slow query: {}ms", elapsed)` |
| `error!(...)` | Error | `error!("failed: {}", err)` |
| `log!(level, ...)` | Any | `log!(Level::WRN, "custom={}", v)` |

The `log!` macro **automatically selects the optimal encoding path** at compile time:
Expand All @@ -143,16 +143,16 @@ The `log!` macro **automatically selects the optimal encoding path** at compile
## Format Specifiers

```rust
ferrilog::logi!("decimal: {}", 255u32); // 255
ferrilog::logi!("hex: {:x}", 255u32); // ff
ferrilog::logi!("upper hex: {:#X}", 255u32); // 0xFF
ferrilog::logi!("binary: {:#b}", 10u32); // 0b1010
ferrilog::logi!("octal: {:o}", 255u32); // 377
ferrilog::logi!("precision: {:.3}", 3.14159f64); // 3.142
ferrilog::logi!("right-pad: {:>10}", 42u32); // 42
ferrilog::logi!("left-pad: {:<10}", 42u32); // 42
ferrilog::logi!("center: {:^10}", 42u32); // 42
ferrilog::logi!("zero-pad: {:08}", 42u32); // 00000042
ferrilog::info!("decimal: {}", 255u32); // 255
ferrilog::info!("hex: {:x}", 255u32); // ff
ferrilog::info!("upper hex: {:#X}", 255u32); // 0xFF
ferrilog::info!("binary: {:#b}", 10u32); // 0b1010
ferrilog::info!("octal: {:o}", 255u32); // 377
ferrilog::info!("precision: {:.3}", 3.14159f64); // 3.142
ferrilog::info!("right-pad: {:>10}", 42u32); // 42
ferrilog::info!("left-pad: {:<10}", 42u32); // 42
ferrilog::info!("center: {:^10}", 42u32); // 42
ferrilog::info!("zero-pad: {:08}", 42u32); // 00000042
```

## Custom Types
Expand All @@ -173,7 +173,7 @@ impl std::fmt::Display for Order {
}
}

ferrilog::logi!("new order: {}", Order { id: 1001, price: 49.99, qty: 100 });
ferrilog::info!("new order: {}", Order { id: 1001, price: 49.99, qty: 100 });
// → new order: Order{id=1001, price=49.99, qty=100}
```

Expand Down Expand Up @@ -230,7 +230,7 @@ use std::thread;
for i in 0..4 {
thread::spawn(move || {
ferrilog::set_thread_name(&format!("worker-{i}"));
ferrilog::logi!("hello from thread {}", i as u32);
ferrilog::info!("hello from thread {}", i as u32);
});
}
```
Expand Down
14 changes: 13 additions & 1 deletion benches/core_component_bench.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ fn write_static_complex_header(
level: ferrilog_core::Level,
thread_name: &[u8],
location: &str,
_span_id: u64,
) {
output.reserve(37 + thread_name.len() + location.len());
output.extend_from_slice(thread_name);
Expand Down Expand Up @@ -615,6 +616,7 @@ fn bench_header_output(criterion: &mut Criterion) {
INFO,
b"bench-thread",
"core/benches/core_component_bench.rs:1",
0u64,
);
black_box(output);
},
Expand All @@ -633,6 +635,7 @@ fn bench_header_output(criterion: &mut Criterion) {
INFO,
b"bench-thread",
"core/benches/core_component_bench.rs:1",
0u64,
);
black_box(output);
},
Expand All @@ -658,6 +661,7 @@ fn bench_header_output(criterion: &mut Criterion) {
INFO,
b"bench-thread",
"core/benches/core_component_bench.rs:1",
0u64,
);
black_box(output);
},
Expand All @@ -676,6 +680,7 @@ fn bench_header_output(criterion: &mut Criterion) {
INFO,
b"bench-thread",
"core/benches/core_component_bench.rs:1",
0u64,
);
black_box(output);
},
Expand All @@ -695,6 +700,7 @@ fn bench_header_output(criterion: &mut Criterion) {
INFO,
b"bench-thread",
"core/benches/core_component_bench.rs:1",
0u64,
);
black_box(output);
},
Expand All @@ -714,6 +720,7 @@ fn bench_header_output(criterion: &mut Criterion) {
INFO,
b"bench-thread",
"core/benches/core_component_bench.rs:1",
0u64,
);
black_box(output);
},
Expand All @@ -735,6 +742,7 @@ fn bench_header_output(criterion: &mut Criterion) {
INFO,
b"bench-thread",
"core/benches/core_component_bench.rs:1",
0u64,
|buffer| number_format::append_u32(buffer, 42),
)
.unwrap();
Expand All @@ -754,7 +762,7 @@ fn bench_header_output(criterion: &mut Criterion) {
},
|mut output| {
output
.write_record(black_box(0), INFO, b"bench", "bench", |buffer| unsafe {
.write_record(black_box(0), INFO, b"bench", "bench", 0, |buffer| unsafe {
decode_static_body(std::ptr::null(), buffer);
})
.unwrap();
Expand All @@ -779,6 +787,7 @@ fn bench_header_output(criterion: &mut Criterion) {
INFO,
b"bench-thread",
"core/benches/core_component_bench.rs:1",
0u64,
|buffer| number_format::append_u32(buffer, 42),
)
.unwrap();
Expand All @@ -803,6 +812,7 @@ fn bench_header_output(criterion: &mut Criterion) {
INFO,
b"bench-thread",
"core/benches/core_component_bench.rs:1",
0u64,
|buffer| number_format::append_u32(buffer, 42),
)
.unwrap();
Expand All @@ -827,6 +837,7 @@ fn bench_header_output(criterion: &mut Criterion) {
INFO,
b"bench-thread",
"core/benches/core_component_bench.rs:1",
0u64,
|buffer| number_format::append_u32(buffer, 42),
)
.unwrap();
Expand All @@ -848,6 +859,7 @@ fn bench_header_output(criterion: &mut Criterion) {
INFO,
b"bench-thread",
"core/benches/core_component_bench.rs:1",
0u64,
|buffer| number_format::append_u32(buffer, index),
)
.unwrap();
Expand Down
56 changes: 51 additions & 5 deletions core/src/header.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,20 @@ impl HeaderDateCache {
}
}

pub type StaticHeaderWriteFn = fn(&mut HeaderDateCache, &mut Vec<u8>, i64, Level, &[u8], &str);
/// Appends a span id to `output`, using `-` for the root/no-span id.
#[inline]
pub fn append_span_id(output: &mut Vec<u8>, span_id: u64) {
if span_id == 0 {
output.push(b'-');
return;
}

let mut buffer = [0u8; 20];
let len = number_format::format_u64(&mut buffer, span_id);
output.extend_from_slice(&buffer[..len]);
}

pub type StaticHeaderWriteFn = fn(&mut HeaderDateCache, &mut Vec<u8>, i64, Level, &[u8], &str, u64);

#[derive(Debug, Clone)]
pub struct StaticHeader {
Expand All @@ -82,8 +95,17 @@ impl StaticHeader {
level: Level,
thread_name: &[u8],
location: &str,
span_id: u64,
) {
(self.write)(&mut self.date_cache, output, nanoseconds, level, thread_name, location);
(self.write)(
&mut self.date_cache,
output,
nanoseconds,
level,
thread_name,
location,
span_id,
);
}
}

Expand Down Expand Up @@ -112,6 +134,7 @@ impl DefaultHeader {
level: Level,
thread_name: &[u8],
location: &str,
_span_id: u64,
) {
self.date_cache.ensure_date(nanoseconds);
output.reserve(DEFAULT_HEADER_FIXED_LEN + thread_name.len() + location.len());
Expand Down Expand Up @@ -148,6 +171,7 @@ impl LevelThreadLocationHeader {
level: Level,
thread_name: &[u8],
location: &str,
_span_id: u64,
) {
output.reserve(LEVEL_THREAD_LOCATION_HEADER_FIXED_LEN + thread_name.len() + location.len());
output.extend_from_slice(level.as_bytes());
Expand All @@ -172,6 +196,8 @@ pub enum HeaderToken {
Nanosecond,
/// The log level.
Level,
/// The current span id (`-` when no span is active).
SpanId,
/// The thread name.
ThreadName,
/// The source location.
Expand All @@ -194,8 +220,8 @@ impl TokenizedHeader {

/// Parses a header pattern string into a `TokenizedHeader`.
///
/// Supported placeholders: `{date}`, `{time}`, `{ns}`, `{level}`, `{thread}`, `{location}`.
/// Use `{{` and `}}` for literal braces.
/// Supported placeholders: `{date}`, `{time}`, `{ns}`, `{level}`, `{span}`, `{thread}`,
/// `{location}`. Use `{{` and `}}` for literal braces.
pub fn parse_pattern(pattern: &str) -> io::Result<Self> {
let mut pattern_buffer = Vec::new();
let mut tokens = Vec::new();
Expand Down Expand Up @@ -269,8 +295,9 @@ impl TokenizedHeader {
level: Level,
thread_name: &[u8],
location: &str,
span_id: u64,
) {
output.reserve(self.pattern_buffer.len() + thread_name.len() + location.len() + 32);
output.reserve(self.pattern_buffer.len() + thread_name.len() + location.len() + 52);

for index in 0..self.tokens.len() {
match self.tokens[index] {
Expand All @@ -283,6 +310,7 @@ impl TokenizedHeader {
HeaderToken::Time => append_header_time(output, nanoseconds),
HeaderToken::Nanosecond => append_header_nanoseconds(output, nanoseconds),
HeaderToken::Level => output.extend_from_slice(level.as_bytes()),
HeaderToken::SpanId => append_span_id(output, span_id),
HeaderToken::ThreadName => output.extend_from_slice(thread_name),
HeaderToken::Location => output.extend_from_slice(location.as_bytes()),
}
Expand Down Expand Up @@ -315,6 +343,7 @@ fn parse_named_token(name: &str) -> io::Result<HeaderToken> {
"time" => Ok(HeaderToken::Time),
"ns" => Ok(HeaderToken::Nanosecond),
"level" => Ok(HeaderToken::Level),
"span" => Ok(HeaderToken::SpanId),
"thread" => Ok(HeaderToken::ThreadName),
"location" => Ok(HeaderToken::Location),
_ => Err(io::Error::new(
Expand Down Expand Up @@ -419,6 +448,7 @@ mod tests {
Level::INF,
b"worker-1",
"src/main.rs:10",
0,
);
let string = String::from_utf8(output).unwrap();
assert!(string.contains("INF|worker-1|src/main.rs:10|"));
Expand All @@ -439,13 +469,15 @@ mod tests {
Level::INF,
b"worker-1",
"src/main.rs:10",
0,
);
specialized.write_header(
&mut specialized_output,
timestamp,
Level::INF,
b"worker-1",
"src/main.rs:10",
0,
);

assert_eq!(specialized_output, tokenized_output);
Expand All @@ -466,15 +498,29 @@ mod tests {
Level::INF,
b"worker-1",
"src/main.rs:10",
0,
);
specialized.write_header(
&mut specialized_output,
timestamp,
Level::INF,
b"worker-1",
"src/main.rs:10",
0,
);

assert_eq!(specialized_output, tokenized_output);
}

#[test]
fn span_header_token_renders_dash_for_root_and_decimal_for_span() {
let mut header = TokenizedHeader::parse_pattern("{level}|span={span}|").unwrap();
let mut root = Vec::new();
header.write_header(&mut root, 0, Level::TRC, b"worker-1", "src/main.rs:10", 0);
assert_eq!(String::from_utf8(root).unwrap(), "TRC|span=-|");

let mut span = Vec::new();
header.write_header(&mut span, 0, Level::TRC, b"worker-1", "src/main.rs:10", 42);
assert_eq!(String::from_utf8(span).unwrap(), "TRC|span=42|");
}
}
Loading
Loading