README.md

# LoggerLogfmt

A logfmt formatter for Elixir's Logger that outputs structured logs in the logfmt format.

## Overview

LoggerLogfmt provides a simple, text-based structured logging format where each log line consists of key-value pairs. The format is both human-readable and machine-parseable, making it ideal for log aggregation and analysis.

**Example output:**
```
timestamp="2024-01-15 10:30:45.123" level=info message="User logged in" user_id=42 request_id=abc123
```

## Features

- 🎯 **Flexible Configuration** - Customize which fields appear in your logs
- 🔒 **Metadata Filtering** - Whitelist or blacklist sensitive metadata
- ⏱️ **Multiple Timestamp Formats** - Elixir, ISO8601, or Unix epoch
- 🔤 **Automatic Quoting** - Proper escaping of special characters
- 🗂️ **Nested Maps** - Support for nested structures with dot notation
- 🚀 **Zero Dependencies** - Pure Elixir implementation

## Installation

Add `logger_logfmt` to your list of dependencies in `mix.exs`:

```elixir
def deps do
  [
    {:logger_logfmt, "~> 0.1.0"}
  ]
end
```

## Configuration

### Basic Setup

Configure the formatter in your `config/config.exs`:

```elixir
config :logger, :console,
  format: {Logger.Backends.Logfmt, :format},
  metadata: [:request_id, :user_id]

config :logger, :logfmt,
  format: [:timestamp, :level, :message, :metadata],
  metadata: [:application, :request_id],
  mode: :whitelist,
  timestamp_format: :iso8601
```

### Format Options

The `:format` option accepts a list of atoms that determine which fields to include:

| Field | Description |
|-------|-------------|
| `:timestamp` | Log event timestamp |
| `:level` | Log level (debug, info, warn, error) |
| `:message` | Log message |
| `:domain` | Logger domain |
| `:node` | Node name |
| `:pid` | Process identifier |
| `:metadata` | Additional metadata |
| `:file` | Source file |
| `:line` | Line number |

**Example:**
```elixir
config :logger, :logfmt,
  format: [:timestamp, :level, :message, :metadata]
```

### Metadata Filtering

#### Whitelist Mode (Default)

Only specified metadata keys will be included:

```elixir
config :logger, :logfmt,
  metadata: [:request_id, :user_id, :application],
  mode: :whitelist
```

#### Blacklist Mode

All metadata except specified keys will be included:

```elixir
config :logger, :logfmt,
  metadata: [:password, :credit_card, :secret_token],
  mode: :blacklist
```

### Timestamp Formats

Configure the timestamp format for log entries:

```elixir
# Elixir format: "2024-01-15 10:30:45.123"
config :logger, :logfmt, timestamp_format: :elixir

# ISO8601 format (default): "2024-01-15T10:30:45.123"
config :logger, :logfmt, timestamp_format: :iso8601

# Unix epoch time: 1705318245
config :logger, :logfmt, timestamp_format: :epoch_time
```

### Metadata Timestamp Format

Configure how DateTime/NaiveDateTime values in metadata are formatted:

```elixir
# Unix epoch time (default): created_at=1705318245
config :logger, :logfmt, metadata_timestamp_format: :epoch_time

# ISO8601 format: created_at="2024-01-15T10:30:45.123456Z"
config :logger, :logfmt, metadata_timestamp_format: :iso8601

# Elixir format: created_at="2024-01-15 10:30:45.123456"
config :logger, :logfmt, metadata_timestamp_format: :elixir
```

When using `:epoch_time`, the time unit is auto-detected from the key suffix:

- `*_ms` → milliseconds
- `*_us` → microseconds
- `*_ns` → nanoseconds
- No suffix → seconds

### Custom Field Keys

Rename the keys used for standard fields:

```elixir
config :logger, :logfmt,
  timestamp_key: "ts",
  level_key: "severity",
  message_key: "msg",
  domain_key: "app_domain",
  node_key: "node_name",
  pid_key: "process",
  file_key: "source_file",
  line_key: "source_line"
```

## Usage Examples

### Basic Logging

```elixir
require Logger

Logger.info("Application started")
# Output: timestamp="2024-01-15 10:30:45.123" level=info message="Application started"

Logger.error("Database connection failed")
# Output: timestamp="2024-01-15 10:30:45.123" level=error message="Database connection failed"
```

### Logging with Metadata

```elixir
Logger.info("User action", user_id: 42, action: "login", ip: "192.168.1.1")
# Output: timestamp="2024-01-15 10:30:45.123" level=info message="User action" user_id=42 action=login ip=192.168.1.1

Logger.warn("High memory usage", memory_mb: 512.5, threshold_mb: 500)
# Output: timestamp="2024-01-15 10:30:45.123" level=warn message="High memory usage" memory_mb=512.5 threshold_mb=500
```

### Logging Nested Data

```elixir
user = %{name: "John", address: %{city: "Berlin", zip: "10115"}}
Logger.info("User registered", user: user)
# Output: timestamp="2024-01-15 10:30:45.123" level=info message="User registered" user.name=John user.address.city=Berlin user.address.zip=10115
```

### DateTime and NaiveDateTime Values

DateTime and NaiveDateTime values in metadata are automatically formatted based on the `metadata_timestamp_format` option (defaults to `:epoch_time`):

```elixir
Logger.info("Event occurred", created_at: DateTime.utc_now())
# Output: timestamp="..." level=info message="Event occurred" created_at=1701619200

# Time unit is auto-detected from key suffix:
Logger.info("Event", created_at_ms: DateTime.utc_now())  # milliseconds
Logger.info("Event", created_at_us: DateTime.utc_now())  # microseconds
Logger.info("Event", created_at_ns: DateTime.utc_now())  # nanoseconds
```

### Special Characters Handling

```elixir
Logger.info("Message with \"quotes\" and spaces", tag: "special=chars")
# Output: timestamp="2024-01-15 10:30:45.123" level=info message="Message with \"quotes\" and spaces" tag="special=chars"
```

## Integration Examples

### Phoenix Application

In your Phoenix app's `config/config.exs`:

```elixir
config :logger, :console,
  format: {Logger.Backends.Logfmt, :format},
  metadata: [:request_id, :user_id]

config :logger, :logfmt,
  format: [:timestamp, :level, :message, :metadata],
  metadata: [:request_id, :user_id, :method, :path],
  mode: :whitelist,
  timestamp_format: :iso8601
```

Add request metadata in your endpoint:

```elixir
defmodule MyAppWeb.Endpoint do
  use Phoenix.Endpoint, otp_app: :my_app

  plug Plug.RequestId
  plug Plug.Logger

  plug :add_logger_metadata

  defp add_logger_metadata(conn, _opts) do
    Logger.metadata(
      request_id: Logger.metadata()[:request_id],
      method: conn.method,
      path: conn.request_path
    )
    conn
  end
end
```

### Production Logging

For production environments with log aggregation:

```elixir
# config/prod.exs
config :logger, :console,
  format: {Logger.Backends.Logfmt, :format},
  metadata: :all

config :logger, :logfmt,
  format: [:timestamp, :level, :message, :node, :pid, :metadata],
  timestamp_format: :iso8601,
  mode: :blacklist,
  metadata: [:gl, :mfa, :__sentry__, :ansi_color]
```

## API Documentation

Full API documentation is available at [HexDocs](https://hexdocs.pm/logger_logfmt).

### Main Modules

- **`Logger.Backends.Logfmt`** - Main formatter with configuration options
- **`Logger.Backends.Logfmt.Encoder`** - Handles encoding of different data types
- **`Logger.Backends.Logfmt.Quoter`** - Manages quoting and escaping

## Testing

Run the test suite:

```bash
cd libs/logger_logfmt
mix test
```

Run with coverage:

```bash
mix test --cover
```

## Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

## License

Apache License 2.0 - See LICENSE file for details.