Parte 9 — Ingeniería de software políglota · ⏱️ Duración estimada: 90 min · Nivel: Intermedio ✅ Clase construida — 10 implementaciones del núcleo verificadas contra
casos.json.
El depurador de la clase anterior sirve mientras tienes el programa delante; en producción, con miles de peticiones concurrentes y sin poder pausar nada, tu única ventana al interior del sistema es lo que el propio programa haya dejado escrito. Ese rastro es el registro (logging), y saber leerlo y producirlo es lo que separa "no sé qué pasó" de "aquí está la línea exacta". McConnell, en Code Complete, trata la instrumentación como parte del oficio, no como un añadido: un programa que no cuenta lo que hace es una caja negra imposible de operar.
La unidad básica es un log con dos ingredientes: un nivel que expresa su gravedad —DEBUG, INFO, WARN, ERROR— y unos datos que describen el evento. Nuestro ejercicio produce la línea mínima log=[INFO] procesados=n: un nivel y un dato. Parece poco, pero encierra la decisión clave del logging moderno —el logging estructurado—, donde cada registro es un conjunto de campos legibles por máquina (procesados=5) y no una frase suelta. Sobre esa base se construye la observabilidad: la capacidad de entender el estado interno de un sistema desde sus salidas, articulada en los tres pilares clásicos —logs, métricas y trazas— que permiten operar en producción sin adivinar.
Al finalizar, podrás:
| # | Tema | Por qué importa |
|---|---|---|
| 1 | Logging estructurado | Registros con campos legibles por máquina, no frases sueltas, se pueden consultar |
| 2 | Niveles | DEBUG/INFO/WARN/ERROR permiten filtrar por gravedad y bajar el ruido |
| 3 | Observabilidad | Los tres pilares (logs, métricas, trazas) hacen operable un sistema en marcha |
| 4 | Bibliotecas por lenguaje | Cada ecosistema tiene su estándar (logging, SLF4J, slog, tracing…) |
procesados=5), WARN (algo anómalo pero recuperable) y ERROR (fallo que exige atención). El nivel permite filtrar: en producción sueles emitir de INFO hacia arriba y activar DEBUG solo al investigar."se procesaron 5 elementos"), se emiten campos (procesados=5, o un JSON {"procesados":5}). La diferencia es enorme: los campos se filtran, agregan y grafican en herramientas como Grafana Loki o Elastic, mientras que el texto libre solo se puede leer a ojo.Son las tres de la madrugada y el servicio de pedidos ha empezado a tardar. No puedes conectar un depurador —pausarías a miles de usuarios—, así que abres el panel de logs. Filtras por nivel WARN y aparece una línea repetida: [WARN] cola_reintentos procesados=0. En segundos sabes que los reintentos no avanzan, algo que un mensaje de texto plano habría enterrado en el ruido. Un registro estructurado como [INFO] procesados=5 es exactamente lo que hace posible esa consulta: campos, no prosa.
n (elementos procesados)log=[INFO] procesados=<n>Especificación y verificación en casos.json:
| stdin | esperado |
|---|---|
5 |
log=[INFO] procesados=5 |
0 |
log=[INFO] procesados=0 |
3 |
log=[INFO] procesados=3 |
LEER n ; ESCRIBIR log de nivel INFO con procesados=n
Mismo algoritmo, forma idiomática en cada lenguaje. Todas producen la salida de casos.json.
Cada bloque es el archivo real de implementaciones/. Para que la salida sea verificable byte a byte, aquí construimos el log a mano con un print, en vez de invocar la biblioteca de logging real de cada lenguaje; pero la forma del mensaje —nivel entre corchetes y un campo clave=valor— imita deliberadamente lo que esas bibliotecas producen.
python/main.py · python main.pyimport sys
n = int(sys.stdin.readline())
print(f"log=[INFO] procesados={n}")
🧬 El mismo programa en la familia Scripting dinámico: Ruby · Perl · Lua · Tcl · R
La línea f"log=[INFO] procesados={n}" es un log estructurado en miniatura: [INFO] es el nivel y procesados={n} es un campo con clave y valor. En un servicio real no lo escribirías así, sino con el módulo estándar logging: logging.info("procesados=%d", n), que además añadiría el timestamp, el nombre del módulo y respetaría el nivel configurado —si el umbral fuera WARNING, este INFO ni se emitiría—. Esa es la ventaja de una biblioteca frente al print: el mismo código de aplicación produce más o menos detalle según la configuración del entorno, sin tocar la lógica. Ramalho, en Fluent Python, recomienda logging sobre print justo por eso: separa qué quieres registrar de cuánto se registra en cada despliegue.
javascript/main.mjs · node main.mjsimport { readFileSync } from "node:fs";
const n = parseInt(readFileSync(0, "utf8").trim(), 10);
console.log(`log=[INFO] procesados=${n}`);
🧬 El mismo programa en la familia JavaScript / web: Dart · ActionScript
typescript/main.ts · pnpm exec tsx main.tsimport { readFileSync } from "node:fs";
const n: number = parseInt(readFileSync(0, "utf8").trim(), 10);
console.log(`log=[INFO] procesados=${n}`);
🧬 El mismo programa en la familia JavaScript / web: Dart · ActionScript
java/Main.java · java Main.javaimport java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class Main {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
int n = Integer.parseInt(br.readLine().trim());
System.out.println("log=[INFO] procesados=" + n);
}
}
🧬 El mismo programa en la familia JVM: Kotlin · Scala · Groovy · Clojure
csharp/Program.cs · dotnet runusing System;
int n = int.Parse(Console.In.ReadToEnd().Trim());
Console.WriteLine($"log=[INFO] procesados={n}");
🧬 El mismo programa en la familia .NET: F# · VB.NET
go/main.go · go run main.gopackage main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
line, _ := bufio.NewReader(os.Stdin).ReadString('\n')
n, _ := strconv.Atoi(strings.TrimSpace(line))
fmt.Printf("log=[INFO] procesados=%d\n", n)
}
🧬 El mismo programa en la familia Sistemas: Zig · Nim · D
rust/main.rs · rustc main.rs -o main && ./mainuse std::io::Read;
fn main() {
let mut s = String::new();
std::io::stdin().read_to_string(&mut s).unwrap();
let n: i64 = s.trim().parse().unwrap();
println!("log=[INFO] procesados={n}");
}
🧬 El mismo programa en la familia Sistemas: Zig · Nim · D
c/main.c · cc main.c -o main && ./main#include <stdio.h>
int main(void) {
long n;
if (scanf("%ld", &n) != 1) return 1;
printf("log=[INFO] procesados=%ld\n", n);
return 0;
}
🧬 El mismo programa en la familia C / llaves: C++ · Objective-C
sql/main.sql · sqlite3 :memory: < main.sql-- SQL: registro con una tabla/consulta de auditoría.
WITH t(n) AS (VALUES (5))
SELECT printf('log=[INFO] procesados=%d', n) AS resultado FROM t;
🧬 El mismo programa en la familia Lógica y declarativa: Prolog · Datalog
php/main.php · php main.php<?php
$n = (int) trim(fgets(STDIN));
echo "log=[INFO] procesados=$n\n";
🧬 El mismo programa en la familia Scripting dinámico: Ruby · Perl · Lua · Tcl · R
En un sistema real, cada uno de estos programas delegaría el registro en la biblioteca canónica de su ecosistema, y el contraste es instructivo. Java rara vez usa System.out.println para logs: emplea la fachada SLF4J con una implementación como Logback por debajo, de modo que el código depende de una interfaz y la configuración decide el destino y el formato —una aplicación directa del principio de programar contra abstracciones que defiende Bloch. Go incorpora desde la versión 1.21 el paquete log/slog en la biblioteca estándar, con logging estructurado nativo (slog.Info("procesado", "n", n)), superando al viejo log de solo texto. Rust favorece la fachada tracing (o log), que unifica logs y trazas de spans en un mismo modelo, muy alineado con la observabilidad moderna. JavaScript va de console.log en desarrollo a bibliotecas como pino o winston en producción, que serializan a JSON de alto rendimiento. Distintas casas, la misma idea: nivel, campos estructurados y un destino configurable.
SQL es declarativo: no lee de stdin como los demás; su implementación muestra la misma idea sobre una tabla de casos, y el verificador la marca como ilustrativa.
El concepto de niveles es universal; lo que cambia es la biblioteca estándar de cada ecosistema y si el logging estructurado viene de fábrica.
| Lenguaje | Biblioteca de referencia | ¿Estructurado nativo? | Llamada idiomática |
|---|---|---|---|
| Python | logging (estándar) |
Parcial (vía extra/handlers) |
logging.info("procesados=%d", n) |
| JavaScript | pino / winston (console en dev) |
Sí (JSON) | logger.info({ procesados: n }) |
| TypeScript | pino / winston | Sí (JSON) | logger.info({ procesados: n }) |
| Java | SLF4J + Logback / Log4j 2 | Sí (con encoders) | log.info("procesados={}", n) |
| C# | Microsoft.Extensions.Logging / Serilog |
Sí | logger.LogInformation("procesados={N}", n) |
| Go | log/slog (estándar, 1.21+) |
Sí | slog.Info("proc", "n", n) |
| Rust | tracing / log |
Sí | info!(procesados = n) |
| C | syslog / bibliotecas propias | No | syslog(LOG_INFO, "...") |
| SQL | tablas de auditoría / logs del motor | según motor | INSERT INTO auditoria … |
| PHP | Monolog (estándar de facto, PSR-3) | Sí | $log->info('proc', ['n' => $n]) |
Dos observaciones útiles. La primera: los lenguajes modernos convergen hacia el logging estructurado de serie —slog en Go, tracing en Rust, la abstracción ILogger en .NET—, reconociendo que un log que una máquina puede consultar vale mucho más que uno que solo un humano puede leer. La segunda: PHP estandarizó su ecosistema con PSR-3, una interfaz común que Monolog y otros implementan, de modo que cambiar de biblioteca no rompe el código de aplicación; es la misma idea de fachada que Java resolvió con SLF4J.
SLF4J/Logback y Log4j 2 (Java), logging (Python), Serilog y Microsoft.Extensions.Logging (.NET), slog y zap (Go), tracing (Rust), Monolog (PHP), pino y winston (Node): todos son variaciones del mismo modelo —un evento con nivel, mensaje y campos, dirigido a uno o varios destinos configurables—. Por encima de ellos, la observabilidad los integra con métricas y trazas distribuidas mediante estándares como OpenTelemetry, que unifica cómo los servicios exportan estas tres señales.
Los mismos casos para todas las implementaciones: casos.json. Verifica la equivalencia:
python scripts/verificar_equivalencia.py 142
Detalle en reto.md.
"procesé 5 pedidos del usuario 12" es imposible de consultar; procesados=5 usuario=12 se filtra y agrega. Prefiere siempre el formato estructurado.print en producción. print/console.log no tienen niveles ni destino configurable y suelen ir a stdout sin control. Usa la biblioteca de logging del lenguaje, que separa qué registras de cuánto se emite.procesados=5) se filtran, agregan y grafican en las herramientas de observabilidad, mientras que el texto libre solo se lee a ojo. Estructurar el log es lo que lo convierte en dato consultable.Libros de la parte:
Libros de los lenguajes del núcleo:
⏮️ Clase 141 · 📂 Parte · 📚 Índice · 🌐 Atlas · Clase 143 ⏭️