Tutoriales

Parseando Timestamps de Logs: Guía Práctica con Ejemplos

Introducción

Los archivos de log contienen timestamps en muchos formatos diferentes. Saber cómo parsearlos correctamente es esencial para análisis de logs, monitoreo y debugging.

Lo que Aprenderás

  • ✅ Formatos comunes de timestamps en logs
  • ✅ Expresiones regulares para cada formato
  • ✅ Código de parsing en Python y JavaScript
  • ✅ Normalización a formato estándar
  • ✅ Manejo de zonas horarias

Formatos Comunes de Log

1. Apache/Nginx (CLF - Common Log Format)

[01/Dec/2024:10:15:30 +0000]

Regex:

regex
\[(\d{2}/\w{3}/\d{4}:\d{2}:\d{2}:\d{2} [+-]\d{4})\]

Python:

from datetime import datetime

def parse_apache(timestamp_str):
    # Ejemplo: "01/Dec/2024:10:15:30 +0000"
    return datetime.strptime(
        timestamp_str, 
        "%d/%b/%Y:%H:%M:%S %z"
    )

log_line = '[01/Dec/2024:10:15:30 +0000] "GET / HTTP/1.1" 200'
match = re.search(r'\[([^\]]+)\]', log_line)
if match:
    dt = parse_apache(match.group(1))
    print(dt.isoformat())

2. ISO 8601 / RFC 3339

2024-12-01T10:15:30Z
2024-12-01T10:15:30+01:00

Regex:

regex
\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:?\d{2})

JavaScript:

function parseIso8601(timestampStr) {
  return new Date(timestampStr);
}

// Ejemplo
const dt = parseIso8601("2024-12-01T10:15:30Z");
console.log(dt.toISOString());

3. Syslog (RFC 3164)

Dec  1 10:15:30 hostname app[123]: mensaje

Nota: Este formato NO incluye año ni zona horaria.

Python:

from datetime import datetime

def parse_syslog(timestamp_str, year=None):
    # Ejemplo: "Dec  1 10:15:30"
    if year is None:
        year = datetime.now().year

    dt = datetime.strptime(timestamp_str, "%b %d %H:%M:%S")
    return dt.replace(year=year)

# Extraer del log
import re
log_line = "Dec  1 10:15:30 servidor app[123]: error"
match = re.match(r'^(\w{3}\s+\d{1,2}\s\d{2}:\d{2}:\d{2})', log_line)
if match:
    dt = parse_syslog(match.group(1), year=2024)

4. Unix Timestamp

1701425730
1701425730000

JavaScript:

function parseUnixTimestamp(raw) {
  const str = String(raw);
  if (str.length === 10) {
    return new Date(Number(raw) * 1000);
  } else if (str.length === 13) {
    return new Date(Number(raw));
  }
  throw new Error('Formato desconocido');
}

Parser Universal

Python

from datetime import datetime, timezone
from dateutil import parser as dateutil_parser
import re

def parse_log_timestamp(raw: str) -> datetime:
    """Parser universal para timestamps de logs"""
    s = raw.strip()

    # 1. Intentar ISO 8601 / RFC 3339
    try:
        dt = dateutil_parser.isoparse(s)
        if dt.tzinfo is None:
            dt = dt.replace(tzinfo=timezone.utc)
        return dt
    except:
        pass

    # 2. Apache/Nginx: [01/Dec/2024:10:15:30 +0000]
    if s.startswith('[') and s.endswith(']'):
        inner = s[1:-1]
        dt = datetime.strptime(inner, "%d/%b/%Y:%H:%M:%S %z")
        return dt

    # 3. Unix timestamp numérico
    if s.isdigit():
        n = len(s)
        if n == 10:
            return datetime.fromtimestamp(int(s), tz=timezone.utc)
        if n == 13:
            return datetime.fromtimestamp(int(s)/1000, tz=timezone.utc)

    raise ValueError(f"Formato no reconocido: {raw}")

JavaScript

import { DateTime } from 'luxon';

function parseLogTimestamp(raw) {
  const s = raw.trim();

  // 1. ISO 8601
  const iso = DateTime.fromISO(s, { setZone: true });
  if (iso.isValid) return iso.toUTC();

  // 2. Apache/Nginx
  const apacheMatch = s.match(
    /\[(\d{2}\/\w{3}\/\d{4}:\d{2}:\d{2}:\d{2} [+-]\d{4})\]/
  );
  if (apacheMatch) {
    const dt = DateTime.fromFormat(
      apacheMatch[1], 
      "dd/MMM/yyyy:HH:mm:ss ZZZ"
    );
    if (dt.isValid) return dt.toUTC();
  }

  // 3. Unix timestamp
  if (/^\d+$/.test(s)) {
    const len = s.length;
    if (len === 10) return DateTime.fromSeconds(Number(s));
    if (len === 13) return DateTime.fromMillis(Number(s));
  }

  throw new Error(`Formato no reconocido: ${raw}`);
}

Normalización

Siempre normaliza a un formato estándar después de parsear:

def normalizar_timestamp(dt: datetime) -> dict:
    """Normaliza datetime a formatos útiles"""
    utc = dt.astimezone(timezone.utc)
    return {
        'iso': utc.isoformat(),
        'unix_s': int(utc.timestamp()),
        'unix_ms': int(utc.timestamp() * 1000),
        'utc': utc
    }

Mejores Prácticas

  1. Parsea a UTC primero - Evita problemas de zona horaria
  2. Valida el rango - Rechaza fechas implausibles
  3. Maneja años faltantes - Syslog no incluye año
  4. Usa bibliotecas probadas - No reinventes el wheel
  5. Registra errores de parsing - Para depuración

Resumen

Has aprendido a parsear timestamps de:

  • Apache/Nginx (CLF)
  • ISO 8601 / RFC 3339
  • Syslog
  • Unix timestamps

¡Usa nuestro Convertidor por Lotes para procesar múltiples timestamps!