Files
CoreUtils/Log.cpp
T
Brad ArantandClaude Sonnet 5 05374bdb15 Fix MString::read() infinite retry loop, HTTPRequest parser fd leak, Log deadlock, log rotation symlink handling, and JString bounds checks
- MString::read() now gives up after repeated I/O errors instead of
  looping forever and crashing via realloc() failure (root cause of a
  recurring barant.com production crash).
- HTTPRequest::parse() no longer leaks lineParser/headerParser on a
  malformed request.
- Log gained size-based rotation (config-driven) and a non-static-mutex
  fix; a separate fix stopped rotation from destroying the logs symlink
  on its first trigger.
- File.cpp: symlink-preserving rotation support.
- JString: fixed an array-index out-of-bounds and a setString issue.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-06 17:38:14 +00:00

114 lines
2.9 KiB
C++

#include "Log.h"
#include "LogListener.h"
#include <sstream>
#include <iostream>
#include <iomanip>
#include <sys/syscall.h>
#include <unistd.h>
namespace coreutils {
LogListener *Log::logListener = NULL;
File *Log::logFile = NULL;
int Log::seq = 0;
std::mutex Log::mtx;
size_t Log::maxLogSize = 100 * 1024 * 1024;
int Log::maxLogBackups = 5;
Log::Log(LogListener *logListener) {
this->logListener = logListener;
}
Log::Log(File *logFile) {
this->logFile = logFile;
}
Log::Log(int level) {
output = true;
std::stringstream temp;
struct timespec timex;
clock_gettime(CLOCK_REALTIME, &timex);
temp << timex.tv_sec << "." << std::setfill('0') << std::setw(9) << timex.tv_nsec;
*this << temp.str();
*this << " ";
*this << "[" << syscall(SYS_gettid) << "]";
switch (level) {
case LOG_NONE:
*this << "[NONE] :";
break;
case LOG_INFO:
*this << "[INFO] :";
break;
case LOG_WARN:
*this << "[WARN] :";
break;
case LOG_EXCEPT:
*this << "[EXCEPT]: ";
break;
case LOG_DEBUG_1:
*this << "[DEBUG1]: ";
break;
case LOG_DEBUG_2:
*this << "[DEBUG2]: ";
break;
case LOG_DEBUG_3:
*this << "[DEBUG3]: ";
break;
case LOG_DEBUG_4:
*this << "[DEBUG4]: ";
break;
default:
*this << "[?] ?";
break;
};
}
Log::~Log() {
if (output) {
std::stringstream out;
out << this->str() << std::endl;
std::string text = out.str();
mtx.lock();
if (logFile) {
logFile->write(text);
if (maxLogSize > 0 && logFile->currentSize() >= maxLogSize)
logFile->rotate(maxLogBackups);
}
// std::cout << text;
++seq;
mtx.unlock();
// Called AFTER releasing mtx - a real, confirmed (via gdb thread
// dump on a hung production BARANTMail) lock-order inversion:
// ConsoleServer::logSend() takes TCPServer's own `lock`
// (recursive_mutex) to echo this line to connected console
// sessions, but TCPServer::connectionRequest() holds that SAME
// lock for the whole span of accepting+TLS-handshaking a new
// connection - during which another Log() call (e.g. from TLS's
// own handshake-complete callback, on a DIFFERENT thread) needs
// mtx. One thread held mtx and waited on TCPServer::lock; another
// held TCPServer::lock and waited on mtx - neither the
// recursive_mutex fix (which only helps the SAME thread re-enter
// TCPServer::lock) nor anything else here prevented it, since
// that's a same-thread-only guarantee and this is a two-thread,
// two-different-mutex inversion. logListener->logSend() only
// relays an already-built string and touches no other Log state,
// so it doesn't need mtx held at all.
if (logListener)
logListener->logSend(text);
}
}
}