# ServerLogger
Persistent Erlang `:logger` handler for Elixir/Phoenix applications. Captures log events into an ETS buffer, batch-inserts them into PostgreSQL with automatic monthly partitioning, and provides configurable retention with auto-pruning. Includes an optional Phoenix LiveDashboard page for real-time log viewing and search.
## Features
- **Zero-overhead buffering** — Log events are stored as raw tuples in ETS by the caller process; formatting is deferred to flush time
- **Batch inserts** — Configurable flush interval and max buffer size; bulk `INSERT` into PostgreSQL
- **Monthly partitions** — Automatic partition creation and rotation; old partitions are dropped when all levels expire
- **Per-level retention** — Configure lifetime per log level (debug: 1 day, error: 90 days, etc.), including "never save" and "keep forever"
- **Crash recovery** — ETS table is owned by the supervisor; unflushed entries survive `BufferServer` restarts
- **LiveDashboard integration** — Optional page with filtering, sorting, search, pagination, auto-refresh, and metrics
## Installation
Add `server_logger` to your dependencies:
```elixir
def deps do
[
{:server_logger, "~> 0.1.0"}
]
end
```
## Setup
### 1. Configure
```elixir
# config/config.exs
config :server_logger,
repo: MyApp.Repo,
enabled: true,
buffer_flush_interval_ms: 1_000,
buffer_max_size: 2_000,
lifetime_days: [
debug: 1,
info: 7,
warning: 30,
error: 90,
critical: 0 # 0 = keep forever
],
memory_limits: [
max_message_size_mb: 8
]
```
### 2. Generate and run the migration
```bash
mix server_logger.gen.migration
mix ecto.migrate
```
### 3. Add to your supervision tree
```elixir
# lib/my_app/application.ex
def start(_type, _args) do
children = [
MyApp.Repo,
# ... other children ...
ServerLogger.Supervisor
]
Supervisor.start_link(children, strategy: :one_for_one)
end
```
### 4. (Optional) Add LiveDashboard page
```elixir
# lib/my_app_web/router.ex
live_dashboard "/dashboard",
additional_pages: [
server_logs: ServerLogger.Dashboard.Page
]
```
## Configuration Reference
| Key | Type | Default | Description |
|-----|------|---------|-------------|
| `repo` | module | *required* | Your Ecto Repo module |
| `enabled` | boolean | `true` | Enable/disable the entire system |
| `buffer_flush_interval_ms` | integer | `1_000` | Periodic flush interval in ms |
| `buffer_max_size` | integer | `2_000` | Max ETS entries before forced flush |
| `logging_enabled` | `:stdio` \| `:stderr` \| `nil` | `nil` | ServerLogger's own diagnostic logging |
| `prune_interval_ms` | integer | `21_600_000` (6h) | How often the pruner checks for expired logs |
| `lifetime_days` | keyword | see below | Per-level retention policy |
| `memory_limits` | keyword | `[max_message_size_mb: 8]` | Message truncation limits |
### Lifetime days
- `nil` — Never save logs of this level
- `0` — Save and keep forever (never prune)
- Positive number — Prune after N days (supports 0.25 increments)
Default: `[debug: 1, info: 7, warning: 30, error: 90, critical: 0]`
## Architecture
```
┌─────────────┐ ┌───────────┐ ┌──────────────┐ ┌────────────┐
│ Your App │────▸│ :logger │────▸│ Handler │────▸│ ETS Buffer │
│ Logger.info │ │ (Erlang) │ │ (caller pid) │ │ (public) │
└─────────────┘ └───────────┘ └──────────────┘ └─────┬──────┘
│
┌──────▼──────┐
│ BufferServer│
│ (periodic │
│ flush) │
└──────┬──────┘
│
┌──────▼──────┐
│ PostgreSQL │
│ (partitioned│
│ table) │
└─────────────┘
```
## AI Disclosure
This library was vibe engineered with the assistance of Anthropic's Claude 4.6 Opus. See (docs/implementation-plans)[./docs/implementation-plans/] for implementation reference prompts.
## License
MIT