... // Logging #include #include #include #include #include #include #include #include #include #include #include #if defined(Q_OS_ANDROID) #include #elif defined(Q_OS_IOS) #include #include #include #include #endif QString sessionID; void submitLog(QString category, QString file, QString function, int line, QString message, QString type, QString origin) { // submit only WARNINGS, CRITICAL and FATAL logs, DEBUG/VERBOSE and INFO skip if (type == "WARNING" || type == "CRITICAL" || type == "FATAL") { QOperatingSystemVersion osVersion = QOperatingSystemVersion::current(); QJsonObject log; log["timestamp"] = QDateTime::currentMSecsSinceEpoch(); log["version"] = QCoreApplication::applicationVersion(); log["type"] = type; log["origin"] = origin; log["message"] = message; log["os"] = osVersion.name(); log["os_version"] = QString("%1.%2.%3").arg(osVersion.majorVersion()).arg(osVersion.minorVersion()).arg(osVersion.microVersion()); log["file"] = file; log["line"] = line; log["function"] = function; log["category"] = category; log["device"] = CoreFunctions().getDevice(); log["session"] = sessionID; //QByteArray jsonLog = QJsonDocument(log).toJson(QJsonDocument::Compact); QNetworkAccessManager *manager = new QNetworkAccessManager(); QNetworkRequest request(QUrl("YOUR_HTTP_ENDPOINT_TO_RECEIVE_LOGS")); request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); //request.setRawHeader("Authorization", "Bearer YOUR_TOKEN_HERE"); //Send POST request //QNetworkReply *reply = manager->post(request, jsonLog); // if I want to see reply, the nuse this line, however its design flow because it will print to console log the reply and this reply will be also used as additional log manager->post(request, QJsonDocument(log).toJson()); // this instead of line above to retrieve reply (whcih is recommended) // REPLY RECEIVED // if I want to see reply, the nuse this line, however its design flow because it will print to console log the reply and this reply will be also used as additional log // QObject::connect(manager, &QNetworkAccessManager::finished, [&](QNetworkReply *reply) { // QByteArray serverReply = reply->readAll(); // fprintf(stderr, "%s\n", serverReply.constData()); // fflush(stderr); // reply->deleteLater(); // }); } } void customMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg) { // Type of message QString typeStr; switch (type) { case QtDebugMsg: typeStr = "DEBUG"; break; case QtInfoMsg: typeStr = "INFO"; break; case QtWarningMsg: typeStr = "WARNING"; break; case QtCriticalMsg: typeStr = "CRITICAL"; break; case QtFatalMsg: typeStr = "FATAL"; break; } // Origin of message QString origin; QString file = context.file ? QString(context.file) : ""; QString function = context.function ? QString(context.function) : ""; if (file.endsWith(".cpp") || function.contains("::")) { origin = "C++"; } else if (file.contains(".qml") || msg.contains("qrc:/") || msg.contains("file:///") || msg.contains("qml:")) { origin = "QML/JavaScript"; } else if (file.isEmpty() && function.isEmpty() && context.line == 0) { origin = "Possibly QML"; } else { origin = "Unknown"; } // Submit the message submitLog(context.category, context.file, context.function, context.line, msg, typeStr, origin); } void startLogcatPipe() { #if defined(Q_OS_ANDROID) QProcess *logcat = new QProcess; QObject::connect(logcat, &QProcess::readyReadStandardOutput, [&, logcat]() { while (logcat->canReadLine()) { QString line = QString::fromUtf8(logcat->readLine()).trimmed(); if (!line.isEmpty()) { static const QRegularExpression re(R"((\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3})\s+([VDIWEF])\/([^:]+):\s+(.*))"); QRegularExpressionMatch match = re.match(line); if (match.hasMatch()) { QString level = match.captured(2); QString tag = match.captured(3); QString message = match.captured(4); QString formatted = QString("[%1] %2: %3").arg(tag, level, message); QString typeStr; if (level == "V") { typeStr = "VERBOSE"; } else if (level == "D") { typeStr = "DEBUG"; } else if (level == "I") { typeStr = "INFO"; } else if (level == "W") { typeStr = "WARNING"; } else if (level == "E") { typeStr = "CRITICAL"; } // for Android there is no Critical, only Error else if (level == "F") { typeStr = "FATAL"; } // hopefully F means Fatal (and not simple Fail) submitLog("", "", "", 0, formatted, typeStr, "Android"); } } } }); logcat->start("logcat", QStringList() << "-v" << "time"); #endif } void startIOSLogPipe() { #if defined(Q_OS_IOS) int pipefd[2]; if (pipe(pipefd) == -1) { return; } // Redirect stdout and stderr to the pipe dup2(pipefd[1], STDOUT_FILENO); dup2(pipefd[1], STDERR_FILENO); close(pipefd[1]); QSocketNotifier *notifier = new QSocketNotifier(pipefd[0], QSocketNotifier::Read); QObject::connect(notifier, &QSocketNotifier::activated, [&, pipefd]() { char buffer[1024]; ssize_t bytesRead = read(pipefd[0], buffer, sizeof(buffer) - 1); if (bytesRead > 0) { buffer[bytesRead] = '\0'; QString line = QString::fromUtf8(buffer).trimmed(); if (!line.isEmpty()) { QString typeStr; if (line.contains("[VERBOSE]") { typeStr = "VERBOSE"; } else if (line.contains("[DEBUG]")) { typeStr = "DEBUG"; } else if (line.contains("[INFO]")) { typeStr = "INFO"; } else if (line.contains("[WARNING]")) { typeStr = "WARNING"; } else if (line.contains("[CRITICAL]")) { typeStr = "CRITICAL"; } else if (line.contains("[ERROR]")) { typeStr = "ERROR"; } else if (line.contains("[FATAL]")) { typeStr = "FATAL"; } else { typeStr = "UNDEFINED"; } submitLog("", "", "", 0, line, typeStr, "iOS"); // NSString *nsLog = [NSString stringWithUTF8String:jsonLog.constData()]; // NSLog(@"%@", nsLog); } } }); #endif } int main(int argc, char *argv[]) { // Generate SessionID for (int i=0; i<32; i++) { sessionID.append(QString("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789").at(QRandomGenerator::global()->bounded(QString("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789").length()))); } // Debug Message Handler qInstallMessageHandler(customMessageHandler); QGuiApplication app(argc, argv); // native Android/iOS debugger #if defined(Q_OS_ANDROID) startLogcatPipe(); #elif defined(Q_OS_IOS) startIOSLogPipe(); #endif QQmlApplicationEngine engine; const QUrl url("qrc:/filmtoro/Main.qml"); QObject::connect( &engine, &QQmlApplicationEngine::objectCreationFailed, &app, []() { QCoreApplication::exit(-1); }, Qt::QueuedConnection); engine.load(url); return app.exec(); }