From f332abaa05f5e0c85fa6d0df09f4cf5d79b1a9b5 Mon Sep 17 00:00:00 2001 From: ZhanGSKen Date: Tue, 11 Nov 2025 20:42:47 +0800 Subject: [PATCH] =?UTF-8?q?=E6=BA=90=E7=A0=81=E6=95=B4=E7=90=86=EF=BC=8C?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E4=B8=80=E4=BA=9B=E5=87=BD=E6=95=B0=E7=9A=84?= =?UTF-8?q?=E5=91=BD=E5=90=8D=E6=96=B9=E5=BC=8F=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- appbase/build.properties | 4 +- libappbase/build.properties | 4 +- .../winboll/studio/libappbase/LogUtils.java | 661 +++++++++++------- .../cc/winboll/studio/libappbase/LogView.java | 6 +- 4 files changed, 417 insertions(+), 258 deletions(-) diff --git a/appbase/build.properties b/appbase/build.properties index 3f261f9e..b824b053 100644 --- a/appbase/build.properties +++ b/appbase/build.properties @@ -1,8 +1,8 @@ #Created by .winboll/winboll_app_build.gradle -#Tue Nov 11 12:22:58 GMT 2025 +#Tue Nov 11 12:40:49 GMT 2025 stageCount=10 libraryProject=libappbase baseVersion=15.10 publishVersion=15.10.9 -buildCount=4 +buildCount=5 baseBetaVersion=15.10.10 diff --git a/libappbase/build.properties b/libappbase/build.properties index 3f261f9e..b824b053 100644 --- a/libappbase/build.properties +++ b/libappbase/build.properties @@ -1,8 +1,8 @@ #Created by .winboll/winboll_app_build.gradle -#Tue Nov 11 12:22:58 GMT 2025 +#Tue Nov 11 12:40:49 GMT 2025 stageCount=10 libraryProject=libappbase baseVersion=15.10 publishVersion=15.10.9 -buildCount=4 +buildCount=5 baseBetaVersion=15.10.10 diff --git a/libappbase/src/main/java/cc/winboll/studio/libappbase/LogUtils.java b/libappbase/src/main/java/cc/winboll/studio/libappbase/LogUtils.java index 536fdde0..2318ae94 100644 --- a/libappbase/src/main/java/cc/winboll/studio/libappbase/LogUtils.java +++ b/libappbase/src/main/java/cc/winboll/studio/libappbase/LogUtils.java @@ -1,10 +1,11 @@ package cc.winboll.studio.libappbase; /** - * @Author ZhanGSKen - * @Date 2024/08/12 13:44:06 - * @Describe LogUtils - * @Describe 应用日志类 + * @Author ZhanGSKen&豆包大模型 + * @Date 2025/11/11 20:36 + * @Describe WinBoLl 应用日志管理工具类(单例逻辑) + * 核心功能:日志分级控制、日志文件读写、TAG 过滤配置、应用内所有 TAG 自动扫描 + * 支持 Debug/Release 模式区分存储路径,日志持久化与清理 */ import android.content.Context; import cc.winboll.studio.libappbase.GlobalApplication; @@ -28,353 +29,511 @@ import java.util.List; import java.util.Locale; import java.util.Map; - public class LogUtils { + /** 当前工具类的日志 TAG */ public static final String TAG = "LogUtils"; + /** + * 日志级别枚举(从低到高:关闭→错误→警告→信息→调试→详细) + * 级别越高,输出的日志越详细 + */ public static enum LOG_LEVEL { Off, Error, Warn, Info, Debug, Verbose } - static volatile boolean _IsInited = false; - static Context _mContext; - // 日志显示时间格式 - static SimpleDateFormat mSimpleDateFormat = new SimpleDateFormat("[yyyyMMdd_HHmmss_SSS]", Locale.getDefault()); - // 应用日志文件夹 - static File _mfLogCacheDir; - static File _mfLogDataDir; - // 应用日志文件 - static File _mfLogCatchFile; - static File _mfLogUtilsBeanFile; - static LogUtilsBean _mLogUtilsBean; - public static Map mapTAGList = new HashMap(); + /** 是否初始化完成标志(volatile 保证多线程可见性) */ + private static volatile boolean sIsInited = false; + /** 全局上下文(用于获取存储路径、包信息) */ + private static Context sContext; + /** 日志时间格式化工具(格式:[yyyyMMdd_HHmmss_SSS],精确到毫秒) */ + private static SimpleDateFormat sSimpleDateFormat = new SimpleDateFormat("[yyyyMMdd_HHmmss_SSS]", Locale.getDefault()); + /** 日志缓存文件夹(Debug 模式下存储在外部缓存,Release 存储在内部缓存) */ + private static File sLogCacheDir; + /** 日志配置文件夹(存储 TAG 配置文件) */ + private static File sLogDataDir; + /** 日志存储文件(所有日志写入此文件) */ + private static File sLogFile; + /** 日志配置文件(存储日志级别、TAG 启用状态等配置) */ + private static File sLogConfigFile; + /** 日志配置实体类(封装日志级别等配置) */ + private static LogUtilsBean sLogConfigBean; + /** TAG 过滤映射表(key:TAG 名称;value:是否启用该 TAG 的日志输出) */ + public static Map sTagEnableMap = new HashMap<>(); - // - // 初始化函数 - // + /** + * 初始化日志工具(默认日志级别:Off,不输出日志) + * @param context 全局上下文(建议传入 Application 实例) + */ public static void init(Context context) { - _mContext = context; + sContext = context; init(context, LOG_LEVEL.Off); } - // - // 初始化函数 - // + /** + * 初始化日志工具(指定日志级别) + * 1. 根据 Debug/Release 模式初始化日志存储路径; + * 2. 加载日志配置文件; + * 3. 扫描应用内所有类的 TAG 并初始化过滤映射表; + * 4. 标记初始化完成。 + * @param context 全局上下文 + * @param logLevel 初始日志级别 + */ public static void init(Context context, LOG_LEVEL logLevel) { + sContext = context; + // 根据 Debug 模式选择存储路径(外部/内部存储) if (GlobalApplication.isDebugging()) { - // 初始化日志缓存文件路径 - _mfLogCacheDir = new File(context.getApplicationContext().getExternalCacheDir(), TAG); - if (!_mfLogCacheDir.exists()) { - _mfLogCacheDir.mkdirs(); - } - _mfLogCatchFile = new File(_mfLogCacheDir, "log.txt"); - - // 初始化日志配置文件路径 - _mfLogDataDir = context.getApplicationContext().getExternalFilesDir(TAG); - if (!_mfLogDataDir.exists()) { - _mfLogDataDir.mkdirs(); - } - _mfLogUtilsBeanFile = new File(_mfLogDataDir, TAG + ".json"); + // Debug 模式:存储在外部缓存目录(可通过文件管理器查看) + sLogCacheDir = new File(context.getApplicationContext().getExternalCacheDir(), TAG); + sLogDataDir = context.getApplicationContext().getExternalFilesDir(TAG); } else { - // 初始化日志缓存文件路径 - _mfLogCacheDir = new File(context.getApplicationContext().getCacheDir(), TAG); - if (!_mfLogCacheDir.exists()) { - _mfLogCacheDir.mkdirs(); + // Release 模式:存储在内部缓存目录(仅应用自身可访问) + sLogCacheDir = new File(context.getApplicationContext().getCacheDir(), TAG); + sLogDataDir = new File(context.getApplicationContext().getFilesDir(), TAG); + } + + // 创建日志文件夹(不存在则创建) + createDirIfNotExists(sLogCacheDir); + createDirIfNotExists(sLogDataDir); + + // 初始化日志文件和配置文件路径 + sLogFile = new File(sLogCacheDir, "log.txt"); + sLogConfigFile = new File(sLogDataDir, TAG + ".json"); + + // 加载日志配置(从文件读取,读取失败则创建默认配置) + sLogConfigBean = LogUtilsBean.loadBeanFromFile(sLogConfigFile.getPath(), LogUtilsBean.class); + if (sLogConfigBean == null) { + sLogConfigBean = new LogUtilsBean(); + sLogConfigBean.setLogLevel(logLevel); + // 保存默认配置到文件 + sLogConfigBean.saveBeanToFile(sLogConfigFile.getPath(), sLogConfigBean); + } + + // 扫描应用内所有类的 TAG 并添加到过滤映射表 + scanAllClassTags(); + // 加载已保存的 TAG 启用状态配置 + loadTagEnableSettings(); + // 标记初始化完成 + sIsInited = true; + + // 打印初始化日志(调试用) + d(TAG, String.format("TAG 过滤映射表初始化完成:%s", sTagEnableMap.toString())); + } + + /** + * 获取 TAG 过滤映射表(外部可通过此方法获取所有 TAG 及其启用状态) + * @return TAG 名称与启用状态的映射 + */ + public static Map getTagEnableMap() { + return sTagEnableMap; + } + + /** + * 加载已保存的 TAG 启用状态配置 + * 从 LogUtilsClassTAGBean 列表中读取每个 TAG 的启用状态,更新到映射表 + */ + private static void loadTagEnableSettings() { + ArrayList tagSettingList = new ArrayList<>(); + // 从文件加载 TAG 配置列表 + LogUtilsClassTAGBean.loadBeanList(sContext, tagSettingList, LogUtilsClassTAGBean.class); + + // 遍历配置列表,更新 TAG 启用状态 + for (LogUtilsClassTAGBean tagSetting : tagSettingList) { + String tag = tagSetting.getTag(); + boolean isEnable = tagSetting.getEnable(); + // 仅更新已存在的 TAG(避免无效配置) + if (sTagEnableMap.containsKey(tag)) { + sTagEnableMap.put(tag, isEnable); } - _mfLogCatchFile = new File(_mfLogCacheDir, "log.txt"); - - // 初始化日志配置文件路径 - _mfLogDataDir = new File(context.getApplicationContext().getFilesDir(), TAG); - if (!_mfLogDataDir.exists()) { - _mfLogDataDir.mkdirs(); - } - _mfLogUtilsBeanFile = new File(_mfLogDataDir, TAG + ".json"); - } - -// Toast.makeText(context, -// "_mfLogUtilsBeanFile : " + _mfLogUtilsBeanFile -// + "\n_mfLogCatchFile : " + _mfLogCatchFile, -// Toast.LENGTH_SHORT).show(); -// - _mLogUtilsBean = LogUtilsBean.loadBeanFromFile(_mfLogUtilsBeanFile.getPath(), LogUtilsBean.class); - if (_mLogUtilsBean == null) { - _mLogUtilsBean = new LogUtilsBean(); - _mLogUtilsBean.saveBeanToFile(_mfLogUtilsBeanFile.getPath(), _mLogUtilsBean); - } - - // 加载当前应用下的所有类的 TAG - addClassTAGList(); - loadTAGBeanSettings(); - _IsInited = true; - LogUtils.d(TAG, String.format("mapTAGList : %s", mapTAGList.toString())); - } - - public static Map getMapTAGList() { - return mapTAGList; - } - - static void loadTAGBeanSettings() { - ArrayList list = new ArrayList(); - LogUtilsClassTAGBean.loadBeanList(_mContext, list, LogUtilsClassTAGBean.class); - for (int i = 0; i < list.size(); i++) { - LogUtilsClassTAGBean beanSetting = list.get(i); - for (Map.Entry entry : mapTAGList.entrySet()) { - if (entry.getKey().equals(beanSetting.getTag())) { - entry.setValue(beanSetting.getEnable()); - } - } - } } - static void saveTAGBeanSettings() { - ArrayList list = new ArrayList(); - for (Map.Entry entry : mapTAGList.entrySet()) { - list.add(new LogUtilsClassTAGBean(entry.getKey(), entry.getValue())); + /** + * 保存当前 TAG 启用状态配置到文件 + * 将映射表中的 TAG 及其启用状态转换为 LogUtilsClassTAGBean 列表,持久化到文件 + */ + private static void saveTagEnableSettings() { + ArrayList tagSettingList = new ArrayList<>(); + // 遍历映射表,构建配置列表 + for (Map.Entry entry : sTagEnableMap.entrySet()) { + tagSettingList.add(new LogUtilsClassTAGBean(entry.getKey(), entry.getValue())); } - LogUtilsClassTAGBean.saveBeanList(_mContext, list, LogUtilsClassTAGBean.class); + // 保存配置列表到文件 + LogUtilsClassTAGBean.saveBeanList(sContext, tagSettingList, LogUtilsClassTAGBean.class); } - static void addClassTAGList() { - //ClassLoader classLoader = getClass().getClassLoader(); + /** + * 扫描应用内所有类的 TAG 并添加到过滤映射表 + * 1. 通过 DexFile 读取 APK 中所有类; + * 2. 过滤指定包名前缀(cc.winboll.studio)的类; + * 3. 反射获取类中 public static final String TAG 字段的值; + * 4. 将 TAG 加入映射表,默认禁用(false)。 + */ + private static void scanAllClassTags() { try { - //String packageName = context.getPackageName(); - String packageNamePrefix = "cc.winboll.studio"; - List classNames = new ArrayList<>(); - String apkPath = _mContext.getPackageCodePath(); - //Log.d("APK_PATH", "The APK path is: " + apkPath); - LogUtils.d(TAG, String.format("apkPath : %s", apkPath)); - //String apkPath = "/data/app/" + packageName + "-"; + // 应用 APK 路径(通过上下文获取) + String apkPath = sContext.getPackageCodePath(); + d(TAG, String.format("APK 路径:%s", apkPath)); - //DexFile dexfile = new DexFile(apkPath + "1/base.apk"); - DexFile dexfile = new DexFile(apkPath); + // 读取 APK 中的所有类 + DexFile dexFile = new DexFile(apkPath); + Enumeration classNames = dexFile.entries(); - int countTemp = 0; - Enumeration entries = dexfile.entries(); - while (entries.hasMoreElements()) { - countTemp++; - String className = entries.nextElement(); - if (className.startsWith(packageNamePrefix)) { - classNames.add(className); + int totalClassCount = 0; // 总类数(调试用) + List targetClassNames = new ArrayList<>(); // 目标包名下的类名列表 + String targetPackagePrefix = "cc.winboll.studio"; // 目标包名前缀 + + // 过滤目标包名下的类 + while (classNames.hasMoreElements()) { + totalClassCount++; + String className = classNames.nextElement(); + if (className.startsWith(targetPackagePrefix)) { + targetClassNames.add(className); } } - LogUtils.d(TAG, String.format("countTemp : %d\nClassNames size : %d", countTemp, classNames.size())); + // 打印扫描统计(调试用) + d(TAG, String.format("APK 总类数:%d,目标包下类数:%d", totalClassCount, targetClassNames.size())); - for (String className : classNames) { + // 反射获取每个类的 TAG 字段 + for (String className : targetClassNames) { try { Class clazz = Class.forName(className); + // 获取类中所有声明的字段 Field[] fields = clazz.getDeclaredFields(); for (Field field : fields) { - if (Modifier.isStatic(field.getModifiers()) && Modifier.isPublic(field.getModifiers()) && field.getType() == String.class && "TAG".equals(field.getName())) { + // 过滤条件:public static String 类型,且字段名是 "TAG" + if (Modifier.isStatic(field.getModifiers()) + && Modifier.isPublic(field.getModifiers()) + && field.getType() == String.class + && "TAG".equals(field.getName())) { + // 获取 TAG 字段的值(静态字段,传入 null 即可) String tagValue = (String) field.get(null); - //Log.d("TAG_INFO", "Class: " + className + ", TAG value: " + tagValue); - //LogUtils.d(TAG, String.format("Tag Value : %s", tagValue)); - //mapTAGList.put(tagValue, true); - mapTAGList.put(tagValue, false); + // 添加到映射表,默认禁用 + sTagEnableMap.put(tagValue, false); } } } catch (NoClassDefFoundError | ClassNotFoundException | IllegalAccessException e) { - LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace()); - //LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); - //Toast.makeText(context, TAG + " : " + e.getMessage(), Toast.LENGTH_SHORT).show(); + // 捕获反射异常,避免单个类扫描失败影响整体 + d(TAG, e.getMessage(), Thread.currentThread().getStackTrace()); } } } catch (IOException e) { - LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); - //Toast.makeText(context, TAG + " : " + e.getMessage(), Toast.LENGTH_SHORT).show(); + // 捕获 APK 读取异常 + d(TAG, e, Thread.currentThread().getStackTrace()); } } - public static void setTAGListEnable(String tag, boolean isEnable) { - Iterator> iterator = mapTAGList.entrySet().iterator(); + /** + * 设置单个 TAG 的启用状态 + * @param tag TAG 名称 + * @param isEnable 是否启用(true:输出该 TAG 的日志;false:不输出) + */ + public static void setTagEnable(String tag, boolean isEnable) { + // 遍历映射表,更新目标 TAG 的状态 + Iterator> iterator = sTagEnableMap.entrySet().iterator(); while (iterator.hasNext()) { Map.Entry entry = iterator.next(); if (tag.equals(entry.getKey())) { entry.setValue(isEnable); - //System.out.println("Key: " + entry.getKey() + ", Value: " + entry.getValue()); break; } } - saveTAGBeanSettings(); - LogUtils.d(TAG, String.format("mapTAGList : %s", mapTAGList.toString())); + // 保存配置到文件(持久化) + saveTagEnableSettings(); + d(TAG, String.format("TAG 配置更新:%s", sTagEnableMap.toString())); } - public static void setALlTAGListEnable(boolean isEnable) { - Iterator> iterator = mapTAGList.entrySet().iterator(); + /** + * 设置所有 TAG 的启用状态(批量控制) + * @param isEnable 是否启用(true:所有 TAG 均输出日志;false:所有 TAG 均不输出) + */ + public static void setAllTagsEnable(boolean isEnable) { + // 遍历映射表,批量更新所有 TAG 的状态 + Iterator> iterator = sTagEnableMap.entrySet().iterator(); while (iterator.hasNext()) { Map.Entry entry = iterator.next(); entry.setValue(isEnable); - //System.out.println("Key: " + entry.getKey() + ", Value: " + entry.getValue()); } - saveTAGBeanSettings(); - LogUtils.d(TAG, String.format("mapTAGList : %s", mapTAGList.toString())); + // 保存配置到文件(持久化) + saveTagEnableSettings(); + d(TAG, String.format("所有 TAG 配置更新:%s", sTagEnableMap.toString())); } + /** + * 设置全局日志级别(控制日志输出的详细程度) + * @param logLevel 目标日志级别 + */ public static void setLogLevel(LOG_LEVEL logLevel) { - LogUtils._mLogUtilsBean.setLogLevel(logLevel); - _mLogUtilsBean.saveBeanToFile(_mfLogUtilsBeanFile.getPath(), _mLogUtilsBean); + if (sLogConfigBean != null) { + sLogConfigBean.setLogLevel(logLevel); + // 保存配置到文件(持久化) + sLogConfigBean.saveBeanToFile(sLogConfigFile.getPath(), sLogConfigBean); + } } + /** + * 获取当前全局日志级别 + * @return 当前日志级别 + */ public static LOG_LEVEL getLogLevel() { - return LogUtils._mLogUtilsBean.getLogLevel(); + return sLogConfigBean != null ? sLogConfigBean.getLogLevel() : LOG_LEVEL.Off; } - static boolean isLoggable(String tag, LOG_LEVEL logLevel) { - if (!_IsInited) { + /** + * 判断当前日志是否可输出(校验初始化状态、TAG 启用状态、日志级别) + * @param tag 日志 TAG + * @param logLevel 日志级别 + * @return true:可输出;false:不可输出 + */ + private static boolean isLoggable(String tag, LOG_LEVEL logLevel) { + // 未初始化:不输出 + if (!sIsInited) { return false; - } - if (mapTAGList.get(tag) == null - || !mapTAGList.get(tag)) { + } + // TAG 未配置或未启用:不输出 + if (sTagEnableMap.get(tag) == null || !sTagEnableMap.get(tag)) { return false; - } - if (!isInTheLevel(logLevel)) { + } + // 日志级别未达到:不输出 + if (!isLevelMatched(logLevel)) { return false; } return true; } - static boolean isInTheLevel(LOG_LEVEL logLevel) { - return (LogUtils._mLogUtilsBean.getLogLevel().ordinal() == logLevel.ordinal() - || LogUtils._mLogUtilsBean.getLogLevel().ordinal() > logLevel.ordinal()); + /** + * 判断日志级别是否匹配(当前全局级别 >= 目标级别时可输出) + * 例:全局级别为 Debug(4),则 Error(1)、Warn(2)、Info(3)、Debug(4)均可输出 + * @param logLevel 目标日志级别 + * @return true:级别匹配;false:不匹配 + */ + private static boolean isLevelMatched(LOG_LEVEL logLevel) { + if (sLogConfigBean == null) { + return false; + } + // 枚举的 ordinal() 方法返回索引(Off=0,Error=1,...,Verbose=5) + return sLogConfigBean.getLogLevel().ordinal() >= logLevel.ordinal(); } - // - // 获取应用日志文件夹 - // + /** + * 获取日志缓存文件夹路径(外部可通过此方法获取日志存储目录) + * @return 日志缓存文件夹 + */ public static File getLogCacheDir() { - return _mfLogCacheDir; + return sLogCacheDir; } - // - // 调试日志写入函数 - // - public static void e(String szTAG, String szMessage) { - if (isLoggable(szTAG, LogUtils.LOG_LEVEL.Error)) { - saveLog(szTAG, LogUtils.LOG_LEVEL.Error, szMessage); + /** + * 输出 Error 级别日志 + * @param tag TAG 名称 + * @param message 日志内容 + */ + public static void e(String tag, String message) { + if (isLoggable(tag, LOG_LEVEL.Error)) { + saveLog(tag, LOG_LEVEL.Error, message); } } - // - // 调试日志写入函数 - // - public static void w(String szTAG, String szMessage) { - if (isLoggable(szTAG, LogUtils.LOG_LEVEL.Warn)) { - saveLog(szTAG, LogUtils.LOG_LEVEL.Warn, szMessage); + /** + * 输出 Warn 级别日志 + * @param tag TAG 名称 + * @param message 日志内容 + */ + public static void w(String tag, String message) { + if (isLoggable(tag, LOG_LEVEL.Warn)) { + saveLog(tag, LOG_LEVEL.Warn, message); } } - // - // 调试日志写入函数 - // - public static void i(String szTAG, String szMessage) { - if (isLoggable(szTAG, LogUtils.LOG_LEVEL.Info)) { - saveLog(szTAG, LogUtils.LOG_LEVEL.Info, szMessage); + /** + * 输出 Info 级别日志 + * @param tag TAG 名称 + * @param message 日志内容 + */ + public static void i(String tag, String message) { + if (isLoggable(tag, LOG_LEVEL.Info)) { + saveLog(tag, LOG_LEVEL.Info, message); } } - // - // 调试日志写入函数 - // - public static void d(String szTAG, String szMessage) { - if (isLoggable(szTAG, LogUtils.LOG_LEVEL.Debug)) { - saveLog(szTAG, LogUtils.LOG_LEVEL.Debug, szMessage); + /** + * 输出 Debug 级别日志(基础版) + * @param tag TAG 名称 + * @param message 日志内容 + */ + public static void d(String tag, String message) { + if (isLoggable(tag, LOG_LEVEL.Debug)) { + saveLog(tag, LOG_LEVEL.Debug, message); } } - // - // 调试日志写入函数 - // 包含线程调试堆栈信息 - // - public static void d(String szTAG, String szMessage, StackTraceElement[] listStackTrace) { - if (isLoggable(szTAG, LogUtils.LOG_LEVEL.Debug)) { - StringBuilder sbMessage = new StringBuilder(szMessage); - sbMessage.append(" \nAt "); - sbMessage.append(listStackTrace[2].getMethodName()); - sbMessage.append(" ("); - sbMessage.append(listStackTrace[2].getFileName()); - sbMessage.append(":"); - sbMessage.append(listStackTrace[2].getLineNumber()); - sbMessage.append(")"); - saveLog(szTAG, LogUtils.LOG_LEVEL.Debug, sbMessage.toString()); + /** + * 输出 Debug 级别日志(带调用栈信息) + * 包含调用方法名、文件名、行号,便于调试定位 + * @param tag TAG 名称 + * @param message 日志内容 + * @param stackTrace 线程调用栈(通常传入 Thread.currentThread().getStackTrace()) + */ + public static void d(String tag, String message, StackTraceElement[] stackTrace) { + if (isLoggable(tag, LOG_LEVEL.Debug)) { + StringBuilder sb = new StringBuilder(message); + // 拼接调用栈信息(stackTrace[2] 为实际调用处) + sb.append(" \nAt ") + .append(stackTrace[2].getMethodName()) + .append(" (") + .append(stackTrace[2].getFileName()) + .append(":") + .append(stackTrace[2].getLineNumber()) + .append(")"); + saveLog(tag, LOG_LEVEL.Debug, sb.toString()); } } - // - // 调试日志写入函数 - // 包含异常信息和线程调试堆栈信息 - // - public static void d(String szTAG, Exception e, StackTraceElement[] listStackTrace) { - if (isLoggable(szTAG, LogUtils.LOG_LEVEL.Debug)) { - StringBuilder sbMessage = new StringBuilder(e.getClass().toGenericString()); - sbMessage.append(" : "); - sbMessage.append(e.getMessage()); - sbMessage.append(" \nAt "); - sbMessage.append(listStackTrace[2].getMethodName()); - sbMessage.append(" ("); - sbMessage.append(listStackTrace[2].getFileName()); - sbMessage.append(":"); - sbMessage.append(listStackTrace[2].getLineNumber()); - sbMessage.append(")"); - saveLog(szTAG, LogUtils.LOG_LEVEL.Debug, sbMessage.toString()); + /** + * 输出 Debug 级别日志(带异常信息和调用栈) + * 包含异常类型、异常信息、调用位置,便于异常定位 + * @param tag TAG 名称 + * @param e 异常对象 + * @param stackTrace 线程调用栈 + */ + public static void d(String tag, Exception e, StackTraceElement[] stackTrace) { + if (isLoggable(tag, LOG_LEVEL.Debug)) { + StringBuilder sb = new StringBuilder(); + // 拼接异常信息 + sb.append(e.getClass().toGenericString()) + .append(" : ") + .append(e.getMessage()) + // 拼接调用栈信息 + .append(" \nAt ") + .append(stackTrace[2].getMethodName()) + .append(" (") + .append(stackTrace[2].getFileName()) + .append(":") + .append(stackTrace[2].getLineNumber()) + .append(")"); + saveLog(tag, LOG_LEVEL.Debug, sb.toString()); } } - // - // 调试日志写入函数 - // - public static void v(String szTAG, String szMessage) { - if (isLoggable(szTAG, LogUtils.LOG_LEVEL.Verbose)) { - saveLog(szTAG, LogUtils.LOG_LEVEL.Verbose, szMessage); + /** + * 输出 Verbose 级别日志(最详细级别) + * @param tag TAG 名称 + * @param message 日志内容 + */ + public static void v(String tag, String message) { + if (isLoggable(tag, LOG_LEVEL.Verbose)) { + saveLog(tag, LOG_LEVEL.Verbose, message); } } - // - // 日志文件保存函数 - // - static void saveLog(String szTAG, LogUtils.LOG_LEVEL logLevel, String szMessage) { + /** + * 核心日志保存方法(将日志写入文件) + * 日志格式:[级别] [时间戳] [TAG] + * 日志内容 + * @param tag TAG 名称 + * @param logLevel 日志级别 + * @param message 日志内容 + */ + private static void saveLog(String tag, LOG_LEVEL logLevel, String message) { + BufferedWriter writer = null; try { - BufferedWriter out = null; - out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(_mfLogCatchFile, true), "UTF-8")); - out.write("[" + logLevel + "] " + mSimpleDateFormat.format(System.currentTimeMillis()) + " [" + szTAG + "]\n" + szMessage + "\n"); - out.close(); + // 以追加模式打开日志文件,UTF-8 编码 + writer = new BufferedWriter( + new OutputStreamWriter( + new FileOutputStream(sLogFile, true), + "UTF-8" + ) + ); + // 拼接日志内容(级别 + 时间 + TAG + 消息) + String logContent = String.format( + "[%s] %s [%s]\n%s\n", + logLevel.name(), // 日志级别(如 Debug) + sSimpleDateFormat.format(System.currentTimeMillis()), // 时间戳 + tag, // TAG 名称 + message // 日志内容 + ); + // 写入文件 + writer.write(logContent); } catch (IOException e) { - LogUtils.d(TAG, "IOException : " + e.getMessage()); - } - } - - // - // 历史日志加载函数 - // - public static String loadLog() { - if (_mfLogCatchFile.exists()) { - StringBuffer sb = new StringBuffer(); - try { - BufferedReader in = null; - in = new BufferedReader(new InputStreamReader(new FileInputStream(_mfLogCatchFile), "UTF-8")); - String line = ""; - while ((line = in.readLine()) != null) { - sb.append(line); - sb.append("\n"); + // 日志写入失败时,输出内部调试日志 + d(TAG, "日志写入失败:" + e.getMessage()); + } finally { + // 关闭流,避免资源泄漏 + if (writer != null) { + try { + writer.close(); + } catch (IOException e) { + e.printStackTrace(); } - } catch (IOException e) { - LogUtils.d(TAG, "IOException : " + e.getMessage()); - } - return sb.toString(); - } - return ""; - } - - // - // 清理日志函数 - // - public static void cleanLog() { - if (_mfLogCatchFile.exists()) { - try { - UTF8FileUtils.writeStringToFile(_mfLogCatchFile.getPath(), ""); - //LogUtils.d(TAG, "cleanLog"); - } catch (IOException e) { - LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); } } } + + /** + * 加载历史日志(读取日志文件所有内容) + * @return 历史日志字符串(空字符串表示文件不存在或读取失败) + */ + public static String loadLog() { + // 日志文件不存在,返回空 + if (sLogFile == null || !sLogFile.exists()) { + return ""; + } + + StringBuilder logContent = new StringBuilder(); + BufferedReader reader = null; + try { + // 以 UTF-8 编码读取日志文件 + reader = new BufferedReader( + new InputStreamReader( + new FileInputStream(sLogFile), + "UTF-8" + ) + ); + String line; + // 逐行读取并拼接 + while ((line = reader.readLine()) != null) { + logContent.append(line).append("\n"); + } + } catch (IOException e) { + // 读取失败时,输出内部调试日志 + d(TAG, "日志读取失败:" + e.getMessage()); + } finally { + // 关闭流,避免资源泄漏 + if (reader != null) { + try { + reader.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + return logContent.toString(); + } + + /** + * 清理历史日志(清空日志文件内容) + */ + public static void cleanLog() { + if (sLogFile == null || !sLogFile.exists()) { + return; + } + + try { + // 写入空字符串到文件,实现清空 + UTF8FileUtils.writeStringToFile(sLogFile.getPath(), ""); + } catch (IOException e) { + // 清空失败时,输出内部调试日志(带调用栈) + d(TAG, e, Thread.currentThread().getStackTrace()); + } + } + + /** + * 辅助方法:创建文件夹(不存在则创建) + * @param dir 目标文件夹 + */ + private static void createDirIfNotExists(File dir) { + if (dir != null && !dir.exists()) { + dir.mkdirs(); + } + } } + diff --git a/libappbase/src/main/java/cc/winboll/studio/libappbase/LogView.java b/libappbase/src/main/java/cc/winboll/studio/libappbase/LogView.java index 61dd87b2..db7675ef 100644 --- a/libappbase/src/main/java/cc/winboll/studio/libappbase/LogView.java +++ b/libappbase/src/main/java/cc/winboll/studio/libappbase/LogView.java @@ -213,7 +213,7 @@ public class LogView extends RelativeLayout { } // 加载标签列表 - Map mapTAGList = LogUtils.getMapTAGList(); + Map mapTAGList = LogUtils.getTagEnableMap(); boolean isAllSelect = true; for (Map.Entry entry : mapTAGList.entrySet()) { if (entry.getValue() == false) { @@ -237,7 +237,7 @@ public class LogView extends RelativeLayout { mSelectAllTAGCheckBox.setOnClickListener(new View.OnClickListener(){ @Override public void onClick(View v) { - LogUtils.setALlTAGListEnable(mSelectAllTAGCheckBox.isChecked()); + LogUtils.setAllTagsEnable(mSelectAllTAGCheckBox.isChecked()); //LogUtils.setALlTAGListEnable(false); //mTAGListAdapter.notifyDataSetChanged(); mTAGListAdapter.reload(); @@ -483,7 +483,7 @@ public class LogView extends RelativeLayout { @Override public void onClick(View v) { - LogUtils.setTAGListEnable(item.getTag(), ((CheckBox)v).isChecked()); + LogUtils.setTagEnable(item.getTag(), ((CheckBox)v).isChecked()); } });