Manipulando Logs com Qt5

Logs com Qt5

O Qt, hoje na versão 5.X, é uma poderosa ferramenta que cada vez mais ganha novos recursos e aprimora os já existentes, provendo recursos para que a mesma aplicação possa ser executada em um Android, Windows, Linux, além de dispositivos embarcados utilizando Linux ou Windows, e poderosos RTOS’s como QNX e VxWorks. Mais informações em Plataformas Suportadas.

Uma prática comum na maioria das aplicações executando sobre Linux e Linux Embarcado é gerar logs, estes contendo informações triviais sobre a execução do software ou informações de alertas, problemas e erros. E o Qt5 possui algumas funções prontas para auxiliar nesta tarefa, ou então configurar o uso delas de acordo com a sua necessidade.

O que iremos abordar neste artigo é quais são estas funções, onde usar, como usar, variáveis de ambiente para controle destas funções e um recurso que surgiu no Qt5.6 para utilizar syslog ou journald (desde o 5.3) para estas informações, estes dois sendo os sistemas de logs padrão na grande maioria dos SO Linux.

Ambiente

  • Linux Mint 15 Olivia AMD64
  • Qt Creator 4.1.x
  • Qt 5.6.2 (requisito > 5.6.x)

Funções Qt5 para logging

As funções utilizadas para manipular os logs de acordo com os seus propósitos são:

  • qDebug(): Usado para customizar a saída de uma Depuração; 
  • qInfo(): Usado para criar mensagens informativas; 
  • qWarning(): Usado para reportar alertas e erros recuperados da aplicação; 
  • qCritical(): Usado para reportar erros críticos e erros no sistema; 
  • qFatal(): Usado para reportar erros fatais, normalmente reportados logo antes de sair da aplicação.

Em algumas literaturas você pode encontrar a referência e citação destas funções como Qt logging framework, estas funções que fazer parte da listas Global de Macros em <QtGlobal>.

Existe uma outra solução caso as funções acima não tenham agradado ou adequado a aplicação, onde você pode criar o seu arquivo de log em um local específico e ir acrescentando dados conforme a necessidade. Para isso usa-se o QTextStream, que também veremos num simples exemplo, ou pode criar sua própria estrutura de logging na aplicação, vide exemplo no Qt Wiki – Simple-logger.

Um detalhe muito importante é sobre o uso de stdout (Standard Output) e stderr (Standard Error) no Qt, onde se você utilizar std::cout da iostream ou printf() da stdio estes serão canalizados para stdout padrão, já qDebug(), qInfo() e demais funções apresentadas todas serão canalizadas para stderr, logo mais iremos validar isso.

Criando Projeto Exemplo

Abra a Qt Creator, e vamos criar um simples projeto que será somente console, sem Interface Gráfica. Para isso vá em File > New File or Project…, selecione Application e logo ao lado direito Qt Console Application e clique em Choose, pode digitar qualquer nome, no caso irei usar lab01-logging e no diretório para criar o projeto pode escolher onde quer salvá-lo, as próximas etapas é só ir avançando e clicar em Finish.

Se tudo ocorreu bem, foi criado um arquivo main.ccp com o seguinte conteúdo:

#include <QCoreApplication>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    return a.exec();
}

Vamos adicionar um qDebug() apenas como exemplo e ver como seria sua implementação.

#include <QcoreApplication>
#include <QDebug>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    QString debugMsg = "Aplicação correndo com sucesso!";

    qDebug("[DEBUG] Qt Console Application - Embarcados");
    qDebug("[DEBUG] %s", debugMsg.toUtf8().constData());
    qDebug() << "[DEBUG] Mais um jeito de usar qDebug()";
 
   return EXIT_SUCCESS;
}

Então podemos usar o qDebug() escrevendo um texto dentro dos (), adicionar um %s e incluir uma variável QString, ou ainda usar << para anexar uma quantidade maior de dados ou variáveis no mesmo qDebug().

Compilando e executando teremos:

build-lab01-logging-Desktop_Qt_5_6_2_GCC_64bit-Release $ ./lab01-logging
[DEBUG] Qt Console Application - Embarcados
[DEBUG] Aplicação correndo com sucesso!
[DEBUG] Mais um jeito de usar qDebug()

Atente sobre o uso de .toUtf8().constData(), isso sempre será necessário quando se usa %s em qInfo(), qWarning(), qDebug(), qFatal() e qCritical() pois ele espera codificação UTF-8, caso contrário um erro sera reportado.

A partir do Qt5.4 foi incluída a função qUtf8Printable(const Qstring), que equivale a fazer .toUtf8().constData(), uma maneira mais amigável de realizar a mesma tarefa.

Vamos alterar nosso código e fazer o seguinte teste, iremos incluir um printf() e um std::cout, sim usaremos ambos juntos, e qDebug() e qInfo() no mesmo código, então nosso main.cpp ficará como o código abaixo:

#include <QCoreApplication>
#include <iostream>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    /* STDOUT */
    printf("[printf] Qt Console Application – Embarcados\n");
    std::cout << "[std::cout] Qt Console Application - Embarcados" << std::endl;

    /* STDERR */
    qDebug("[qDebug] Qt Console Application - Embarcados");
    qInfo("[qInfo] Qt Console Application - Embarcados");   
    return EXIT_SUCCESS;
}

Após salvar, compilar a aplicação e executar, teremos como resultado o conteúdo abaixo:

build-lab01-logging-Desktop_Qt_5_6_2_GCC_64bit-Release $ ./lab01-logging

Qt Console Application – Embarcados

Qt Console Application – Embarcados [qDebug] Qt Console Application – Embarcados [qInfo] Qt Console Application – Embarcados

Mas agora iremos executar o binário via terminal, vá até onde criou o projeto e acesse o diretório de build, e execute o binário como abaixo:

build-lab01-logging-Desktop_Qt_5_6_2_GCC_64bit-Release $ ./lab01-logging

Qt Console Application – Embarcados

Qt Console Application – Embarcados [qDebug] Qt Console Application – Embarcados [qInfo] Qt Console Application – Embarcados

Agora vamos executar a aplicação novamente, mas iremos redirecionar o stdout (fd 1) para um arquivo em /tmp/lab01.stdout. Em seguida, executar novamente e redirecionar o stderr (fd 2) para o arquivo /tmp/lab01.stderr e visualizar a saída de ambos.

build-lab01-logging-Desktop_Qt_5_6_2_GCC_64bit-Release $
build-lab01-logging-Desktop_Qt_5_6_2_GCC_64bit-Release $ ./lab01-logging 1> /tmp/lab01.stdout
[qDebug] Qt Console Application - Embarcados
[qInfo] Qt Console Application - Embarcados
build-lab01-logging-Desktop_Qt_5_6_2_GCC_64bit-Release $
build-lab01-logging-Desktop_Qt_5_6_2_GCC_64bit-Release $ ./lab01-logging 2> /tmp/lab01.stderr

Qt Console Application – Embarcados

Qt Console Application – Embarcados build-lab01-logging-Desktop_Qt_5_6_2_GCC_64bit-Release $ build-lab01-logging-Desktop_Qt_5_6_2_GCC_64bit-Release $ cat /tmp/lab01.stdout

Qt Console Application – Embarcados

Qt Console Application – Embarcados build-lab01-logging-Desktop_Qt_5_6_2_GCC_64bit-Release $ build-lab01-logging-Desktop_Qt_5_6_2_GCC_64bit-Release $ cat /tmp/lab01.stderr [qDebug] Qt Console Application – Embarcados [qInfo] Qt Console Application – Embarcados

Então conseguimos executar e comprovar que qDebug(), qInfo() e demais funções utilizam stderr, e std::cout e printf() o stdout como padrão.

Criando o seu próprio logging com QTextStream

As funções que vimos podem ajudar muito durante o desenvolvimento e etapas de informações, alertas e erros, mas as vezes é necessário alimentar um arquivo com informações de algumas etapas.

Desta forma, podemos criar um simples logging usando a classe QTextStream, um excelente e poderoso recurso para se trabalhar com Stream de texto em Qt, conseguindo manipular dispositivos ou arquivos em disco. Utilizando os operadores << e >> consegue-se facilmente escrever e ler do QTextStream, além de possibilitar recursos como configurador um padrão de codificação como UTF-8.

Para este exemplo iremos criar o Projeto lab02-logging e adicionar o seguinte conteúdo:

#include <QCoreApplication>
#include <QDebug>
#include <QFile>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    QString filename_log;
    QFile handler_log;

    /* Definindo o local do log no mesmo local do binario, utilizar o mesmo nome do binario
     * e adicionar a extensão .log
     */
    filename_log = a.applicationDirPath()+"/"+a.applicationName()+".log";

    qInfo() << "ARQUIVO DE LOG: " << filename_log << endl;

    /* Adicionando o filename_log ao QFile */
    handler_log.setFileName(filename_log);

    /* Tentamos abrir nosso arquivo, onde será apenas escrita
     *      - QIODevice::WriteOnly   -> Os dados serão truncados, se arquivo existir e possuir dados, será apagado
     *      - QIODevice::Text               -> Se aberto para leitura terminador '\n', em escrita os terminadores serão '\r\n'
     *      - QIODevice::Append       -> O arquivo é aberto no modo de append, inserindo os dados no final do arquivo
     */
    if(!handler_log.open(QIODevice::WriteOnly | QIODevice::Text ))
    {
        qDebug() << "[DEBUG] Não foi possivel abrir o arquivo" << filename_log << "para escrita de log.";
        return EXIT_FAILURE;
    }

    /*
     * Cria o objeto ts usando como arquivo de manipulação nosso arquivo de log
     */
    QTextStream ts(&handler_log);

    /* Parar escrever no log podemos usar operador << */
    ts << "[LOG]" << "TESTE LOG LOCAL" << endl;

    /* Esvazia o buffer de dados escrevendo no dispositivo ou arquivo */
    handler_log.flush();
    handler_log.close();

    return EXIT_SUCCESS;
}

Salve o código, compile e execute, e um arquivo com o mesmo nome do binário será criado com a extensão .log e irá conter o conteúdo “[LOG] TESTE LOG LOCAL“, como abaixo.

build-lab02-logging-Desktop_Qt_5_6_2_GCC_64bit-Release $ ./lab02-logging
ARQUIVO DE LOG: "~/Projetos/Qt/Embarcados/QtLogging/build-lab02-logging-Desktop_Qt_5_6_2_GCC_64bit-Release/lab02-logging.log"
build-lab02-logging-Desktop_Qt_5_6_2_GCC_64bit-Release $ cat lab02-logging.log
[LOG]TESTE LOG LOCAL

O código e a ideia é simples, mas pode ser estendida e bem elaborada em uma classe para anexar log de diversos eventos da aplicação, pode-se basear no exemplo de Simples-Logger.

Utilizando o log do Sistema

Este é um recurso novo a partir do Qt5.6.0 que permitir anexar logs da aplicação no sistema de log do Sistema, aqui iremos adotar o SysLog para estudo, padrão este utilizando em Sistemas Unix e Linux de servidores a roteadores, mas também aplica-se ao journald.

Porém, este recurso não vem habilitado por padrão, então caso compilou o Qt5 em seu host ou realizou compilação-cruzada para algum target embarcado, este recurso deverá ser habilitado para utilizá-lo, para confirmar é só visualizar o arquivo config.summary gerado durante o processo de configuração do Qtbase. Abaixo o config.summary de uma compilação-cruzada para uso em um ARM.

Image formats:
  GIF .................. yes (plugin, using bundled copy)
  JPEG ................. yes (plugin, using system library)
  PNG .................. yes (in QtGui, using system library)
  libinput................ yes

Logging backends:
  journald ............... no
  syslog ............... no
  mtdev .................. no

Networking:
  getaddrinfo .......... yes
  getifaddrs ........... yes
  IPv6 ifname .......... yes
  libproxy.............. no
  OpenSSL .............. yes (loading libraries at run-time)

Para habilitar as opções deve-se incluir nos parâmetros do configure o -syslog e/ou -journald.

Com esta dependência resolvida, iremos criar o Projeto lab03-logging, configurar uma variável ambiente e informar ao Qt para utilizar o Log do Sistema, segue o código:

#include <QCoreApplication>
#include <QDebug>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    // Necessario para habilitar log para syslog oy journald
    //qputenv("QT_LOGGING_TO_CONSOLE", QByteArray("0"));

    QString debugMsg = "Aplicação correndo com sucesso!";
    QString infoMsg = "Iniciando execução da aplicação";

    qDebug("[DEBUG] %s", debugMsg.toUtf8().constData());
    qInfo("[INFO] %s", qUtf8Printable(infoMsg) );
    qDebug() << "[DEBUG] Fim da aplicação" << endl;

    return EXIT_SUCCESS;
}

Se executarmos o código, irá funcionar da mesma maneira anterior, todas as mensagens irão para o console. Para “forçar” do redirecionamento das mensagens do qDebug() e demais pra o Sistema de Log deve-se configurar uma variável ambiente para isso, chamada QT_LOGGING_TO_CONSOLE.

Na linha 9 onde esta comentado, podemos desabilitar o envio para syslog ou journald configurando como 1 ou habitar o envio configurando como 0.

Inseri comentado no código para mostrar que é possível deixar configurado na aplicação desta forma, mas iremos fazer nosso exemplo utilizando variável ambiente no Linux.

~# ./lab03-logging
[DEBUG] Aplicação correndo com sucesso!
[INFO] Iniciando execução da aplicação
[PRINTF] Qt Console Logging - Embarcados
[DEBUG] Fim da aplicação
~#
~# export QT_LOGGING_TO_CONSOLE="1"
~# ./lab03-logging
[DEBUG] Aplicação correndo com sucesso!
[INFO] Iniciando execução da aplicação
[PRINTF] Qt Console Logging - Embarcados
[DEBUG] Fim da aplicação
~#
~# export QT_LOGGING_TO_CONSOLE="0"
~# ./lab03-logging
[PRINTF] Qt Console Logging – Embarcados
~#
~# cat /var/log/syslog
...
Nov 29 10:24:21 arm-dev user.debug lab03-logging: [DEBUG] Aplicação correndo com sucesso!
Nov 29 10:24:21 arm-dev user.info lab03-logging: [INFO] Iniciando execução da aplicação
Nov 29 10:24:21 arm-dev user.debug lab03-logging: [DEBUG] Fim da aplicação

Na saída acima, configurando QT_LOGGING_TO_CONSOLE como 0 teremos apenas a saída do printf() no console e as mensagens de stderr no /var/log/syslog.

Dicas Extras

Para encerrar este artigo, iremos juntar todas as funções de manipulação de logs, e aplicar algumas configurações que podem ajudar e muito no desenvolvimento de um software, utilizando variáveis ambientes e alguns defines. Vamos criar o projeto lab04-logging e utilizar o código abaixo:

#include <QCoreApplication>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    qDebug("[qDebug]");
    qInfo("[qInfo]");
    qWarning("[qWarning]");
    qCritical("[qCritical]");
    qFatal("[qFatal]");

    printf("[printf]\r\n");

    return a.exec();
} 

Compilando e executando a mensagem do printf() não será exibida, pois em qualquer momento que houver um qFatal() a aplicação é abortada e retorna algo diferente de 0.

Porém, conseguimos o mesmo efeito com qWarning() e qCritical(), setando as variáveis ambiente QT_FATAL_WARNINGS e QT_FATAL_CRITICALS para 1, vamos exportar no terminal o QT_FATAL_WARNINGS para 1 e executar a aplicação novamente, na sequência o mesmo com QT_FATAL_CRITICALS.

build-lab04-logging-Desktop_Qt_5_6_2_GCC_64bit-Release $ export QT_FATAL_WARNINGS="1"
build-lab04-logging-Desktop_Qt_5_6_2_GCC_64bit-Release $
build-lab04-logging-Desktop_Qt_5_6_2_GCC_64bit-Release $ ./lab04-logging
[qDebug]
[qInfo]
[qWarning]
Aborted
build-lab04-logging-Desktop_Qt_5_6_2_GCC_64bit-Release $
build-lab04-logging-Desktop_Qt_5_6_2_GCC_64bit-Release $ echo $?
134
build-lab04-logging-Desktop_Qt_5_6_2_GCC_64bit-Release $
build-lab04-logging-Desktop_Qt_5_6_2_GCC_64bit-Release $ unset QT_FATAL_WARNINGS
build-lab04-logging-Desktop_Qt_5_6_2_GCC_64bit-Release $ export QT_FATAL_CRITICALS="1"
build-lab04-logging-Desktop_Qt_5_6_2_GCC_64bit-Release $
build-lab04-logging-Desktop_Qt_5_6_2_GCC_64bit-Release $ ./lab04-logging
[qDebug]
[qInfo]
[qWarning]
[qCritical]
Aborted
build-lab04-logging-Desktop_Qt_5_6_2_GCC_64bit-Release $ echo $?
134
build-lab04-logging-Desktop_Qt_5_6_2_GCC_64bit-Release $

Desta maneira, podemos configurar quando ocorrer qualquer qWarning(), qCritical() a aplicação pode ser abortada, isso utilizando variáveis de ambiente que podem ser configuradas na aplicação ou exportadas no terminal antes de executar.

Outra dica muito útil é sobre as mensagens do qDebug(), em meus projetos acabo abusando muito no uso de mensagens de qDebug(), qInfo() e qWarning(), mas na versão de produção muitas vezes você não quer ver estas mensagens ou ver apenas qInfo() ou qWarning(), para isso conseguimos manipular com três defines QT_NO_DEBUG_OUTPUT, QT_NO_WARNING_OUTPUT e QT_NO_INFO_OUTPUT.

Vou adicionar no lab04-logging.pro o QT_NO_DEBUG_OUTPUT.

QT += core
QT -= gui

CONFIG += c++11

# Omite a saide de qDebug() na aplicação
DEFINES += QT_NO_DEBUG_OUTPUT

TARGET = lab04-logging

CONFIG += console
CONFIG -= app_bundle

TEMPLATE = app

SOURCES += main.cpp

Compilando e executando o lab04-logging.

build-lab04-logging-Desktop_Qt_5_6_2_GCC_64bit-Release $ ./lab04-logging
[qInfo]
[qWarning]
[qCritical]
Aborted

Agora irei adicionar também QT_NO_WARNING_OUTPUT no lab04-logging.pro e irei comentar o qFatal() no main.cpp.

build-lab04-logging-Desktop_Qt_5_6_2_GCC_64bit-Release $ ./lab04-logging
[qInfo]
[qCritical]

Não temos mais qDebug() e qWarning() nas saídas e sem o qFatal() o nosso printf() foi exibido. Por último podemos também ignorar o qInfo() adicionando QT_NO_INFO_OUTPUT, nosso lab04-logging.pro ficando como abaixo:

QT += core
QT -= gui

CONFIG += c++11

# Omite a saide de qDebug() na aplicação
DEFINES += QT_NO_DEBUG_OUTPUT

# Omite a saida de qWarning() na aplicação
DEFINES += QT_NO_WARNING_OUTPUT

# Omite a saida de qInfo() na aplicação
DEFINES += QT_NO_INFO_OUTPUT

TARGET = lab04-logging

CONFIG += console
CONFIG -= app_bundle

TEMPLATE = app

SOURCES += main.cpp

Compilando e executando:

build-lab04-logging-Desktop_Qt_5_6_2_GCC_64bit-Release $ ./lab04-logging
[qCritical]

^C build-lab04-logging-Desktop_Qt_5_6_2_GCC_64bit-Release $

Muito útil estes defines, onde você pode utilizar diversos qDebug() pelo projeto e na versão final de produção, ele pode ser omitido sem você ter que varrer N linhas para comentar ou remover estas entradas.

Outra dica é remover espaços e citação “” no texto utilizando qDebug(), vamos alterar o exemplo lab01-logging.

#include <QCoreApplication>
#include <QDebug>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    QString debugMsg = "Aplicação correndo com sucesso!";

    qDebug("[DEBUG] Qt Console Application - Embarcados");
    qDebug("[DEBUG]->%s", debugMsg.toUtf8().constData());

    qDebug() << "[DEBUG] Mais um jeito de usar qDebug()";
    qDebug() << "[DEBUG]->" << debugMsg;

    return EXIT_SUCCESS;
}

Compilando e executando:

build-lab01-logging-Desktop_Qt_5_6_2_GCC_64bit-Release $ ./lab01-logging 
[DEBUG] Qt Console Application - Embarcados
[DEBUG]->Aplicação correndo com sucesso!
[DEBUG] Mais um jeito de usar qDebug()
[DEBUG]-> "Aplicação correndo com sucesso!"

O qDebug() utilizado nas linhas 11 e 14 eram para apresentar a mesma saída, porém o segundo incluiu um espaço e adicionou “” na QString da variável debugMsg, isso sempre irá ocorrer quando se utilizar ‘qDebug() << “VARIAVEL OU TEXTO”‘, para resolver este impasse, iremos utilizar funções do próprio qDebug(), noquote() e quote() para remover ou inserir citação, e nospace() e space() para remover ou adicionar espaço.

Iremos alterar a linha 14 de:

qDebug() << "[DEBUG]->" << debugMsg;

Para:

qDebug().noquote().nospace() << "[DEBUG]->" << debugMsg;

Compilando e executando:

build-lab01-logging-Desktop_Qt_5_6_2_GCC_64bit-Release $ ./lab01-logging 
[DEBUG] Qt Console Application - Embarcados
[DEBUG]->Aplicação correndo com sucesso!
[DEBUG] Mais um jeito de usar qDebug()
[DEBUG]->Aplicação correndo com sucesso!

Conseguimos o mesmo resultado com das suas maneiras de utilizar qDebug(), a citação “” no texto pode-se remover inserindo .toUtf8().constData(), mas se fosse um texto direto não resolveria, então só com noquote().

Estas informações e muitas outras encontram-se na documentação da classe QDebug.

Não acaba aqui…

O que apresentamos aqui neste artigo, foi apenas uma base do que podemos fazer com a parte de logging no Qt5, além disso, ele possibilita diversos outros recursos para você customizar e adotar em seu projeto como o QtMessageHandler(QtMsgType, QmessageLogContex e QString), qSetMessagePattern e qInstallMessageHandler onde você podera criar e montar a sua própria estrutura e formatação das mensagens.

Poderá ser tema ou aplicado em um próximo artigo sobre Qt5 ou utilizado como exemplo em um projeto.

Esperamos que você tenha gostado do artigo, abuse das sintaxes e do framework Qt, fique à vontade para enviar sugestões ou algum comentário.

Boa diversão!

Referências

https://doc.qt.io/qt-5/qtglobal.html
https://doc.qt.io/qt-5/debug.html
https://doc.qt.io/qt-5/qtextstream.html
https://doc.qt.io/qt-5/qtglobal.html#qInstallMessageHandler
https://doc.qt.io/qt-5/qtglobal.html#QtMessageHandler-typedef
https://doc.qt.io/qt-5/qtglobal.html#QtMsgType-enum
Licença Creative Commons Esta obra está licenciada com uma Licença Creative Commons Atribuição-CompartilhaIgual 4.0 Internacional.
Comentários:
Notificações
Notificar
1 Comentário
recentes
antigos mais votados
Inline Feedbacks
View all comments
jota
jota
04/08/2020 16:53

Muito bom. Aguardando novos posts desse tema.

Home » Software » Manipulando Logs com Qt5

EM DESTAQUE

WEBINARS

VEJA TAMBÉM

JUNTE-SE HOJE À COMUNIDADE EMBARCADOS

Talvez você goste: