当前位置: 首页 > news >正文

Android 日志输出模块

Android 日志输出模块

本文主要记录下封装的日志输出模块.

1: 主要功能

  1. 日志模块初始化,并设置日志级别
  2. 支持将日志写入文件
  3. 日志文件单个限制200M,按天记录到指定文件,文件达到阈值后,记录新的日志文件.
  4. 支持导出日志文件zip.

2: 具体实现

  1. 日志整体初始化使用静态内部类的方式

    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() {
    }
    
  2. 日志级别在manager中定义枚举级别.

    // 定义日志级别枚举
    public enum LogLevel {
        DEBUG, INFO, WARN, ERROR
    }
    
    private LogLevel currentLogLevel = LogLevel.DEBUG; // 默认日志级别为 DEBUG
    
    // 设置日志级别方法
    public void setLogLevel(LogLevel logLevel) {
        this.currentLogLevel = logLevel;
    }
    
  3. 记录日志到文件

    /**
     * 记录日志到文件
     *
     * @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

相关文章:

  • 现在AI大模型能帮做数据分析吗?
  • ScholarCopilot:“学术副驾驶“
  • 【数据结构】励志大厂版·初阶(复习+刷题):复杂度
  • SpringBoot条件注解全解析:核心作用与使用场景详解
  • STM32 HAL库 ADC+TIM+DMA 3路 1S采样一次电压
  • C++对象池设计:从高频`new/delete`到性能飞跃的工业级解决方案
  • pycharm中调试功能讲解
  • PGAdmin下载、安装、基础使用
  • Oracle OCP知识点详解3:使用 vim 编辑文件
  • Web 前端技术解析:构建高效、动态的用户体验
  • qt中,父类中有Q_OBJECT,子类中还需要加Q_OBJECT吗
  • Shell的运行原理以及Linux当中权限问题
  • Flutter 文本组件深度剖析:从基础到高级应用
  • TGCTF web
  • SQL刷题日志(day1)
  • 狂神SQL学习笔记二:安装MySQL
  • DeepSeek AI大模型:中国智能时代的“争气机“-AI生成
  • Python实例题:Python自动获取小说工具
  • 如何将一个8s的接口优化到500ms以下
  • 【Pandas】pandas DataFrame keys
  • 柴德赓、纪庸与叫歇碑
  • 玉渊谭天丨中方减少美国农产品进口后,舟山港陆续出现巴西大豆船
  • 新加坡选情渐热:播客、短视频各显神通,总理反对身份政治
  • 经济日报金观平:统筹国内经济工作和国际经贸斗争
  • 起底网红热敷贴“苗古金贴”:“传承人”系AI生成,“千年秘方”实为贴牌货
  • 经济日报:上海车展展现独特魅力