refactor(daemon): More helpful and less noisy logging

This commit is contained in:
Nick Bolton 2025-03-14 11:31:24 +00:00
parent e8ea9f53ee
commit 16517ca541
6 changed files with 69 additions and 61 deletions

View file

@ -350,6 +350,8 @@ void ArchDaemonWindows::setStatus(DWORD state, DWORD step, DWORD waitHint)
{ {
assert(s_daemon != NULL); assert(s_daemon != NULL);
LOG_DEBUG("setting service status: state=%d, step=%d, waitHint=%d", state, step, waitHint);
SERVICE_STATUS status; SERVICE_STATUS status;
status.dwServiceType = SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS; status.dwServiceType = SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS;
status.dwCurrentState = state; status.dwCurrentState = state;

View file

@ -55,20 +55,20 @@ void DaemonApp::saveLogLevel(const QString &logLevel) const
// saves setting for next time the daemon starts. // saves setting for next time the daemon starts.
ARCH->setting("LogLevel", logLevel.toStdString()); ARCH->setting("LogLevel", logLevel.toStdString());
} catch (XArch &e) { } catch (XArch &e) {
LOG((CLOG_ERR "failed to save log level setting: %s", e.what())); LOG_ERR("failed to save log level setting: %s", e.what());
} }
} }
void DaemonApp::setElevate(bool elevate) void DaemonApp::setElevate(bool elevate)
{ {
LOG((CLOG_DEBUG "elevate value changed: %s", elevate ? "yes" : "no")); LOG_DEBUG("elevate value changed: %s", elevate ? "yes" : "no");
m_elevate = elevate; m_elevate = elevate;
try { try {
// saves setting for next time the daemon starts. // saves setting for next time the daemon starts.
ARCH->setting("Elevate", std::string(elevate ? "1" : "0")); ARCH->setting("Elevate", std::string(elevate ? "1" : "0"));
} catch (XArch &e) { } catch (XArch &e) {
LOG((CLOG_ERR "failed to save elevate setting: %s", e.what())); LOG_ERR("failed to save elevate setting: %s", e.what());
} }
} }
@ -81,7 +81,7 @@ void DaemonApp::setCommand(const QString &command)
// saves setting for next time the daemon starts. // saves setting for next time the daemon starts.
ARCH->setting("Command", command.toStdString()); ARCH->setting("Command", command.toStdString());
} catch (XArch &e) { } catch (XArch &e) {
LOG((CLOG_ERR "failed to save command setting: %s", e.what())); LOG_ERR("failed to save command setting: %s", e.what());
} }
} }
@ -230,21 +230,22 @@ int DaemonApp::mainLoop()
LOG_INFO("daemon is running"); LOG_INFO("daemon is running");
m_events.loop(); m_events.loop();
LOG_INFO("daemon is stopping");
} catch (std::exception &e) { // NOSONAR - Catching all exceptions } catch (std::exception &e) { // NOSONAR - Catching all exceptions
LOG((CLOG_CRIT "daemon error: %s", e.what())); LOG((CLOG_CRIT "daemon error: %s", e.what()));
} catch (...) { // NOSONAR - Catching remaining exceptions } catch (...) { // NOSONAR - Catching remaining exceptions
LOG((CLOG_CRIT "daemon unknown error")); LOG((CLOG_CRIT "daemon unknown error"));
} }
LOG_INFO("daemon is stopping");
#if SYSAPI_WIN32 #if SYSAPI_WIN32
try { try {
LOG_DEBUG("stopping process watchdog"); LOG_DEBUG("stopping process watchdog");
m_pWatchdog->stop(); m_pWatchdog->stop();
} catch (std::exception &e) { // NOSONAR - Catching all exceptions } catch (std::exception &e) { // NOSONAR - Catching all exceptions
LOG((CLOG_CRIT "stop watchdog error: %s", e.what())); LOG((CLOG_CRIT "daemon stop watchdog error: %s", e.what()));
} catch (...) { // NOSONAR - Catching remaining exceptions } catch (...) { // NOSONAR - Catching remaining exceptions
LOG((CLOG_CRIT "stop watchdog unknown error")); LOG((CLOG_CRIT "daemon stop watchdog unknown error"));
} }
#endif #endif

View file

@ -191,7 +191,7 @@ void CoreProcess::daemonIpcClientConnected()
m_daemonFileTail->deleteLater(); m_daemonFileTail->deleteLater();
} }
qInfo() << "daemon log path:" << logPath; qDebug() << "daemon log path:" << logPath;
m_daemonFileTail = new FileTail(logPath, this); m_daemonFileTail = new FileTail(logPath, this);
connect(m_daemonFileTail, &FileTail::newLine, this, &CoreProcess::handleLogLines); connect(m_daemonFileTail, &FileTail::newLine, this, &CoreProcess::handleLogLines);
} }

View file

@ -27,21 +27,21 @@ DaemonIpcClient::DaemonIpcClient(QObject *parent)
bool DaemonIpcClient::connectToServer() bool DaemonIpcClient::connectToServer()
{ {
qInfo("connecting to daemon ipc"); qDebug() << "daemon ipc client connecting to server:" << kDaemonIpcName;
m_socket->connectToServer(kDaemonIpcName); m_socket->connectToServer(kDaemonIpcName);
if (!m_socket->waitForConnected(kTimeout)) { if (!m_socket->waitForConnected(kTimeout)) {
qWarning() << "ipc client failed to connect to server:" << kDaemonIpcName; qWarning() << "daemon ipc client failed to connect";
return false; return false;
} }
if (!sendMessage("hello", "hello", false)) { if (!sendMessage("hello", "hello", false)) {
qWarning() << "ipc client failed to send hello"; qWarning() << "daemon ipc client failed to send hello";
return false; return false;
} }
m_connected = true; m_connected = true;
qInfo() << "ipc client connected to server:" << kDaemonIpcName; qDebug() << "daemon ipc client connected";
Q_EMIT connected(); Q_EMIT connected();
return true; return true;
@ -49,17 +49,17 @@ bool DaemonIpcClient::connectToServer()
void DaemonIpcClient::handleDisconnected() void DaemonIpcClient::handleDisconnected()
{ {
qWarning() << "ipc client disconnected from server"; qWarning() << "daemon ipc client disconnected from server";
m_connected = false; m_connected = false;
if (!connectToServer()) { if (!connectToServer()) {
qWarning() << "ipc client failed to reconnect to server"; qWarning() << "daemon ipc client failed to reconnect to server";
} }
} }
void DaemonIpcClient::handleErrorOccurred() void DaemonIpcClient::handleErrorOccurred()
{ {
qWarning() << "ipc client error:" << m_socket->errorString(); qWarning() << "daemon ipc client error:" << m_socket->errorString();
m_connected = false; m_connected = false;
} }
@ -73,49 +73,49 @@ bool DaemonIpcClient::sendMessage(const QString &message, const QString &expectA
QByteArray messageData = message.toUtf8() + "\n"; QByteArray messageData = message.toUtf8() + "\n";
m_socket->write(messageData); m_socket->write(messageData);
if (!m_socket->waitForBytesWritten(kTimeout)) { if (!m_socket->waitForBytesWritten(kTimeout)) {
qWarning() << "ipc client failed to write command"; qWarning() << "daemon ipc client failed to write command";
return false; return false;
} }
if (!expectAck.isEmpty()) { if (!expectAck.isEmpty()) {
qDebug() << "ipc client waiting for ack: " << expectAck; qDebug() << "daemon ipc client waiting for ack: " << expectAck;
if (!m_socket->waitForReadyRead(kTimeout)) { if (!m_socket->waitForReadyRead(kTimeout)) {
qWarning() << "ipc client failed to read response"; qWarning() << "daemon ipc client failed to read response";
return false; return false;
} }
QByteArray response = m_socket->readAll(); QByteArray response = m_socket->readAll();
if (response.isEmpty()) { if (response.isEmpty()) {
qWarning() << "ipc client got empty response"; qWarning() << "daemon ipc client got empty response";
return false; return false;
} }
QString responseData = QString::fromUtf8(response); QString responseData = QString::fromUtf8(response);
if (responseData.isEmpty()) { if (responseData.isEmpty()) {
qWarning() << "ipc client failed to convert response to string"; qWarning() << "daemon ipc client failed to convert response to string";
return false; return false;
} }
if (responseData != expectAck + "\n") { if (responseData != expectAck + "\n") {
qWarning() << "ipc client got unexpected response: " << responseData; qWarning() << "daemon ipc client got unexpected response: " << responseData;
return false; return false;
} }
} }
qDebug() << "ipc client sent message: " << messageData; qDebug() << "daemon ipc client sent message: " << messageData;
return true; return true;
} }
bool DaemonIpcClient::keepAlive() bool DaemonIpcClient::keepAlive()
{ {
if (!isConnected() && !connectToServer()) { if (!isConnected() && !connectToServer()) {
qWarning() << "ipc client keep alive failed to connect"; qWarning() << "daemon ipc client keep alive failed to connect";
return false; return false;
} }
if (!sendMessage("noop")) { if (!sendMessage("noop")) {
qWarning() << "ipc client keep alive ping failed"; qWarning() << "daemon ipc client keep alive ping failed";
m_connected = false; m_connected = false;
return false; return false;
} }
@ -164,31 +164,31 @@ QString DaemonIpcClient::requestLogPath()
} }
if (!m_socket->waitForReadyRead(kTimeout)) { if (!m_socket->waitForReadyRead(kTimeout)) {
qWarning() << "ipc client failed to read log path response"; qWarning() << "daemon ipc client failed to read log path response";
return QString(); return QString();
} }
QByteArray response = m_socket->readAll(); QByteArray response = m_socket->readAll();
if (response.isEmpty()) { if (response.isEmpty()) {
qWarning() << "ipc client got empty log path response"; qWarning() << "daemon ipc client got empty log path response";
return QString(); return QString();
} }
QString responseData = QString::fromUtf8(response); QString responseData = QString::fromUtf8(response);
if (responseData.isEmpty()) { if (responseData.isEmpty()) {
qWarning() << "ipc client failed to convert log path response to string"; qWarning() << "daemon ipc client failed to convert log path response to string";
return QString(); return QString();
} }
// Trimming removes newline from end of message. // Trimming removes newline from end of message.
QStringList parts = responseData.trimmed().split("="); QStringList parts = responseData.trimmed().split("=");
if (parts.size() != 2) { if (parts.size() != 2) {
qWarning() << "ipc client got invalid log path response: " << responseData; qWarning() << "daemon ipc client got invalid log path response: " << responseData;
return QString(); return QString();
} }
if (parts[0] != "logPath") { if (parts[0] != "logPath") {
qWarning() << "ipc client got unexpected log path response: " << responseData; qWarning() << "daemon ipc client got unexpected log path response: " << responseData;
return QString(); return QString();
} }

View file

@ -90,7 +90,7 @@ DWORD MSWindowsProcess::waitForExit()
{ {
const auto kMaxWaitMilliseconds = 10000; const auto kMaxWaitMilliseconds = 10000;
LOG_INFO("waiting for process to exit, pid: %lu", m_info.dwProcessId); LOG_DEBUG("waiting for process to exit, pid: %lu", m_info.dwProcessId);
if (WaitForSingleObject(m_info.hProcess, kMaxWaitMilliseconds) != WAIT_OBJECT_0) { if (WaitForSingleObject(m_info.hProcess, kMaxWaitMilliseconds) != WAIT_OBJECT_0) {
LOG_ERR("process did not exit within the expected time"); LOG_ERR("process did not exit within the expected time");
TerminateProcess(m_info.hProcess, 1); TerminateProcess(m_info.hProcess, 1);

View file

@ -62,7 +62,7 @@ HANDLE openProcessForKill(const PROCESSENTRY32 &entry)
HANDLE handle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, entry.th32ProcessID); HANDLE handle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, entry.th32ProcessID);
if (handle == nullptr) { if (handle == nullptr) {
LOG((CLOG_ERR "could not open process handle for kill")); LOG_ERR("could not open process handle for kill");
throw XArch(new XArchEvalWindows); throw XArch(new XArchEvalWindows);
} }
@ -101,15 +101,15 @@ void MSWindowsWatchdog::stop()
m_running = false; m_running = false;
if (!m_mainThread->wait(kThreadWaitSeconds)) { if (!m_mainThread->wait(kThreadWaitSeconds)) {
LOG((CLOG_WARN "could not stop main thread")); LOG_WARN("could not stop main thread");
} }
if (!m_outputThread->wait(kThreadWaitSeconds)) { if (!m_outputThread->wait(kThreadWaitSeconds)) {
LOG((CLOG_WARN "could not stop output thread")); LOG_WARN("could not stop output thread");
} }
if (!m_sasThread->wait(kThreadWaitSeconds)) { if (!m_sasThread->wait(kThreadWaitSeconds)) {
LOG((CLOG_WARN "could not stop sas thread")); LOG_WARN("could not stop sas thread");
} }
} }
@ -121,11 +121,11 @@ MSWindowsWatchdog::duplicateProcessToken(HANDLE process, LPSECURITY_ATTRIBUTES s
BOOL tokenRet = OpenProcessToken(process, TOKEN_ASSIGN_PRIMARY | TOKEN_ALL_ACCESS, &sourceToken); BOOL tokenRet = OpenProcessToken(process, TOKEN_ASSIGN_PRIMARY | TOKEN_ALL_ACCESS, &sourceToken);
if (!tokenRet) { if (!tokenRet) {
LOG((CLOG_ERR "could not open token, process handle: %d", process)); LOG_ERR("could not open token, process handle: %d", process);
throw XArch(new XArchEvalWindows()); throw XArch(new XArchEvalWindows());
} }
LOG((CLOG_DEBUG "got token %i, duplicating", sourceToken)); LOG_DEBUG("got token %i, duplicating", sourceToken);
HANDLE newToken; HANDLE newToken;
BOOL duplicateRet = DuplicateTokenEx( BOOL duplicateRet = DuplicateTokenEx(
@ -133,11 +133,11 @@ MSWindowsWatchdog::duplicateProcessToken(HANDLE process, LPSECURITY_ATTRIBUTES s
); );
if (!duplicateRet) { if (!duplicateRet) {
LOG((CLOG_ERR "could not duplicate token %i", sourceToken)); LOG_ERR("could not duplicate token %i", sourceToken);
throw XArch(new XArchEvalWindows()); throw XArch(new XArchEvalWindows());
} }
LOG((CLOG_DEBUG "duplicated, new token: %i", newToken)); LOG_DEBUG("duplicated, new token: %i", newToken);
return newToken; return newToken;
} }
@ -152,7 +152,7 @@ MSWindowsWatchdog::getUserToken(LPSECURITY_ATTRIBUTES security, bool elevatedTok
// and so would be unusable with the new elevated process taking focus. // and so would be unusable with the new elevated process taking focus.
if (elevatedToken || m_session.isProcessInSession("logonui.exe", nullptr)) { if (elevatedToken || m_session.isProcessInSession("logonui.exe", nullptr)) {
LOG((CLOG_DEBUG "getting elevated token, %s", (elevatedToken ? "elevation required" : "at login screen"))); LOG_DEBUG("getting elevated token, %s", (elevatedToken ? "elevation required" : "at login screen"));
HANDLE process; HANDLE process;
if (!m_session.isProcessInSession("winlogon.exe", &process)) { if (!m_session.isProcessInSession("winlogon.exe", &process)) {
@ -167,7 +167,7 @@ MSWindowsWatchdog::getUserToken(LPSECURITY_ATTRIBUTES security, bool elevatedTok
throw e; throw e;
} }
} else { } else {
LOG((CLOG_DEBUG "getting non-elevated token")); LOG_DEBUG("getting non-elevated token");
return m_session.getUserToken(security); return m_session.getUserToken(security);
} }
} }
@ -178,6 +178,7 @@ void MSWindowsWatchdog::mainLoop(void *)
LOG_DEBUG("starting watchdog main loop"); LOG_DEBUG("starting watchdog main loop");
while (m_running) { while (m_running) {
LOG_DEBUG3("locking process state mutex in watchdog main loop");
std::unique_lock lock(m_processStateMutex); std::unique_lock lock(m_processStateMutex);
if (!m_command.empty() && !m_foreground && m_session.hasChanged()) { if (!m_command.empty() && !m_foreground && m_session.hasChanged()) {
@ -190,11 +191,11 @@ void MSWindowsWatchdog::mainLoop(void *)
using enum ProcessState; using enum ProcessState;
case Idle: case Idle:
LOG_DEBUG3("watchdog: process idle"); LOG_DEBUG3("watchdog process state idle");
break; break;
case StartScheduled: { case StartScheduled: {
LOG_DEBUG3("watchdog: process start scheduled"); LOG_DEBUG3("watchdog process start scheduled");
if (m_nextStartTime.has_value() && m_nextStartTime.value() <= ARCH->time()) { if (m_nextStartTime.has_value() && m_nextStartTime.value() <= ARCH->time()) {
LOG_DEBUG("start time reached, queueing process start"); LOG_DEBUG("start time reached, queueing process start");
m_processState = StartPending; m_processState = StartPending;
@ -202,7 +203,7 @@ void MSWindowsWatchdog::mainLoop(void *)
} break; } break;
case StartPending: { case StartPending: {
LOG_INFO("daemon starting new process"); LOG_DEBUG("watchdog starting new process");
try { try {
startProcess(); startProcess();
m_startFailures = 0; m_startFailures = 0;
@ -217,15 +218,15 @@ void MSWindowsWatchdog::mainLoop(void *)
} break; } break;
case Running: { case Running: {
LOG_DEBUG3("watchdog: process running"); LOG_DEBUG3("watchdog process in running state");
if (!isProcessRunning()) { if (!isProcessRunning()) {
LOG((CLOG_WARN "detected application not running, pid=%d", m_process->info().dwProcessId)); LOG_WARN("detected application not running, pid=%d", m_process->info().dwProcessId);
m_processState = StartPending; m_processState = StartPending;
} }
} break; } break;
case StopPending: { case StopPending: {
LOG_INFO("stopping running process"); LOG_DEBUG("watchdog stopping current process");
if (m_process != nullptr) { if (m_process != nullptr) {
m_process->shutdown(); m_process->shutdown();
m_process.reset(); m_process.reset();
@ -237,16 +238,18 @@ void MSWindowsWatchdog::mainLoop(void *)
} break; } break;
} }
LOG_DEBUG3("unlocking process state mutex in watchdog main loop");
lock.unlock(); lock.unlock();
// Sleep for only 100ms rather than 1 second so that the service can shut down faster. // Sleep for only 100ms rather than 1 second so that the service can shut down faster.
LOG_DEBUG3("watchdog main loop sleeping");
ARCH->sleep(0.1); ARCH->sleep(0.1);
} }
LOG_DEBUG("watchdog main loop finished"); LOG_DEBUG("watchdog main loop finished");
if (m_process != nullptr) { if (m_process != nullptr) {
LOG((CLOG_DEBUG "terminated running process on exit")); LOG_DEBUG("terminated running process on exit");
m_process->shutdown(); m_process->shutdown();
m_process.reset(); m_process.reset();
} }
@ -272,7 +275,7 @@ void MSWindowsWatchdog::startProcess()
} }
if (m_process != nullptr) { if (m_process != nullptr) {
LOG((CLOG_DEBUG "closing existing process to make way for new one")); LOG_DEBUG("closing existing process to make way for new one");
m_process->shutdown(); m_process->shutdown();
m_process.reset(); m_process.reset();
} }
@ -305,14 +308,13 @@ void MSWindowsWatchdog::startProcess()
} }
if (!createRet) { if (!createRet) {
LOG((CLOG_CRIT "could not launch command"));
DWORD exitCode = 0; DWORD exitCode = 0;
GetExitCodeProcess(m_process->info().hProcess, &exitCode); GetExitCodeProcess(m_process->info().hProcess, &exitCode);
LOG((CLOG_ERR "exit code: %d", exitCode)); LOG_ERR("daemon failed to run command, exit code: %d", exitCode);
throw XArch(new XArchEvalWindows); throw XArch(new XArchEvalWindows);
} else { } else {
// Wait for program to fail. This needs to be 1 second, as the process may take some time to fail. // Wait for program to fail. This needs to be 1 second, as the process may take some time to fail.
LOG_DEBUG("waiting for process start result"); LOG_DEBUG("watchdog waiting for process start result");
ARCH->sleep(1); ARCH->sleep(1);
if (!isProcessRunning()) { if (!isProcessRunning()) {
@ -320,19 +322,20 @@ void MSWindowsWatchdog::startProcess()
throw XArch("process immediately stopped"); throw XArch("process immediately stopped");
} }
LOG((CLOG_DEBUG "started core process from daemon")); LOG_DEBUG("started core process from watchdog");
LOG( LOG_DEBUG2(
(CLOG_DEBUG2 "process info, session=%i, elevated: %s, command=%s", m_session.getActiveSessionId(), "process info, session=%i, elevated=%s, command: %s", //
m_elevateProcess ? "yes" : "no", m_command.c_str()) m_session.getActiveSessionId(), m_elevateProcess ? "yes" : "no", m_command.c_str()
); );
} }
} }
void MSWindowsWatchdog::setProcessConfig(const std::string_view &command, bool elevate) void MSWindowsWatchdog::setProcessConfig(const std::string_view &command, bool elevate)
{ {
LOG_DEBUG("updating watchdog process config"); LOG_DEBUG1("locking process state mutex for watchdog config change");
std::lock_guard lock(m_processStateMutex); std::lock_guard lock(m_processStateMutex);
LOG_DEBUG("setting watchdog process config");
m_command = command; m_command = command;
m_elevateProcess = elevate; m_elevateProcess = elevate;
@ -390,7 +393,7 @@ void MSWindowsWatchdog::shutdownExistingProcesses()
// first we need to take a snapshot of the running processes // first we need to take a snapshot of the running processes
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, kAllProcesses); HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, kAllProcesses);
if (snapshot == INVALID_HANDLE_VALUE) { if (snapshot == INVALID_HANDLE_VALUE) {
LOG((CLOG_ERR "could not get process snapshot")); LOG_ERR("could not get process snapshot");
throw XArch(new XArchEvalWindows); throw XArch(new XArchEvalWindows);
} }
@ -401,7 +404,7 @@ void MSWindowsWatchdog::shutdownExistingProcesses()
// unlikely we can go any further // unlikely we can go any further
BOOL gotEntry = Process32First(snapshot, &entry); BOOL gotEntry = Process32First(snapshot, &entry);
if (!gotEntry) { if (!gotEntry) {
LOG((CLOG_ERR "could not get first process entry")); LOG_ERR("could not get first process entry");
throw XArch(new XArchEvalWindows); throw XArch(new XArchEvalWindows);
} }
@ -409,7 +412,7 @@ void MSWindowsWatchdog::shutdownExistingProcesses()
while (gotEntry) { while (gotEntry) {
if (HANDLE handle = openProcessForKill(entry); handle != nullptr) { if (HANDLE handle = openProcessForKill(entry); handle != nullptr) {
LOG((CLOG_INFO "shutting down process, name=%s, pid=%d", entry.szExeFile, entry.th32ProcessID)); LOG_DEBUG("shutting down process, name=%s, pid=%d", entry.szExeFile, entry.th32ProcessID);
deskflow::platform::MSWindowsProcess::shutdown(handle, entry.th32ProcessID); deskflow::platform::MSWindowsProcess::shutdown(handle, entry.th32ProcessID);
CloseHandle(handle); CloseHandle(handle);
} }
@ -422,7 +425,7 @@ void MSWindowsWatchdog::shutdownExistingProcesses()
if (err != ERROR_NO_MORE_FILES) { if (err != ERROR_NO_MORE_FILES) {
// only worry about error if it's not the end of the snapshot // only worry about error if it's not the end of the snapshot
LOG((CLOG_ERR "could not get next process entry")); LOG_ERR("could not get next process entry");
throw XArch(new XArchEvalWindows); throw XArch(new XArchEvalWindows);
} }
} }
@ -558,13 +561,15 @@ void MSWindowsWatchdog::initSasFunc()
void MSWindowsWatchdog::sasLoop(void *) // NOSONAR - Thread entry point signature void MSWindowsWatchdog::sasLoop(void *) // NOSONAR - Thread entry point signature
{ {
LOG_DEBUG3("watchdog creating sas event");
if (m_sendSasFunc == nullptr) { if (m_sendSasFunc == nullptr) {
throw XArch("SendSAS function not initialized"); throw XArch("SendSAS function not initialized");
} }
while (m_running) { while (m_running) {
if (m_processState != ProcessState::Running) { if (m_processState != ProcessState::Running) {
LOG_DEBUG2("watchdog: not running, skipping SendSAS"); LOG_DEBUG2("watchdog not running, skipping SendSAS");
ARCH->sleep(1); ARCH->sleep(1);
continue; continue;
} }