Agrega logging para depuración y monitoreo
Aprende cómo agregar logging a tu aplicación Dart para ayudar con la depuración y el monitoreo.
En este capítulo, aprenderás cómo agregar logging a tu aplicación Dart. El logging es una herramienta fundamental para la depuración, el monitoreo y la comprensión del comportamiento de tu aplicación en diferentes entornos.
Qué lograrás
Prerrequisitos
#Antes de comenzar este capítulo, asegúrate de:
- Haber completado el Capítulo 11 y tener un
entorno de desarrollo de Dart en funcionamiento con el proyecto
dartpedia. - Entender los conceptos básicos de la depuración y por qué es importante realizar un seguimiento de los errores y eventos en tu aplicación.
Tareas
#En este capítulo, agregarás logging a la aplicación de CLI dartpedia para
ayudar a rastrear errores y monitorear su comportamiento.
Esto implicará agregar el paquete logging,
crear una instancia de Logger y escribir mensajes de log en un archivo.
Tarea 1: Agregar el paquete logging
#
Primero, agrega el paquete logging a las dependencias de tu proyecto.
Abre el archivo
cli/pubspec.yaml.Localiza la sección
dependencies.-
Agrega el paquete
logginga tus dependencias:yamldependencies: http: ^1.3.0 command_runner: path: ../command_runner wikipedia: path: ../wikipedia # Add the following line logging: ^1.2.0 Ejecuta
dart pub geten el directorioclipara obtener la nueva dependencia.
Tarea 2: Crear un logger
#A continuación, crea una instancia de Logger y configúrala para
escribir mensajes de log en un archivo. Esto implica
crear un nuevo archivo para el logger y configurar las importaciones necesarias.
Crea un nuevo archivo llamado
cli/lib/src/logger.dart.-
Agrega las importaciones necesarias y define la función
initFileLogger.cli/lib/src/logger.dartdartimport 'dart:io'; import 'package:logging/logging.dart'; Logger initFileLogger(String name) { // Enables logging from child loggers. hierarchicalLoggingEnabled = true; // Create a logger instance with the provided name. final logger = Logger(name); final now = DateTime.now(); // The rest of the function will be added below. // ... return logger; } -
Agrega el código para buscar el directorio raíz del proyecto, crea un directorio
logssi no existe uno, y crea un archivo de log único.dartLogger initFileLogger(String name) { hierarchicalLoggingEnabled = true; final logger = Logger(name); final now = DateTime.now(); // Get the path to the project directory from the current script. final scriptFile = File(Platform.script.toFilePath()); final projectDir = scriptFile.parent.parent.path; // Create a 'logs' directory if it doesn't exist. final dir = Directory('$projectDir/logs'); if (!dir.existsSync()) dir.createSync(); // Create a log file with a unique name based on // the current date and logger name. final logFile = File( '${dir.path}/${now.year}_${now.month}_${now.day}_$name.txt', ); // The rest of the function will be added below. // ... return logger; } -
Configura el nivel del logger y establece un listener para escribir mensajes de log en el archivo.
dartLogger initFileLogger(String name) { hierarchicalLoggingEnabled = true; final logger = Logger(name); final now = DateTime.now(); final scriptFile = File(Platform.script.toFilePath()); final projectDir = scriptFile.parent.parent.path; final dir = Directory('$projectDir/logs'); if (!dir.existsSync()) dir.createSync(); final logFile = File( '${dir.path}/${now.year}_${now.month}_${now.day}_$name.txt', ); // Set the logger level to ALL, so it logs all messages regardless of severity. // Level.ALL is useful for development and debugging, but you'll likely want to // use a more restrictive level like Level.INFO or Level.WARNING in production. logger.level = Level.ALL; // Listen for log records and write each one to the log file. logger.onRecord.listen((record) { final msg = '[${record.time} - ${record.loggerName}] ${record.level.name}: ${record.message}'; logFile.writeAsStringSync('$msg \n', mode: FileMode.append); }); return logger; }Este código hace lo siguiente:
- Habilita el registro jerárquico usando
hierarchicalLoggingEnabled = true. - Crea una instancia de
Loggercon el nombre proporcionado. - Obtiene el directorio del proyecto a partir de
Platform.script.path. - Crea un directorio
logssi no existe. - Crea un archivo de log con la fecha actual y el nombre del logger.
- Establece el nivel del logger en
Level.ALL, lo que significa que registrará todos los mensajes. Esto es útil para el desarrollo y la depuración, pero probablemente querrás usar un nivel más restrictivo comoLevel.INFOoLevel.WARNINGen producción. - Escucha los registros de log y los escribe en el archivo de log.
- Habilita el registro jerárquico usando
-
Crea un nuevo archivo llamado
cli/lib/cli.darty exportalogger.dart. Esto hace queinitFileLoggeresté disponible para otras partes de tu aplicación.cli/lib/cli.dartdartexport 'src/commands/get_article.dart'; export 'src/commands/search.dart'; export 'src/logger.dart';
Tarea 3: Usar el logger en cli.dart
#
Ahora, usa la función initFileLogger en cli/bin/cli.dart para
crear una instancia de logger y registrar mensajes en un archivo.
Abre el archivo
cli/bin/cli.dart.-
Agrega la importación para el logger:
cli/bin/cli.dartdartimport 'package:cli/cli.dart'; import 'package:command_runner/command_runner.dart'; -
Modifica la función
mainpara inicializar el logger y pasarlo a los comandos:cli/bin/cli.dartdartimport 'package:cli/cli.dart'; import 'package:command_runner/command_runner.dart'; void main(List<String> arguments) async { final errorLogger = initFileLogger('errors'); final app = CommandRunner( onOutput: (String output) async { await write(output); }, onError: (Object error) { if (error is Error) { errorLogger.severe( '[Error] ${error.toString()}\n${error.stackTrace}', ); throw error; } if (error is Exception) { errorLogger.warning(error); print(error); } }, ) ..addCommand(HelpCommand()) ..addCommand(SearchCommand(logger: errorLogger)) ..addCommand(GetArticleCommand(logger: errorLogger)); app.run(arguments); }Este código hace lo siguiente:
- Inicializa una instancia de
LoggerusandoinitFileLogger('errors'). - Pasa la instancia del
loggeraCommandRunnery a los comandos individuales.
- Inicializa una instancia de
Tarea 4: Crear el comando SearchCommand
#La funcionalidad principal de la CLI reside en sus comandos.
Crea los archivos SearchCommand y GetArticleCommand y
agrega el código necesario, incluyendo el logging y el manejo de errores.
Crea un nuevo archivo llamado
/cli/lib/src/commands/search.dart.-
Agrega las importaciones y una estructura de clase básica. Esta clase
SearchCommandextiende deCommand, y su constructor acepta una instancia deLogger. Aceptar el logger en el constructor es un patrón común llamado inyección de dependencias, que permite al comando registrar eventos sin necesidad de crear su propio logger.dartimport 'dart:async'; import 'dart:io'; import 'package:command_runner/command_runner.dart'; import 'package:logging/logging.dart'; import 'package:wikipedia/wikipedia.dart'; class SearchCommand extends Command { SearchCommand({required this.logger}); final Logger logger; @override String get description => 'Search for Wikipedia articles.'; @override bool get requiresArgument => true; @override String get name => 'search'; @override String get valueHelp => 'STRING'; @override String get help => 'Prints a list of links to Wikipedia articles that match the given term.'; @override FutureOr<String> run(ArgResults args) async { // The rest of the function will be added below. // ... } } -
Ahora, agrega la lógica principal al método
run. Este código verifica si hay un argumento válido, llama a la funciónsearch()del paquetewikipedia, da formato a los resultados y devuelve los resultados como una cadena.dartimport 'dart:async'; import 'dart:io'; import 'package:command_runner/command_runner.dart'; import 'package:logging/logging.dart'; import 'package:wikipedia/wikipedia.dart'; class SearchCommand extends Command { SearchCommand({required this.logger}); final Logger logger; @override String get description => 'Search for Wikipedia articles.'; @override bool get requiresArgument => true; @override String get name => 'search'; @override String get valueHelp => 'STRING'; @override String get help => 'Prints a list of links to Wikipedia articles that match the given term.'; @override FutureOr<String> run(ArgResults args) async { if (requiresArgument && (args.commandArg == null || args.commandArg!.isEmpty)) { throw ArgumentException('Please include a search term', name); } final buffer = StringBuffer('Search results:\n'); final SearchResults results = await search(args.commandArg!); for (var result in results.results) { buffer.writeln('${result.title} - ${result.url}'); } return buffer.toString(); } } -
A continuación, agrega la característica "Voy a tener suerte" añadiendo una bandera (flag) al constructor. Luego, en el método
run, agrega la lógica para verificar si la bandera está establecida y, de ser así, obtener el resumen del primer resultado de búsqueda.dartimport 'dart:async'; import 'dart:io'; import 'package:command_runner/command_runner.dart'; import 'package:logging/logging.dart'; import 'package:wikipedia/wikipedia.dart'; class SearchCommand extends Command { SearchCommand({required this.logger}) { addFlag( 'im-feeling-lucky', help: 'If true, prints the summary of the top article that the search returns.', ); } final Logger logger; @override String get description => 'Search for Wikipedia articles.'; @override bool get requiresArgument => true; @override String get name => 'search'; @override String get valueHelp => 'STRING'; @override String get help => 'Prints a list of links to Wikipedia articles that match the given term.'; @override FutureOr<String> run(ArgResults args) async { if (requiresArgument && (args.commandArg == null || args.commandArg!.isEmpty)) { throw ArgumentException('Please include a search term', name); } final buffer = StringBuffer('Search results:\n'); final SearchResults results = await search(args.commandArg!); if (args.flag('im-feeling-lucky')) { final title = results.results.first.title; final Summary article = await getArticleSummaryByTitle(title); buffer.writeln('Lucky you!'); buffer.writeln(article.titles.normalized.titleText); if (article.description != null) { buffer.writeln(article.description); } buffer.writeln(article.extract); buffer.writeln(); buffer.writeln('All results:'); } for (var result in results.results) { buffer.writeln('${result.title} - ${result.url}'); } return buffer.toString(); } } -
Finalmente, envuelve la lógica principal en un bloque
try/catch. Esto te permite manejar posibles excepciones que podrían surgir por problemas de red o de formato de datos. Usarás elloggerinyectado para registrar estos errores en el archivo de log.dartimport 'dart:async'; import 'dart:io'; import 'package:command_runner/command_runner.dart'; import 'package:logging/logging.dart'; import 'package:wikipedia/wikipedia.dart'; class SearchCommand extends Command { SearchCommand({required this.logger}) { addFlag( 'im-feeling-lucky', help: 'If true, prints the summary of the top article that the search returns.', ); } final Logger logger; @override String get description => 'Search for Wikipedia articles.'; @override bool get requiresArgument => true; @override String get name => 'search'; @override String get valueHelp => 'STRING'; @override String get help => 'Prints a list of links to Wikipedia articles that match the given term.'; @override FutureOr<String> run(ArgResults args) async { if (requiresArgument && (args.commandArg == null || args.commandArg!.isEmpty)) { throw ArgumentException('Please include a search term', name); } final buffer = StringBuffer('Search results:\n'); try { final SearchResults results = await search(args.commandArg!); if (args.flag('im-feeling-lucky')) { final title = results.results.first.title; final Summary article = await getArticleSummaryByTitle(title); buffer.writeln('Lucky you!'); buffer.writeln(article.titles.normalized.titleText); if (article.description != null) { buffer.writeln(article.description); } buffer.writeln(article.extract); buffer.writeln(); buffer.writeln('All results:'); } for (var result in results.results) { buffer.writeln('${result.title} - ${result.url}'); } return buffer.toString(); } on HttpException catch (e) { logger ..warning(e.message) ..warning(e.uri) ..info(usage); return e.message; } on FormatException catch (e) { logger ..warning(e.message) ..warning(e.source) ..info(usage); return e.message; } } }
Tarea 5: Crear el comando GetArticleCommand
#Ahora, crea el archivo GetArticleCommand y agrega el código necesario.
El código es similar al del anterior SearchCommand, ya que también
utiliza un bloque try/catch para manejar posibles errores de red o de datos.
Crea un nuevo archivo llamado cli/lib/src/commands/get_article.dart.
-
Agrega el siguiente código a
get_article.dart.dartimport 'dart:async'; import 'dart:io'; import 'package:command_runner/command_runner.dart'; import 'package:logging/logging.dart'; import 'package:wikipedia/wikipedia.dart'; class GetArticleCommand extends Command { GetArticleCommand({required this.logger}); final Logger logger; @override String get description => 'Read an article from Wikipedia'; @override String get name => 'article'; @override String get help => 'Gets an article by exact canonical wikipedia title.'; @override String get defaultValue => 'cat'; @override String get valueHelp => 'STRING'; @override FutureOr<String> run(ArgResults args) async { try { var title = args.commandArg ?? defaultValue; final List<Article> articles = await getArticleByTitle(title); // API returns a list of articles, but we only care about the closest hit. final article = articles.first; final buffer = StringBuffer('\n=== ${article.title.titleText} ===\n\n'); buffer.write(article.extract.split(' ').take(500).join(' ')); return buffer.toString(); } on HttpException catch (e) { logger ..warning(e.message) ..warning(e.uri) ..info(usage); return e.message; } on FormatException catch (e) { logger ..warning(e.message) ..warning(e.source) ..info(usage); return e.message; } } }Revisa el código que acabas de agregar. Los comandos
SearchCommandyGetArticleCommandahora:- Importan los paquetes necesarios como
command_runner,loggingywikipediapara usar sus clases y funciones. - Aceptan una instancia de
Loggera través de su constructor. Este es un patrón común llamado inyección de dependencias, que permite al comando registrar eventos sin necesidad de crear su propio logger. - Implementan un método
runque define la lógica del comando. Este método llama a la API de Wikipedia correspondiente y da formato a la salida. - Incluyen bloques
try/catchpara manejar de forma elegante errores de red (HttpException) y errores de análisis de datos (FormatException), registrándolos para depuración.
- Importan los paquetes necesarios como
Tarea 6: Ejecutar la aplicación y verificar los logs
#Ahora que has agregado logging a tu aplicación, ejecútala y verifica el archivo de log para ver los resultados.
-
Ejecuta la aplicación con un comando que pueda producir un error. For ejemplo, intenta buscar un artículo que no exista o que cause una
FormatException.bashdart run bin/cli.dart search blahblahblahblah -
Verifica el directorio
logsen tu proyecto. Deberías ver un archivo con la fecha actual y el nombreerrors.txt. -
Abre el archivo de log y verifica que el mensaje de error esté registrado.
[2025-02-20 16:23:45.678 - errors] WARNING: HttpException: HttpException: , uri = https://en.wikipedia.org/w/api.php?action=opensearch&format=json&search=blahblahblahblah [2025-02-20 16:23:45.678 - errors] INFO: Usage: dart bin/cli.dart <command> [commandArg?] [...options?]
Revisión
#Qué lograste
Aquí tienes un resumen de lo que construiste y aprendiste en esta lección.Agregaste y aprendiste sobre el paquete logging
Agregaste logging a tus dependencias y lo importaste en tu CLI. El paquete proporciona
las clases Logger, Level y LogRecord para un registro estructurado con niveles de severidad configurables.
Configuraste niveles de registro y salida a archivos
Creaste initFileLogger() para configurar el registro jerárquico en archivos con marca de tiempo en el
directorio logs/. Esto te permite revisar el comportamiento de la aplicación a posteriori.
Integraste el logging en tus comandos de CLI
Pasaste el logger a los comandos mediante inyección de dependencias, luego usaste logger.warning(),
logger.severe() y logger.info() para registrar errores de diferentes niveles junto con el contexto relevante. Esta configuración te ayuda a depurar y filtrar problemas más fácilmente tanto en desarrollo como en producción.
Cuestionario
#Pon a prueba tu comprensión
1 / 3logging package in Dart?Para gestionar dependencias en tu proyecto.
No exactamente.
Las dependencias se gestionan en
pubspec.yaml. El paqueteloggingtrata sobre el comportamiento en tiempo de ejecución, no sobre la configuración del proyecto.Para proporcionar una forma de registrar eventos y errores en tu aplicación.
¡Correcto!
El paquete
loggingproporciona un sistema flexible para registrar eventos, advertencias, errores y otros mensajes durante la ejecución de la aplicación.Para manejar solicitudes HTTP.
No exactamente.
Las solicitudes HTTP son manejadas por
package:http. El paqueteloggingsirve para un propósito diferente.Para crear una interfaz de línea de comandos.
No exactamente.
La creación de CLI utiliza paquetes como
argso código personalizado. El paqueteloggingayuda con un aspecto diferente de las aplicaciones.
hierarchicalLoggingEnabled = true; line do?Habilita el registro de solicitudes HTTP.
No exactamente.
El registro de HTTP requiere una configuración específica en tu código HTTP. Esta configuración afecta a la estructura interna del sistema de registro.
Deshabilita el registro en la consola.
No exactamente.
La salida de la consola se controla mediante listeners, no mediante esta configuración. Esta configuración afecta a cómo interactúan los loggers entre sí.
Permite a un logger capturar eventos de loggers hijos.
¡Correcto!
Con el registro jerárquico habilitado, los loggers padres pueden recibir y procesar eventos de sus loggers hijos.
Habilita el registro en un sistema de archivos jerárquico.
No exactamente.
Esta configuración no afecta a los sistemas de archivos ni a dónde se almacenan los logs. "Jerárquico" se refiere a algo completamente diferente.
logger.severe(), logger.warning(), and logger.info(). Why use different log levels instead of just print()?Puedes filtrar los logs por severidad, mostrando solo advertencias y errores en producción mientras ves todo durante el desarrollo.
¡Correcto!
Los niveles de log te permiten establecer un umbral. En producción, puede que solo registres advertencias y niveles superiores, mientras que en desarrollo también verás mensajes de información y depuración.
Diferentes niveles usan diferentes colores en la consola.
No exactamente.
Los colores son una elección de presentación, no la razón principal. El beneficio real es más fundamental que la apariencia.
Cada nivel escribe en un archivo diferente automáticamente.
No exactamente.
Los destinos de los archivos se deben configurar por separado. Los niveles no determinan automáticamente dónde se almacenan los logs.
El uso de niveles es requerido por Dart.
print()no funciona en producción.No exactamente.
print()funciona en todas partes en Dart. Los niveles son opcionales pero proporcionan ventajas significativas queprint()no puede ofrecer.
Siguiente lección
#¡Felicitaciones! Has completado todos los capítulos principales del tutorial de inicio de Dart. ¿Quieres seguir aprendiendo? Revisa el siguiente paso en la ruta de aprendizaje de Dart y Flutter en el sitio de Flutter.
A menos que se indique lo contrario, la documentación de este sitio refleja Dart 3.12.2. Página actualizada por última vez el 17-06-2026. Ver fuente oreportar un problema.