Guías
Timestamps en Logs: Parsing, Normalización y Corrección de Errores
Por qué esta página
Los logs llegan con formatos mixtos, zonas horarias y precisiones diferentes. Un timestamp incorrecto rompe el ordenamiento, dashboards y líneas de tiempo de incidentes. Esta guía te da verificaciones rápidas, plantillas regex y fragmentos de código para normalizar tiempos de logs rápidamente—y te dirige a convertidores cuando necesitas verificar casos límite.
Lista de verificación de diagnóstico rápido
- Identificar formato: ISO 8601/RFC 3339, Apache/Nginx (
[dd/Mon/yyyy:HH:mm:ss Z]), syslog (MMM d HH:mm:ss), o personalizado. - Verificar zona horaria: desplazamiento explícito (
+0100,Z) vs. local implícito. Usar UTC por defecto si falta. - Verificar precisión: segundos vs. milisegundos vs. microsegundos; asegurar longitud consistente.
- Manejar bordes DST: parsear a UTC primero; evitar parsing local naive.
- Aplicar guardias de rango: rechazar años implausibles (ej.,
<2000o>2100).
Formatos de log comunes (plantillas)
- ISO 8601 / RFC 3339:
2024-12-01T10:15:30Z,2024-12-01T18:15:30+01:00 - Apache/Nginx:
[01/Dec/2024:10:15:30 +0000] - Syslog:
Dec 1 10:15:30 hostname app[123]: mensaje(sin año/desplazamiento) - Numérico personalizado:
1701425730(segundos),1701425730000(ms)
Pasos de normalización
- Detectar formato y zona horaria. Si falta, asumir UTC.
- Parsear a datetime aware (UTC).
- Si es epoch numérico: escalar por longitud (10s/13ms/16µs/19ns).
- Almacenar en columna canónica (ej., millis UTC) y opcionalmente mantener la cadena raw.
- Validar rango; descartar o marcar valores atípicos antes de indexar.
Fragmentos de código
JavaScript / Node.js
import { DateTime } from "luxon";
export function parseLogTime(raw) {
const trimmed = raw.trim();
// ISO/RFC primero
const iso = DateTime.fromISO(trimmed, { setZone: true });
if (iso.isValid) return iso.toUTC().toMillis();
// Apache/Nginx: [01/Dec/2024:10:15:30 +0000]
const m = trimmed.match(/\[(\d{2}\/\w{3}\/\d{4}:\d{2}:\d{2}:\d{2} [+-]\d{4})\]/);
if (m) {
const dt = DateTime.fromFormat(m[1], "dd/MMM/yyyy:HH:mm:ss ZZZZ", { zone: "utc" });
if (dt.isValid) return dt.toMillis();
}
// Epoch numérico (segundos/ms/us/ns)
if (/^\d+$/.test(trimmed)) {
const len = trimmed.length;
if (len === 10) return Number(trimmed) * 1000;
if (len === 13) return Number(trimmed);
if (len === 16) return Number(trimmed) / 1000;
if (len === 19) return Number(trimmed) / 1_000_000;
}
throw new Error("Timestamp de log no reconocido");
}
Python
from datetime import datetime, timezone, timedelta
from dateutil import parser
def parse_log_time(raw: str) -> int:
s = raw.strip()
# ISO/RFC 3339, syslog con tz, etc.
try:
dt = parser.isoparse(s)
if dt.tzinfo is None:
dt = dt.replace(tzinfo=timezone.utc)
return int(dt.astimezone(timezone.utc).timestamp() * 1000)
except Exception:
pass
# 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 int(dt.astimezone(timezone.utc).timestamp() * 1000)
# Epoch numérico
if s.isdigit():
n = len(s)
if n == 10:
return int(s) * 1000
if n == 13:
return int(s)
if n == 16:
return int(int(s) / 1000)
if n == 19:
return int(int(s) / 1_000_000)
raise ValueError("Timestamp de log no reconocido")
Plantillas Regex
regex
# ISO 8601 / RFC 3339 con desplazamiento
^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:?\d{2})$
# Apache/Nginx
^\[\d{2}/[A-Za-z]{3}/\d{4}:\d{2}:\d{2}:\d{2} [+-]\d{4}\]$
# Syslog (sin año/desplazamiento, añadir externamente)
^[A-Za-z]{3}\s+\d{1,2}\s\d{2}:\d{2}:\d{2}\s.+$
FAQ
- ¿Qué hacer si falta la zona horaria? Asumir UTC y documentar la suposición.
- ¿Cómo manejar logs de múltiples fuentes? Normalizar todo a UTC millis antes de agregación.
- ¿Qué hay de los logs syslog sin año? Usar el año actual o inferir del contexto del archivo.