#include "DebugLog.h" #include #include #include #include #include #include #include #include #include #include #include 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 g_throttle; constexpr int64_t kThrottleWindowMs = 2000; // 2 seconds pid_t currentTid() { return static_cast(syscall(SYS_gettid)); } int64_t nowMs() { timespec ts{}; clock_gettime(CLOCK_MONOTONIC, &ts); return static_cast(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 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(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 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 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 lk(g_mtx); if (g_fp) std::fflush(g_fp); } std::string getLogPath() { std::lock_guard lk(g_mtx); return g_path; } } // namespace DebugLog