https://github.com/ifeanyi-ugwu/winston_rs
https://github.com/ifeanyi-ugwu/winston_rs
Last synced: about 2 months ago
JSON representation
- Host: GitHub
- URL: https://github.com/ifeanyi-ugwu/winston_rs
- Owner: ifeanyi-ugwu
- License: mit
- Created: 2024-08-22T17:32:47.000Z (10 months ago)
- Default Branch: main
- Last Pushed: 2024-11-09T00:02:13.000Z (7 months ago)
- Last Synced: 2024-11-09T00:28:23.741Z (7 months ago)
- Language: Rust
- Size: 128 KB
- Stars: 0
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE.md
Awesome Lists containing this project
README
# winston

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, 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