Files
face_sdk/app/src/main/cpp/DebugLog.cpp
T
2026-04-23 22:18:22 +08:00

166 lines
4.5 KiB
C++

#include "DebugLog.h"
#include <android/log.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <unistd.h>
#include <cstdarg>
#include <cstdio>
#include <ctime>
#include <cstring>
#include <mutex>
#include <string>
#include <unordered_map>
namespace {
std::mutex g_mtx;
FILE* g_fp = nullptr;
std::string g_path;
constexpr size_t kMaxSize = 2 * 1024 * 1024; // 2 MB before rotation
// Throttle bookkeeping: one slot per distinct key passed to log_throttled().
struct ThrottleSlot {
int64_t last_emit_ms = 0; // when we last actually wrote a line for this key
uint32_t suppressed = 0; // number of calls swallowed since last_emit_ms
};
std::unordered_map<std::string, ThrottleSlot> g_throttle;
constexpr int64_t kThrottleWindowMs = 2000; // 2 seconds
pid_t currentTid() {
return static_cast<pid_t>(syscall(SYS_gettid));
}
int64_t nowMs() {
timespec ts{};
clock_gettime(CLOCK_MONOTONIC, &ts);
return static_cast<int64_t>(ts.tv_sec) * 1000 + ts.tv_nsec / 1000000;
}
// Assumes g_mtx is held. Writes a single pre-formatted line to disk + logcat.
void writeLineLocked(const char* body) {
pid_t tid = currentTid();
timespec ts{};
clock_gettime(CLOCK_REALTIME, &ts);
struct tm tm_info{};
localtime_r(&ts.tv_sec, &tm_info);
char timebuf[32];
strftime(timebuf, sizeof(timebuf), "%H:%M:%S", &tm_info);
if (g_fp) {
std::fprintf(g_fp, "[%s.%03ld][tid=%d] %s\n",
timebuf, ts.tv_nsec / 1000000, tid, body);
std::fflush(g_fp);
}
__android_log_print(ANDROID_LOG_INFO, "FACE_DBG", "[tid=%d] %s", tid, body);
}
} // namespace
namespace DebugLog {
void init(const char* internalDataPath) {
std::lock_guard<std::mutex> lk(g_mtx);
if (g_fp) {
return; // already initialized
}
const char* base = (internalDataPath && *internalDataPath) ? internalDataPath : "/data/local/tmp";
g_path = std::string(base) + "/face_sdk_debug.log";
// Rotate once if current file is too large.
struct stat st{};
if (stat(g_path.c_str(), &st) == 0 && static_cast<size_t>(st.st_size) > kMaxSize) {
std::string backup = g_path + ".old";
std::remove(backup.c_str());
std::rename(g_path.c_str(), backup.c_str());
}
g_fp = std::fopen(g_path.c_str(), "a");
if (g_fp) {
time_t now = time(nullptr);
struct tm tm_info{};
localtime_r(&now, &tm_info);
char tbuf[64];
strftime(tbuf, sizeof(tbuf), "%Y-%m-%d %H:%M:%S", &tm_info);
std::fprintf(g_fp,
"\n========== DebugLog opened @ %s (pid=%d) ==========\n",
tbuf, getpid());
std::fflush(g_fp);
}
__android_log_print(ANDROID_LOG_INFO, "FACE_DBG",
"DebugLog file path: %s", g_path.c_str());
}
void log(const char* fmt, ...) {
char buf[1024];
va_list ap;
va_start(ap, fmt);
int n = std::vsnprintf(buf, sizeof(buf), fmt, ap);
va_end(ap);
if (n < 0) return;
std::lock_guard<std::mutex> lk(g_mtx);
writeLineLocked(buf);
}
void log_throttled(const char* key, const char* fmt, ...) {
char buf[1024];
va_list ap;
va_start(ap, fmt);
int n = std::vsnprintf(buf, sizeof(buf), fmt, ap);
va_end(ap);
if (n < 0) return;
const char* k = key ? key : "";
int64_t now = nowMs();
std::lock_guard<std::mutex> lk(g_mtx);
auto it = g_throttle.find(k);
if (it == g_throttle.end()) {
// First time we see this key -> always log.
ThrottleSlot slot;
slot.last_emit_ms = now;
slot.suppressed = 0;
g_throttle.emplace(k, slot);
writeLineLocked(buf);
return;
}
ThrottleSlot& slot = it->second;
int64_t elapsed = now - slot.last_emit_ms;
if (elapsed < kThrottleWindowMs) {
// Still inside the 2s window -> swallow.
++slot.suppressed;
return;
}
// Window elapsed: emit the line, annotated with how many we swallowed.
if (slot.suppressed > 0) {
char annotated[1200];
std::snprintf(annotated, sizeof(annotated),
"%s (repeated %u times in last %lldms, key=%s)",
buf, slot.suppressed, (long long)elapsed, k);
writeLineLocked(annotated);
} else {
writeLineLocked(buf);
}
slot.last_emit_ms = now;
slot.suppressed = 0;
}
void flush() {
std::lock_guard<std::mutex> lk(g_mtx);
if (g_fp) std::fflush(g_fp);
}
std::string getLogPath() {
std::lock_guard<std::mutex> lk(g_mtx);
return g_path;
}
} // namespace DebugLog