Un print("Error al guardar") permite comprobar rápidamente si entramos en una rama del código. Pero cuando aparecen decenas de mensajes, cuesta reconstruir qué ocurrió y desde qué componente. Y si el fallo sucede en el dispositivo de otra persona, necesitamos algo más que la salida de una sesión de depuración.
Apple ofrece un sistema de registro unificado que incorpora niveles de severidad, categorías y controles de privacidad. Su API Logger, disponible desde iOS 14 y macOS 11, permite registrar información que después podemos consultar con herramientas del sistema.
Organizar los mensajes desde el principio
Un Logger se configura con un subsistema, normalmente el identificador de la aplicación, y una categoría que identifica un área funcional. Podemos dedicar categorías al guardado, las notificaciones o la reproducción de audio. Así filtramos por responsabilidades sin buscar palabras sueltas entre todos los mensajes.
Imaginemos una app que guarda recordatorios para regar plantas. Queremos distinguir cuándo empieza un guardado, cuándo termina y cuándo falla:
import Foundation
import OSLog
struct WateringReminder: Codable {
let plantName: String
let nextWatering: Date
}
struct ReminderStore {
private let logger = Logger(
subsystem: "com.example.Riego",
category: "Guardado"
)
func save(_ reminders: [WateringReminder], to url: URL) throws {
let operationID = UUID().uuidString
logger.notice("Inicio de guardado: \(operationID, privacy: .public)")
logger.debug("Recordatorios pendientes: \(reminders.count)")
do {
let data = try JSONEncoder().encode(reminders)
try data.write(to: url, options: .atomic)
logger.notice("Guardado completado: \(operationID, privacy: .public)")
} catch {
logger.error("Guardado fallido: \(operationID, privacy: .public); detalle: \(error.localizedDescription, privacy: .private)")
throw error
}
}
}
El identificador aleatorio permite relacionar los mensajes de una misma operación. Lo marcamos como público porque se genera para este diagnóstico y no contiene información del usuario. Además, registrar un error no lo resuelve: lo propagamos para que quien llama decida cómo reaccionar.
El nivel también determina qué podremos recuperar
Los niveles de registro expresan la importancia del mensaje y afectan a su conservación:
| Nivel | Uso habitual | Conservación predeterminada |
|---|---|---|
debug | Detalles para depurar | No se guarda en disco |
info | Contexto complementario | Normalmente en memoria; puede recogerse con log |
notice | Eventos relevantes para el diagnóstico | En disco, con límites |
error | Fallos durante la ejecución | En disco, con límites |
fault | Posibles defectos o supuestos internos incumplidos | En disco, con límites |
No conviene utilizar error para que cualquier mensaje destaque. Tampoco debemos confiar en debug para investigar días después un fallo: los registros no son un historial permanente. La configuración y los límites de almacenamiento condicionan qué información sigue disponible.
La interpolación importa para el rendimiento
Como explica Apple en Explore logging in Swift, Logger conserva una representación optimizada del mensaje y sus argumentos, en lugar de construir inmediatamente una cadena completa. Esto permite aplazar el formato y evitar trabajo cuando un mensaje de depuración se descarta.
Para aprovecharlo, pasa el literal interpolado directamente al método. Si calculas antes una descripción costosa y la guardas en una variable, ese cálculo ya se habrá ejecutado aunque después el mensaje no se registre. Tampoco introduzcas lógica de negocio dentro de la interpolación: su evaluación puede omitirse.
Revisar la privacidad de cada valor
Por defecto, las cadenas y los objetos interpolados se ocultan, mientras que los valores numéricos son visibles. Por eso, un identificador personal representado como Int también necesita una decisión explícita de privacidad.
Las opciones de OSLogPrivacy permiten marcar valores como .private o .public. En el ejemplo ocultamos la descripción del error porque podría incorporar rutas o nombres de archivos. Si añadimos el nombre de una planta, también podemos protegerlo:
let logger = Logger(subsystem: "com.example.Riego", category: "Recordatorios")
let plantName = "Orquídea del despacho"
logger.debug("Recordatorio seleccionado: \(plantName, privacy: .private)")
Hacer público un valor solo para verlo durante una prueba puede exponerlo en diagnósticos posteriores. Conviene decidir qué información necesitamos antes de registrarla.
Consultar los registros con contexto
La consola de depuración de Xcode permite filtrar por nivel, subsistema y categoría, además de consultar metadatos y localizar la llamada en el código. La app Consola de macOS también permite observar mensajes de dispositivos conectados.
Para observar los del Mac desde Terminal:
log stream --level debug \
--predicate 'subsystem == "com.example.Riego"' \
--style compact
Este comando muestra eventos en directo; no recupera mensajes debug antiguos ni selecciona automáticamente un iPhone conectado.
Una migración gradual puede empezar por los fallos, continuar con los eventos relevantes y reservar debug para el detalle. print() sigue siendo adecuado para producir la salida de una herramienta de Terminal. Para diagnosticar una app, Logger aporta el contexto que necesitamos sin diseñar nuestro propio sistema de registros.