Adicione logs para depuração e monitoramento
Aprenda como adicionar logs à sua aplicação Dart para ajudar na depuração e no monitoramento.
Neste capítulo, você vai aprender como adicionar logs à sua aplicação Dart. Logs são uma ferramenta essencial para depurar, monitorar e entender o comportamento da aplicação em diferentes ambientes.
O que você vai realizar
Pré-requisitos
#Antes de começar este capítulo, certifique-se de:
-
Ter concluído o Capítulo 12 e ter um
ambiente de desenvolvimento Dart funcionando com o projeto
dartpedia. - Entender o básico de depuração e por que é importante registrar erros e eventos da aplicação.
Tarefas
#
Neste capítulo, você vai concluir a aplicação de CLI dartpedia integrando
os comandos do pacote wikipedia e adicionando logs para
ajudar a registrar erros e monitorar o comportamento da aplicação.
Isso envolve adicionar dependências de pacotes,
criar os comandos de CLI, configurar uma instância de Logger
e executar a aplicação completa.
Tarefa 1: Adicione dependências ao pacote cli
#
Primeiro, adicione o pacote wikipedia (que você construiu nos capítulos anteriores)
e o pacote logging às dependências do projeto da CLI.
Abra o arquivo
cli/pubspec.yaml.Localize a seção
dependencies.-
Adicione os pacotes
wikipediaeloggingàs suas dependências:cli/pubspec.yamlyamldependencies: http: ^1.3.0 command_runner: path: ../command_runner # Add the following lines wikipedia: path: ../wikipedia logging: ^1.2.0 -
Execute
dart pub getno diretórioclipara buscar as novas dependências.
Tarefa 2: Crie um logger
#
Em seguida, crie uma instância de Logger e configure-a para
gravar mensagens de log em um arquivo. Isso envolve
criar um novo arquivo para o logger e configurar os imports necessários.
Crie um novo arquivo chamado
cli/lib/src/logger.dart.-
Adicione os imports necessários e defina a função
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; } -
Adicione o código para encontrar o diretório raiz do projeto, criar um diretório
logscaso ele não exista e criar um arquivo de log com um nome único.cli/lib/src/logger.dartdartLogger 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; } -
Configure o nível do logger e configure um listener para gravar as mensagens de log no arquivo.
cli/lib/src/logger.dartdartLogger 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; }A função
initFileLoggerretorna uma instância deLoggerconfigurada que acrescenta registros com data e hora em um arquivo no diretóriologs/:logger.level = Level.ALL: Captura todas as mensagens, independentemente da severidade, durante o desenvolvimento. Aplicações em produção normalmente usam limites mais altos, comoLevel.INFOouLevel.WARNING.logger.onRecord.listen(...): Assina o stream de eventos de log, formatando cada entrada com seu horário, o nome do logger e o nível de severidade antes de gravá-la no disco.
Tarefa 3: Crie o comando SearchCommand
#
A funcionalidade principal da CLI está nos seus comandos.
Crie os arquivos SearchCommand e GetArticleCommand e
adicione o código necessário, incluindo logs e tratamento de erros.
Crie um novo arquivo chamado
cli/lib/src/commands/search.dart.-
Adicione os imports e uma estrutura básica de classe. Esta classe
SearchCommandestendeCommand, e o construtor dela recebe uma instância deLogger. Receber o logger pelo construtor é um padrão comum chamado injeção de dependência, que permite ao comando registrar eventos sem precisar criar o próprio logger.cli/lib/src/commands/search.dartdartimport '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 { // Command logic will be added below. // ... return ''; } } -
Implemente a lógica do comando para buscar na Wikipedia e formatar os resultados.
cli/lib/src/commands/search.dartdart// ... @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(); } // ... -
Por fim, envolva a lógica principal em um bloco
try/catch. Isso permite tratar possíveis exceções que podem surgir de problemas de rede ou de formatação dos dados. Você vai usar ologgerinjetado para registrar esses erros no arquivo de log.cli/lib/src/commands/search.dartdartimport '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; } } }
Tarefa 4: Crie o comando GetArticleCommand
#
Agora, crie o arquivo GetArticleCommand e adicione o código necessário.
O código é parecido com o do SearchCommand anterior, pois também
usa um bloco try/catch para tratar possíveis erros de rede ou de dados.
Crie um novo arquivo chamado
cli/lib/src/commands/get_article.dart.-
Adicione o seguinte código em
get_article.dart:cli/lib/src/commands/get_article.dartdartimport '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; } } }Revise o código que você acabou de adicionar. Os comandos
SearchCommandeGetArticleCommandagora:- Importam os pacotes necessários, como
command_runner,loggingewikipedia, para usar suas classes e funções. - Recebem uma instância de
Loggerpelo construtor. Esse é um padrão comum chamado injeção de dependência, que permite ao comando registrar eventos sem precisar criar o próprio logger. - Implementam um método
runque define a lógica do comando. Esse método chama a API apropriada da Wikipedia e formata a saída. - Incluem blocos
try/catchpara tratar com elegância erros de rede (HttpException) e erros de análise de dados (FormatException), registrando-os para depuração.
- Importam os pacotes necessários, como
Tarefa 5: Exporte os comandos e conecte tudo no cli.dart
#
Agora, exporte o logger e os comandos da biblioteca cli
e, em seguida, conecte-os em cli/bin/cli.dart para
criar a aplicação de CLI completa.
-
Abra o arquivo
cli/lib/cli.dart. Substitua o conteúdo provisório pelas exportações do seu logger e dos seus comandos:cli/lib/cli.dartdartexport 'src/commands/get_article.dart'; export 'src/commands/search.dart'; export 'src/logger.dart';Esse arquivo funciona como a interface pública da biblioteca, exportando
initFileLogger,SearchCommandeGetArticleCommandpara quecli/bin/cli.dartpossa importá-los depackage:cli/cli.dart. Abra o arquivo
cli/bin/cli.dart.-
Adicione os imports de
cliecommand_runner:cli/bin/cli.dartdartimport 'package:cli/cli.dart'; import 'package:command_runner/command_runner.dart'; -
Modifique a função
mainpara inicializar o logger e registrar os comandos noCommandRunner: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); }Essa configuração inicializa um logger de arquivo
errors, passa-o paraSearchCommandeGetArticleCommand, e registra todos os comandos noCommandRunner.
Tarefa 6: Execute a aplicação e verifique os logs
#
Agora que você implementou os comandos de CLI,
conectou o CommandRunner em bin/cli.dart e configurou o logging,
teste a aplicação pelo terminal.
Certifique-se de executar estes comandos a partir do diretório cli
(/dartpedia/cli).
-
Execute a aplicação de CLI para buscar um artigo:
bashdart run bin/cli.dart search "Dart programming"Você deve ver no terminal uma lista de artigos da Wikipedia que correspondem ao seu termo de busca.
-
Tente executar o comando com a flag
--im-feeling-lucky:bashdart run bin/cli.dart search "Dart" --im-feeling-lucky -
Execute a aplicação sem o argumento de busca obrigatório para testar o log de erros:
bashdart run bin/cli.dart searchVocê deve ver o erro impresso no terminal:
ArgumentException: Please include a search term -
Verifique o diretório
cli/logsdo seu projeto. Você deve ver um arquivo com a data atual no nome, como<ano>_<mês>_<dia>_errors.txt. -
Abra o arquivo de log e verifique se a mensagem de erro foi registrada:
[2025-02-20 16:23:45.678 - errors] WARNING: ArgumentException: Please include a search term -
Verifique se a estrutura do seu workspace corresponde ao projeto concluído:
dartpedia/ ├── pubspec.yaml # Workspace root configuration ├── cli/ │ ├── bin/ │ │ └── cli.dart # Application entrypoint │ ├── lib/ │ │ ├── cli.dart # Library exports │ │ └── src/ │ │ ├── commands/ │ │ │ ├── get_article.dart │ │ │ └── search.dart │ │ └── logger.dart # Logging configuration │ ├── logs/ │ │ └── <date>_errors.txt # Generated error logs │ └── pubspec.yaml ├── command_runner/ │ ├── lib/ │ │ ├── command_runner.dart │ │ └── src/ │ │ ├── arguments.dart │ │ ├── command_runner_base.dart │ │ ├── console.dart │ │ ├── exceptions.dart │ │ └── help_command.dart │ └── pubspec.yaml └── wikipedia/ ├── lib/ │ ├── wikipedia.dart │ └── src/ │ ├── api/ │ │ ├── get_article.dart │ │ ├── search.dart │ │ └── summary.dart │ └── model/ │ ├── article.dart │ ├── search_results.dart │ ├── summary.dart │ └── title_set.dart └── pubspec.yaml
Revisão
#O que você realizou
Veja um resumo do que você construiu e aprendeu nesta lição.Adicionou e conheceu o pacote logging
Você adicionou logging às suas dependências e o importou na CLI. O pacote oferece as classes
Logger, Level e LogRecord para logs estruturados com níveis de severidade configuráveis.
Configurou níveis de log e saída em arquivo
Você criou initFileLogger() para configurar logs hierárquicos em arquivos com data e hora no diretório
logs/. Isso permite revisar o comportamento da aplicação depois que os fatos acontecem.
Integrou logs aos seus comandos de CLI
Você passou o logger para os comandos usando injeção de dependência, e depois usou logger.warning(),
logger.severe() e logger.info() para registrar erros de diferentes níveis junto com o contexto relevante. Essa configuração ajuda a depurar e filtrar problemas com mais facilidade, tanto no desenvolvimento quanto em produção.
Quiz
#Confira seu entendimento
1 / 3logging em Dart?
-
Oferecer uma forma de registrar eventos e erros na sua aplicação.
Isso mesmo!
O pacote
loggingoferece um sistema flexível para registrar eventos, avisos, erros e outras mensagens durante a execução da aplicação. -
Tratar requisições HTTP.
Não exatamente.
Requisições HTTP são tratadas pelo
package:http. O pacoteloggingtem outra finalidade. -
Gerenciar dependências do projeto.
Não exatamente.
As dependências são gerenciadas no
pubspec.yaml. O pacoteloggingtrata do comportamento em tempo de execução, e não da configuração do projeto. -
Criar uma interface de linha de comando.
Não exatamente.
A criação de CLIs usa pacotes como
argsou código próprio. O pacoteloggingajuda em outro aspecto das aplicações.
hierarchicalLoggingEnabled = true; faz?
-
Habilita um logger para capturar eventos de loggers filhos.
Isso mesmo!
Com o logging hierárquico habilitado, loggers pais podem receber e processar eventos dos seus loggers filhos.
-
Habilita o logging em um sistema de arquivos hierárquico.
Não exatamente.
Essa configuração não afeta sistemas de arquivos nem onde os logs são armazenados. "Hierárquico" se refere a outra coisa.
-
Desabilita o logging no console.
Não exatamente.
A saída no console é controlada por listeners, e não por essa configuração. Essa configuração afeta como os loggers interagem entre si.
-
Habilita o logging de requisições HTTP.
Não exatamente.
O logging de HTTP exige configuração específica no seu código HTTP. Essa configuração afeta a estrutura interna do sistema de logging.
logger.severe(), logger.warning() e logger.info(). Por que usar níveis de log diferentes em vez de apenas print()?
-
Você pode filtrar os logs por severidade, mostrando só avisos e erros em produção enquanto vê tudo durante o desenvolvimento.
Isso mesmo!
Os níveis de log permitem definir um limite. Em produção, você pode registrar apenas avisos e níveis superiores, enquanto no desenvolvimento também vê mensagens de info e debug.
-
Níveis diferentes usam cores diferentes no console.
Não exatamente.
Cores são uma escolha de apresentação, e não o motivo principal. O benefício real é mais fundamental do que a aparência.
-
Cada nível grava automaticamente em um arquivo diferente.
Não exatamente.
Os destinos de arquivo precisam ser configurados separadamente. Os níveis não determinam automaticamente onde os logs são armazenados.
-
O uso de níveis é obrigatório no Dart. O
print()não funciona em produção.Não exatamente.
O
print()funciona em qualquer lugar no Dart. Os níveis são opcionais, mas oferecem vantagens importantes que oprint()não consegue oferecer.
Próxima lição
#Parabéns! Você concluiu todos os capítulos principais do tutorial de introdução ao Dart. Quer continuar aprendendo? Confira o próximo passo na trilha de aprendizado de Dart e Flutter no site do Flutter.
Salvo indicação em contrário, a documentação neste site reflete Dart 3.13.7. Página atualizada pela última vez em 2026-11-12. Ver código-fonte ou relatar um problema.
Encontrou essa página sem tradução ou que precisa de correção? Abra uma issue e nos ajude a manter esse site em PT-BR para ajudar pessoas como você.