Files
kmtnflower/backend/inject_test_logs.py

170 lines
7.0 KiB
Python

"""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)