package com.anyun.h264; import android.os.Environment; import android.util.Log; import timber.log.Timber; import java.io.File; import java.io.FileFilter; import java.io.FileWriter; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; import java.util.concurrent.TimeUnit; /** * Timber Tree implementation that logs to files. * Logs are saved to sdcard/nvlog/h264_yyyyMMdd.log format. */ public class FileLoggingTree extends Timber.DebugTree { private static final String LOG_DIR = "nvlog"; private static final String LOG_PREFIX = "h264_"; private static final String LOG_SUFFIX = ".log"; // 日志文件保留天数 private static final int LOG_RETENTION_DAYS = 3; private static final String DATE_FORMAT = "yyyyMMdd"; private static final String TIMESTAMP_FORMAT = "yyyy-MM-dd HH:mm:ss.SSS"; private String currentLogFile = null; private SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT, Locale.CHINA); private SimpleDateFormat timestampFormat = new SimpleDateFormat(TIMESTAMP_FORMAT, Locale.CHINA); @Override protected void log(int priority, String tag, String message, Throwable t) { try { // Get today's log file path String today = dateFormat.format(new Date()); String logFileName = LOG_PREFIX + today + LOG_SUFFIX; // Check if we need to update the log file (new day) if (!logFileName.equals(currentLogFile)) { currentLogFile = logFileName; // 新的一天,顺便清理过期日志文件 cleanupExpiredLogFiles(LOG_RETENTION_DAYS); } File logFile = getLogFile(logFileName); if (logFile == null) { return; } // Format log entry String logEntry = formatLogEntry(priority, tag, message, t); // Write to file (append mode) synchronized (this) { try (FileWriter writer = new FileWriter(logFile, true)) { writer.append(logEntry); writer.append("\n"); writer.flush(); } catch (IOException e) { // If file writing fails, log to system log as fallback Log.e("FileLoggingTree", "Failed to write log to file", e); } } } catch (Exception e) { // If anything goes wrong, log to system log Log.e("FileLoggingTree", "Error in FileLoggingTree", e); } } /** * Get the log file. Creates directory if needed. */ private File getLogFile(String fileName) { try { File logDir = new File(Environment.getExternalStorageDirectory(), LOG_DIR); if (!logDir.exists()) { if (!logDir.mkdirs()) { Log.e("FileLoggingTree", "Failed to create log directory: " + logDir.getAbsolutePath()); return null; } } File logFile = new File(logDir, fileName); if (!logFile.exists()) { if (!logFile.createNewFile()) { Log.e("FileLoggingTree", "Failed to create log file: " + logFile.getAbsolutePath()); return null; } } return logFile; } catch (IOException e) { Log.e("FileLoggingTree", "Error getting log file", e); return null; } } /** * 清理超出保留天数的日志文件 */ private void cleanupExpiredLogFiles(int retentionDays) { if (retentionDays <= 0) { return; } try { File logDir = new File(Environment.getExternalStorageDirectory(), LOG_DIR); if (!logDir.exists() || !logDir.isDirectory()) { return; } long retentionMillis = TimeUnit.DAYS.toMillis(Math.max(1, retentionDays)); long cutoffTime = System.currentTimeMillis() - retentionMillis; File[] files = logDir.listFiles(new FileFilter() { @Override public boolean accept(File pathname) { String name = pathname.getName(); return name.startsWith(LOG_PREFIX) && name.endsWith(LOG_SUFFIX); } }); if (files == null || files.length == 0) { return; } for (File file : files) { if (file.lastModified() < cutoffTime) { boolean deleted = file.delete(); if (deleted) { Log.i("FileLoggingTree", "Deleted expired log file: " + file.getAbsolutePath()); } else { Log.w("FileLoggingTree", "Failed to delete expired log file: " + file.getAbsolutePath()); } } } } catch (Exception e) { Log.e("FileLoggingTree", "Error cleaning up expired log files", e); } } /** * Format a log entry with timestamp, priority, tag, and message. */ private String formatLogEntry(int priority, String tag, String message, Throwable t) { StringBuilder sb = new StringBuilder(); // Timestamp sb.append(timestampFormat.format(new Date())); sb.append(" "); // Priority level sb.append(getPriorityString(priority)); sb.append("/"); // Tag sb.append(tag); sb.append(": "); // Message sb.append(message); // Throwable stack trace if (t != null) { sb.append("\n"); sb.append(Log.getStackTraceString(t)); } return sb.toString(); } /** * Get priority string representation. */ private String getPriorityString(int priority) { switch (priority) { case Log.VERBOSE: return "V"; case Log.DEBUG: return "D"; case Log.INFO: return "I"; case Log.WARN: return "W"; case Log.ERROR: return "E"; case Log.ASSERT: return "A"; default: return "?"; } } }