Làm thế nào để chuyển hướng đầu ra qDebug, qWarning, qCritical, v.v.?


84

Tôi đang sử dụng rất nhiều qDebug() <<câu lệnh để gỡ lỗi đầu ra. Có cách nào đa nền tảng để tôi có thể chuyển hướng đầu ra gỡ lỗi đó đến một tệp mà không cần dùng đến các tập lệnh shell không? Tôi đoán rằng open ()Dup2 () sẽ thực hiện công việc trong Linux, nhưng liệu nó có hoạt động được biên dịch với MinGW trong Windows không?

Và có thể có một cách Qt để làm điều đó?

Câu trả lời:


120

Bạn phải cài đặt một trình xử lý thông báo bằng qInstallMsgHandlerhàm và sau đó, bạn có thể sử dụng QTextStreamđể ghi thông báo gỡ lỗi vào một tệp. Đây là một ví dụ mẫu:

#include <QtGlobal>
#include <stdio.h>
#include <stdlib.h>

void myMessageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
    QByteArray localMsg = msg.toLocal8Bit();
    switch (type) {
    case QtDebugMsg:
        fprintf(stderr, "Debug: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function);
        break;
    case QtInfoMsg:
        fprintf(stderr, "Info: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function);
        break;
    case QtWarningMsg:
        fprintf(stderr, "Warning: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function);
        break;
    case QtCriticalMsg:
        fprintf(stderr, "Critical: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function);
        break;
    case QtFatalMsg:
        fprintf(stderr, "Fatal: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function);
        abort();
    }
}

int main(int argc, char **argv)
{
    qInstallMessageHandler(myMessageOutput); // Install the handler
    QApplication app(argc, argv);
    ...
    return app.exec();
}

Lấy từ tài liệu của qInstallMsgHandler(tôi chỉ thêm các nhận xét):

Trong ví dụ trên, hàm myMessageOutputsử dụng stderrmà bạn có thể muốn thay thế bằng một số dòng tệp khác hoặc viết lại hoàn toàn hàm!

Khi bạn viết và cài đặt chức năng này, tất cả thư qDebug(cũng như qWarning, qCriticalv.v.) của bạn sẽ được chuyển hướng đến tệp bạn đang viết trong trình xử lý.


3
Này, cảm ơn rất nhiều. Nó không chỉ cho phép tôi chuyển hướng đầu ra gỡ lỗi thành một tệp, nó còn cho phép tôi in thêm thông tin hữu ích, chẳng hạn như dấu thời gian :)
Septagram

2
@Septagram: Chính xác. Bạn có thể thêm một số thông điệp hữu ích trong chính hanlder; và bạn thậm chí có thể ra thông điệp khác nhau đến các tập tin khác nhau, dựa trên những gì bạn sử dụng qDebug, qWarning, qCriticalvà như vậy!
Nawaz

1
Nhân tiện, lệnh gọi lại thực hiện đầu ra - void myMessageOutput (QtMsgType type, const char * msg) - nó nhận được thông báo ở dạng mã hóa nào?
Septagram

8
Các liên kết tài liệu và API đã thay đổi một chút. qInstallMsgHandlerkhông được dùng nữa và được thay thế bằng qInstallMessageHandler(cùng ý tưởng) trong Qt5. Đối với 5.0 qInstallMsgHandlerlà tại qt-project.org/doc/qt-5.0/qtcore/…qInstallMessageHandlercũng có ở đó. Đối với 5.1, qInstallMsgHandlerđã bị xóa hoàn toàn.
Jason C

1
@Aditya: Trong Qt4, lệnh gọi lại chỉ có hai đối số. Vì vậy, bạn có thể sử dụng:void myMessageOutput(QtMsgType type, const char *msg) { ... }
Nawaz

19

Từ đây tất cả tín dụng đều chuyển sang tinh thần .

#include <QApplication>
#include <QtDebug>
#include <QFile>
#include <QTextStream>

void myMessageHandler(QtMsgType type, const QMessageLogContext &, const QString & msg)
{
    QString txt;
    switch (type) {
    case QtDebugMsg:
        txt = QString("Debug: %1").arg(msg);
        break;
    case QtWarningMsg:
        txt = QString("Warning: %1").arg(msg);
    break;
    case QtCriticalMsg:
        txt = QString("Critical: %1").arg(msg);
    break;
    case QtFatalMsg:
        txt = QString("Fatal: %1").arg(msg);
    break;
    }
    QFile outFile("log");
    outFile.open(QIODevice::WriteOnly | QIODevice::Append);
    QTextStream ts(&outFile);
    ts << txt << endl;
}

int main( int argc, char * argv[] )
{
    QApplication app( argc, argv );
    qInstallMessageHandler(myMessageHandler);   
    ...
    return app.exec();
}

case QtFatalMsg: ... abort (); // nó sẽ bỏ trước khi viết nhật ký
raidsan

Bắt đầu từ QT 5, qInstallMessageHandlernên được sử dụng thay vì qInstallMsgHandlerđể thay đổi trình xử lý thông báo.
SuB

Trình xử lý thư này không an toàn theo chuỗi. Bạn sẽ mất thông báo nhật ký nếu chúng được gửi bởi hai chuỗi cùng một lúc (outFile.open () sẽ trả về false cho một trong các chuỗi). Bạn có thể khóa QMutex trước khi cố mở tệp, sau đó mở khóa mutex sau khi đóng tệp. Đây là cách tiếp cận đơn giản nhất nhưng nó sẽ giới thiệu sự cạnh tranh của chủ đề. Nếu không, bạn sẽ cần phải xem xét hàng đợi thư an toàn theo luồng chi phí thấp ... và bạn có thể sử dụng một khung công tác tốt hơn.
Anthony Hayward

9

Đây là một ví dụ hoạt động của việc nối trình xử lý thư mặc định.

Xin cảm ơn @Ross Rogers!

// -- main.cpp

// Get the default Qt message handler.
static const QtMessageHandler QT_DEFAULT_MESSAGE_HANDLER = qInstallMessageHandler(0);

void myCustomMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
    // Handle the messages!

    // Call the default handler.
    (*QT_DEFAULT_MESSAGE_HANDLER)(type, context, msg);
}

int main(int argc, char *argv[])
{
    qInstallMessageHandler(myCustomMessageHandler);

    QApplication a(argc, argv);

    qDebug() << "Wello Horld!";

    return 0;
}

8

Đây là một giải pháp đa nền tảng để đăng nhập vào bảng điều khiển, nếu ứng dụng được chạy từ Qt Creator và vào debug.logtệp, khi nó được biên dịch và chạy như một ứng dụng độc lập.

main.cpp :

#include <QApplication>
#include <QtGlobal>
#include <QtDebug>
#include <QTextStream>
#include <QTextCodec>
#include <QLocale>
#include <QTime>
#include <QFile>   

const QString logFilePath = "debug.log";
bool logToFile = false;
    
void customMessageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
    QHash<QtMsgType, QString> msgLevelHash({{QtDebugMsg, "Debug"}, {QtInfoMsg, "Info"}, {QtWarningMsg, "Warning"}, {QtCriticalMsg, "Critical"}, {QtFatalMsg, "Fatal"}});
    QByteArray localMsg = msg.toLocal8Bit();
    QTime time = QTime::currentTime();
    QString formattedTime = time.toString("hh:mm:ss.zzz");
    QByteArray formattedTimeMsg = formattedTime.toLocal8Bit();
    QString logLevelName = msgLevelHash[type];
    QByteArray logLevelMsg = logLevelName.toLocal8Bit();

    if (logToFile) {
        QString txt = QString("%1 %2: %3 (%4)").arg(formattedTime, logLevelName, msg,  context.file);
        QFile outFile(logFilePath);
        outFile.open(QIODevice::WriteOnly | QIODevice::Append);
        QTextStream ts(&outFile);
        ts << txt << endl;
        outFile.close();
    } else {
        fprintf(stderr, "%s %s: %s (%s:%u, %s)\n", formattedTimeMsg.constData(), logLevelMsg.constData(), localMsg.constData(), context.file, context.line, context.function);
        fflush(stderr);
    }

    if (type == QtFatalMsg)
        abort();
}

int main(int argc, char *argv[])
{
    QByteArray envVar = qgetenv("QTDIR");       //  check if the app is ran in Qt Creator

    if (envVar.isEmpty())
        logToFile = true;

    qInstallMessageHandler(customMessageOutput); // custom message handler for debugging

    QApplication a(argc, argv);
    // ...and the rest of 'main' follows

Định dạng nhật ký được xử lý bởi QString("%1 %2: %3 (%4)").arg...(đối với tệp) vàfprintf(stderr, "%s %s: %s (%s:%u, %s)\n"... (đối với bảng điều khiển).

Nguồn cảm hứng: https://gist.github.com/polovik/10714049 .


Tôi thấy rằng bạn gọi "outFile.close ()" trong mọi sự kiện nhật ký. Tôi có thể bỏ qua nó được không?
thợ lặn

Tôi không khuyên bạn nên sử dụng nó trong thiết lập này, vì bạn luôn mở tệp nhật ký và do đó nó sẽ được đóng lại. Nhưng bạn có thể thay đổi thuật toán theo cách nào đó, tệp nhật ký đó chỉ được mở một lần tại init của ứng dụng. Bằng cách này, bạn sẽ chỉ cần đóng nó một lần khi ứng dụng thoát ra.
Neurotransmitter

1
Cảm ơn! Nó rất hữu ích.
Aaron

Trình xử lý thư này không an toàn theo chuỗi. Bạn sẽ mất thông báo nhật ký nếu chúng được gửi bởi hai chuỗi cùng một lúc (outFile.open () sẽ trả về false cho một trong các chuỗi). Bạn có thể khóa QMutex trước khi cố mở tệp, sau đó mở khóa mutex sau khi đóng tệp. Đây là cách tiếp cận đơn giản nhất nhưng nó sẽ giới thiệu sự cạnh tranh của chủ đề. Nếu không, bạn sẽ cần phải xem xét hàng đợi thư an toàn theo chuỗi chi phí thấp ... và bạn có thể sử dụng một khuôn khổ tốt hơn!
Anthony Hayward

Tôi đồng ý với bạn - nó còn lâu mới hoàn hảo. Nhưng nó thực hiện công việc của nó hầu hết thời gian. Dù sao, bất kỳ sửa đổi nào đều được hoan nghênh!
Neurotransmitter,

6

Tôi sẽ nói rằng thời điểm bạn cần chuyển hướng đầu ra gỡ lỗi của mình sang bất kỳ thứ gì khác với stderr là lúc bạn có thể nghĩ về một số công cụ ghi nhật ký. Nếu bạn cảm thấy mình cần, tôi khuyên bạn nên sử dụng QxtLogger( "Lớp QxtLogger là một công cụ ghi nhật ký dễ sử dụng, dễ mở rộng." ) Từ Qxtthư viện.


0

Đây là một ví dụ Qt thành ngữ an toàn theo chuỗi đơn giản để đăng nhập cả hai vào stderr và tệp:

void messageHandler (kiểu QtMsgType, const QMessageLogContext & context, const QString & message)
{
    tĩnh QMutex mutex;
    Khóa QMutexLocker (& mutex);

    static QFile logFile (LOGFILE_LOCATION);
    static bool logFileIsOpen = logFile.open (QIODevice :: Append | QIODevice :: Text);

    std :: cerr << qPrintable (qFormatLogMessage (type, context, message)) << std :: endl;

    if (logFileIsOpen) {
        logFile.write (qFormatLogMessage (loại, ngữ cảnh, thông báo) .toUtf8 () + '\ n');
        logFile.flush ();
    }
}

Cài đặt nó với qInstallMessageHandler(messageHandler)như được mô tả trong các câu trả lời khác.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.