Introdução
Um log de erros é um arquivo de texto que registra a cronologia de eventos que ocorrem em um sistema operacional, aplicativo ou serviço. Esses registros são essenciais para monitoramento, auditoria, segurança e correção de problemas. Desde o momento em que o sistema operacional é iniciado, os primeiros logs começam a ser gravados, registrando informações como data, horário, carregamento do kernel e possíveis erros ou ações excepcionais executadas. Os logs de erro são valiosos para desenvolvedores e administradores, pois fornecem insights sobre falhas e exceções durante a execução de programas, permitindo a identificação e resolução de problemas.
Em resumo, o log de erros é uma ferramenta fundamental para solucionar bugs, melhorar a estabilidade e otimizar o desempenho de sistemas e aplicativos. Ele armazena informações sobre erros, exceções e falhas, permitindo que os profissionais de suporte identifiquem e corrijam problemas, garantindo um funcionamento mais confiável e seguro.
Qt
No Qt, o QtLogging pode ser usado, em que um Message Handler precisa ser programado e o logger instalado na aplicação usando o qInstallMessageHandler()
, antes do objeto de QApplication
ser criado.
Após a instalação de um handler, as macros qInfo()
, qDebug()
, qWarning()
, qCritical()
e qFatal()
podem ser usadas para enviar mensagens para o logger.
Um Exemplo Logger no Qt pode ser visto no repositório do github e o código será visto a seguir:
#include "mainwindow.h"
#include <iostream>
#include <QApplication>
#include <QDate>
#include <QDebug>
#include <QFile>
void logMessageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
QString message = qFormatLogMessage(type, context, msg);
QString fileBase = qApp->applicationDirPath() + "/";
QString fileName = fileBase + QString( "log" + QDate::currentDate().toString("yyyyMMdd") + ".txt" );
QHash<QtMsgType, QString> msgLevelHash({{QtDebugMsg, "Debug"}, {QtInfoMsg, "Info"}, {QtWarningMsg, "Warning"}, {QtCriticalMsg, "Critical"}, {QtFatalMsg, "Fatal"}});
QFile outputFile( fileName );
if ( outputFile.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text) ){
QTextStream textStream( &outputFile );
QString printMessage;
QString logLevelName = msgLevelHash[type];
printMessage = QString("%1 %2: %3 (%4:%5, %6)")
.arg( logLevelName,
QDateTime::currentDateTime().toString("hh:mm:ss"),
message,
QString( context.file ),
QString::number( context.line ),
QString( context.function ) );
textStream << printMessage << Qt::endl;
std::cout << printMessage.toStdString() << std::endl;
outputFile.close();
if( type == QtFatalMsg )
abort();
}
}
int main(int argc, char *argv[])
{
qInstallMessageHandler( logMessageOutput );
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
Um arquivo de log é criado com a data de hoje no mesmo diretório do executável, o handler logMessageOutput
que cria o arquivo, se múltiplas aplicações ou threads escreverem no mesmo arquivo, um semáforo deverá ser criado.
A função qFormatLogMessage()
é chamada para gerar a string com os dados do log de erro, posteriormente algumas informações são adicionadas ao log de erros e escritas no arquivo usando o QTextStream
. A mensagem também é escrita no terminal com o std::cout
.
Utilizando as MACROS da seguinte maneira:
qInfo() << "qInfo" << Qt::endl;
qWarning() << "qWarning" << Qt::endl;;
qCritical() << "qCritical" << Qt::endl;;
//qFatal() << "qFatal" << Qt::endl;;
qDebug() << "qDebug" << Qt::endl;;
O resultado do log é o seguinte:
Info 18:57:36: qInfo
(C:/exemplos/LoggingQt/mainwindow.cpp:10, MainWindow::MainWindow(QWidget*))
Warning 18:57:36: qWarning
(C:/exemplos/LoggingQt/mainwindow.cpp:12, MainWindow::MainWindow(QWidget*))
Critical 18:57:36: qCritical
(C:/exemplos/LoggingQt/mainwindow.cpp:14, MainWindow::MainWindow(QWidget*))
Debug 18:57:36: qDebug
(C:/exemplos/LoggingQt/mainwindow.cpp:18, MainWindow::MainWindow(QWidget*))
Perceba que recebemos o arquivo em que a macro foi chamada e a linha de código, com o timestamp e o nome da classe, essas informações são muito úteis para identificar possíveis problemas ou dar suporte remotamente ao usuário.
Se a linha de código contendo a Macro qFatal()
for descomentada, o código é abortado com a função abort()
.
Logger Genérico
Com o logger do Qt é possível enviar o arquivo e linha de código em que o erro ocorreu e os dados de classificação de severidade com as macros qInfo()
, qDebug()
, qWarning()
, qCritical()
e qFatal()
. Porém, muitas vezes precisamos de mais dados, num projeto complexo e grande, somente esses dados podem ser pouca informação ou algumas dependências podem não necessitar de Qt, pois a equipe que está desenvolvendo esta parte do backend não possui licenças ou com tantas mensagens, podemos querer agrupar ou categorizar por tipo de mensagem de erro ou biblioteca e formatar os dados num csv, para depois filtrar estas mensagens e facilitar a manipulação dos dados.
Para um logger mais genérico ou que necessita de muitas personalizações, vamos utilizar somente o STL do C++ para gerar um log de erros multithread, para ser multithread podemos alcançar de duas maneiras, a primeira é a de criar uma instância única (singleton) de log e a segunda é de cada aplicação escrever utilizando locks. Creio que a primeira é a mais fácil e melhor de implementar, pois mitigam as chances de erros e diminui a complexidade.
FILE e LINE
Em C++, __FILE__
é uma macro do pré-processador que se expande para o caminho completo do arquivo atual. Essa macro é útil em várias situações, principalmente em loggers.
E __LINE__
é uma macro do pré-processador que fornece o número da linha.
Um arquivo chamado ‘mainwindow.cpp’ com o seguinte código retorna C:/myApp/mainwindow.cpp 12
.
#include "mainwindow.h" // 1
#include "./ui_mainwindow.h" // 2
// 3
#include <QDebug> // 4
// 5
MainWindow::MainWindow(QWidget *parent) // 6
: QMainWindow(parent) // 7
, ui(new Ui::MainWindow) // 8
{ // 9
ui->setupUi(this); // 10
// 11
qInfo() << __FILE__ << __LINE__; // 12
}
MainWindow::~MainWindow()
{
delete ui;
}
Lembre-se de que FILE é uma macro de string literal e não uma classe. Um erro comum é de inserir essas duas macros dentro da biblioteca de log, enquanto na verdade, você deseja o nome de arquivo e linha de código da aplicação que chamou a biblioteca de log.
Por exemplo, você possui uma assinatura de função:
void myLogFunc( QString qStr, const char* cFile = __FILE__ , const int iLine = __LINE__ );
Ao chamar esta função com:
myLogFunc( "My Message" );
O retorno é o arquivo de header e linha que ela foi declarada e não a linha da função que chamou a função de log.
Portanto, as macros sempre devem ser passadas explicitamente, conforme o exemplo a seguir:
// Assinatura de função na biblioteca
/// header file
void myLogFunc( QString qStr, const char* cFile , const int iLine );
A chamada da função pela aplicação:
myLogFunc( "My Message", __FILE__, __LINE__ );
ps.: Nos exemplos acima, a implementação da função myLogFunc
não foi mostrada, somente um exemplo abstrato da declaração da função e da chamada dela.