20 KiB
LogWisp - Multi-Stream Log Monitoring Service
A high-performance log streaming service with multi-stream architecture, supporting both TCP and HTTP/SSE protocols with real-time file monitoring, rotation detection, regex-based filtering, and rate limiting.
Features
- Multi-Stream Architecture: Run multiple independent log streams, each with its own configuration
- Dual Protocol Support: TCP (raw streaming) and HTTP/SSE (browser-friendly)
- Real-time Monitoring: Instant updates with per-stream configurable check intervals
- File Rotation Detection: Automatic detection and handling of log rotation
- Regex-based Filtering: Include/exclude patterns with AND/OR logic per stream
- Path-based Routing: Optional HTTP router for consolidated access
- Rate Limiting: Per-IP or global rate limiting with token bucket algorithm
- Connection Limiting: Configurable concurrent connection limits per IP
- Per-Stream Configuration: Independent settings including check intervals, filters, and rate limits
- Connection Statistics: Real-time monitoring of active connections, filter, and rate limit metrics
- Flexible Targets: Monitor individual files or entire directories
- Version Management: Git tag-based versioning with build information
- Configurable Heartbeats: Keep connections alive with customizable formats
- Minimal Direct Dependencies: panjf2000/gnet/v2, valyala/fasthttp, lixenwraith/config, and stdlib
Quick Start
# Build with version information
make build
# Run with default configuration if ~/.config/logwisp.toml doesn't exists
./logwisp
# Run with custom config
./logwisp --config /etc/logwisp/production.toml
# Run with HTTP router (path-based routing)
./logwisp --router
# Show version information
./logwisp --version
Architecture
LogWisp uses a service-oriented architecture where each stream is an independent pipeline:
LogStream Service
├── Stream["app-logs"]
│ ├── Monitor (watches files)
│ ├── Filter Chain (optional)
│ ├── Rate Limiter (optional)
│ ├── TCP Server (optional)
│ └── HTTP Server (optional)
├── Stream["system-logs"]
│ ├── Monitor
│ ├── Filter Chain (optional)
│ ├── Rate Limiter (optional)
│ └── HTTP Server
└── HTTP Router (optional, for path-based routing)
Configuration
Configuration file location: ~/.config/logwisp.toml
Basic Multi-Stream Configuration
# Application logs stream
[[streams]]
name = "app"
[streams.monitor]
# Per-stream check interval in milliseconds
check_interval_ms = 100
targets = [
{ path = "/var/log/myapp", pattern = "*.log", is_file = false },
{ path = "/var/log/myapp/app.log", is_file = true }
]
# Filter configuration (optional)
[[streams.filters]]
type = "include" # Only show matching logs
logic = "or" # Match any pattern
patterns = [
"(?i)error", # Case-insensitive error
"(?i)warn", # Case-insensitive warning
"(?i)fatal" # Fatal errors
]
[streams.httpserver]
enabled = true
port = 8080
buffer_size = 2000
stream_path = "/stream"
status_path = "/status"
# Heartbeat configuration
[streams.httpserver.heartbeat]
enabled = true
interval_seconds = 30
format = "comment" # or "json" for structured events
include_timestamp = true
include_stats = false
# Rate limiting configuration
[streams.httpserver.rate_limit]
enabled = true
requests_per_second = 10.0
burst_size = 20
limit_by = "ip"
response_code = 429
response_message = "Rate limit exceeded"
max_connections_per_ip = 5
# System logs stream with slower check interval
[[streams]]
name = "system"
[streams.monitor]
# Check every 60 seconds for slowly updating logs
check_interval_ms = 60000
targets = [
{ path = "/var/log/syslog", is_file = true },
{ path = "/var/log/auth.log", is_file = true }
]
# Exclude debug logs
[[streams.filters]]
type = "exclude"
patterns = ["DEBUG", "TRACE"]
[streams.tcpserver]
enabled = true
port = 9090
buffer_size = 5000
# TCP heartbeat (always JSON format)
[streams.tcpserver.heartbeat]
enabled = true
interval_seconds = 300 # 5 minutes
include_timestamp = true
include_stats = true
# TCP rate limiting
[streams.tcpserver.rate_limit]
enabled = true
requests_per_second = 5.0
burst_size = 10
limit_by = "ip"
Target Configuration
Monitor targets support both files and directories:
# Directory monitoring with pattern
{ path = "/var/log", pattern = "*.log", is_file = false }
# Specific file monitoring
{ path = "/var/log/app.log", is_file = true }
# All .log files in a directory
{ path = "./logs", pattern = "*.log", is_file = false }
Filter Configuration
Control which logs are streamed using regex patterns:
# Include filter - only matching logs pass
[[streams.filters]]
type = "include"
logic = "or" # Match ANY pattern
patterns = [
"ERROR",
"WARN",
"CRITICAL"
]
# Exclude filter - matching logs are dropped
[[streams.filters]]
type = "exclude"
logic = "or" # Drop if ANY pattern matches
patterns = [
"DEBUG",
"healthcheck",
"/metrics"
]
# Complex filter with AND logic
[[streams.filters]]
type = "include"
logic = "and" # Must match ALL patterns
patterns = [
"database", # Must contain "database"
"error", # AND must contain "error"
"connection" # AND must contain "connection"
]
Multiple filters are applied sequentially - all must pass for a log to be streamed.
Check Interval Configuration
Each stream can have its own check interval based on log update frequency:
- High-frequency logs: 50-100ms (e.g., application debug logs)
- Normal logs: 100-1000ms (e.g., application logs)
- Low-frequency logs: 10000-60000ms (e.g., system logs, archives)
Rate Limiting Configuration
Control request rates and connection limits per stream:
[streams.httpserver.rate_limit]
enabled = true # Enable/disable rate limiting
requests_per_second = 10.0 # Token refill rate
burst_size = 20 # Maximum burst capacity
limit_by = "ip" # "ip" or "global"
response_code = 429 # HTTP response code when limited
response_message = "Too many requests"
max_connections_per_ip = 5 # Max concurrent connections per IP
max_total_connections = 100 # Max total connections (global)
Heartbeat Configuration
Keep connections alive and detect stale clients with configurable heartbeats:
[streams.httpserver.heartbeat]
enabled = true
interval_seconds = 30
format = "comment" # "comment" for SSE comments, "json" for events
include_timestamp = true # Add timestamp to heartbeat
include_stats = true # Include connection count and uptime
Heartbeat Formats:
Comment format (SSE):
: heartbeat 2025-01-07T10:30:00Z clients=5 uptime=3600s
JSON format (SSE):
event: heartbeat
data: {"type":"heartbeat","timestamp":"2025-01-07T10:30:00Z","active_clients":5,"uptime_seconds":3600}
TCP always uses JSON format with newline delimiter.
Usage Modes
1. Standalone Mode (Default)
Each stream runs on its configured ports:
./logwisp
# Stream endpoints:
# - app: http://localhost:8080/stream
# - system: tcp://localhost:9090 and https://localhost:8443/logs
2. Router Mode
All HTTP streams share ports with path-based routing:
./logwisp --router
# Routed endpoints:
# - app: http://localhost:8080/app/stream
# - system: http://localhost:8080/system/logs
# - global: http://localhost:8080/status
Client Examples
HTTP/SSE Stream
# Connect to a stream
curl -N http://localhost:8080/stream
# Check stream status (includes filter and rate limit stats)
curl http://localhost:8080/status
# With authentication (when implemented)
curl -u admin:password -N https://localhost:8443/logs
TCP Stream
# Using netcat
nc localhost 9090
# Using telnet
telnet localhost 9090
# With TLS (when implemented)
openssl s_client -connect localhost:9443
JavaScript Client
const eventSource = new EventSource('http://localhost:8080/stream');
eventSource.addEventListener('connected', (e) => {
const data = JSON.parse(e.data);
console.log('Connected with ID:', data.client_id);
});
eventSource.addEventListener('message', (e) => {
const logEntry = JSON.parse(e.data);
console.log(`[${logEntry.time}] ${logEntry.level}: ${logEntry.message}`);
});
eventSource.addEventListener('heartbeat', (e) => {
const heartbeat = JSON.parse(e.data);
console.log('Heartbeat:', heartbeat);
});
eventSource.addEventListener('error', (e) => {
if (e.status === 429) {
console.error('Rate limited - backing off');
// Implement exponential backoff
}
});
Log Entry Format
All log entries are streamed as JSON:
{
"time": "2024-01-01T12:00:00.123456Z",
"source": "app.log",
"level": "ERROR",
"message": "Connection timeout",
"fields": {
"user_id": "12345",
"request_id": "abc-def-ghi"
}
}
API Endpoints
Stream Endpoints (per stream)
GET {stream_path}- SSE log streamGET {status_path}- Stream statistics and configuration
Global Endpoints (router mode)
GET /status- Aggregated status for all streamsGET /{stream_name}/{path}- Stream-specific endpoints
Status Response
{
"service": "LogWisp",
"version": "v1.0.0",
"server": {
"type": "http",
"port": 8080,
"active_clients": 5,
"uptime_seconds": 3600
},
"monitor": {
"active_watchers": 3,
"total_entries": 15420,
"dropped_entries": 0
},
"filters": {
"filter_count": 2,
"total_processed": 15420,
"total_passed": 1234,
"filters": [
{
"type": "include",
"logic": "or",
"pattern_count": 3,
"total_processed": 15420,
"total_matched": 1234,
"total_dropped": 0
}
]
},
"features": {
"rate_limit": {
"enabled": true,
"total_requests": 45678,
"blocked_requests": 234,
"active_ips": 23,
"total_connections": 5,
"config": {
"requests_per_second": 10,
"burst_size": 20,
"limit_by": "ip"
}
}
}
}
Real-time Statistics
LogWisp provides comprehensive statistics at multiple levels:
- Per-Stream Stats: Monitor performance, connection counts, data throughput
- Per-Watcher Stats: File size, position, entries read, rotation count
- Filter Stats: Processed entries, matched patterns, dropped logs
- Rate Limit Stats: Total requests, blocked requests, active IPs
- Global Stats: Aggregated view of all streams (in router mode)
Access statistics via status endpoints or watch the console output:
[15:04:05] Active streams: 2
app: watchers=3 entries=1542 tcp_conns=2 http_conns=5
system: watchers=2 entries=8901 tcp_conns=0 http_conns=3
Advanced Features
Log Filtering
LogWisp implements powerful regex-based filtering:
- Include Filters: Whitelist patterns - only matching logs pass
- Exclude Filters: Blacklist patterns - matching logs are dropped
- Logic Options: OR (match any) or AND (match all) for pattern combinations
- Filter Chains: Multiple filters applied sequentially
- Performance: Patterns compiled once at startup for efficiency
Filter statistics help monitor effectiveness:
# Watch filter statistics
watch -n 1 'curl -s http://localhost:8080/status | jq .filters'
Rate Limiting
LogWisp implements token bucket rate limiting with:
- Per-IP limiting: Each IP gets its own token bucket
- Global limiting: All clients share a single token bucket
- Connection limits: Restrict concurrent connections per IP
- Automatic cleanup: Stale IP entries removed after 5 minutes
- Non-blocking: Excess requests are immediately rejected with 429 status
Monitor rate limiting effectiveness:
# Watch rate limit statistics
watch -n 1 'curl -s http://localhost:8080/status | jq .features.rate_limit'
File Rotation Detection
LogWisp automatically detects log rotation through multiple methods:
- Inode change detection
- File size decrease
- Modification time anomalies
- Position beyond file size
When rotation is detected, a special log entry is generated:
{
"level": "INFO",
"message": "Log rotation detected (#1): inode change"
}
Buffer Management
- Non-blocking delivery: Messages are dropped rather than blocking when buffers fill
- Per-client buffers: Each client has independent buffer space
- Configurable sizes: Adjust buffer sizes based on expected load
Per-Stream Check Intervals
Optimize resource usage by configuring check intervals based on log update frequency:
# High-frequency application logs
[streams.monitor]
check_interval_ms = 50 # Check every 50ms
# Low-frequency system logs
[streams.monitor]
check_interval_ms = 60000 # Check every minute
Performance Tuning
Monitor Settings
check_interval_ms: Lower values = faster detection, higher CPU usage- Configure per-stream based on expected update frequency
- Use 10000ms+ for archival or slowly updating logs
Filter Optimization
- Place most selective filters first
- Use simple patterns when possible
- Consider combining patterns:
"ERROR|WARN"vs separate patterns - Monitor filter statistics to identify bottlenecks
Rate Limiting
requests_per_second: Balance between protection and availabilityburst_size: Set to 2-3x the per-second rate for traffic spikesmax_connections_per_ip: Prevent resource exhaustion from single IPs
File Watcher Optimization
- Use specific file paths when possible (more efficient than directory scanning)
- Adjust patterns to minimize unnecessary file checks
- Consider separate streams for different update frequencies
Network Optimization
- TCP: Best for high-volume, low-latency requirements
- HTTP/SSE: Best for browser compatibility and firewall traversal
- Router mode: Reduces port usage but adds slight routing overhead
Building from Source
# Clone repository
git clone https://github.com/lixenwraith/logwisp
cd logwisp
# Install dependencies
go mod init logwisp
go get github.com/panjf2000/gnet/v2
go get github.com/valyala/fasthttp
go get github.com/lixenwraith/config
# Build with version information
make build
# Run tests
make test
# Test rate limiting
./test_ratelimit.sh
# Test router functionality
./test_router.sh
# Create a release
make release TAG=v1.0.0
Makefile Targets
make build- Build binary with version informationmake install- Install to /usr/local/binmake clean- Remove built binarymake test- Run test suitemake release TAG=vX.Y.Z- Create and push git tag
Deployment
Systemd Service
[Unit]
Description=LogWisp Multi-Stream Log Monitor
After=network.target
[Service]
Type=simple
ExecStart=/usr/local/bin/logwisp --config /etc/logwisp/production.toml
Restart=always
User=logwisp
Group=logwisp
# Security hardening
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadOnlyPaths=/var/log
# Rate limiting at system level
LimitNOFILE=65536
[Install]
WantedBy=multi-user.target
Docker
FROM golang:1.24 AS builder
WORKDIR /app
COPY . .
RUN make build
FROM debian:bookworm-slim
RUN useradd -r -s /bin/false logwisp
COPY --from=builder /app/logwisp /usr/local/bin/
USER logwisp
EXPOSE 8080 9090
CMD ["logwisp"]
Docker Compose
version: '3.8'
services:
logwisp:
build: .
volumes:
- /var/log:/var/log:ro
- ./config.toml:/etc/logwisp/config.toml:ro
ports:
- "8080:8080"
- "9090:9090"
restart: unless-stopped
command: ["logwisp", "--config", "/etc/logwisp/config.toml"]
deploy:
resources:
limits:
cpus: '1.0'
memory: 512M
Security Considerations
Current Implementation
- Read-only file access
- Regex pattern validation at startup
- Rate limiting for DDoS protection
- Connection limits to prevent resource exhaustion
- No authentication (placeholder configuration only)
- No TLS/SSL support (placeholder configuration only)
Filter Security
⚠️ SECURITY: Be aware of potential ReDoS (Regular Expression Denial of Service) attacks:
- Complex nested patterns can cause CPU spikes
- Patterns are validated at startup but not for complexity
- Monitor filter processing time in production
- Consider pattern complexity limits for public-facing streams
Planned Security Features
- Authentication: Basic, Bearer/JWT, mTLS
- TLS/SSL: For both HTTP and TCP streams
- IP Filtering: Whitelist/blacklist support
- Audit Logging: Access and authentication events
- RBAC: Role-based access control per stream
Best Practices
- Run with minimal privileges (read-only access to log files)
- Configure appropriate rate limits based on expected traffic
- Use network-level security until authentication is implemented
- Place behind a reverse proxy for production HTTPS
- Monitor rate limit statistics for potential attacks
- Regularly update dependencies
- Test filter patterns for performance impact
- Limit regex complexity in production environments
Rate Limiting Best Practices
- Start with conservative limits and adjust based on monitoring
- Use per-IP limiting for public endpoints
- Use global limiting for resource protection
- Set connection limits to prevent memory exhaustion
- Monitor blocked request statistics for anomalies
Troubleshooting
Filter Issues
- Check filter statistics to see matched/dropped counts
- Test patterns with sample log entries
- Verify filter type (include vs exclude)
- Check filter logic (or vs and)
- Monitor CPU usage for complex patterns
Rate Limit Issues
- Check rate limit statistics in status endpoint
- Verify appropriate
requests_per_secondfor your use case - Ensure
burst_sizeaccommodates normal traffic spikes - Monitor for distributed attacks if per-IP limiting isn't effective
No Log Entries Appearing
- Check file permissions (LogWisp needs read access)
- Verify file paths in configuration
- Ensure files match the specified patterns
- Check monitor statistics in status endpoint
- Verify check_interval_ms is appropriate for log update frequency
- Review filter configuration - logs might be filtered out
High Memory Usage
- Reduce buffer sizes in configuration
- Lower the number of concurrent watchers
- Enable rate limiting to prevent connection floods
- Increase check interval for less critical logs
- Use TCP instead of HTTP for high-volume streams
- Check for complex regex patterns causing backtracking
Connection Drops
- Check heartbeat configuration
- Verify network stability
- Monitor client-side errors
- Review dropped entry statistics
- Check if rate limits are too restrictive
Version Information
Use ./logwisp --version to see:
- Version tag (from git tags)
- Git commit hash
- Build timestamp
License
BSD-3-Clause
Contributing
Contributions are welcome! Please read our contributing guidelines and submit pull requests to our repository.
Roadmap
- Multi-stream architecture
- File and directory monitoring
- TCP and HTTP/SSE streaming
- Path-based HTTP routing
- Per-stream check intervals
- Version management
- Configurable heartbeats
- Rate and connection limiting
- Regex-based log filtering
- Log transformation (field extraction, formatting)
- Configurable logging/stdout support
- Authentication (Basic, JWT, mTLS)
- TLS/SSL support
- Prometheus metrics export
- WebSocket support