Android 日志输出模块
Android 日志输出模块
本文主要记录下封装的日志输出模块.
1: 主要功能
- 日志模块初始化,并设置日志级别
- 支持将日志写入文件
- 日志文件单个限制200M,按天记录到指定文件,文件达到阈值后,记录新的日志文件.
- 支持导出日志文件zip.
2: 具体实现
-
日志整体初始化使用静态内部类的方式
private Context context; // 单例模式:静态内部类实现 private static class SingletonHolder { private static final LogManager INSTANCE = new LogManager(); } public static LogManager getInstance(Context context) { if (SingletonHolder.INSTANCE.context == null) { SingletonHolder.INSTANCE.context = context.getApplicationContext(); } return SingletonHolder.INSTANCE; } private LogManager() { }
-
日志级别在manager中定义枚举级别.
// 定义日志级别枚举 public enum LogLevel { DEBUG, INFO, WARN, ERROR } private LogLevel currentLogLevel = LogLevel.DEBUG; // 默认日志级别为 DEBUG // 设置日志级别方法 public void setLogLevel(LogLevel logLevel) { this.currentLogLevel = logLevel; }
-
记录日志到文件
/** * 记录日志到文件 * * @param tag 日志标签 * @param message 日志内容 * @param logLevel 日志级别 */ public void log(String tag, String message, LogLevel logLevel) { String logFilePath = getCurrentLogFilePath(); File logFile = new File(logFilePath); if (logFile.exists() && logFile.length() >= MAX_LOG_FILE_SIZE) { // 如果当前文件已满,创建新文件 logFilePath = getCurrentLogFilePath(); } try (FileOutputStream fos = new FileOutputStream(logFilePath, true); BufferedOutputStream bos = new BufferedOutputStream(fos)) { String logContent = String.format("[%s] [%s] [PID:%d] [TID:%d] [%s] %s\n", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()), tag, android.os.Process.myPid(), // 进程号 Thread.currentThread().getId(), // 线程号 logLevel.name(), // 打印传入的日志级别 message); bos.write(logContent.getBytes()); // 根据传入的日志级别输出到控制台 switch (logLevel) { case DEBUG: Log.d(tag, logContent.trim()); break; case INFO: Log.i(tag, logContent.trim()); break; case WARN: Log.w(tag, logContent.trim()); break; case ERROR: Log.e(tag, logContent.trim()); break; } } catch (IOException e) { Log.e("LogManager", "Failed to write log to file", e); } }
主要判断当前日志的大小,如果达到阈值200M.创建新的文件.
/** * 获取当前日志文件路径 * * @return 当前日志文件路径 */ private String getCurrentLogFilePath() { String currentDate = new SimpleDateFormat("yyyyMMdd").format(new Date()); File logDir = new File(context.getExternalFilesDir(null), LOG_DIR); if (!logDir.exists()) { logDir.mkdirs(); } int fileIndex = 1; File logFile; do { String fileName = LOG_FILE_PREFIX + currentDate + "_" + String.format("%03d", fileIndex) + LOG_FILE_EXTENSION; logFile = new File(logDir, fileName); fileIndex++; } while (logFile.exists() && logFile.length() >= MAX_LOG_FILE_SIZE); return logFile.getAbsolutePath(); }
4: 导出日志
主要操作为导出所有日志文件为 ZIP 包,获取到文件列表将文件添加到 ZIP 包中.
/** * 导出所有日志文件为 ZIP 包 * * @return 导出的 ZIP 文件路径 */ public String exportLogsToZip() { File logDir = new File(context.getExternalFilesDir(null), LOG_DIR); File exportDir = new File(context.getExternalFilesDir(null), EXPORT_DIR); if (!exportDir.exists()) { exportDir.mkdirs(); } File zipFile = new File(exportDir, EXPORT_FILE_NAME); try (FileOutputStream fos = new FileOutputStream(zipFile); ZipOutputStream zos = new ZipOutputStream(new BufferedOutputStream(fos))) { File[] logFiles = logDir.listFiles(); if (logFiles != null) { int totalFiles = logFiles.length; for (int i = 0; i < totalFiles; i++) { addToZipFile(logFiles[i], zos); if (exportProgressListener != null) { int progress = (int) (((double) (i + 1) / totalFiles) * 100); exportProgressListener.onProgress(progress); } } } } catch (IOException e) { Log.e("LogManager", "Failed to export logs to ZIP", e); return null; } if (exportProgressListener != null) { exportProgressListener.onComplete(zipFile.getAbsolutePath()); } return zipFile.getAbsolutePath(); }
/** * 将文件添加到 ZIP 包中 * * @param file 文件 * @param zos ZIP 输出流 * @throws IOException 如果发生 I/O 错误 */ private void addToZipFile(File file, ZipOutputStream zos) throws IOException { try (FileInputStream fis = new FileInputStream(file); BufferedInputStream bis = new BufferedInputStream(fis)) { ZipEntry zipEntry = new ZipEntry(file.getName()); zos.putNextEntry(zipEntry); byte[] buffer = new byte[1024]; int length; while ((length = bis.read(buffer)) > 0) { zos.write(buffer, 0, length); } zos.closeEntry(); } }
添加导出日志的回调.可根据回调进度自定义进度条等效果.
public interface OnExportProgressListener { void onProgress(int progress); void onComplete(String exportPath); } private OnExportProgressListener exportProgressListener; public void setOnExportProgressListener(OnExportProgressListener listener) { this.exportProgressListener = listener; }
下面附上LogManager的代码:
package com.zh.logmanager; import android.content.Context; import android.util.Log; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; /** * @Author: zh * @Time: 25-4-5. * @Email: * @Describe: */ public class LogManager { private static final String LOG_DIR = "logs"; private static final String EXPORT_DIR = "exports"; private static final String LOG_FILE_PREFIX = "app_log_"; private static final String LOG_FILE_EXTENSION = ".txt"; private static final String EXPORT_FILE_NAME = "logs_export.zip"; private static final long MAX_LOG_FILE_SIZE = 200 * 1024 * 1024; // 200MB private Context context; // 单例模式:静态内部类实现 private static class SingletonHolder { private static final LogManager INSTANCE = new LogManager(); } public static LogManager getInstance(Context context) { if (SingletonHolder.INSTANCE.context == null) { SingletonHolder.INSTANCE.context = context.getApplicationContext(); } return SingletonHolder.INSTANCE; } private LogManager() { } /** * 获取当前日志文件路径 * * @return 当前日志文件路径 */ private String getCurrentLogFilePath() { String currentDate = new SimpleDateFormat("yyyyMMdd").format(new Date()); File logDir = new File(context.getExternalFilesDir(null), LOG_DIR); if (!logDir.exists()) { logDir.mkdirs(); } int fileIndex = 1; File logFile; do { String fileName = LOG_FILE_PREFIX + currentDate + "_" + String.format("%03d", fileIndex) + LOG_FILE_EXTENSION; logFile = new File(logDir, fileName); fileIndex++; } while (logFile.exists() && logFile.length() >= MAX_LOG_FILE_SIZE); return logFile.getAbsolutePath(); } // 定义日志级别枚举 public enum LogLevel { DEBUG, INFO, WARN, ERROR } private LogLevel currentLogLevel = LogLevel.DEBUG; // 默认日志级别为 DEBUG // 设置日志级别方法 public void setLogLevel(LogLevel logLevel) { this.currentLogLevel = logLevel; } public void d(String tag, String message){ this.log(tag, message,LogLevel.DEBUG); } public void i(String tag, String message){ this.log(tag, message,LogLevel.INFO); } public void w(String tag, String message){ this.log(tag, message,LogLevel.WARN); } public void e(String tag, String message){ this.log(tag, message,LogLevel.ERROR); } public void log(String tag, String message){ this.log(tag, message,LogLevel.DEBUG); } /** * 记录日志到文件 * * @param tag 日志标签 * @param message 日志内容 * @param logLevel 日志级别 */ public void log(String tag, String message, LogLevel logLevel) { String logFilePath = getCurrentLogFilePath(); File logFile = new File(logFilePath); if (logFile.exists() && logFile.length() >= MAX_LOG_FILE_SIZE) { // 如果当前文件已满,创建新文件 logFilePath = getCurrentLogFilePath(); } try (FileOutputStream fos = new FileOutputStream(logFilePath, true); BufferedOutputStream bos = new BufferedOutputStream(fos)) { String logContent = String.format("[%s] [%s] [PID:%d] [TID:%d] [%s] %s\n", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()), tag, android.os.Process.myPid(), // 进程号 Thread.currentThread().getId(), // 线程号 logLevel.name(), // 打印传入的日志级别 message); bos.write(logContent.getBytes()); // 根据传入的日志级别输出到控制台 switch (logLevel) { case DEBUG: Log.d(tag, logContent.trim()); break; case INFO: Log.i(tag, logContent.trim()); break; case WARN: Log.w(tag, logContent.trim()); break; case ERROR: Log.e(tag, logContent.trim()); break; } } catch (IOException e) { Log.e("LogManager", "Failed to write log to file", e); } } public interface OnExportProgressListener { void onProgress(int progress); void onComplete(String exportPath); } private OnExportProgressListener exportProgressListener; public void setOnExportProgressListener(OnExportProgressListener listener) { this.exportProgressListener = listener; } /** * 导出所有日志文件为 ZIP 包 * * @return 导出的 ZIP 文件路径 */ public String exportLogsToZip() { File logDir = new File(context.getExternalFilesDir(null), LOG_DIR); File exportDir = new File(context.getExternalFilesDir(null), EXPORT_DIR); if (!exportDir.exists()) { exportDir.mkdirs(); } File zipFile = new File(exportDir, EXPORT_FILE_NAME); try (FileOutputStream fos = new FileOutputStream(zipFile); ZipOutputStream zos = new ZipOutputStream(new BufferedOutputStream(fos))) { File[] logFiles = logDir.listFiles(); if (logFiles != null) { int totalFiles = logFiles.length; for (int i = 0; i < totalFiles; i++) { addToZipFile(logFiles[i], zos); if (exportProgressListener != null) { int progress = (int) (((double) (i + 1) / totalFiles) * 100); exportProgressListener.onProgress(progress); } } } } catch (IOException e) { Log.e("LogManager", "Failed to export logs to ZIP", e); return null; } if (exportProgressListener != null) { exportProgressListener.onComplete(zipFile.getAbsolutePath()); } return zipFile.getAbsolutePath(); } /** * 将文件添加到 ZIP 包中 * * @param file 文件 * @param zos ZIP 输出流 * @throws IOException 如果发生 I/O 错误 */ private void addToZipFile(File file, ZipOutputStream zos) throws IOException { try (FileInputStream fis = new FileInputStream(file); BufferedInputStream bis = new BufferedInputStream(fis)) { ZipEntry zipEntry = new ZipEntry(file.getName()); zos.putNextEntry(zipEntry); byte[] buffer = new byte[1024]; int length; while ((length = bis.read(buffer)) > 0) { zos.write(buffer, 0, length); } zos.closeEntry(); } } }
3: 调用+结果
初始化很简单,如下:
LogManager logManager = LogManager.getInstance(this);
Button logButton = findViewById(R.id.log_button);
Button exportButton = findViewById(R.id.export_button);
logButton.setOnClickListener(v -> {
logManager.log("MainActivity", "This is a test log message");
logManager.i("MainActivity", "This is a test log message");
logManager.w("MainActivity", "This is a test log message");
logManager.e("MainActivity", "This is a test log message");
});
可以看到/storage/emulated/0/Android/data/com.zh.logmanager/files/logs/app_log_20250405_001.txt 路径下产生了新的日志文件.
文件内容:
[2025-04-05 17:21:50] [MainActivity] [PID:29019] [TID:2] [DEBUG] This is a test log message
[2025-04-05 17:21:50] [MainActivity] [PID:29019] [TID:2] [INFO] This is a test log message
[2025-04-05 17:21:50] [MainActivity] [PID:29019] [TID:2] [WARN] This is a test log message
[2025-04-05 17:21:50] [MainActivity] [PID:29019] [TID:2] [ERROR] This is a test log message
导出日志如下: /storage/emulated/0/Android/data/com.zh.logmanager/files/exports/logs_export.zip