diff --git a/android/black/GetCardInfoScript.cpp b/android/black/GetCardInfoScript.cpp index b37cd23..237b25b 100644 --- a/android/black/GetCardInfoScript.cpp +++ b/android/black/GetCardInfoScript.cpp @@ -37,7 +37,7 @@ void GetCardInfoScript::doStart() { return; } - const TaskDumpMeta dumpMeta{}; + const TaskDumpMeta dumpMeta{{}, 0, {}, {}, app.bankProfileId, app.phone}; TaskDumpRecorder::Scope dumpScope("black", "FETCH_CARDS", m_deviceId, dumpMeta, &m_error); // Читаем текущий экран diff --git a/android/black/GetProfileInfoScript.cpp b/android/black/GetProfileInfoScript.cpp index 23bf45e..18abbf0 100644 --- a/android/black/GetProfileInfoScript.cpp +++ b/android/black/GetProfileInfoScript.cpp @@ -35,7 +35,7 @@ void GetProfileInfoScript::doStart() { return; } - const TaskDumpMeta dumpMeta{}; + const TaskDumpMeta dumpMeta{{}, 0, {}, {}, app.bankProfileId, app.phone}; TaskDumpRecorder::Scope dumpScope("black", "FETCH_PROFILE_INFO", m_deviceId, dumpMeta, &m_error); // Читаем текущий экран diff --git a/android/black/LoginAndCheckAccountsScript.cpp b/android/black/LoginAndCheckAccountsScript.cpp index 2596222..11e6579 100644 --- a/android/black/LoginAndCheckAccountsScript.cpp +++ b/android/black/LoginAndCheckAccountsScript.cpp @@ -36,7 +36,7 @@ void LoginAndCheckAccountsScript::doStart() { return; } - const TaskDumpMeta dumpMeta{}; + const TaskDumpMeta dumpMeta{{}, 0, {}, {}, app.bankProfileId, app.phone}; TaskDumpRecorder::Scope dumpScope("black", "FETCH_PROFILE", m_deviceId, dumpMeta, &m_error); xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter)); diff --git a/android/dump/TaskDumpRecorder.cpp b/android/dump/TaskDumpRecorder.cpp index 38664e1..531d081 100644 --- a/android/dump/TaskDumpRecorder.cpp +++ b/android/dump/TaskDumpRecorder.cpp @@ -166,22 +166,30 @@ TaskDumpRecorder::TaskDumpRecorder(const QString &bank, const QString &taskKind, } QString TaskDumpRecorder::buildCaption(const QString &errorMessage) const { - const QString apiToken = SettingsDAO::get("api_key"); const QString timestamp = QDateTime::currentDateTime().toString(Qt::ISODate); + // api-токен в caption больше не пишем: он общий на нескольких операторов и + // не позволяет отличить, чей профиль упал. Идентифицируем по UUID профиля, + // материалу и телефону профиля (см. m_meta). QStringList head{QStringLiteral("[TASK_ERROR]")}; - if (!apiToken.isEmpty()) - head << QStringLiteral("[%1]").arg(apiToken.toHtmlEscaped()); head << QStringLiteral("[%1]").arg(m_bank.toHtmlEscaped()); head << QStringLiteral("[%1]").arg(m_taskKind.toHtmlEscaped()); - QString body = QStringLiteral("Device: %1").arg(m_adbDeviceIdRaw.toHtmlEscaped()); + const QString desktopId = SettingsDAO::get("desktop_id"); + QString body; + if (!desktopId.isEmpty()) + body += QStringLiteral("Desktop: %1\n").arg(desktopId.toHtmlEscaped()); + body += QStringLiteral("Device: %1").arg(m_adbDeviceIdRaw.toHtmlEscaped()); + if (!m_meta.bankProfileId.isEmpty()) + body += QStringLiteral("\nProfile: %1").arg(m_meta.bankProfileId.toHtmlEscaped()); if (!m_meta.materialId.isEmpty()) body += QStringLiteral("\nMaterial: %1").arg(m_meta.materialId.toHtmlEscaped()); + if (!m_meta.profilePhone.isEmpty()) + body += QStringLiteral("\nPhone: %1").arg(m_meta.profilePhone.toHtmlEscaped()); if (m_meta.amount != 0) body += QStringLiteral("\nAmount: %1").arg(static_cast(m_meta.amount)); if (!m_meta.phone.isEmpty()) - body += QStringLiteral("\nPhone: %1").arg(m_meta.phone.toHtmlEscaped()); + body += QStringLiteral("\nRecipient: %1").arg(m_meta.phone.toHtmlEscaped()); if (!m_meta.card.isEmpty()) body += QStringLiteral("\nCard: %1").arg(m_meta.card.toHtmlEscaped()); body += QStringLiteral("\nError: %1").arg(errorMessage.left(800).toHtmlEscaped()); @@ -238,9 +246,13 @@ void TaskDumpRecorder::finishInternal(const QString &errorMessage) { if (ef.open(QIODevice::WriteOnly | QIODevice::Truncate)) { QTextStream ts(&ef); ts.setEncoding(QStringConverter::Utf8); + const QString desktopId = SettingsDAO::get("desktop_id"); + if (!desktopId.isEmpty()) ts << "desktop_id: " << desktopId << "\n"; ts << "bank: " << m_bank << "\n" << "task: " << m_taskKind << "\n" << "device: " << m_adbDeviceIdRaw << "\n"; + if (!m_meta.bankProfileId.isEmpty()) ts << "profile_id: " << m_meta.bankProfileId << "\n"; + if (!m_meta.profilePhone.isEmpty()) ts << "profile_phone: " << m_meta.profilePhone << "\n"; if (!m_meta.materialId.isEmpty()) ts << "material_id: " << m_meta.materialId << "\n"; if (m_meta.amount != 0) ts << "amount: " << static_cast(m_meta.amount) << "\n"; if (!m_meta.phone.isEmpty()) ts << "phone: " << m_meta.phone << "\n"; @@ -255,6 +267,17 @@ void TaskDumpRecorder::finishInternal(const QString &errorMessage) { const QString archivePath = m_folder + ".zip"; const QString text = buildCaption(errorMessage); + // Тот же идентификатор профиля/материала/телефона добавляем в caption архива + // логов ([LOGS]), чтобы по общему api-токену можно было разделить операторов. + QStringList logsIdLines; + if (!m_meta.bankProfileId.isEmpty()) + logsIdLines << QStringLiteral("Profile: %1").arg(m_meta.bankProfileId.toHtmlEscaped()); + if (!m_meta.materialId.isEmpty()) + logsIdLines << QStringLiteral("Material: %1").arg(m_meta.materialId.toHtmlEscaped()); + if (!m_meta.profilePhone.isEmpty()) + logsIdLines << QStringLiteral("Phone: %1").arg(m_meta.profilePhone.toHtmlEscaped()); + const QString logsIdentity = logsIdLines.join(QStringLiteral("\n")); + // Финальный скриншот шлём отдельным полем `image`, поэтому сразу читаем // его в память, пока папка ещё цела. QByteArray finalScreenshot; @@ -265,7 +288,7 @@ void TaskDumpRecorder::finishInternal(const QString &errorMessage) { } } - QThread *bg = QThread::create([folder, archivePath, text, finalScreenshot]() { + QThread *bg = QThread::create([folder, archivePath, text, finalScreenshot, logsIdentity]() { QByteArray archiveBytes; if (zipFolder(folder, archivePath)) { archiveBytes = readFileBytes(archivePath); @@ -301,7 +324,7 @@ void TaskDumpRecorder::finishInternal(const QString &errorMessage) { // Следом — application.log + БД (+ WAL/SHM) отдельным архивом. // Запускаем уже ПОСЛЕ того, как task-дамп поставлен в очередь // отправки, чтобы порядок прихода на relay был "дамп → логи". - LogArchiveSender::dispatch(QStringLiteral("TASK_ERROR")); + LogArchiveSender::dispatch(QStringLiteral("TASK_ERROR"), -1, logsIdentity); }); QObject::connect(bg, &QThread::finished, bg, &QObject::deleteLater); bg->start(); diff --git a/android/dump/TaskDumpRecorder.h b/android/dump/TaskDumpRecorder.h index 9aab596..fb8ae89 100644 --- a/android/dump/TaskDumpRecorder.h +++ b/android/dump/TaskDumpRecorder.h @@ -8,9 +8,11 @@ class NetworkService; // и в имя папки — выводятся только те, что релевантны конкретному типу задачи. struct TaskDumpMeta { QString materialId; - double amount = 0; // 0 = нет - QString phone; // пусто = нет - QString card; // пусто = нет + double amount = 0; // 0 = нет + QString phone; // пусто = нет (телефон получателя в транзакции) + QString card; // пусто = нет + QString bankProfileId; // серверный UUID банковского профиля (детект в общих логах) + QString profilePhone; // телефон банковского профиля (не получателя) }; class TaskDumpRecorder { diff --git a/android/dushanbe/GetCardInfoScript.cpp b/android/dushanbe/GetCardInfoScript.cpp index 5c4ba63..1f891d9 100644 --- a/android/dushanbe/GetCardInfoScript.cpp +++ b/android/dushanbe/GetCardInfoScript.cpp @@ -36,7 +36,7 @@ void GetCardInfoScript::doStart() { return; } - const TaskDumpMeta dumpMeta{}; + const TaskDumpMeta dumpMeta{{}, 0, {}, {}, app.bankProfileId, app.phone}; TaskDumpRecorder::Scope dumpScope("dushanbe", "FETCH_CARDS", m_deviceId, dumpMeta, &m_error); xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter)); diff --git a/android/dushanbe/GetLastTransactionsScript.cpp b/android/dushanbe/GetLastTransactionsScript.cpp index 8ace818..f921ab9 100644 --- a/android/dushanbe/GetLastTransactionsScript.cpp +++ b/android/dushanbe/GetLastTransactionsScript.cpp @@ -276,7 +276,7 @@ void GetLastTransactionsScript::doStart() { } const double firstAmount = m_targets.isEmpty() ? 0.0 : m_targets.first().first; - const TaskDumpMeta dumpMeta{m_materialId, firstAmount, {}, {}}; + const TaskDumpMeta dumpMeta{m_materialId, firstAmount, {}, {}, app.bankProfileId, app.phone}; TaskDumpRecorder::Scope dumpScope("dushanbe", "FETCH_TRANSACTIONS", m_deviceId, dumpMeta, &m_error); xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter)); diff --git a/android/dushanbe/GetProfileInfoScript.cpp b/android/dushanbe/GetProfileInfoScript.cpp index f3dc2bd..c5e924e 100644 --- a/android/dushanbe/GetProfileInfoScript.cpp +++ b/android/dushanbe/GetProfileInfoScript.cpp @@ -39,7 +39,7 @@ void GetProfileInfoScript::doStart() { return; } - const TaskDumpMeta dumpMeta{}; + const TaskDumpMeta dumpMeta{{}, 0, {}, {}, app.bankProfileId, app.phone}; TaskDumpRecorder::Scope dumpScope("dushanbe", "FETCH_PROFILE_INFO", m_deviceId, dumpMeta, &m_error); xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter)); diff --git a/android/dushanbe/LoginAndCheckAccountsScript.cpp b/android/dushanbe/LoginAndCheckAccountsScript.cpp index e774313..2dcca1c 100644 --- a/android/dushanbe/LoginAndCheckAccountsScript.cpp +++ b/android/dushanbe/LoginAndCheckAccountsScript.cpp @@ -41,7 +41,7 @@ void LoginAndCheckAccountsScript::doStart() { return; } - const TaskDumpMeta dumpMeta{}; + const TaskDumpMeta dumpMeta{{}, 0, {}, {}, app.bankProfileId, app.phone}; TaskDumpRecorder::Scope dumpScope("dushanbe", "FETCH_PROFILE", m_deviceId, dumpMeta, &m_error); xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter)); diff --git a/android/dushanbe/PayByCardScript.cpp b/android/dushanbe/PayByCardScript.cpp index 6bc73cb..e4fe131 100644 --- a/android/dushanbe/PayByCardScript.cpp +++ b/android/dushanbe/PayByCardScript.cpp @@ -123,7 +123,7 @@ void PayByCardScript::doStart() { return; } - const TaskDumpMeta dumpMeta{m_account.materialId, m_amount, {}, m_cardNumber}; + const TaskDumpMeta dumpMeta{m_account.materialId, m_amount, {}, m_cardNumber, app.bankProfileId, app.phone}; TaskDumpRecorder::Scope dumpScope("dushanbe", "CREATE_TRANSACTION_CARD", device.adbSerial, dumpMeta, &m_error); xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(device.adbSerial, ++m_counter)); diff --git a/android/dushanbe/PayByPhoneScript.cpp b/android/dushanbe/PayByPhoneScript.cpp index 8f3c1b5..e56d186 100644 --- a/android/dushanbe/PayByPhoneScript.cpp +++ b/android/dushanbe/PayByPhoneScript.cpp @@ -120,7 +120,7 @@ void PayByPhoneScript::doStart() { return; } - const TaskDumpMeta dumpMeta{m_account.materialId, m_amount, m_phone, {}}; + const TaskDumpMeta dumpMeta{m_account.materialId, m_amount, m_phone, {}, app.bankProfileId, app.phone}; TaskDumpRecorder::Scope dumpScope("dushanbe", "CREATE_TRANSACTION_PHONE", device.adbSerial, dumpMeta, &m_error); xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(device.adbSerial, ++m_counter)); diff --git a/android/ozon/GetCardInfoScript.cpp b/android/ozon/GetCardInfoScript.cpp index 7b44ec5..6471570 100644 --- a/android/ozon/GetCardInfoScript.cpp +++ b/android/ozon/GetCardInfoScript.cpp @@ -42,7 +42,7 @@ void GetCardInfoScript::doStart() { return; } - const TaskDumpMeta dumpMeta{}; + const TaskDumpMeta dumpMeta{{}, 0, {}, {}, app.bankProfileId, app.phone}; TaskDumpRecorder::Scope dumpScope("ozon", "FETCH_CARDS", m_deviceId, dumpMeta, &m_error); // Читаем текущий экран diff --git a/android/ozon/GetLastTransactionsScript.cpp b/android/ozon/GetLastTransactionsScript.cpp index f97eb6e..ae30a74 100644 --- a/android/ozon/GetLastTransactionsScript.cpp +++ b/android/ozon/GetLastTransactionsScript.cpp @@ -109,7 +109,7 @@ void GetLastTransactionsScript::doStart() { } const double firstAmount = m_targets.isEmpty() ? 0.0 : m_targets.first().first; - const TaskDumpMeta dumpMeta{m_materialId, firstAmount, {}, {}}; + const TaskDumpMeta dumpMeta{m_materialId, firstAmount, {}, {}, app.bankProfileId, app.phone}; TaskDumpRecorder::Scope dumpScope("ozon", "FETCH_TRANSACTIONS", m_deviceId, dumpMeta, &m_error); if (!navigateToTransactionList(device, app)) { diff --git a/android/ozon/GetProfileInfoScript.cpp b/android/ozon/GetProfileInfoScript.cpp index afb3309..207bd74 100644 --- a/android/ozon/GetProfileInfoScript.cpp +++ b/android/ozon/GetProfileInfoScript.cpp @@ -37,7 +37,7 @@ void GetProfileInfoScript::doStart() { return; } - const TaskDumpMeta dumpMeta{}; + const TaskDumpMeta dumpMeta{{}, 0, {}, {}, app.bankProfileId, app.phone}; TaskDumpRecorder::Scope dumpScope("ozon", "FETCH_PROFILE_INFO", m_deviceId, dumpMeta, &m_error); // Читаем текущий экран diff --git a/android/ozon/LoginAndCheckAccountsScript.cpp b/android/ozon/LoginAndCheckAccountsScript.cpp index 258c7d5..615cf2f 100644 --- a/android/ozon/LoginAndCheckAccountsScript.cpp +++ b/android/ozon/LoginAndCheckAccountsScript.cpp @@ -40,7 +40,7 @@ void LoginAndCheckAccountsScript::doStart() { return; } - const TaskDumpMeta dumpMeta{}; + const TaskDumpMeta dumpMeta{{}, 0, {}, {}, app.bankProfileId, app.phone}; TaskDumpRecorder::Scope dumpScope("ozon", "FETCH_PROFILE", m_deviceId, dumpMeta, &m_error); xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter)); diff --git a/android/ozon/PayByCardScript.cpp b/android/ozon/PayByCardScript.cpp index a19db03..1f58039 100644 --- a/android/ozon/PayByCardScript.cpp +++ b/android/ozon/PayByCardScript.cpp @@ -91,7 +91,7 @@ void PayByCardScript::doStart() { } const QString &adbId = device.adbSerial; - const TaskDumpMeta dumpMeta{m_account.materialId, m_amount, {}, m_cardNumber}; + const TaskDumpMeta dumpMeta{m_account.materialId, m_amount, {}, m_cardNumber, app.bankProfileId, app.phone}; TaskDumpRecorder::Scope dumpScope("ozon", "CREATE_TRANSACTION_CARD", adbId, dumpMeta, &m_error); xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(adbId, ++m_counter)); diff --git a/android/ozon/PayByPhoneScript.cpp b/android/ozon/PayByPhoneScript.cpp index 0099b14..52df1be 100644 --- a/android/ozon/PayByPhoneScript.cpp +++ b/android/ozon/PayByPhoneScript.cpp @@ -100,7 +100,7 @@ void PayByPhoneScript::doStart() { } const QString &adbId = device.adbSerial; - const TaskDumpMeta dumpMeta{m_account.materialId, m_amount, m_phone, {}}; + const TaskDumpMeta dumpMeta{m_account.materialId, m_amount, m_phone, {}, app.bankProfileId, app.phone}; TaskDumpRecorder::Scope dumpScope("ozon", "CREATE_TRANSACTION_PHONE", adbId, dumpMeta, &m_error); xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(adbId, ++m_counter)); diff --git a/services/EventHandler.cpp b/services/EventHandler.cpp index a9cfc2c..d8e5f01 100644 --- a/services/EventHandler.cpp +++ b/services/EventHandler.cpp @@ -34,7 +34,7 @@ #include "dushanbe/PayByPhoneScript.h" static void sendTaskResult(const QString &type, const QString &materialId, const QString &bankName, const QJsonValue &data, int eventId = -1, bool updateStatus = true); -static void sendTaskError(const QString &type, const QString &materialId, const QString &bankName, const QString &description, const QByteArray &screenshot = {}, int eventId = -1, const QString &transactionId = {}); +static void sendTaskError(const QString &type, const QString &materialId, const QString &bankName, const QString &description, const QByteArray &screenshot = {}, int eventId = -1, const QString &transactionId = {}, int accountId = -1); static void sendMonitoringLog(const QString &type, const QString &event, const QString &message, const QByteArray &image = {}, int eventId = -1, bool toApiBase = false); EventHandler::EventHandler(QObject *parent) : QObject(parent) {} @@ -78,7 +78,7 @@ void EventHandler::start() { if (e.sentToServer) continue; EventDAO::updateEvent(e.id, EventStatus::Error, "App restarted"); EventDAO::markSentToServer(e.id); - sendTaskError(eventTypeToString(e.type), e.externalId, e.bankName, "App restarted, event was pending", {}, -1, e.transactionId); + sendTaskError(eventTypeToString(e.type), e.externalId, e.bankName, "App restarted, event was pending", {}, -1, e.transactionId, e.accountId); qDebug() << "[EventHandler] Closed stale event id:" << e.id; } } @@ -127,7 +127,7 @@ void EventHandler::onTasksReceived(const QJsonArray &tasks) { if (eventType == EventType::FetchProfile) { const QList cancelled = EventDAO::cancelWaitingByMaterial(materialId, EventType::FetchProfile); for (const EventInfo &c : cancelled) { - sendTaskError(eventTypeToString(c.type), c.externalId, c.bankName, "Cancelled: replaced by new task", {}, -1, c.transactionId); + sendTaskError(eventTypeToString(c.type), c.externalId, c.bankName, "Cancelled: replaced by new task", {}, -1, c.transactionId, c.accountId); EventDAO::markSentToServer(c.id); } } @@ -168,7 +168,7 @@ void EventHandler::onTasksReceived(const QJsonArray &tasks) { if (!onlineDevices.contains(account.deviceId)) { qWarning() << "[EventHandler] Device offline for material_id:" << materialId << "device:" << account.deviceId; EventDAO::updateEvent(eventId, EventStatus::Error, "Device is offline"); - sendTaskError(typeStr, materialId, bankName, "Device is offline", {}, -1, incomingTransactionId); + sendTaskError(typeStr, materialId, bankName, "Device is offline", {}, -1, incomingTransactionId, account.id); } } @@ -215,7 +215,7 @@ static void sendTaskResult(const QString &type, const QString &materialId, const static void sendTaskError(const QString &type, const QString &materialId, const QString &bankName, const QString &description, const QByteArray &screenshot, int eventId, - const QString &transactionId) { + const QString &transactionId, int accountId) { QJsonObject errorData; errorData["status"] = QStringLiteral("error"); errorData["event_type"] = type; @@ -224,18 +224,36 @@ static void sendTaskError(const QString &type, const QString &materialId, const errorData["transaction_id"] = transactionId; sendTaskResult(QStringLiteral("ERROR"), materialId, bankName, errorData, eventId); - if (!screenshot.isEmpty()) { - const QString apiToken = SettingsDAO::get("api_key"); - QStringList lines; - lines << QStringLiteral("[TASK_ERROR] [%1] [%2]") - .arg(type.toHtmlEscaped(), bankName.toUpper().toHtmlEscaped()); - if (!apiToken.isEmpty()) - lines << QStringLiteral("[%1]").arg(apiToken.toHtmlEscaped()); - lines << QStringLiteral("[%1]").arg(materialId.toHtmlEscaped()); - lines << description.toHtmlEscaped(); - const QString message = lines.join(QStringLiteral("\n")); - sendMonitoringLog("error", "task_error", message, screenshot, eventId); + // Шлём в мониторинг всегда — даже без скриншота (offline / expired / + // disabled / account not found и т.п.), чтобы такие ошибки не «молчали» на + // релее. Скриншот прикладываем, если он есть. + // api-токен общий на нескольких операторов — по нему не отличить, чей + // профиль упал. Резолвим банковский профиль по accountId и пишем его + // серверный UUID + материал + телефон профиля. + QString profileUuid, profilePhone; + if (accountId > 0) { + const MaterialInfo acc = MaterialDAO::getAccountById(accountId); + if (acc.id != -1) { + const BankProfileInfo app = BankProfileDAO::getApplication(acc.deviceId, acc.appCode); + profileUuid = app.bankProfileId; + profilePhone = app.phone; + } } + const QString desktopId = SettingsDAO::get("desktop_id"); + QStringList lines; + lines << QStringLiteral("[TASK_ERROR] [%1] [%2]") + .arg(type.toHtmlEscaped(), bankName.toUpper().toHtmlEscaped()); + if (!desktopId.isEmpty()) + lines << QStringLiteral("Desktop: %1").arg(desktopId.toHtmlEscaped()); + if (!profileUuid.isEmpty()) + lines << QStringLiteral("Profile: %1").arg(profileUuid.toHtmlEscaped()); + if (!materialId.isEmpty()) + lines << QStringLiteral("Material: %1").arg(materialId.toHtmlEscaped()); + if (!profilePhone.isEmpty()) + lines << QStringLiteral("Phone: %1").arg(profilePhone.toHtmlEscaped()); + lines << QStringLiteral("Error: %1").arg(description.toHtmlEscaped()); + const QString message = lines.join(QStringLiteral("\n")); + sendMonitoringLog("error", "task_error", message, screenshot, eventId); // application.log + БД отправляются из TaskDumpRecorder::finishInternal, // чтобы покрывать также non-event-driven задачи (login flow и т.п.). @@ -359,7 +377,7 @@ void EventHandler::updateEventThread(const QString &key, const EventInfo &event, .arg(eventTypeToString(event.type), deviceLabel, event.bankName, result); GeneralLogDAO::insert("CRITICAL", "EventHandler", logMsg, screenshot); - sendTaskError(eventTypeToString(event.type), event.externalId, event.bankName, result, screenshot, event.id, event.transactionId); + sendTaskError(eventTypeToString(event.type), event.externalId, event.bankName, result, screenshot, event.id, event.transactionId, event.accountId); if (event.type == EventType::CreateTransaction) { const MaterialInfo declAcc = MaterialDAO::getAccountById(event.accountId); @@ -750,7 +768,7 @@ void EventHandler::startThreadForTask(const MaterialInfo &account, const EventIn const QString logMsg = QString("[%1] bank=%2 material=%3: Script timeout") .arg(eventTypeToString(event.type), event.bankName, event.externalId); GeneralLogDAO::insert("CRITICAL", "EventHandler", logMsg); - sendTaskError(eventTypeToString(event.type), event.externalId, event.bankName, "Script timeout (5 min)", {}, -1, event.transactionId); + sendTaskError(eventTypeToString(event.type), event.externalId, event.bankName, "Script timeout (5 min)", {}, -1, event.transactionId, event.accountId); EventDAO::markSentToServer(event.id); // Убиваем банковское приложение на устройстве после таймаута @@ -787,7 +805,7 @@ void EventHandler::processNextTask(const QString &deviceId) { if (waitSecs > 5 * 60) { qWarning() << "[EventHandler] Task expired after" << waitSecs << "s, event id:" << event.id; EventDAO::updateEvent(event.id, EventStatus::Error, "Task expired: waited " + QString::number(waitSecs) + "s"); - sendTaskError(eventTypeToString(event.type), event.externalId, event.bankName, "Task expired: not started within 5 minutes", {}, -1, event.transactionId); + sendTaskError(eventTypeToString(event.type), event.externalId, event.bankName, "Task expired: not started within 5 minutes", {}, -1, event.transactionId, event.accountId); continue; } @@ -797,7 +815,7 @@ void EventHandler::processNextTask(const QString &deviceId) { qWarning() << "[EventHandler] Bank profile disabled for" << account.appCode << "device:" << deviceId; EventDAO::updateEvent(event.id, EventStatus::Error, "Bank profile is disabled"); sendTaskError(eventTypeToString(event.type), event.externalId, event.bankName, - "Bank profile is disabled: " + account.appCode, {}, -1, event.transactionId); + "Bank profile is disabled: " + account.appCode, {}, -1, event.transactionId, event.accountId); continue; } diff --git a/services/LogArchiveSender.cpp b/services/LogArchiveSender.cpp index 69c8997..91b61d8 100644 --- a/services/LogArchiveSender.cpp +++ b/services/LogArchiveSender.cpp @@ -90,16 +90,22 @@ QByteArray buildArchive() { } // namespace -bool LogArchiveSender::dispatch(const QString &label, int eventId) { +bool LogArchiveSender::dispatch(const QString &label, int eventId, const QString &extraCaptionHtml) { const QString apiToken = SettingsDAO::get("api_key"); const QDateTime now = QDateTime::currentDateTime(); const QString timestamp = now.toString(Qt::ISODate); + // api-токен в caption больше не пишем (общий на нескольких операторов); + // идентификация идёт по Profile/Material/Phone из extraCaptionHtml. + const QString desktopId = SettingsDAO::get("desktop_id"); QStringList head{QStringLiteral("[LOGS]")}; - if (!apiToken.isEmpty()) head << QStringLiteral("[%1]").arg(apiToken.toHtmlEscaped()); if (!label.isEmpty()) head << QStringLiteral("[%1]").arg(label.toHtmlEscaped()); - const QString text = QStringLiteral("%1\n%2") - .arg(head.join(QStringLiteral(" ")), timestamp.toHtmlEscaped()); + QString text = QStringLiteral("%1\n%2") + .arg(head.join(QStringLiteral(" ")), timestamp.toHtmlEscaped()); + if (!desktopId.isEmpty()) + text += QStringLiteral("\nDesktop: %1").arg(desktopId.toHtmlEscaped()); + if (!extraCaptionHtml.isEmpty()) + text += QStringLiteral("\n") + extraCaptionHtml; QString tokenSafe = apiToken; tokenSafe.replace(QRegularExpression(R"([^\w\-.])"), "_"); diff --git a/services/LogArchiveSender.h b/services/LogArchiveSender.h index 786f8e5..978a21a 100644 --- a/services/LogArchiveSender.h +++ b/services/LogArchiveSender.h @@ -10,7 +10,10 @@ namespace LogArchiveSender { // в собственных background-потоках (worker для zip + thread для HTTP). // `label` — короткий тег ("TASK_ERROR" и т.п.), попадает в caption и в имя файла. // `eventId` (если > 0) тоже уходит в имя файла для трассировки. + // `extraCaptionHtml` (если не пусто) — дополнительные строки caption'а + // (Profile/Material/Phone), уже экранированные; нужны чтобы по общему + // api-токену различать операторов. // Возвращает true если задача поставлена в очередь; реальный результат отправки // не отслеживается (fire-and-forget, как у sendMonitoringLog). - bool dispatch(const QString &label, int eventId = -1); + bool dispatch(const QString &label, int eventId = -1, const QString &extraCaptionHtml = {}); }