# log-easy

[![Crates.io](https://img.shields.io/crates/v/log-easy.svg)](https://crates.io/crates/log-easy)
[![Docs.rs](https://img.shields.io/docsrs/log-easy.svg)](https://docs.rs/log-easy)
[![License: MIT OR Apache-2.0](https://img.shields.io/badge/license-MIT%20OR%20Apache--2.0-blue.svg)](./LICENSE.txt)

Easy-to-use file logger that writes messages in a simple, readable format.
Built for straightforward file logging with minimal setup.

## Installation

> [!NOTE]
> Minimum supported Rust version (MSRV): 1.85

Add this to your `Cargo.toml`:

```toml
[dependencies]
log-easy = "0.3"
```

## Features
- Log levels: Trace, Debug, Info, Warn, Error
- Global macros for ergonomic logging (`info!`, `warn!`, etc.)
- Instance-based API for explicit control
- Minimal dependencies

## Log Format

Each entry is written as:

```text
[YYYY-MM-DD HH:MM:SS][LOG_LEVEL] Message
```

## Quick Start (Global Logger + Macros)

```rust
use log_easy::{error, info, init_with, warn, LogLevel, Logger};

fn main() -> std::io::Result<()> {
    init_with(Logger::new("app.log").with_level(LogLevel::Info))?;

    info!("Application started");
    warn!("This is a warning");
    error!("Something went wrong");

    Ok(())
}
```

> [!NOTE]
> Global macros require `init` or `init_with` to be called once before use.

## Instance Logger (No Globals)

```rust
use log_easy::{LogLevel, Logger};

fn main() -> std::io::Result<()> {
    let logger = Logger::new("app-instance.log").with_level(LogLevel::Debug);

    logger.debug("Instance logger ready");
    logger.info("Application started");

    Ok(())
}
```

## Formatting Messages

Macros accept formatting arguments like `println!`:

```rust
use log_easy::{info, init_with, LogLevel, Logger};

fn main() -> std::io::Result<()> {
    init_with(Logger::new("app.log").with_level(LogLevel::Info))?;

    let user = "alice";
    let id = 42;
    info!("user={} id={}", user, id);

    Ok(())
}
```

Instance methods accept a preformatted string:

```rust
use log_easy::{LogLevel, Logger};

fn main() -> std::io::Result<()> {
    let logger = Logger::new("app.log").with_level(LogLevel::Info);

    logger.info(&format!("user={} id={}", "alice", 42));

    Ok(())
}
```

## Log Levels

Messages below the configured level are ignored.

```rust
use log_easy::{LogLevel, Logger};

let logger = Logger::new("app.log").with_level(LogLevel::Warn);
logger.info("This will not be logged");
logger.warn("This will be logged");
```

## String Parsing

Log levels can be parsed from strings (case-insensitive) with `.parse()`.
Invalid strings return `ParseLogLevelError`.

Valid names: `trace`, `debug`, `info`, `warn`, `warning`, `error`

Numeric aliases: `5` = trace, `4` = debug, `3` = info, `2` = warn, `1` = error

```rust
use log_easy::LogLevel;

assert_eq!("INFO".parse::<LogLevel>()?, LogLevel::Info);
assert_eq!("debug".parse::<LogLevel>()?, LogLevel::Debug);
assert_eq!("WARN".parse::<LogLevel>()?, LogLevel::Warn);
assert_eq!("5".parse::<LogLevel>()?, LogLevel::Trace);
# Ok::<(), log_easy::ParseLogLevelError>(())
```

### Example: Parse log level from string

```rust
use log_easy::{LogLevel, Logger};

fn main() -> std::io::Result<()> {
    let level: LogLevel = "Debug".parse().expect("valid log level");
    let logger = Logger::new("target/instance.log").with_level(level);
    logger.info("Parser working");
    Ok(())
}
```

## Initialization (`init` vs `init_with`)

- `init(path)` initializes the global logger at the default level (`Info`).
- `init_with(logger)` lets you provide a fully configured `Logger`.

```rust
use log_easy::{init, init_with, LogLevel, Logger};

init("app.log")?;
init_with(Logger::new("app.log").with_level(LogLevel::Trace))?;
```

Both return `std::io::Result<()>` and will error if the global logger is already initialized.

## Error Handling

The convenience methods (`info`, `warn`, etc.) are non-intrusive and never return errors.
If a write fails, the logger prints a message to stderr.

If you need to handle errors explicitly, use the `try_*` methods:

```rust
use log_easy::{LogLevel, Logger};

fn main() -> std::io::Result<()> {
    let logger = Logger::new("app.log").with_level(LogLevel::Info);

    logger.try_info("This can fail")?;
    Ok(())
}
```

The `try_*` macros return `std::io::Result<()>` as well:

```rust
use log_easy::{init_with, try_info, LogLevel, Logger};

fn main() -> std::io::Result<()> {
    init_with(Logger::new("app.log").with_level(LogLevel::Info))?;

    try_info!("This can fail")?;

    Ok(())
}
```

## Log Rotation

`log-easy` does not manage log rotation or retention. Use external tools such as
`logrotate`, systemd/Docker logging configuration, or your deployment platform's
log management for rotation, compression, and cleanup.

## License

Licensed under either of:

- Apache License, Version 2.0
- MIT license

at your option.
