|
| 1 | +#!/usr/bin/env python3 |
| 2 | +""" |
| 3 | +loadgen.py — Log load generator for testing log-explainer. |
| 4 | +
|
| 5 | +Appends realistic log lines to a file for a given duration, |
| 6 | +with weighted severity distribution. |
| 7 | +
|
| 8 | +Usage: |
| 9 | + python3 loadgen.py # append to sample.log for 5s |
| 10 | + python3 loadgen.py --output /tmp/test.log # custom output file |
| 11 | + python3 loadgen.py --duration 30 # run for 30 seconds |
| 12 | + python3 loadgen.py --rate 5 # 5 lines per second |
| 13 | +""" |
| 14 | + |
| 15 | +import argparse |
| 16 | +import random |
| 17 | +import time |
| 18 | +from datetime import datetime |
| 19 | + |
| 20 | +# ── Log line pool (100 entries) ─────────────────────────────────────────────── |
| 21 | +# Each entry is (severity, message) |
| 22 | + |
| 23 | +LOG_LINES = [ |
| 24 | + # INFO (40 entries) |
| 25 | + ("INFO", "Starting application on port 8000"), |
| 26 | + ("INFO", "Connected to PostgreSQL at postgres:5432"), |
| 27 | + ("INFO", "Redis cache initialized successfully"), |
| 28 | + ("INFO", "Worker pool started with 4 threads"), |
| 29 | + ("INFO", "GET /api/users 200 OK (45ms)"), |
| 30 | + ("INFO", "POST /api/orders 201 Created (112ms)"), |
| 31 | + ("INFO", "GET /api/products 200 OK (23ms)"), |
| 32 | + ("INFO", "PUT /api/users/42 200 OK (67ms)"), |
| 33 | + ("INFO", "DELETE /api/sessions/8821 200 OK (18ms)"), |
| 34 | + ("INFO", "Scheduled job 'cleanup_expired_tokens' started"), |
| 35 | + ("INFO", "Scheduled job 'cleanup_expired_tokens' completed in 340ms"), |
| 36 | + ("INFO", "Cache hit ratio: 87.4% over last 60s"), |
| 37 | + ("INFO", "Database connection pool size: 10/20"), |
| 38 | + ("INFO", "Listening on 0.0.0.0:8000"), |
| 39 | + ("INFO", "Health check passed: all systems nominal"), |
| 40 | + ("INFO", "Config reloaded from /etc/app/config.yaml"), |
| 41 | + ("INFO", "TLS certificate valid, expires in 87 days"), |
| 42 | + ("INFO", "New user registered: user_id=10482"), |
| 43 | + ("INFO", "Session created for user_id=10482, ttl=3600s"), |
| 44 | + ("INFO", "Metrics exported to Prometheus endpoint /metrics"), |
| 45 | + ("INFO", "Graceful shutdown initiated, draining connections"), |
| 46 | + ("INFO", "Background task 'send_digest_emails' queued"), |
| 47 | + ("INFO", "S3 upload completed: reports/2026-04-12.csv (2.3MB)"), |
| 48 | + ("INFO", "Kafka consumer group 'order-events' rebalanced, partitions=3"), |
| 49 | + ("INFO", "Feature flag 'new_checkout_flow' enabled for 10% of traffic"), |
| 50 | + ("INFO", "Rate limiter reset for IP 203.0.113.42"), |
| 51 | + ("INFO", "Audit log written: admin user_id=1 deleted user_id=304"), |
| 52 | + ("INFO", "gRPC server started on port 50051"), |
| 53 | + ("INFO", "Dependency check passed: postgres=14.5, redis=7.0.5"), |
| 54 | + ("INFO", "Request tracing enabled, trace_id=7f3a9c21b4e0"), |
| 55 | + ("INFO", "CDN cache purged for path /static/app.js"), |
| 56 | + ("INFO", "OAuth token refreshed for service account svc-worker"), |
| 57 | + ("INFO", "Compaction triggered on table 'events' (2.1GB freed)"), |
| 58 | + ("INFO", "Websocket connection established, client_id=ws-8821"), |
| 59 | + ("INFO", "Batch job processed 1200 records in 4.2s"), |
| 60 | + ("INFO", "Replica lag is 0ms, replication healthy"), |
| 61 | + ("INFO", "Circuit breaker for payment-service is CLOSED"), |
| 62 | + ("INFO", "Auto-scaling: added 1 worker node, total=5"), |
| 63 | + ("INFO", "Log rotation completed, archived to /var/log/app/2026-04-11.gz"), |
| 64 | + ("INFO", "Startup complete, ready to serve traffic"), |
| 65 | + |
| 66 | + # WARN (30 entries) |
| 67 | + ("WARNING", "Retrying database connection attempt 1 of 5"), |
| 68 | + ("WARNING", "Retrying database connection attempt 2 of 5"), |
| 69 | + ("WARNING", "Response time degraded: GET /api/reports took 2300ms"), |
| 70 | + ("WARNING", "Memory usage at 78%, approaching threshold"), |
| 71 | + ("WARNING", "Deprecated API endpoint /v1/users called by client 10.0.0.5"), |
| 72 | + ("WARNING", "Redis cache miss rate exceeded 30% in last 60s"), |
| 73 | + ("WARNING", "Disk usage at 82% on /var/data, consider cleanup"), |
| 74 | + ("WARNING", "JWT token expiring soon for user_id=204, issuing refresh"), |
| 75 | + ("WARNING", "Rate limit approaching for IP 198.51.100.7: 95/100 requests"), |
| 76 | + ("WARNING", "Kafka consumer lag growing: topic=order-events lag=1500"), |
| 77 | + ("WARNING", "Slow query detected: SELECT * FROM events took 4100ms"), |
| 78 | + ("WARNING", "Connection pool saturation: 19/20 connections in use"), |
| 79 | + ("WARNING", "TLS certificate expires in 14 days, renewal required"), |
| 80 | + ("WARNING", "Retry attempt 1 for HTTP POST to payment-service"), |
| 81 | + ("WARNING", "Retry attempt 2 for HTTP POST to payment-service"), |
| 82 | + ("WARNING", "Background worker queue depth: 850 jobs pending"), |
| 83 | + ("WARNING", "Config value 'MAX_UPLOAD_SIZE' not set, using default 10MB"), |
| 84 | + ("WARNING", "Health check latency elevated: postgres responded in 850ms"), |
| 85 | + ("WARNING", "Graceful shutdown timeout exceeded, forcing exit"), |
| 86 | + ("WARNING", "S3 upload retrying due to transient error (attempt 2/3)"), |
| 87 | + ("WARNING", "Feature flag service unreachable, falling back to defaults"), |
| 88 | + ("WARNING", "CPU usage spike to 91% on worker-3"), |
| 89 | + ("WARNING", "Replica lag increased to 250ms, monitoring"), |
| 90 | + ("WARNING", "Circuit breaker for inventory-service is HALF-OPEN"), |
| 91 | + ("WARNING", "Auto-vacuum running on table 'orders', may cause slowdown"), |
| 92 | + ("WARNING", "Timeout waiting for lock on resource 'user:8821'"), |
| 93 | + ("WARNING", "Websocket client ws-4421 disconnected unexpectedly"), |
| 94 | + ("WARNING", "DNS resolution for cache.internal took 900ms"), |
| 95 | + ("WARNING", "OpenTelemetry exporter dropped 12 spans due to buffer overflow"), |
| 96 | + ("WARNING", "Rollout paused: error rate above 2% threshold"), |
| 97 | + |
| 98 | + # ERROR (20 entries) |
| 99 | + ("ERROR", "Connection refused to postgres:5432"), |
| 100 | + ("ERROR", "Traceback (most recent call last): File 'db.py' line 42 in connect"), |
| 101 | + ("ERROR", "psycopg2.OperationalError: could not connect to server"), |
| 102 | + ("ERROR", "HTTP 500 Internal Server Error on POST /api/checkout"), |
| 103 | + ("ERROR", "Failed to publish event to Kafka: topic=order-events"), |
| 104 | + ("ERROR", "Redis SETNX failed: timeout after 5000ms"), |
| 105 | + ("ERROR", "Unhandled exception in worker thread: ZeroDivisionError"), |
| 106 | + ("ERROR", "S3 upload failed after 3 retries: AccessDenied"), |
| 107 | + ("ERROR", "Payment service returned 503: Service Unavailable"), |
| 108 | + ("ERROR", "Database transaction rolled back: deadlock detected"), |
| 109 | + ("ERROR", "JWT verification failed: signature mismatch for user_id=8801"), |
| 110 | + ("ERROR", "Failed to acquire connection from pool after 10s"), |
| 111 | + ("ERROR", "gRPC call to inventory-service failed: UNAVAILABLE"), |
| 112 | + ("ERROR", "Config file /etc/app/config.yaml not found, cannot reload"), |
| 113 | + ("ERROR", "Batch job 'send_digest_emails' failed: SMTP connection refused"), |
| 114 | + ("ERROR", "Disk write error on /var/data: No space left on device"), |
| 115 | + ("ERROR", "Failed to parse request body: invalid JSON"), |
| 116 | + ("ERROR", "Websocket write error for client ws-8821: broken pipe"), |
| 117 | + ("ERROR", "Elasticsearch index 'logs-2026' is read-only: disk watermark exceeded"), |
| 118 | + ("ERROR", "Circuit breaker for payment-service OPENED after 5 consecutive failures"), |
| 119 | + |
| 120 | + # CRITICAL (10 entries) |
| 121 | + ("CRITICAL", "FATAL: max connection retries exhausted, shutting down"), |
| 122 | + ("CRITICAL", "OOMKilled: process exceeded memory limit of 512MB"), |
| 123 | + ("CRITICAL", "PANIC: nil pointer dereference in handler.go:84"), |
| 124 | + ("CRITICAL", "segfault at address 0x00000000, core dumped"), |
| 125 | + ("CRITICAL", "FATAL: WAL log corrupted, database cannot start"), |
| 126 | + ("CRITICAL", "OutOfMemory: JVM heap space exhausted, killing process"), |
| 127 | + ("CRITICAL", "FATAL: SSL certificate validation failed, refusing to start"), |
| 128 | + ("CRITICAL", "Data loss detected: replication stream broken for 120s"), |
| 129 | + ("CRITICAL", "FATAL: etcd cluster quorum lost, all writes rejected"), |
| 130 | + ("CRITICAL", "Kernel OOM killer terminated PID 8821 (app-worker)"), |
| 131 | +] |
| 132 | + |
| 133 | +# ── Severity weights (must sum to 100) ─────────────────────────────────────── |
| 134 | +# Mirrors realistic production log distribution |
| 135 | +SEVERITY_WEIGHTS = { |
| 136 | + "INFO": 40, |
| 137 | + "WARNING": 30, |
| 138 | + "ERROR": 20, |
| 139 | + "CRITICAL": 10, |
| 140 | +} |
| 141 | + |
| 142 | +# Pre-bucket lines by severity for fast weighted sampling |
| 143 | +_BUCKETS = {} |
| 144 | +for sev, weight in SEVERITY_WEIGHTS.items(): |
| 145 | + _BUCKETS[sev] = [msg for s, msg in LOG_LINES if s == sev] |
| 146 | + |
| 147 | +_POPULATION = list(SEVERITY_WEIGHTS.keys()) |
| 148 | +_WEIGHTS = list(SEVERITY_WEIGHTS.values()) |
| 149 | + |
| 150 | + |
| 151 | +def pick_line() -> tuple[str, str]: |
| 152 | + severity = random.choices(_POPULATION, weights=_WEIGHTS, k=1)[0] |
| 153 | + message = random.choice(_BUCKETS[severity]) |
| 154 | + return severity, message |
| 155 | + |
| 156 | + |
| 157 | +def write_line(f, severity: str, message: str): |
| 158 | + ts = datetime.now().strftime("%Y-%m-%d %H:%M:%S,%f")[:-3] |
| 159 | + line = f"{ts} {severity:<8} {message}\n" |
| 160 | + f.write(line) |
| 161 | + f.flush() |
| 162 | + print(line, end="") |
| 163 | + |
| 164 | + |
| 165 | +def main(): |
| 166 | + parser = argparse.ArgumentParser( |
| 167 | + description="Append realistic log lines to a file for testing log-explainer.", |
| 168 | + formatter_class=argparse.RawDescriptionHelpFormatter, |
| 169 | + epilog=""" |
| 170 | +Examples: |
| 171 | + python3 loadgen.py |
| 172 | + python3 loadgen.py --output /tmp/test.log --duration 30 |
| 173 | + python3 loadgen.py --rate 10 --duration 60 |
| 174 | +
|
| 175 | +Then in another terminal: |
| 176 | + python3 log_parser.py sample.log --model qwen2.5-coder:1.5b |
| 177 | + """, |
| 178 | + ) |
| 179 | + parser.add_argument("--output", default="sample.log", help="Log file to append to (default: sample.log)") |
| 180 | + parser.add_argument("--duration", default=5, type=int, help="How long to run in seconds (default: 5)") |
| 181 | + parser.add_argument("--rate", default=2, type=int, help="Log lines per second (default: 2)") |
| 182 | + args = parser.parse_args() |
| 183 | + |
| 184 | + interval = 1.0 / args.rate |
| 185 | + end_time = time.time() + args.duration |
| 186 | + |
| 187 | + print(f"Writing to '{args.output}' for {args.duration}s at {args.rate} lines/sec") |
| 188 | + print(f"Severity weights: {SEVERITY_WEIGHTS}") |
| 189 | + print("─" * 60) |
| 190 | + |
| 191 | + count = 0 |
| 192 | + with open(args.output, "a") as f: |
| 193 | + while time.time() < end_time: |
| 194 | + severity, message = pick_line() |
| 195 | + write_line(f, severity, message) |
| 196 | + count += 1 |
| 197 | + time.sleep(interval) |
| 198 | + |
| 199 | + print("─" * 60) |
| 200 | + print(f"Done. Wrote {count} lines to '{args.output}'.") |
| 201 | + |
| 202 | + |
| 203 | +if __name__ == "__main__": |
| 204 | + main() |
0 commit comments