""" Parse nginx ingress-controller log lines. Log format: - - [] " " "" "" [] [] """ import re import logging from datetime import datetime, timezone logger = logging.getLogger(__name__) # The leading \S* tolerates stray chars (e.g. 'z') sometimes prepended by log shippers. _LOG_RE = re.compile( r'\S*ingress-nginx\S+\s+' # pod name r'(?P\S+)\s+' # container r'(?P[\d.a-fA-F:]+)\s+-\s+-\s+' # IP - - r'\[(?P[^\]]+)\]\s+' # [timestamp] r'"(?P\S+)\s+(?P\S+)\s+' # "METHOD /path r'(?P[^"]+)"\s+' # PROTO" r'(?P\d+)\s+' # status r'(?P\d+)\s+' # bytes r'"(?P[^"]*)"\s+' # "referer" r'"(?P[^"]*)"\s+' # "ua" r'(?P\d+)\s+' # req_len r'(?P[\d.]+)\s+' # req_time r'\[(?P[^\]]*)\]\s+' # [service] r'\[(?P[^\]]*)\]\s+' # [] r'(?P\S+)\s+' # upstream addr r'(?P\d+)\s+' # upstream bytes r'(?P[\d.]+)\s+' # upstream time r'(?P\d+)\s+' # upstream status r'(?P\S+)' # request id ) _TIME_FMT = '%d/%b/%Y:%H:%M:%S %z' def parse_line(line: str) -> dict | None: """Return a parsed dict or None if the line doesn't match.""" m = _LOG_RE.search(line) if not m: return None d = m.groupdict() try: ts = datetime.strptime(d['time_local'], _TIME_FMT) except ValueError: logger.debug('Bad timestamp: %s', d['time_local']) return None return { 'timestamp': ts, 'remote_addr': d['remote_addr'], 'method': d['method'], 'request_uri': d['request_uri'], 'protocol': d['protocol'].strip(), 'status': int(d['status']), 'body_bytes_sent': int(d['body_bytes_sent']), 'http_referer': d['http_referer'], 'http_user_agent': d['http_user_agent'], 'request_length': int(d['request_length']), 'request_time': float(d['request_time']), 'service': d['service'], 'upstream_addr': d['upstream_addr'], 'upstream_response_time': float(d['upstream_response_time']), 'upstream_status': int(d['upstream_status']), 'request_id': d['request_id'], } def parse_lines(text: str) -> list[dict]: results = [] for line in text.splitlines(): line = line.strip() if not line: continue parsed = parse_line(line) if parsed: results.append(parsed) else: logger.debug('Skipped unparseable line: %.120s', line) return results