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 "?";
|
}
|
}
|
}
|