Skip to content

Commit 37c9518

Browse files
Merge pull request #18 from saverioscagnoli/span
feat: add span support and v3.0.0
2 parents 6a07e32 + ba1701e commit 37c9518

14 files changed

Lines changed: 1490 additions & 14 deletions

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
target
22
.logs
3+
logs
34
node_modules
4-
dist
5+
dist

CHANGELOG.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,3 +88,28 @@ All the dates in this changelog are formatted as day/month/year.
8888
# 2.2.7 - 10/12/2025
8989

9090
- Fixed links in README
91+
92+
# 3.0.0 - 10/12/2025
93+
94+
- Added span-based context management for structured logging with automatic hierarchical context.
95+
- New `span!` macro to create spans with key-value pairs
96+
- New `Span`, `SpanGuard` types for programmatic span management
97+
- New `enter()` and `current_context()` functions for working with spans
98+
- Spans are thread-local and automatically cleaned up when they go out of scope
99+
- Context from active spans is automatically included in all log messages
100+
101+
- Added configurable span positioning in log output.
102+
- New `SpanPosition` enum with variants: `End` (default), `Start`, `AfterLevel`, `None`
103+
- Updated `DefaultFormatter` to support span positioning
104+
- New convenience methods: `with_span_at_start()`, `with_span_after_level()`, `with_span_at_end()`, `without_span()`
105+
106+
- Added formatter utilities for easier custom formatter creation.
107+
- New `FormatterBuilder` for creating custom formatters with span positioning support
108+
- New `format_span_context()` function for formatting span context with default styling
109+
- New `format_span_context_with()` function for custom span formatting
110+
- New `format_with_span_position()` helper function for consistent span positioning
111+
112+
- Added `context` field to `Record` struct containing active span information.
113+
114+
- Changed `DefaultFormatter` from unit struct to a configurable struct (breaking change for direct instantiation).
115+
- Use `DefaultFormatter::new()` instead of `DefaultFormatter` when creating instances

docs/span-positioning-quick-ref.md

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
# Span Positioning Quick Reference
2+
3+
## TL;DR
4+
5+
Use `ConfigurableFormatter` to control where span context appears in your logs.
6+
7+
```rust
8+
use traccia::{Config, DefaultFormatter, Console, LogLevel, init_with_config};
9+
10+
let config = Config {
11+
level: LogLevel::Info,
12+
targets: vec![Box::new(Console::new())],
13+
format: Some(Box::new(DefaultFormatter::with_span_at_start())),
14+
};
15+
16+
init_with_config(config);
17+
```
18+
19+
## Four Positions Available
20+
21+
| Position | Convenience Method | Output Format |
22+
|----------|-------------------|---------------|
23+
| **End** (default) | `with_span_at_end()` | `[INFO] message [span: key=value]` |
24+
| **Start** | `with_span_at_start()` | `[span: key=value] [INFO] message` |
25+
| **After Level** | `with_span_after_level()` | `[INFO] [span: key=value] message` |
26+
| **None** | `without_span()` | `[INFO] message` |
27+
28+
## Visual Comparison
29+
30+
Given this code:
31+
```rust
32+
let _request = span!("request", "id" => 42);
33+
let _db = span!("database", "table" => "users");
34+
info!("Query executed");
35+
```
36+
37+
### Position: End (Default)
38+
```
39+
[INFO] Query executed [request: id=42] [database: table=users]
40+
```
41+
42+
### Position: Start
43+
```
44+
[request: id=42] [database: table=users] [INFO] Query executed
45+
```
46+
47+
### Position: AfterLevel
48+
```
49+
[INFO] [request: id=42] [database: table=users] Query executed
50+
```
51+
52+
### Position: None
53+
```
54+
[INFO] Query executed
55+
```
56+
57+
## Complete Examples
58+
59+
### Using Convenience Methods
60+
61+
```rust
62+
use traccia::{Config, DefaultFormatter, Console, LogLevel, init_with_config};
63+
64+
// Span at start
65+
let config = Config {
66+
level: LogLevel::Info,
67+
targets: vec![Box::new(Console::new())],
68+
format: Some(Box::new(DefaultFormatter::with_span_at_start())),
69+
};
70+
init_with_config(config);
71+
```
72+
73+
### Using SpanPosition Enum
74+
75+
```rust
76+
use traccia::{Config, DefaultFormatter, Console, LogLevel, SpanPosition, init_with_config};
77+
78+
let config = Config {
79+
level: LogLevel::Info,
80+
targets: vec![Box::new(Console::new())],
81+
format: Some(Box::new(DefaultFormatter::with_position(SpanPosition::AfterLevel))),
82+
};
83+
init_with_config(config);
84+
```
85+
86+
## When to Use Each Position
87+
88+
### End (Default) ✅ Most Readable
89+
**Use when:**
90+
- Human readability is the priority
91+
- Logs are primarily read directly by developers
92+
- Message content is more important than context
93+
94+
**Example use case:** Development environments, debugging sessions
95+
96+
### Start ✅ Best for Parsing
97+
**Use when:**
98+
- Logs are consumed by aggregation systems
99+
- You need structured data at the beginning
100+
- Context-based filtering is important
101+
102+
**Example use case:** Production environments with log aggregation (Elasticsearch, Splunk)
103+
104+
### AfterLevel ✅ Balanced
105+
**Use when:**
106+
- You want both readability and parseability
107+
- Log level should be first for quick scanning
108+
- Context should be easy to extract programmatically
109+
110+
**Example use case:** General purpose logging with both human and machine consumers
111+
112+
### None ✅ Clean Output
113+
**Use when:**
114+
- You want spans in code but not in output
115+
- Testing or specific environments need clean logs
116+
- Performance is critical and span output is unnecessary
117+
118+
**Example use case:** CI/CD pipelines, minimal output modes
119+
120+
## Code Snippets
121+
122+
### Span at Start
123+
```rust
124+
use traccia::{Config, DefaultFormatter, Console, LogLevel, init_with_config, span, info};
125+
126+
let config = Config {
127+
level: LogLevel::Info,
128+
targets: vec![Box::new(Console::new())],
129+
format: Some(Box::new(DefaultFormatter::with_span_at_start())),
130+
};
131+
init_with_config(config);
132+
133+
let _span = span!("api", "endpoint" => "/users");
134+
info!("Request received");
135+
// Output: [api: endpoint=/users] [INFO] Request received
136+
```
137+
138+
### Span After Level
139+
```rust
140+
use traccia::{Config, DefaultFormatter, Console, LogLevel, init_with_config, span, info};
141+
142+
let config = Config {
143+
level: LogLevel::Info,
144+
targets: vec![Box::new(Console::new())],
145+
format: Some(Box::new(DefaultFormatter::with_span_after_level())),
146+
};
147+
init_with_config(config);
148+
149+
let _span = span!("api", "endpoint" => "/users");
150+
info!("Request received");
151+
// Output: [INFO] [api: endpoint=/users] Request received
152+
```
153+
154+
### No Span Context
155+
```rust
156+
use traccia::{Config, DefaultFormatter, Console, LogLevel, init_with_config, span, info};
157+
158+
let config = Config {
159+
level: LogLevel::Info,
160+
targets: vec![Box::new(Console::new())],
161+
format: Some(Box::new(DefaultFormatter::without_span())),
162+
};
163+
init_with_config(config);
164+
165+
let _span = span!("api", "endpoint" => "/users");
166+
info!("Request received");
167+
// Output: [INFO] Request received
168+
```
169+
170+
## All Available Methods
171+
172+
```rust
173+
use traccia::{DefaultFormatter, SpanPosition};
174+
175+
// Convenience constructors
176+
let f1 = DefaultFormatter::with_span_at_start();
177+
let f2 = DefaultFormatter::with_span_after_level();
178+
let f3 = DefaultFormatter::with_span_at_end();
179+
let f4 = DefaultFormatter::without_span();
180+
181+
// Using SpanPosition enum
182+
let f5 = DefaultFormatter::with_position(SpanPosition::Start);
183+
let f6 = DefaultFormatter::with_position(SpanPosition::AfterLevel);
184+
let f7 = DefaultFormatter::with_position(SpanPosition::End);
185+
let f8 = DefaultFormatter::with_position(SpanPosition::None);
186+
187+
// Default is SpanPosition::End
188+
let f9 = DefaultFormatter::default();
189+
```
190+
191+
## See Also
192+
193+
- [Full Spans Documentation](./spans.md)
194+
- [Custom Formatter Guide](../README.md#custom-formatters)
195+
- Examples: `examples/span-at-start.rs`, `examples/span-after-level.rs`

0 commit comments

Comments
 (0)