CLI tool for monitoring Redis streams and PubSub
npm install @ceschiatti/redistailtail -f for log files.
--from and transition to real-time |
bash
Using pnpm (recommended)
pnpm add -g @ceschiatti/redistail
Using npm
npm install -g @ceschiatti/redistail
Using bun
bun add -g @ceschiatti/redistail
`
$3
`bash
pnpm add @ceschiatti/redistail
Run locally
npx redistail --help
`
$3
The npm package ships only the JS bundle (~2 MB). On postinstall, a platform-specific
compiled binary is downloaded from
GitHub Releases. If the download fails
(no network, no release for the platform), the launcher falls back to
node dist/redistail.js automatically.
To skip binary download: REDISTAIL_SKIP_BINARY_DOWNLOAD=1 pnpm add -g @ceschiatti/redistail
$3
`bash
redistail --version
redistail --help
`
---
Quick Start
`bash
Monitor a Pub/Sub channel
redistail pubsub my-channel
Monitor a Redis Stream
redistail stream my-stream
Historical messages + real-time transition
redistail stream my-stream --from "2026-01-09T10:00:00Z"
Custom Redis connection
REDIS_HOST=redis.example.com redistail pubsub notifications
`
---
Architecture
`
CLI args + env vars
|
v
CLIConfigService (parses args, loads env config, shows help/version)
|
v
RedistailConfig (immutable config: redis + display + monitoring)
/ | \
v v v
DisplayService PubSubMonitorService StreamMonitorService
(formatting) (channel monitoring) (stream monitoring)
| | |
| v v
| RedisPubSub RedisStream
| (effect-redis) (effect-redis)
| \ /
| v v
| RedisConnectionOptions
| |
v v
stdout/stderr Redis Server
`
$3
The CLI creates only the Redis connections needed for the selected mode:
| Mode | Redis Connections | Layer Used |
| :--- | :--- | :--- |
| pubsub | 2 (publish + subscribe) | PubSubAppLive |
| stream | 2 (producer + consumer) | StreamAppLive |
| --help / --version | 0 | CLIConfigServiceLive |
| Invalid args | 0 | CLIConfigServiceLive |
---
CLI Reference
$3
`
redistail [OPTIONS]
`
$3
| Argument | Values | Description |
| :--- | :--- | :--- |
| CONNECTION_TYPE | pubsub, stream | Type of Redis connection |
| TOPIC_NAME | any string | Channel name (Pub/Sub) or stream key (Streams) |
$3
| Option | Description |
| :--- | :--- |
| -h, --help | Show help message with all options and environment variables |
| -v, --version | Show version, build hash, compile date, Effect and effect-redis versions |
| --from | (Stream only) Retrieve historical messages from a date, then transition to real-time |
$3
`
redistail 0.0.17
Build: 4cc301e
Compiled: 2026-01-19T10:37:28.864Z (UTC)
Effect: 3.19.16
effect-redis: 0.0.32
`
---
Monitoring Modes
$3
`bash
redistail pubsub channel-name
`
- Subscribes to the specified channel via RedisPubSub.subscribe()
- Displays messages as they are published
- Shows timestamp, channel name, and message content
- Automatic reconnection with exponential backoff (up to maxReconnectAttempts)
- Continues running until interrupted (Ctrl+C)
$3
`bash
redistail stream stream-name
`
- Reads from the stream starting from the latest entry ($)
- Uses blocking XREAD to wait for new entries
- Shows timestamp, entry ID, and all message fields
- Maintains entry ID continuity across reconnections
$3
`bash
redistail stream stream-name --from "2026-01-09T10:00:00Z"
`
1. Retrieves all historical messages from the date onwards via XRANGE
2. Displays them in chronological order
3. Seamlessly transitions to real-time monitoring from the last seen entry ID
4. No messages are lost during the transition
Supported date formats:
| Format | Example |
| :--- | :--- |
| Keywords | today, yesterday |
| ISO 8601 | 2026-01-09T10:00:00Z, 2026-01-09 |
| RFC 2822 | Fri, 09 Jan 2026 10:00:00 GMT |
| Simple | Jan 9 2026, 2026/01/09 |
Edge cases:
- Future date: Warning logged, monitoring starts from latest entry
- Old date: All available messages are retrieved
- Empty stream: Proceeds directly to real-time mode
- Invalid format: Clear error with supported format examples
$3
For daily-partitioned streams (common in the Cedro protocol), provide a **partial
stream name** (3 colon-separated fields) with --from:
`bash
redistail stream stream:raw:WING26 --from yesterday
`
Stream name formats:
- Partial (3 fields): stream: (e.g., stream:raw:WING26)
- Full (6 fields): stream:
How it works:
1. Generates date range from --from date to today
2. Constructs full stream names for each day (e.g., stream:raw:WING26:2026:01:16)
3. Sequentially reads all historical messages from each day's stream
4. On the final day (today), transitions to real-time monitoring
5. Missing or empty streams are skipped with a warning
`
š
Multi-day mode: stream:raw:WING26 from 2026-01-16T00:00:00.000Z
ā³ Iterating through daily streams, then transitioning to real-time...
š Processing stream: stream:raw:WING26:2026:01:16
š Processing stream: stream:raw:WING26:2026:01:17
š Processing stream: stream:raw:WING26:2026:01:18 (switching to real-time)
`
---
Configuration
All configuration is loaded from environment variables via Effect.Config with
validation and defaults.
$3
#### Redis Connection
| Variable | Default | Description |
| :--- | :--- | :--- |
| REDIS_HOST | 127.0.0.1 | Redis server hostname |
| REDIS_PORT | 6379 | Redis server port (1ā65535) |
| REDIS_URL | ā | Complete Redis URL (overrides host/port) |
| REDIS_TIMEOUT | 5000 | Connection timeout in milliseconds |
| REDIS_RETRY_ATTEMPTS | 3 | Number of connection retry attempts |
| REDIS_RETRY_DELAY | 1000 | Delay between retries in milliseconds |
#### Display
| Variable | Default | Description |
| :--- | :--- | :--- |
| REDISTAIL_COLORS | true | Enable ANSI colored output |
| REDISTAIL_TIMESTAMPS | true | Show timestamps in output |
| REDISTAIL_PRETTY_JSON | true | Pretty-print JSON content |
#### Monitoring
| Variable | Default | Description |
| :--- | :--- | :--- |
| REDISTAIL_BLOCK_TIMEOUT | 5000 | Stream blocking timeout in milliseconds |
| REDISTAIL_MAX_RECONNECT_ATTEMPTS | 5 | Maximum reconnection attempts |
$3
`bash
Production
REDIS_URL=redis://user:pass@redis-cluster.prod.com:6380 \
REDISTAIL_COLORS=false \
redistail stream orders
Development
REDIS_HOST=localhost REDISTAIL_PRETTY_JSON=true redistail pubsub app-logs
CI/CD (minimal output)
REDISTAIL_COLORS=false REDISTAIL_TIMESTAMPS=false redistail stream events
`
---
Services & Layers
Redistail is built as a set of Effect-TS services composed via layers.
$3
| Service | Tag | Purpose |
| :--- | :--- | :--- |
| CLIConfigService | 'CLIConfigService' | Argument parsing, env config loading, help/version display |
| DisplayService | '@redistail/DisplayService' | Message formatting, color output, stdout/stderr separation |
| PubSubMonitorService | 'PubSubMonitorService' | Pub/Sub channel monitoring with reconnection |
| StreamMonitorService | 'StreamMonitorService' | Stream monitoring, date filtering, multi-day traversal |
| SignalHandlerService | 'SignalHandlerService' | SIGINT/SIGTERM handling, graceful shutdown |
| DateFilterService | 'DateFilterService' | Date parsing and Redis stream ID conversion |
$3
`ts
// Full application layer (all services)
import { AppLive } from './layers.js';
// Mode-specific layers (optimized connection count)
import { PubSubAppLive, StreamAppLive } from './layers.js';
// Testing layers
import { createTestAppLayer, createMockAppLayer } from './layers.js';
`
$3
`
CLIConfigServiceLive (no deps)
|
v
RedistailConfigLive (depends on CLIConfigService)
/ \
v v
RedisConnectionLive DisplayServiceLive
|
v
RedisPubSubLive / RedisStreamLive
|
v
PubSubMonitorServiceLive / StreamMonitorServiceLive
`
---
Error Model
Every operation fails with RedistailError, a discriminated union of three
tagged error types. All extend Data.TaggedError for pattern matching with
Effect.catchTag.
`ts
class CLIError extends Data.TaggedError('CLIError')<{
readonly reason: 'InvalidArguments' | 'MissingArguments'
| 'UnknownCommand' | 'InvalidDateFormat';
readonly message: string;
readonly context?: string;
}> {}
class ConfigError extends Data.TaggedError('ConfigError')<{
readonly reason: 'InvalidEnvironment' | 'ConnectionFailed'
| 'ValidationFailed';
readonly message: string;
readonly cause?: unknown;
readonly context?: string;
}> {}
class MonitoringError extends Data.TaggedError('MonitoringError')<{
readonly reason: 'ConnectionLost' | 'SubscriptionFailed'
| 'StreamReadFailed';
readonly message: string;
readonly retryable: boolean;
readonly cause?: unknown;
}> {}
type RedistailError = CLIError | ConfigError | MonitoringError;
`
| Error | Tag | When |
| :--- | :--- | :--- |
| CLIError | 'CLIError' | Invalid arguments, missing arguments, bad date format |
| ConfigError | 'ConfigError' | Invalid environment variables, connection failures |
| MonitoringError | 'MonitoringError' | Connection lost, subscription failed, stream read failed |
$3
| Helper | Creates | Retryable |
| :--- | :--- | :--- |
| createInvalidArgumentError(msg) | CLIError (InvalidArguments) | ā |
| createMissingArgumentError(msg) | CLIError (MissingArguments) | ā |
| createDateFilterError(msg) | CLIError (InvalidDateFormat) | ā |
| createEnvironmentError(msg) | ConfigError (InvalidEnvironment) | ā |
| createConnectionError(msg) | ConfigError (ConnectionFailed) | ā |
| createConnectionLostError(msg) | MonitoringError (ConnectionLost) | Yes |
| createSubscriptionFailedError(msg) | MonitoringError (SubscriptionFailed) | Configurable |
| createStreamReadFailedError(msg) | MonitoringError (StreamReadFailed) | Configurable |
---
Types Reference
$3
`ts
interface CLIConfig {
readonly connectionType: 'pubsub' | 'stream';
readonly topicName: string;
readonly help?: boolean;
readonly version?: boolean;
readonly fromDate?: Date;
}
`
$3
`ts
interface RedistailConfig {
readonly redis: {
readonly host: string;
readonly port: number;
readonly url?: string;
readonly timeout: number;
readonly retryAttempts: number;
readonly retryDelay: number;
};
readonly display: {
readonly colors: boolean;
readonly timestamps: boolean;
readonly prettyJson: boolean;
};
readonly monitoring: {
readonly blockTimeout: number;
readonly maxReconnectAttempts: number;
};
}
`
$3
`ts
interface PubSubMessage {
readonly timestamp: Date;
readonly channel: string;
readonly content: string;
}
interface StreamMessage {
readonly timestamp: Date;
readonly streamKey: string;
readonly entryId: string;
readonly fields: Record;
}
`
$3
| Function | Signature | Description |
| :--- | :--- | :--- |
| isPartialStreamName | (name: string) => boolean | Check if name has 3 colon-separated fields |
| isFullStreamName | (name: string) => boolean | Check if name has 6 colon-separated fields |
| constructDailyStreamName | (partial: string, date: Date) => string | Build full name from partial + date |
| generateDateRange | (from: Date, to: Date) => Date[] | Generate inclusive date array |
| generateDailyStreamNames | (partial: string, from: Date, to: Date) => string[] | Generate all daily stream names |
---
Build System
The build process is handled by build-cli.js and produces both ESM modules and
cross-platform compiled binaries.
$3
`bash
pnpm build # or: node build-cli.js
`
$3
1. Version injection: Reads src/version.ts template, replaces placeholders
with git commit hash, package version, and ISO timestamp, writes
src/version-generated.ts
2. ESM bundle: bun build produces dist/redistail.js (Node.js target, ESM)
3. Cross-platform binaries: Compiled via bun build --compile for 5 targets
4. Launcher: Copies redistail-launcher.js to dist/
$3
| Platform | Architecture | Output |
| :--- | :--- | :--- |
| Windows | x64 | redistail-win-x64.exe |
| Linux | x64 | redistail-linux-x64 |
| Linux | ARM64 | redistail-linux-arm64 |
| macOS | x64 | redistail-osx-x64 |
| macOS | ARM64 | redistail-osx-arm64 |
$3
Binaries are excluded from the npm package (via .npmignore) to keep the
tarball under ~2 MB. Instead, binaries are distributed through GitHub Releases:
1. npm package ships: dist/redistail.js + dist/redistail-launcher.js + scripts/postinstall.js
2. postinstall runs scripts/postinstall.js, which downloads the
platform-specific binary from https://github.com/6qat/monorepo/releases/download/redistail-v{version}/{binary}
3. Launcher (redistail-launcher.js) checks for the binary; if absent,
falls back to node dist/redistail.js
$3
After pnpm build, upload the 5 binaries from dist/ to a GitHub Release
tagged redistail-v{version}:
`bash
gh release create redistail-v0.0.18 \
dist/redistail-linux-x64 \
dist/redistail-linux-arm64 \
dist/redistail-osx-x64 \
dist/redistail-osx-arm64 \
dist/redistail-win-x64.exe \
--title "redistail v0.0.18" \
--notes "Pre-compiled binaries for redistail v0.0.18"
`
---
Output Format
$3
`
TIMESTAMP [CHANNEL] MESSAGE_CONTENT
`
`
2026-01-09 15:30:45.123 [notifications] User alice logged in
`
$3
`
TIMESTAMP [STREAM_KEY:ENTRY_ID] FIELD1=VALUE1 FIELD2=VALUE2 ...
`
`
2026-01-09 15:31:12.345 [events:1704636672345-0] user=alice action=login
`
If a stream entry has a single data or message field, the field name is
omitted and the value is displayed directly (unwrapped payload).
$3
| Color | Element |
| :--- | :--- |
| Gray (\x1b[90m) | Timestamps |
| Cyan (\x1b[36m) | Pub/Sub channel names |
| Magenta (\x1b[35m) | Stream names and entry IDs |
| Yellow (\x1b[33m) | Field names in streams |
| White (\x1b[37m) | Message content and field values |
| Red (\x1b[31m) | Error messages |
$3
Non-printable characters are automatically escaped as \xHH. Content with more
than 10% non-printable characters is treated as binary and fully escaped.
---
Examples
$3
`bash
redistail pubsub notifications
`
`
2026-01-09 15:30:45.123 [notifications] Welcome to the system!
2026-01-09 15:30:47.456 [notifications] New user registered
`
$3
`bash
redistail stream stream:raw:WING26 --from "2026-01-16"
`
$3
Input message: {"user":"alice","action":"login"}
`
2026-01-09 15:31:12.345 [api-events] {
"user": "alice",
"action": "login"
}
`
$3
`bash
REDISTAIL_COLORS=false redistail stream events >> /var/log/redis-events.log
`
$3
`bash
redistail pubsub critical-alerts &
redistail stream user-events &
redistail pubsub app-logs &
`
$3
`bash
#!/bin/bash
CHANNELS=("user-events" "system-logs" "error-alerts")
PIDS=()
for channel in "${CHANNELS[@]}"; do
redistail pubsub "$channel" > "/var/log/redis-$channel.log" &
PIDS+=($!)
done
trap 'kill "${PIDS[@]}"; exit' INT TERM
wait
`
---
Troubleshooting
$3
`
ā Error: Connection failed to redis://localhost:6379
`
1. Verify Redis is running: redis-cli ping
2. Check host/port: REDIS_HOST=localhost REDIS_PORT=6379 redistail --help
3. Test directly: redis-cli -h localhost -p 6379 ping
$3
1. Publish a test message: redis-cli publish test-channel "hello"
2. Add a stream entry: redis-cli xadd test-stream * msg hello
3. Verify topic name matches exactly
4. Check stream length: redis-cli xlen stream-name
$3
Include credentials in URL: REDIS_URL=redis://user:pass@host:port
$3
1. Increase retries: REDISTAIL_MAX_RECONNECT_ATTEMPTS=10
2. Adjust delay: REDIS_RETRY_DELAY=2000
3. Check network stability and Redis server logs
$3
1. Set explicitly: REDISTAIL_COLORS=true
2. Piping disables colors ā use REDISTAIL_COLORS=true to force
3. Check terminal: echo $TERM
$3
`bash
Invalid format
$ redistail stream events --from "invalid"
ā CLI Error: Invalid date format: "invalid". Supported formats: ...
--from with pubsub
$ redistail pubsub channel --from "2026-01-09"
ā CLI Error: The --from option is only available for stream mode
Missing value
$ redistail stream events --from
ā CLI Error: The --from option requires a date argument
`
$3
`bash
Test Redis connectivity
redis-cli -h $REDIS_HOST -p $REDIS_PORT ping
Monitor all Redis commands
redis-cli monitor
Test pub/sub manually
redis-cli subscribe test-channel
In another terminal:
redis-cli publish test-channel "hello"
`
---
Peer Dependencies
| Package | Version |
| :--- | :--- |
| effect | (provided via workspace catalog) |
| effect-redis | workspace:* |
| @effect/experimental | (provided via workspace catalog) |
| @effect/platform-bun` | (provided via workspace catalog) |