winston 0.5.0

winston for rust
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
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
# winston

![Crates.io](https://img.shields.io/crates/v/winston)
![Rust](https://img.shields.io/badge/rust-%E2%9C%94-brightgreen)

A Winston.js-inspired logging library for Rust.

## Installation

Add to your `Cargo.toml`:

```toml
[dependencies]
winston = "0.2"
```

Alternatively, run:

```bash
cargo add winston
```

## Quick Start

### Using the Global Logger

```rust
use winston::{flush, configure, log, transports::stdout, Logger, LoggerOptions};

fn main() {
    let new_options = LoggerOptions::new()
        .level("debug")
        .add_transport(stdout());

    configure(Some(new_options));

    log!(info, "Hello, world!");
    log!(warn, "Something might be wrong.");

    flush();
}
```

The global logger is an application-wide static reference that provides centralized logging access, requiring configuration only once to add a transport, as it starts without a default one. It eliminates the need to pass logger instances around, with functions like `log()`, `configure()` `close()` and `flush()` operating directly on this global logger. Macros like `log!()` implicitly use it. Since static references don’t automatically call `drop`, `flush()` is necessary to ensure all logs are processed, particularly before the application exits.

### Creating Your Own Logger

```rust
use winston::{
    format::{combine, json, timestamp},
    log,
    transports::{stdout, File},
    Logger,
};

fn main() {
    let logger = Logger::builder()
        .level("debug")
        .add_transport(stdout())
        .add_transport(
            File::builder()
                .filename("app.log")
                .build()
        )
        .format(combine(vec![timestamp(), json()]))
        .build();

    log!(logger, info, "Logging with multiple transports");
}
```

### Configuration Options

| **Option**              | **Description**                                                                                        | **Default Value**                                  |
| ----------------------- | ------------------------------------------------------------------------------------------------------ | -------------------------------------------------- |
| `level`                 | Minimum severity of log messages to be logged. Anything equal to or higher in severity will be logged. | `info`                                             |
| `levels`                | Severity levels for log entries.                                                                       | `{error: 0, warn: 1, info: 2, debug: 3, trace: 4}` |
| `transports`            | Logging destinations (`stdout`, `stderr`, `File`, `Custom`).                                           | None                                               |
| `format`                | Log message formatting (e.g., `json`, `timestamp`).                                                    | `json`                                             |
| `channel_capacity`      | Maximum size of the log message buffer.                                                                | `1024`                                             |
| `backpressure_strategy` | Action when buffer is full (`Block`, `DropOldest`, `DropCurrent`).                                     | `Block`                                            |

### Logging Basics

#### The `log!` Macro

The simplest way to log messages is using the `log!` macro:

```rust
// Using the global logger
log!(info, "System started");
log!(warn, "Disk space low", usage = 92);

// Using a specific logger
log!(logger, error, "Connection failed", retries = 3, timeout = 120);
```

It takes the following parameters:

- **`level`**: Log level (`info`, `warn`, `error`, etc.).
- **`Message`**: A string message.
- **Optional key-value pairs**: Metadata to add context.

#### Level-specific Methods and Macros

Winston provides macros to create level-specific logging methods and macros:

```rust
use std::collections::HashMap;
use winston::{meta, Logger};

// Define custom logging methods and macros
winston::create_log_methods!(foo, bar, baz, foobar);  // Creates methods like logger.foo(), logger.bar(), etc.
winston::create_level_macros!(foo, bar, baz, foobar);  // Creates macros like foo!(), bar!(), etc.

let logger = Logger::builder()
    .add_transport(stdout())  // Use stdout as the logging transport
    .levels(HashMap::from([  // Define custom log levels and their severity values
        ("foo", 0),  // Most severe
        ("bar", 1),
        ("baz", 2),
        ("foobar", 3)   // Least severe
    ]))
    .level("bar")    // Set the minimum log level to "bar" (log bar and more severe levels)
    .build();  // Build the logger instance

// Usage of the logger methods with various levels and metadata:

// Log a message at the "foo" level with no metadata
logger.foo("Foo-level message", None);

// Log a message at the "foobar" level with metadata (key-value pairs)
logger.foobar(
    "Foobar-level message with metadata",
    Some(meta!(key = "value", timestamp = 1234567890))
);

// Use the "foobar" macro to log a message at the "foobar" level
foobar!(logger, "Foobar-level macro logging");

// with metadata
foobar!(logger, "Foobar-level macro logging with metadata", meta!(key3 = true, key4 = 42.5));

// Log a message at the "foo" level globally (no logger instance needed)
foo!("Global log test");

// Log a message at the "foo" level globally with metadata
foo!(@global, "Another global test", meta!(key3 = true, key4 = 42.5));
```

## Key Concepts

### Transports

Transports define the destinations where log messages are written. Winston includes core transports that leverage Rust's standard I/O capabilities, with additional custom transports possible through community contributions. Each transport implements the `Transport` trait from [winston_transport](https://github.com/ifeanyi-ugwu/winston_transport_rs):

```rust
pub trait Transport: Send + Sync {
    // Required: Handles writing log messages
    fn log(&self, info: LogInfo);

    // Optional: Flushes buffered logs
    fn flush(&self) -> Result<(), String> { Ok(()) }

    // Optional: Gets minimum log level
    fn get_level(&self) -> Option<&String> { None }

    // Optional: Gets format configuration
    fn get_format(&self) -> Option<&Format> { None }

    // Optional: Retrieves matching log entries
    fn query(&self, _options: &LogQuery) -> Result<Vec<LogInfo>, String> { Ok(Vec::new()) }
}
```

#### Built-in Transports

Winston provides two core transports:

##### WriterTransport

A generic transport that writes to any destination implementing Rust's `Write` trait:

```rust
use std::io::{self, Write};
use winston::transports::WriterTransport;

// Write to stdout
let stdout_transport = WriterTransport::new(io::stdout())
    .with_level("info");

// Write to a file
let file = std::fs::File::create("app.log").unwrap();
let file_transport = WriterTransport::new(file)
    .with_format(json());

// Write to a network socket
let stream = std::net::TcpStream::connect("127.0.0.1:8080").unwrap();
let network_transport = WriterTransport::new(stream);
```

There are quick `WriterTransport` creation for common use cases:

```rust
use winston::transports::{stdout, stderr};

// Quick stdout/stderr transports
let logger = Logger::builder()
    .add_transport(stdout())    // Same as WriterTransport::new(io::stdout())
    .add_transport(stderr())    // Same as WriterTransport::new(io::stderr())
    .build();
```

##### File Transport

Specialized file transport with querying capabilities for log retrieval.

#### Creating Custom Transports

To define a custom transport, implement the `Transport` trait and define the `log` method:

```rust
use winston::{log, LogInfo, Transport};

struct MyCustomTransport;

impl Transport for MyCustomTransport {
    fn log(&self, info: LogInfo) {
        println!("Custom transport: {}", info.message);
    }
}

fn main() {
    let custom_transport = MyCustomTransport;

    let logger = Logger::builder()
        .add_transport(custom_transport)
        .build();

    log!(info, "This uses a custom transport!");
}
```

#### Multiple Transports

You can use multiple transports simultaneously, even of the same type. Each transport can have its own configuration:

```rust
use winston::{log, Logger, format::{json, simple}, transports::{stdout, WriterTransport}};
use std::fs::File;

let logger = Logger::builder()
    // Log all info and above to stdout with simple formatting
    .add_transport(
        stdout()
            .with_level("info")
            .with_format(simple())
    )
    // Log all error to file with JSON formatting
    .add_transport(
        WriterTransport::new(File::create("app.log").unwrap())
            .with_level("error")
            .with_format(json())
    )
    .build();

// Usage
log!(error, "Appears in file only");
log!(info, "Appears in both stdout and file");
```

### Logging Levels

Winston's logging levels conform to the severity ordering specified by [RFC 5424](https://datatracker.ietf.org/doc/html/rfc5424), ranked in ascending order of importance. Lower numeric values indicate more critical (important) events.

```rust
levels: {
    error: 0,   // Critical errors - issues causing system failure
    warn:  1,   // Warnings - potential problems or recoverable issues
    info:  2,   // Informational - standard operations tracking
    debug: 3,   // Debugging - diagnostic details for troubleshooting
    trace: 4    // Tracing - the most verbose, fine-grained logs
}
```

#### Custom Logging Levels

In addition to the predefined `rust`, `syslog`, and `cli` levels available in winston via `winston::format::config::`, you can also choose to define your own:

```rust
use std::collections::HashMap;
use winston::Logger;

let custom_levels = HashMap::from([
    ("critical", 0),  // Highest severity
    ("high",     1),
    ("medium",   2),
    ("low",      3)   // Lowest severity
]);

let logger = Logger::builder()
    .levels(custom_levels)
    .build();
```

### Log Level

The `level` configuration represents the minimum severity of messages to be logged. For instance, if the level is set to `"info"`, the logger will process only `info`, `warn`, and `error` messages while ignoring less critical levels like `debug` and `trace`.

```rust
let logger = Logger::builder()
    .level("info")  // Logs only info, warn, and error levels
    .build();
```

#### Per-Transport Log Level

Each transport can define its own log level, overriding the logger level. This allows for targeted logging based on the output medium.

```rust
let logger = Logger::builder()
    .level("info")  // Logger default level
    .add_transport(
        stderr()
            .with_level("error")  // stderr only logs error messages
    )
    .add_transport(
        File::builder()
            .filename("app.log")
            .level("debug")  // File logs debug and above
            .build()
    )
    .build();
```

In this example:

- The logger level is set to `info`.
- The stderr transport logs only `error` messages.
- The file transport logs `debug` and higher (i.e., `debug`, `info`, `warn`, and `error`).

### Formats

For advanced formatting, Winston leverages [logform](https://github.com/ifeanyi-ugwu/logform_rs), which is re-exported as `winston::format` for convenience:

```rust
use winston::{Logger, format::{combine, timestamp, json}};

let logger = Logger::builder()
    .format(combine(vec![timestamp(), json()]))
    .build();
```

Each transport can have its own format, which takes precedence over the logger format:

```rust
let logger = Logger::builder()
    .format(json())  // Logger default format
    .add_transport(
        stdout()
            .with_format(combine(vec![timestamp(), colored()]))  // Colorized console output
    )
    .add_transport(
        File::builder()
            .filename("app.log")
            .format(json())  // Structured JSON for file logs
            .build()
    )
    .build();
```

## Advanced Features

### Backpressure Handling

Winston provides three backpressure strategies when the logging channel is full:

- `Block`: Wait until space is available
- `DropOldest`: Remove the oldest log message
- `DropCurrent`: Discard the current log message

```rust
use winston::{Logger, BackpressureStrategy};

let logger = Logger::builder()
    .channel_capacity(100)
    .backpressure_strategy(BackpressureStrategy::DropOldest)
    .build();
```

### Log Querying

Winston supports retrieving log entries from transports.

_To enable querying for a custom transport, override the `query` method in your `Transport` implementation._

```rust
use winston::{Logger, LogQuery};

let query = LogQuery::new()
    .from("2 hours ago")
    .until("now")
    .levels(vec!["error"])
    .limit(10)
    .order("desc")
    .search_term("critical")

let results = logger.query(query);
```

### LogQuery Configuration Options

| **Option**    | **Description**                                                                                                        | **Default Value**                     |
| ------------- | ---------------------------------------------------------------------------------------------------------------------- | ------------------------------------- |
| `from`        | Start time for the query (supports string formats compatible with [`parse_datetime`](https://docs.rs/parse-datetime/)) | `Utc::now() - Duration::days(1)`      |
| `until`       | End time for the query(supports string formats compatible with [`parse_datetime`](https://docs.rs/parse-datetime/))    | `Utc::now()`                          |
| `limit`       | Maximum number of log entries to retrieve.                                                                             | `50`                                  |
| `start`       | Offset for query results, used for pagination.                                                                         | `0`                                   |
| `order`       | Order of results, either `asc`, `ascending`, `descending` or `desc`.                                                   | `Descending`                          |
| `levels`      | List of log levels to include in the query (e.g., `["error", "info"]`).                                                | `[]` (no filter, includes all levels) |
| `fields`      | List of fields to include in the returned logs (projection). If empty, all fields are returned.                        | `[]`                                  |
| `search_term` | Text to search for in log messages.                                                                                    | `None` (no search term applied)       |

#### Logging Timestamps

**Timestamps are essential for effective log querying.** Log entries must include a `timestamp` field in their metadata (`LogInfo.meta`) for Winston’s querying capabilities to function as expected. The `timestamp` field should be a string compatible with [`dateparser`](https://docs.rs/dateparser/).

Winston's built-in `timestamp` format simplifies this requirement:

```rust
use winston::{Logger, timestamp};

let logger = Logger::new()
    .format(timestamp()) // Adds a timestamp to each log entry
    .build();
```

### Runtime Reconfiguration

Change logging configuration dynamically at runtime:

```rust
use winston::{transports::stdout, Logger, LoggerOptions};

let logger = Logger::default();
logger.configure(
    LoggerOptions::new()
        .level("debug")
        .add_transport(stdout())
);
```

## Performance

- **Configurable Buffering:** Adjust channel capacity to match your application's needs

## Contributing

Contributions are welcome! Please submit issues and pull requests on our GitHub repository.

## License

MIT License