"""Inject test visitor log entries into the shared logs directory. Writes JSON-formatted access log lines (matching the nginx kmtn_json format) for multiple past days so the log parser will pick them up — data is only aggregated a day in arrears, so all dates are strictly before today. Usage: python inject_test_logs.py # writes to /logs (default) python inject_test_logs.py /tmp/logs # writes to custom path python inject_test_logs.py /logs 14 # writes 14 days of data """ import json import os import sys import hashlib import struct from datetime import date, timedelta, timezone # Real, well-known public IPs that resolve in MaxMind GeoLite2 City. # These are actual infrastructure IPs (DNS, CDN, search, cloud) — # not random addresses, so they're far more likely to be in the DB. # Each tuple: (ip, expected_country, expected_city) TEST_IPS = [ # United States ("8.8.8.8", "US", "Mountain View"), # Google DNS ("8.8.4.4", "US", "Mountain View"), # Google DNS ("216.58.214.206", "US", "Mountain View"), # Google ("142.250.80.46", "US", "Mountain View"), # Google ("13.107.42.14", "US", "Redmond"), # Microsoft ("20.190.128.1", "US", "Boydton"), # Microsoft Azure ("208.67.222.222", "US", "San Francisco"), # OpenDNS ("208.67.220.220", "US", "San Francisco"), # OpenDNS ("151.101.1.69", "US", "San Francisco"), # Fastly ("52.84.74.1", "US", "Seattle"), # AWS CloudFront ("99.86.0.1", "US", "Seattle"), # AWS ("54.239.28.85", "US", "Seattle"), # AWS ("185.199.108.1", "US", "San Francisco"), # GitHub Pages # United Kingdom ("212.58.244.3", "GB", "London"), # Virgin Media ("178.173.57.1", "GB", "London"), # DigitalOcean ("51.15.48.1", "GB", "London"), # OVH UK # France ("51.140.137.15", "FR", "Paris"), # OVH Paris ("193.70.150.1", "FR", "Paris"), # Free # Germany ("217.160.0.1", "DE", "Frankfurt"), # Telekom ("212.227.156.1", "DE", "Munich"), # Deutsche Telekom ("217.69.70.1", "DE", "Frankfurt"), # Freenet # Japan ("210.170.232.46", "JP", "Tokyo"), # KDDI ("202.214.69.1", "JP", "Tokyo"), # NTT ("210.188.206.1", "JP", "Tokyo"), # Softbank # Australia ("1.35.128.1", "AU", "Sydney"), # Telstra ("203.50.224.1", "AU", "Melbourne"), # iiNet # Brazil ("177.72.120.1", "BR", "Sao Paulo"), # Vivo ("200.149.130.1", "BR", "Rio de Janeiro"), # Telefonica Brasil ("189.75.1.1", "BR", "Sao Paulo"), # Claro # Canada ("76.69.248.1", "CA", "Toronto"), # Bell ("24.199.12.1", "CA", "Vancouver"), # Shaw ("99.248.1.1", "CA", "Montreal"), # Bell # India ("117.239.1.1", "IN", "Mumbai"), # Reliance Jio ("180.151.1.1", "IN", "New Delhi"), # Airtel ("106.192.1.1", "IN", "Bangalore"), # BSNL # South Korea ("1.235.198.1", "KR", "Seoul"), # KT ("211.36.1.1", "KR", "Seoul"), # SK Telecom # Netherlands ("185.30.1.1", "NL", "Amsterdam"), # KPN ("94.23.1.1", "NL", "Amsterdam"), # XS4ALL # Mexico ("189.203.1.1", "MX", "Mexico City"), # Telmex ("200.44.1.1", "MX", "Guadalajara"), # Telcel # Italy ("217.20.1.1", "IT", "Rome"), # Telecom Italia ("93.45.1.1", "IT", "Milan"), # TIM # Spain ("213.99.1.1", "ES", "Madrid"), # Telefonica Espana ("88.0.1.1", "ES", "Madrid"), # Movistar # Sweden ("213.248.1.1", "SE", "Stockholm"), # Telia # Poland ("79.133.1.1", "PL", "Warsaw"), # Orange Polska # Argentina ("190.15.1.1", "AR", "Buenos Aires"), # Telecom Argentina # South Africa ("196.201.1.1", "ZA", "Johannesburg"), # Telkom SA # Egypt ("213.53.1.1", "EG", "Cairo"), # Telecom Egypt # Turkey ("37.146.1.1", "TR", "Istanbul"), # Turk Telekom # Russia ("77.88.55.1", "RU", "Moscow"), # Yandex DNS ("5.255.255.1", "RU", "Moscow"), # Rostelecom # Singapore ("182.16.1.1", "SG", "Singapore"), # Singtel # New Zealand ("210.55.1.1", "NZ", "Auckland"), # Telecom NZ ] def _deterministic_count(seed: str) -> int: """Return a pseudo-random visit count (1-8) for a given seed.""" h = hashlib.md5(seed.encode()).digest() return struct.unpack("I", h[:4])[0] % 8 + 1 def _deterministic_hour(seed: str) -> int: """Return a pseudo-random hour (0-23) for a given seed.""" h = hashlib.md5(seed.encode()).digest() return struct.unpack("I", h[:4])[0] % 24 def write_test_logs(logs_dir: str, days: int = 14): """Write test log entries for the given number of past days. Skips today (parser only processes files strictly before today). Writes a realistic mix of visitors per day with varying counts. """ os.makedirs(logs_dir, exist_ok=True) today = date.today() total_entries = 0 files_written = 0 # Write for the last `days` days, skipping today for offset in range(1, days + 1): file_date = today - timedelta(days=offset) filename = f"access-{file_date.isoformat()}.log" filepath = os.path.join(logs_dir, filename) entries: list[str] = [] for ip, country, city in TEST_IPS: seed = f"{file_date}-{ip}" count = _deterministic_count(seed) hour = _deterministic_hour(seed) for j in range(count): # Deterministic minute spread (hash() is non-deterministic across runs) ip_hash = struct.unpack("I", hashlib.md5(ip.encode()).digest()[4:8])[0] minute = (j * 7 + ip_hash % 60) % 60 entry = { "time": f"{file_date}T{hour}:{minute:02d}:00+00:00", "remote_addr": ip, "x_forwarded_for": "-", } entries.append(json.dumps(entry)) with open(filepath, "a") as f: for line in entries: f.write(line + "\n") total_entries += len(entries) files_written += 1 print(f"Wrote {total_entries} log entries across {files_written} daily files ({len(TEST_IPS)} unique IPs)") print(f"Date range: {(today - timedelta(days=days)).isoformat()} to {(today - timedelta(days=1)).isoformat()}") print(f"Directory: {logs_dir}") if __name__ == "__main__": target = sys.argv[1] if len(sys.argv) > 1 else "/logs" num_days = int(sys.argv[2]) if len(sys.argv) > 2 else 14 write_test_logs(target, days=num_days)