fibre_logging 0.5.3

A flexible, multimode sync/async logging library that unifies the log and tracing ecosystems, driven by external configuration and featuring powerful debug instrumentation.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
# Usage Guide: fibre_logging

This guide provides a detailed overview of `fibre_logging`'s core concepts, configuration system, and practical examples for its key features.

## Table of Contents

*   [Core Concepts]#core-concepts
    *   [Loggers]#loggers
    *   [Appenders]#appenders
    *   [Encoders]#encoders
*   [Quick Start]#quick-start
*   [The Configuration File]#the-configuration-file
    *   [Top-Level Structure]#top-level-structure
    *   [Configuring Appenders]#configuring-appenders
    *   [Configuring Encoders]#configuring-encoders
    *   [Configuring Loggers]#configuring-loggers
*   [Initialization API]#initialization-api
*   [Specialized Features]#specialized-features
    *   [Tracing Integration]#tracing-integration
    *   [In-App Event Consumption (`custom` appender)]#in-app-event-consumption-custom-appender
    *   [Internal Error Reporting]#internal-error-reporting
    *   [Debug Reports (`debug_report` appender)]#debug-reports-debug_report-appender
*   [Error Handling]#error-handling

## Core Concepts

`fibre_logging`'s behavior is defined by the interaction of three primary components: `Loggers`, `Appenders`, and `Encoders`.

### Loggers

**Loggers** are named filters that control which log events are processed. When a log event is emitted, its **target** (typically the module path, e.g., `my_app::database`) is matched against the configured loggers.

*   A logger has a `level` (e.g., `info`, `debug`, `trace`). An event is only processed if its level is at or above the logger's level.
*   Each logger is associated with one or more `appenders`.
*   The `root` logger is the fallback for any target that doesn't match a more specific logger.
*   The `additive` flag (default: `true`) controls whether an event handled by a specific logger is also passed to the `root` logger's appenders. Setting `additive: false` is useful for isolating a module's logs to a specific file.

### Appenders

**Appenders** are named destinations for log events. They define *where* logs go.

The following appender `kind`s are available:
*   `console`: Writes to standard output.
*   `file`: Writes to a single, non-rolling file.
*   `rolling_file`: Writes to a file that rolls over based on time or size policies.
*   `custom`: Sends structured log events to a channel for consumption within your application.
*   `debug_report`: A debug-only appender that collects events in memory for later inspection.

### Encoders

**Encoders** are responsible for formatting a log event into a stream of bytes before it is sent to an appender. They define *what logs look like*.

The following encoder `kind`s are available:
*   `pattern`: Formats logs using a Log4j-style pattern string (e.g., `[%d] %p - %m%n`).
*   `json_lines`: Formats logs as a stream of newline-delimited JSON objects, ideal for structured logging.

## Quick Start

This example logs `INFO` level messages from the root of the application to both the console and a file, while logging more verbose `DEBUG` messages from the `my_app` module to the file only.

1.  **Create `fibre_logging.yaml`:**
    ```yaml
    # fibre_logging.yaml
    version: 1

    appenders:
      console:
        kind: console
        encoder:
          kind: pattern
          pattern: "[%d] %-5p - %m%n"
      main_log_file:
        kind: file
        path: "logs/my_app.log"
        encoder:
          kind: json_lines

    loggers:
      # Default rules for all modules
      root:
        level: info
        appenders: [console, main_log_file]
      
      # Specific rules for 'my_app' module
      my_app:
        level: debug
        appenders: [main_log_file]
        # Prevents my_app logs from also going to the console
        additive: false 
    ```

2.  **Add the code to `main.rs`:**
    ```rust
    use log::{info, debug};
    use std::path::Path;
    
    mod my_app {
        pub fn do_work() {
            tracing::debug!("Doing some internal work...");
        }
    }
    
    fn main() {
      // Create the log directory for the example.
      if !Path::new("logs").exists() {
        std::fs::create_dir("logs").unwrap();
      }

      // Initialize the library from the config file.
      // The `_guards` variable must be kept alive for logging to work.
      let _guards = fibre_logging::init::init_from_file(Path::new("fibre_logging.yaml"))
        .expect("Failed to initialize fibre_logging");

      info!("Application started. This goes to console and file.");
      debug!("This root-level debug message will be ignored.");
      
      my_app::do_work(); // "Doing some internal work..." goes to the file only.
      
      info!("Application shutting down.");
      // _guards is dropped here, flushing all logs to disk.
    }
    ```

## The Configuration File

### Top-Level Structure

```yaml
version: 1

# Optional: Enable the internal error reporting channel
internal_error_reporting:
  enabled: true

# Define all possible outputs
appenders:
  #... appender definitions

# Define filtering and routing rules
loggers:
  #... logger definitions
```

### Configuring Appenders

#### `console`
```yaml
appenders:
  my_console:
    kind: console
    encoder:
      kind: pattern
      pattern: "[%d] %-5p %t - %m%n"
```

#### `file`
```yaml
appenders:
  main_log:
    kind: file
    path: "logs/application.log"
    encoder:
      kind: json_lines
```

#### `rolling_file`
```yaml
appenders:
  rolling_log:
    kind: rolling_file
    directory: "logs/archive"
    file_name_prefix: "app"
    file_name_suffix: ".log"
    encoder:
      kind: pattern
      pattern: "%m%n"
    policy:
      # Rolls every day. Other options: "minutely", "hourly", "never".
      time_granularity: daily
      # Rolls when the active file exceeds this size.
      max_file_size: "100MB"
      # Keeps the 10 most recent rolled files.
      max_retained_sequences: 10
```

### Configuring Encoders

#### `pattern`
Uses Log4j-style format specifiers.

*   `%d`: Timestamp (e.g., `2023-01-01T12:00:00.000Z`). Can be customized with a format string inside braces, like `%d{%Y-%m-%d %H:%M}`.
*   `%p` or `%l`: Log level (e.g., `INFO`).
*   `%t`: The event's target (typically the module path).
*   `%m`: The primary log message.
*   `%T`: The name of the thread on which the event occurred.
*   `%n`: A platform-specific newline character.
*   `%-5p`: Padding can be applied to specifiers (e.g., left-align the level to 5 characters).
*   `%%`: An escaped literal percent sign.

**Structured Fields with `%X`**

The `%X` specifier is used to include structured fields from `tracing` events in the log line. It has two forms:

1.  **`%X{field_name}` (Specific Field):** This is the recommended way to include specific, important context. If the field doesn't exist on a given event, nothing is printed.

    ```yaml
    # fibre_logging.yaml
    appenders:
      console:
        kind: console
        encoder:
          kind: pattern
          pattern: "[%d] %p - [peer=%X{peer_id}, ballot=%X{ballot}] - %m%n"
    ```
    With the `tracing` event `warn!(peer_id = 1, ballot = "B(1,2)", "Contention detected");`, the output would be:
    ```log
    [2025-11-01T10:00:00.123Z] WARN - [peer=1, ballot=B(1,2)] - Contention detected
    ```

2.  **`%X` (All Fields):** This will print all fields attached to the event in a `{key=value}` format, sorted alphabetically by key. This is useful for debugging but can be very verbose for production logs.

    ```yaml
    # fibre_logging.yaml
    appenders:
      console_debug:
        kind: console
        encoder:
          kind: pattern
          pattern: "[%d] %p - %m %X%n"
    ```
    The same `tracing` event would produce:
    ```log
    [2025-11-01T10:00:00.123Z] WARN - Contention detected {ballot=B(1,2), peer_id=1}
    ```

**Example Pattern:**

```yaml
encoder:
  kind: pattern
  pattern: "[%d{%Y-%m-%d %H:%M:%S}] %-5p [%T] %t - %m {span_id=%X{span_id}}%n"
```

#### `json_lines`
Produces newline-delimited JSON.
*   `flatten_fields`: If `true`, custom event fields are added to the top-level JSON object. If `false` (default), they are nested under a `fields` key.

```yaml
encoder:
  kind: json_lines
  flatten_fields: true
```

### Configuring Loggers

Loggers form a hierarchy based on the `::` separator in their names.

```yaml
loggers:
  # The default logger for any event that doesn't match a more specific logger.
  root:
    level: info
    appenders: [console]

  # Rules for any target starting with "my_app".
  my_app:
    level: debug
    appenders: [main_log_file]

  # Rules for a noisy dependency.
  # "additive: false" is critical: it prevents hyper logs from also going to
  # the 'root' logger's appenders (i.e., the console).
  hyper:
    level: warn
    appenders: [main_log_file]
    additive: false
```

## Initialization API

The primary entry point is `init_from_file`, which sets up the entire logging system.

```rust
pub fn init_from_file(config_path: &Path) -> Result<InitResult>
```

It returns an `InitResult` struct, which is a guard that must be kept alive. When it is dropped, it signals all background threads to shut down and flush any buffered logs.

```rust
pub struct InitResult {
  // Handles to the background I/O threads.
  pub appender_task_handles: Vec<JoinHandle<()>>,

  // Channel to receive internal error reports.
  pub internal_error_rx: Option<fibre::mpsc::BoundedReceiver<InternalErrorReport>>,

  // Map of names to channels for `custom` appenders.
  pub custom_streams: HashMap<String, CustomEventReceiver>,
  
  // ... internal fields
}
```

## Specialized Features

### Tracing Integration

`fibre_logging` automatically captures `tracing` span information.

*   **YAML Config (`fibre_logging.tracing.yaml`):**
    ```yaml
    appenders:
      json_log:
        kind: file
        path: "logs/tracing_example.log"
        encoder:
          kind: json_lines
    loggers:
      root:
        level: info
      database:
        level: debug
        appenders: [json_log]
        additive: false
    ```
*   **Rust Code:**
    ```rust
    use tracing::{info, instrument, info_span};

    #[instrument(target = "database")]
    fn process_user(user_id: u32) {
        info_span!("fetch_data").in_scope(|| {
            info!(task_id = 123, "Fetching data for user.");
        });
    }
    ```
*   **Resulting JSON Log (`logs/tracing_example.log`):**
    ```json
    {"level":"INFO","message":"Fetching data for user.","name":"fetch_data","parent_id":"SpanId(0x...)","span_id":"SpanId(0x...)","target":"database","task_id":123,"timestamp":"..."}
    ```

### In-App Event Consumption (`custom` appender)

Receive log events as structured data within your application.

*   **YAML Config:**
    ```yaml
    appenders:
      metrics_stream:
        kind: custom
        buffer_size: 100
    loggers:
      METRICS: # A custom target
        level: trace
        appenders: [metrics_stream]
    ```
*   **Rust Code:**
    ```rust
    // In main, after initialization...
    let mut init_result = fibre_logging::init::init_from_file(config_path)?;
    if let Some(metrics_rx) = init_result.custom_streams.remove("metrics_stream") {
      std::thread::spawn(move || {
        while let Ok(event) = metrics_rx.recv() {
          println!("Metric Received: {:?}", event.fields);
        }
      });
    }

    // Somewhere else in the code...
    tracing::event!(target: "METRICS", tracing::Level::INFO, query_time_ms = 55);
    ```

### Internal Error Reporting

Monitor the health of the logging system itself.

*   **YAML Config:**
    ```yaml
    internal_error_reporting:
      enabled: true
    ```
*   **Rust Code:**
    ```rust
    let mut init_result = fibre_logging::init::init_from_file(config_path)?;
    if let Some(error_rx) = init_result.internal_error_rx.take() {
        std::thread::spawn(move || {
            if let Ok(report) = error_rx.recv() {
                // e.g., A file appender failed due to permissions
                eprintln!("Internal logging error: {}", report.error_message);
            }
        });
    }
    ```

### Debug Reports (`debug_report` appender)

Get an in-memory snapshot of events for debugging. This feature is only available in debug builds (`#[cfg(debug_assertions)]`).

*   **YAML Config:**
    ```yaml
    appenders:
      in_memory_report:
        kind: debug_report
        # Optional: automatically print a report every 10 seconds
        print_interval: 10s
    loggers:
      my_app:
        level: debug
        appenders: [in_memory_report]
        additive: false
    ```
*   **Rust Code:**
    ```rust
    // ... after initialization and running some code ...

    // Manually print the report collected so far.
    fibre_logging::debug_report::print_debug_report();
    ```

## Error Handling

`fibre_logging` has two categories of errors.

1.  **Initialization Errors (`fibre_logging::Error`)**: These are fatal errors that occur during setup (e.g., config file not found, invalid YAML). They are returned from `init_from_file` in a `Result`.
    ```rust
    pub enum Error {
        ConfigNotFound(String),
        ConfigRead(std::io::Error),
        ConfigParse(String),
        // ... and others
    }
    pub type Result<T, E = Error> = std::result::Result<T, E>;
    ```

2.  **Runtime Errors (`InternalErrorReport`)**: These are non-fatal errors that occur after initialization (e.g., a log file cannot be written to). They are sent to the optional error channel if `internal_error_reporting` is enabled.
    ```rust
    pub struct InternalErrorReport {
      pub source: InternalErrorSource,
      pub error_message: String,
      // ... and others
    }
    ```