tinymux/mux/proxy/hydra_log.cpp
Stephen Dennis 6a449179c5 Hydra proxy: session tokens, gRPC TLS, remaining review fixes
High:
- H-4: Session token TTL (session_token_ttl, default 24h) and rotation
  on re-authentication. Expired tokens rejected in findByPersistId.
- H-5: gRPC TLS support via grpc_tls_cert/grpc_tls_key config options.
  Warning logged when using insecure credentials.

Medium:
- M-1: Replace deprecated getpass() with termios echo-disable on POSIX
- M-3: O(1) findByPersistId via unordered_map persistIdIndex_
- M-4: Account creation rate-limited to 2/hour per IP
- M-6: strerror_r portability guard for GNU vs XSI semantics
- M-7: GMCP cache capped at 64 packages per session
- M-8: OutputItem::render uses heap buffer (4x input + 256) instead of
  fixed 8000-byte stack buffer

Low:
- L-3: SIGHUP log rotation via logReopen() (for logrotate integration)
- L-4: Remove dead stub files front_door.cpp/h, back_door.cpp/h
- L-5: Adaptive event loop poll: 10ms when active, ramps to 100ms idle

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 20:51:37 -06:00

99 lines
2.4 KiB
C++

#include "hydra_log.h"
#include <cstdarg>
#include <cstdio>
#include <ctime>
#include <mutex>
static FILE* s_logFile = nullptr;
static LogLevel s_logLevel = LogLevel::Info;
static std::mutex s_logMutex;
static std::string s_logPath;
static LogLevel parseLevel(const std::string& level) {
if (level == "debug") return LogLevel::Debug;
if (level == "info") return LogLevel::Info;
if (level == "warn") return LogLevel::Warn;
if (level == "error") return LogLevel::Error;
return LogLevel::Info;
}
static const char* levelString(LogLevel level) {
switch (level) {
case LogLevel::Debug: return "DEBUG";
case LogLevel::Info: return "INFO";
case LogLevel::Warn: return "WARN";
case LogLevel::Error: return "ERROR";
}
return "INFO";
}
bool logInit(const std::string& filePath, const std::string& level) {
s_logLevel = parseLevel(level);
s_logPath = filePath;
if (filePath.empty() || filePath == "-") {
s_logFile = stderr;
return true;
}
s_logFile = fopen(filePath.c_str(), "a");
if (!s_logFile) {
fprintf(stderr, "HYDRA: cannot open log file: %s\n", filePath.c_str());
s_logFile = stderr;
return false;
}
return true;
}
void logShutdown() {
if (s_logFile && s_logFile != stderr) {
fclose(s_logFile);
}
s_logFile = nullptr;
}
void logReopen() {
std::lock_guard<std::mutex> lock(s_logMutex);
if (s_logPath.empty() || s_logFile == stderr) return;
FILE* newFile = fopen(s_logPath.c_str(), "a");
if (newFile) {
if (s_logFile && s_logFile != stderr) {
fclose(s_logFile);
}
s_logFile = newFile;
}
// If fopen fails, keep the old file handle
}
void logMessage(LogLevel level, const char* fmt, ...) {
if (level < s_logLevel) return;
if (!s_logFile) return;
time_t now = time(nullptr);
struct tm tm;
#if defined(_WIN32)
_localtime64_s(&tm, &now);
#else
localtime_r(&now, &tm);
#endif
char timebuf[32];
strftime(timebuf, sizeof(timebuf), "%Y-%m-%d %H:%M:%S", &tm);
std::lock_guard<std::mutex> lock(s_logMutex);
fprintf(s_logFile, "%s [%s] ", timebuf, levelString(level));
va_list args;
va_start(args, fmt);
vfprintf(s_logFile, fmt, args);
va_end(args);
fprintf(s_logFile, "\n");
fflush(s_logFile);
}
void hydraGanlLogCallback(const char* message) {
logMessage(LogLevel::Debug, "GANL: %s", message);
}