@@ -1,10 +1,11 @@
package cc.winboll.studio.libappbase ;
/**
* @Author ZhanGSKen<zhangsken@qq.com>
* @Date 2024/08/12 13:44:0 6
* @Describe LogUtils
* @Describe 应用日志类
* @Author ZhanGSKen&豆包大模型 <zhangsken@qq.com>
* @Date 2025/11/11 20:3 6
* @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 < String , Boolean > mapTAGList = new HashMap < String , Boolean > ( ) ;
/** 是否初始化完成标志( 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 < String , Boolean > sTagEnableMap = new HashMap < > ( ) ;
//
// 初始化函数
//
/**
* 初始化日志工具( 默认日志级别: Off, 不输出日志)
* @param context 全局上下文(建议传入 Application 实例)
*/
public static void init ( Context context ) {
_m Context = context ;
s Context = 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 ( ) ) {
// 初始化日志缓存文件路径
_mf LogCacheDir = 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 模式:存储在外部缓存目录(可通过文件管理器查看)
s LogCacheDir = new File ( context . getApplicationContext ( ) . getExternalCacheDir ( ) , TAG ) ;
sLogDataDir = context . getApplicationContext ( ) . getExternalFilesDir ( TAG ) ;
} else {
// 初始化日志缓存文件路径
_mf LogCacheDir = new File ( context . getApplicationContext ( ) . getCacheDir ( ) , TAG ) ;
if ( ! _mfLogCacheDir . exists ( ) ) {
_mfLogCacheDir . mkdirs ( ) ;
// Release 模式:存储在内部缓存目录(仅应用自身可访问)
s LogCacheDir = 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 < String , Boolean > getTagEnableMap ( ) {
return sTagEnableMap ;
}
/**
* 加载已保存的 TAG 启用状态配置
* 从 LogUtilsClassTAGBean 列表中读取每个 TAG 的启用状态,更新到映射表
*/
private static void loadTagEnableSettings ( ) {
ArrayList < LogUtilsClassTAGBean > 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 < String , Boolean > getMapTAGList ( ) {
return mapTAGList ;
}
static void loadTAGBeanSettings ( ) {
ArrayList < LogUtilsClassTAGBean > list = new ArrayList < LogUtilsClassTAGBean > ( ) ;
LogUtilsClassTAGBean . loadBeanList ( _mContext , list , LogUtilsClassTAGBean . class ) ;
for ( int i = 0 ; i < list . size ( ) ; i + + ) {
LogUtilsClassTAGBean beanSetting = list . get ( i ) ;
for ( Map . Entry < String , Boolean > entry : mapTAGList . entrySet ( ) ) {
if ( entry . getKey ( ) . equals ( beanSetting . getTag ( ) ) ) {
entry . setValue ( beanSetting . getEnable ( ) ) ;
}
}
}
}
static void saveTAGBeanSettings ( ) {
ArrayList < LogUtilsClassTAGBean > list = new ArrayList < LogUtilsClassTAGBean > ( ) ;
for ( Map . Entry < String , Boolean > entry : mapTAGList . entrySet ( ) ) {
list . add ( new LogUtilsClassTAGBean ( entry . getKey ( ) , entry . getValue ( ) ) ) ;
/**
* 保存当前 TAG 启用状态配置到文件
* 将映射表中的 TAG 及其启用状态转换为 LogUtilsClassTAGBean 列表,持久化到文件
*/
private static void saveTagEnableSettings ( ) {
ArrayList < LogUtilsClassTAGBean > tagSettingList = new ArrayList < > ( ) ;
// 遍历映射表,构建配置列表
for ( Map . Entry < String , Boolean > 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 < String > 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 dexf ile = new DexFile ( apkPath ) ;
// 读取 APK 中的所有类
DexFile dexF ile = new DexFile ( apkPath ) ;
Enumeration < String > classNames = dexFile . entries ( ) ;
int countTemp = 0 ;
Enumeration < String > entries = dexfile . entries ( ) ;
while ( entries . hasMoreElements ( ) ) {
countTemp + + ;
String className = entries . nextElement ( ) ;
if ( className . startsWith ( packageNamePrefix ) ) {
classNames . add ( className ) ;
int totalClassCount = 0 ; // 总类数(调试用)
List < String > 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("T ag Value : %s", tagValue)) ;
//mapTAGList.put(tagValue, true);
mapTAGList . put ( tagValue , false ) ;
// 添加到映射表,默认禁用
sTagEnableMap . put ( t agValue, 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 < Map . Entry < String , Boolean > > iterator = mapTAGList . entrySet ( ) . iterator ( ) ;
/**
* 设置单个 TAG 的启用状态
* @param tag TAG 名称
* @param isEnable 是否启用( true: 输出该 TAG 的日志; false: 不输出)
*/
public static void setTagEnable ( String tag , boolean isEnable ) {
// 遍历映射表,更新目标 TAG 的状态
Iterator < Map . Entry < String , Boolean > > iterator = sTagEnableMap . entrySet ( ) . iterator ( ) ;
while ( iterator . hasNext ( ) ) {
Map . Entry < String , Boolean > 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 . toStr ing( ) ) ) ;
// 保存配置到文件(持久化)
saveTagEnableSett ings ( ) ;
d ( TAG , String . format ( " TAG 配置更新:%s " , sTagEnableMap . toString ( ) ) ) ;
}
public static void setALlTAGListEnable ( boolean isEnable ) {
Iterator < Map . Entry < String , Boolean > > iterator = mapTAGList . entrySet ( ) . iterator ( ) ;
/**
* 设置所有 TAG 的启用状态(批量控制)
* @param isEnable 是否启用( true: 所有 TAG 均输出日志; false: 所有 TAG 均不输出)
*/
public static void setAllTagsEnable ( boolean isEnable ) {
// 遍历映射表,批量更新所有 TAG 的状态
Iterator < Map . Entry < String , Boolean > > iterator = sTagEnableMap . entrySet ( ) . iterator ( ) ;
while ( iterator . hasNext ( ) ) {
Map . Entry < String , Boolean > entry = iterator . next ( ) ;
entry . setValue ( isEnable ) ;
//System.out.println("Key: " + entry.getKey() + ", Value: " + entry.getValue());
}
saveTAGBeanSettings ( ) ;
LogUtils . d ( TAG , String . format ( " mapTAGList : %s " , mapTAGList . toStr ing( ) ) ) ;
// 保存配置到文件(持久化)
saveTagEnableSett ings ( ) ;
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 s LogConfigBean ! = 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 _mf LogCacheDir;
return s LogCacheDir;
}
//
// 调试日志写入函数
//
public static void e ( String szTAG , String szMessage ) {
if ( isLoggable ( szTAG , LogUtils . LOG_LEVEL . Error ) ) {
saveLog ( szTAG , LogUtils . LOG_LEVEL . Error , szM essage) ;
/**
* 输出 Error 级别日志
* @param tag TAG 名称
* @param message 日志内容
*/
public static void e ( String tag , String m essage) {
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 , szM essage) ;
/**
* 输出 Warn 级别日志
* @param tag TAG 名称
* @param message 日志内容
*/
public static void w ( String tag , String m essage) {
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 , szM essage) ;
/**
* 输出 Info 级别日志
* @param tag TAG 名称
* @param message 日志内容
*/
public static void i ( String tag , String m essage) {
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 , szM essage) ;
/**
* 输出 Debug 级别日志(基础版)
* @param tag TAG 名称
* @param message 日志内容
*/
public static void d ( String tag , String m essage) {
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 ( listS tackTrace[ 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 ( s tackTrace[ 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 , szM essage) ;
/**
* 输出 Verbose 级别日志(最详细级别)
* @param tag TAG 名称
* @param message 日志内容
*/
public static void v ( String tag , String m essage) {
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 ( ) ;
}
}
}