Indice / Practicas / EJ-1.4
EJ-1.4 · Log de servidor: sintactico vs semantico
El archivo JSON es perfectamente válido y sus 800 elementos son
cadenas. json.load no mira lo que hay adentro de una cadena: esa segunda
etapa del parseo es tuya, y ahí están los nueve problemas.
Qué hay que hacer
A partir de servidor_acceso_log.json (800 líneas de acceso), producir:
acceso_parseado.csv- las 791 líneas utilizables, con sus seis campos en columnas y el número de línea.lineas_rechazadas.txt- las 9 que no, con el número, la columna, el motivo y la línea cruda.
Sin pandas: json.load, una expresión regular y un validador por
campo.
ELI5Explicado facil
Es la diferencia entre un formulario completo y un formulario verdadero.
Que estén los seis casilleros llenos lo ve cualquiera de un vistazo. Que el casillero «fecha de nacimiento» diga una fecha posible, no: eso hay que leerlo.
Archivos
- Entrada ·
servidor_acceso_log.json - Objetivo ·
acceso_parseado.csv - Objetivo ·
lineas_rechazadas.txt - Herramienta ·
comparar.py
Las dos clases de línea rota
| Línea | Contenido | Capa | Motivo |
|---|---|---|---|
| 60 | linea invalida | 2 | restriccion_violada |
| 150 | Le faltan dos campos | 2 | restriccion_violada |
| 240 | Vacía | 2 | restriccion_violada |
| 330 | Método FETCH | 3 | dominio_desconocido |
| 420 | Fecha = fecha_invalida | 3 | fecha_invalida |
| 510 | Estado 999 | 3 | fuera_de_rango |
| 600 | Duración -50 ms | 3 | negativo_incoherente |
| 700 | Estado doscientos | 3 | no_numerico |
| 750 | Separada con ; | 2 | restriccion_violada |
fecha_invalida 10.0.0.6 GET / 200 45 tiene seis campos separados por
espacios: pasa el patrón sin problema. Que el primero no sea una fecha
es una pregunta distinta, y la tiene que hacer tu código.
Es el malentendido más caro de la unidad. Si tu resultado da 796 líneas parseadas, validaste solo la forma. Con las cinco de la capa 3 encontradas, da 791.
Cómo saber si te salió
python comparar.py mi_parseado.csv acceso_parseado.csv
El informe de texto se mira contra el extracto de esta página.
El contrato de salida
acceso_parseado.csv · 791 filas
| Columna | Tipo | Que lleva |
|---|---|---|
linea | entero | Posicion del elemento en la lista 'lineas', empezando en 1 |
fecha | texto | La marca de tiempo ISO tal cual vino: AAAA-MM-DDTHH:MM:SS |
ip | texto | La direccion IP tal cual vino |
metodo | texto | Uno de: DELETE, GET, HEAD, PATCH, POST, PUT |
ruta | texto | La ruta pedida, tal cual vino |
estado | entero | El codigo HTTP, entre 100 y 599 |
ms | entero | La duracion en milisegundos, cero o positiva |
lineas_rechazadas.txt · 21 filas
| Columna | Tipo | Que lleva |
|---|
Que hacer con cada defecto
| Defecto | Donde | Accion | Adonde va |
|---|---|---|---|
| la linea no tiene seis campos separados por espacios | 4 lineas: la 60 ('linea invalida'), la 150 (cuatro campos), la 240 (vacia) y la 750 (separada con punto y coma) | rechazar. rechazar. Es capa 2: no hay campos que validar todavia | lineas_rechazadas.txt con motivo restriccion_violada y columna 'linea' |
| el primer campo no es una fecha | linea 420, con el texto literal 'fecha_invalida' | rechazar. rechazar. La linea SI tiene seis campos: pasa la capa 2 y solo la ve la capa 3 | lineas_rechazadas.txt con motivo fecha_invalida |
| metodo fuera del dominio | linea 330, con FETCH | rechazar. rechazar. Los metodos validos son los seis de la lista | lineas_rechazadas.txt con motivo dominio_desconocido |
| codigo de estado que no es un numero | linea 700, con 'doscientos' | rechazar rechazar | lineas_rechazadas.txt con motivo no_numerico |
| codigo de estado fuera de rango | linea 510, con 999 | rechazar. rechazar. Los codigos HTTP van de 100 a 599 | lineas_rechazadas.txt con motivo fuera_de_rango |
| duracion negativa | linea 600, con -50 milisegundos | rechazar. rechazar. Es un numero valido y una duracion imposible | lineas_rechazadas.txt con motivo negativo_incoherente |
| IP con forma incorrecta | 0 lineas en este archivo | la la comprobacion va igual: cuatro grupos de hasta tres digitos separados por punto | lineas_rechazadas.txt con motivo restriccion_violada |
Una fila puede tener mas de un problema. Se informa solo el primero de esta lista:
- capa 2: la linea entera contra el patron de seis campos
- capa 3, en este orden: fecha, ip, metodo, estado numerico, estado en rango, ms numerico, ms no negativo
- la primera comprobacion que falla es la que se informa: una linea se rechaza una sola vez
La capa 2 va primero porque las capas siguientes necesitan campos para mirar. Y dentro de la capa 3 el orden es el de las columnas: no cambia el resultado en este archivo, pero fija el motivo cuando una linea tenga dos defectos.
Conteos clave
| lineas de entrada | 800 |
|---|---|
| acceso_parseado.csv | 791 |
| lineas_rechazadas.txt | 21 |
| rechazadas en la capa 2 | 4 |
| rechazadas en la capa 3 | 5 |
| por motivo | restriccion_violada: 4, fecha_invalida: 1, dominio_desconocido: 1, no_numerico: 1, fuera_de_rango: 1, negativo_incoherente: 1 |
Invariantes
Comprobaciones que tu resultado tiene que cumplir. Corrilas vos mismo antes de comparar los archivos:
len(parseado) == 791
len(parseado) + 9 == 800
parseado['linea'].is_unique
parseado['linea'].is_monotonic_increasing
parseado['linea'].between(1, 800).all()
not parseado.isna().any().any()
parseado['estado'].between(100, 599).all()
(parseado['ms'] >= 0).all()
set(parseado['metodo']) <= {'DELETE', 'GET', 'HEAD', 'PATCH', 'POST', 'PUT'}
parseado['fecha'].str.match(r'\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}$').all()
PISTASSi te trabas, abri esto
- La linea 'fecha_invalida 10.0.0.6 GET / 200 45' TIENE seis campos: el patron la acepta. Si tu resultado la deja pasar, estas confiando en que la regex valida el significado, y solo valida la forma.
- fullmatch, no match: match se conforma con que el principio coincida, asi que '2026-08-24Txx' pasaria como fecha.
- El estado 999 es un entero perfectamente valido. Lo rechaza el rango 100 a 599, que sale del protocolo HTTP y no de los datos.
- Si te dan 796 parseadas, validaste solo la forma. Si te dan 791, encontraste tambien las cinco de la capa 3.
Así tienen que quedar
| linea | fecha | ip | metodo | ruta | estado | ms |
|---|---|---|---|---|---|---|
| 1 | 2026-08-24T08:01:01 | 10.0.0.28 | PUT | /login | 200 | 102 |
| 2 | 2026-08-24T08:01:12 | 10.0.0.31 | DELETE | /salir | 200 | 515 |
| 3 | 2026-08-24T08:01:53 | 10.0.0.31 | POST | /salir | 201 | 484 |
| 4 | 2026-08-24T08:02:07 | 10.0.0.28 | DELETE | /api/users | 200 | 433 |
| 5 | 2026-08-24T08:03:36 | 10.0.0.2 | PUT | /health | 201 | 20 |
| LINEAS RECHAZADAS |
|---|
| ============================================================================== |
| origen : nginx-simulado |
| lineas leidas : 800 |
| lineas parseadas : 791 |
| lineas rechazadas : 9 |
fullmatch, no match
match se conforma con que el principio de la cadena coincida,
así que 2026-08-24Tloquesea pasaría como fecha. fullmatch
exige que coincida entera.
Para el patrón de la línea completa da lo mismo si la anclás con
^ y $, que es lo que hace el ejemplo de la lección 1.5.
El 999 es un entero perfectamente válido: ninguna conversión falla. Lo
que lo rechaza es que los códigos HTTP van de 100 a 599, y eso lo sabés vos, no el
archivo.
Lo mismo con los -50 milisegundos: es un número correcto y una duración
imposible.