---
url: /logly.zig/guide/rotation.md
---
# File Rotation Guide

Logly provides a powerful file rotation system suitable for high-throughput production environments. This guide explains how to configure rotation strategies, retention policies, compression, and global defaults.

## Basics

File rotation prevents log files from growing indefinitely by periodically closing the current file, renaming it, and starting a fresh one.

To enable rotation, use the `rotating` sink or configure a `Rotation` object manually.

```zig
const config = Config.init(allocator)
    .withSink(SinkConfig.rotating("app.log", "daily", 7));
```

Alternatively, you can use the `logger.add()` shortcut with a configuration struct:

```zig
_ = try logger.add(.{
    .path = "app.log",
    .rotation = "daily",
    .retention = 7,
    .sizeLimitStr = "10MB", // Optional: also rotate on size
});
```

## Write Modes and Rotation

Rotation works with all write modes. The `writeMode` field controls how the file is opened initially, while rotation handles file lifecycle:

| Write Mode | Initial Behavior | After Rotation |
|------------|------------------|----------------|
| `.append` (default) | Append to existing file | New file starts fresh (truncated) |
| `.overwrite` | Truncate file on startup | New file starts fresh (truncated) |
| `.appendRotate` | Append with explicit rotation trigger | New file starts fresh (truncated) |

```zig
// Append mode with daily rotation
var sink = logly.SinkConfig.file("app.log");
sink.writeMode = .append;
sink.rotation = "daily";
sink.retention = 7;

// Overwrite mode with size rotation
var sink = logly.SinkConfig.file("session.log");
sink.writeMode = .overwrite;
sink.sizeLimit = 1024 * 1024; // 1MB
sink.retention = 3;
```

After rotation, the old file is renamed (e.g., `app.log.2026-08-05`) and a new fresh file is created. This behavior is consistent across all write modes.

## Global Configuration

You can configure rotation defaults globally in `Config`, which allows you to enforce strategies across all sinks or provide defaults for those that don't specify them.

### Configuration Reference

The `RotationConfig` struct provides the following fields:

| Field | Type | Description |
| :--- | :--- | :--- |
| `enabled` | `bool` | Master switch for default rotation logic. |
| `interval` | `?[]const u8` | Default time interval ("daily", "hourly"). |
| `sizeLimit` | `?u64` | Default size limit in bytes. |
| `retentionCount` | `?usize` | Default max number of files to keep. |
| `maxAgeSeconds` | `?i64` | Default max age of files in seconds. |
| `namingStrategy` | `NamingStrategy` | Default naming strategy (`timestamp`, `date`, `isoDatetime`, `index`). |
| `archiveDir` | `?[]const u8` | Directory to automatically move rotated files into. |
| `cleanEmptyDirs` | `bool` | Whether to delete the archive directory if it becomes empty. |
| `keepOriginal` | `bool` | Keep original file after compression (default: false). |
| `compressOnRetention` | `bool` | Compress files during retention cleanup instead of deleting. |
| `deleteAfterRetentionCompress` | `bool` | Delete originals after retention compression (default: true). |

### Example

```zig
const config = Config{
    .rotation = .{
        .enabled = true,
        .namingStrategy = .isoDatetime,
        .archiveDir = "logs/archive",
        .maxAgeSeconds = 86400 * 30, // 30 days
        .cleanEmptyDirs = true,
        .interval = "daily",
    }
};
```

## Strategies

### Time-Based Rotation

Rotates files based on elapsed time.

* `minutely`: Useful for debugging.
* `hourly`: Good for high-volume logs.
* `daily`: Standard for most applications.
* `weekly`/`monthly`: For lower volume, long-term logs.

### Size-Based Rotation

Rotates files when they reach a specific size limit.

```zig
// Rotate when file hits 100MB, keep 10 files
var config = SinkConfig.file("app.log");
config.sizeLimit = 100 * 1024 * 1024;
config.retention = 10;
```

### Rotation Helper APIs

```zig
var rot = try Rotation.init(allocator, "app.log", "hourly", null, 7);
defer rot.deinit();

if (rot.nextRotationAt()) |epochSeconds| {
    std.debug.print("Next rotation at: {d}\n", .{epochSeconds});
}

const age = rot.rotationAgeSeconds();
std.debug.print("Last rotation age: {d}s\n", .{age});
```

### Sink Configuration Fields

When using `logger.add(.{...})`, you can use the following fields to control rotation:

| Field | Type | Description |
| :--- | :--- | :--- |
| `rotation` | `?[]const u8` | Time interval ("daily", "hourly"). |
| `sizeLimit` | `?u64` | Size limit in bytes. |
| `sizeLimitStr` | `?[]const u8` | Size limit as string (e.g., "10MB"). |
| `retention` | `?usize` | Number of files to keep. |
| `compression` | `CompressionConfig` | Nested struct to enable compression. |

## Naming Strategies

You can control how rotated files are named using `NamingStrategy`.

* **Timestamp** (Default for `size/hourly`): `app.log` -> `app.log.167882233`
* **Date** (Default for `daily`+): `app.log` -> `app.log.2023-10-25`
* **ISO**: `app.log` -> `app.log.2023-10-25T14-30-00`
* **Index**: `app.log` -> `app.log.1`, `app.log.2` (Rolling log style)
* **Custom**: Define your own pattern like `app-{date}.log`.

### Custom Formatting

You can define a custom format string using `namingFormat`.

Supported placeholders:

* `{base}`: Filename without extension (e.g. "app")
* `{ext}`: File extension including dot (e.g. ".log")
* `{date}`: YYYY-MM-DD
* `{time}`: HH-mm-ss
* `{iso}`: ISO 8601 datetime
* `{YYYY}`, `{YY}`, `{MM}`, `{M}`, `{DD}`, `{D}`, `{HH}`, `{H}`, `{mm}`, `{m}`, `{ss}`, `{s}`: Granular date formatting

**Example: `app-2023-12-25.log`**

```zig
_ = try logger.add(.{
    .path = "app.log",
    .rotation = "daily",
    // Custom: Use dots, slashes, or specific ordering
    .namingFormat = "{base}-{DD}-{MM}-{YYYY}{ext}",
});
```

To control the *active* file naming (e.g. writing directly to a date-stamped file), refer to the [Dynamic Path](../examples/dynamic-path.md) documentation.

## Compression

Logly can automatically compress old log files to save space (`.gz` or `.lgz`).

### Compress on Rotation

Compress files immediately after rotation:

```zig
var rot = try Rotation.init(allocator, "app.log", "daily", null, 30);
try rot.withCompression(.{ .algorithm = .deflate });
```

### Keep Original After Compression

By default, the original file is deleted after compression. To keep both:

```zig
var rot = try Rotation.init(allocator, "app.log", "daily", null, 30);
try rot.withCompression(.{ .algorithm = .gzip });
rot.withKeepOriginal(true); // Keep both app.log.2025-01-09 AND app.log.2025-01-09.gz
```

### Compress During Retention (Instead of Delete)

Instead of deleting old files during retention cleanup, compress them:

```zig
var rot = try Rotation.init(allocator, "app.log", "daily", null, 7);
try rot.withCompression(.{ .algorithm = .deflate });
rot.withCompressOnRetention(true); // Old files are compressed, not deleted
```

### Compression Modes Summary

| Mode | Behavior | Use Case |
| :--- | :--- | :--- |
| **Default** | Compress on rotation, delete original | Standard archival |
| `keepOriginal = true` | Compress on rotation, keep both files | Redundant backup |
| `compressOnRetention = true` | Compress during cleanup instead of delete | Archive before purge |
| Both enabled | Compress on rotation + compress during retention | Maximum archival |

### Global Compression Settings

```zig
const config = Config{
    .rotation = .{
        .enabled = true,
        .interval = "daily",
        .retentionCount = 30,
        .keepOriginal = false,           // Delete originals after compression
        .compressOnRetention = true,    // Compress files during retention cleanup
        .deleteAfterRetentionCompress = true, // Delete after retention compress
    },
    .compression = .{
        .enabled = true,
        .algorithm = .gzip,
        .level = .default,
    },
};
```

## Retention Policies

Retention controls how long rotated files are kept.

* **Count Based**: Keep only the last `N` files.
* **Age Based**: Keep files younger than `Seconds`.

Old files are automatically deleted during the rotation process.

### Archiving

You can move rotated files to a specific directory to keep your main log folder clean.

```zig
try rot.withArchiveDir("logs/archive");
```

## Presets

Logly provides comprehensive presets for common rotation scenarios. Use `RotationPresets` for quick setup.

### Time-Based Presets

| Preset | Interval | Retention | Use Case |
|--------|----------|-----------|----------|
| `daily7Days()` | Daily | 7 | Standard weekly cleanup |
| `daily30Days()` | Daily | 30 | Monthly retention |
| `daily90Days()` | Daily | 90 | Quarterly retention |
| `daily365Days()` | Daily | 365 | Yearly archive |
| `hourly24Hours()` | Hourly | 24 | High-volume daily cleanup |
| `hourly7Days()` | Hourly | 168 | Week-long hourly logs |
| `weekly4Weeks()` | Weekly | 4 | Monthly cleanup |
| `monthly12Months()` | Monthly | 12 | Yearly archive |

### Size-Based Presets

| Preset | Size | Retention | Use Case |
|--------|------|-----------|----------|
| `size1MB()` | 1 MB | 5 | Embedded systems |
| `size10MB()` | 10 MB | 5 | Standard apps |
| `size100MB()` | 100 MB | 10 | Enterprise apps |
| `size500MB()` | 500 MB | 3 | High-volume logs |
| `size1GB()` | 1 GB | 2 | Maximum size logs |

### Hybrid Presets (Time OR Size)

| Preset | Interval | Size | Retention |
|--------|----------|------|-----------|
| `dailyOr100MB()` | Daily | 100 MB | 30 |
| `hourlyOr50MB()` | Hourly | 50 MB | 48 |
| `dailyOr500MB()` | Daily | 500 MB | 7 |

### Production Presets

```zig
const RotationPresets = @import("logly").RotationPresets;

// Production: daily, 30 days, gzip compression
var rot = try RotationPresets.production(allocator, "app.log");
defer rot.deinit();

// Enterprise: daily, 90 days, best compression, ISO naming
var ent = try RotationPresets.enterprise(allocator, "server.log");
defer ent.deinit();

// Audit: daily, 365 days, keep all archives
var audit = try RotationPresets.audit(allocator, "audit.log");
defer audit.deinit();

// Debug: minutely, 60 files (for development)
var debug = try RotationPresets.debug(allocator, "debug.log");
defer debug.deinit();

// High-volume: hourly OR 500MB
var hv = try RotationPresets.highVolume(allocator, "traffic.log");
defer hv.deinit();

// Minimal: 10MB, 3 files (resource constrained)
var min = try RotationPresets.minimal(allocator, "embedded.log");
defer min.deinit();
```

### Sink Preset Helpers

```zig
// Quick sink configuration
_ = try logger.addSink(RotationPresets.dailySink("logs/app.log", 30));
_ = try logger.addSink(RotationPresets.hourlySink("logs/access.log", 48));
_ = try logger.addSink(RotationPresets.sizeSink("logs/data.log", 100 * 1024 * 1024, 10));
```

## Performance

Rotation checks are optimized to be `O(1)` (simple time or size check). The actual rotation involves file I/O (renaming, creating) and is protected by a mutex to ensure thread safety. Compression happens after the critical logic, so the impact on the logging thread is managed, but for extremely large files, consider scheduling compression separately.
