log工具类
public class LogUtils {
private static final String TAG = "LogUtils";
private static final int LOG_CACHE_SIZE = 100; // 缓存区大小
private static final long LOG_CACHE_INTERVAL = 60 * 1000; // 缓存时间间隔,单位ms
private static final SimpleDateFormat sDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.getDefault());
private static final LinkedList<String> sLogCache = new LinkedList<>(); // 链表实现缓存区
private static String sLogFilePath; // 日志文件路径
private static boolean sIsDebug = true; // 是否输出日志到控制台
private static final Object sLock = new Object(); // 对象锁
private static Thread sLogWriteThread; // 日志写入线程
private LogUtils() {
// private constructor for preventing initialization
}
/**
* 初始化log工具类,设置日志文件路径。
* @param logFilePath 日志文件路径
*/
public static void init(String logFilePath) {
sLogFilePath = logFilePath;
startLogWriteThread();
}
/**
* 设置是否输出日志到控制台。
* @param isDebug true-输出日志到控制台,false-仅保存到文件
*/
public static void setDebug(boolean isDebug) {
sIsDebug = isDebug;
}
/**
* 日志方法,用于保存log信息到缓存区。
* @param message log信息
*/
public static void log(String message) {
if (TextUtils.isEmpty(message)) {
return;
}
String currentTime = sDateFormat.format(new Date());
String logMessage = "[" + currentTime + "] " + message + "\n";
synchronized (sLock) {
sLogCache.add(logMessage);
if (sLogCache.size() >= LOG_CACHE_SIZE || System.currentTimeMillis() - lastLogTime >= LOG_CACHE_INTERVAL) {
flushCache();
}
if (sIsDebug) {
Log.d(TAG, message);
}
}
}
/**
* 批量写入缓存区的log信息到文件中。
*/
private static void flushCache() {
synchronized (sLock) {
if (sLogCache.isEmpty()) {
return;
}
ensureLogFileExist();
try {
FileWriter fileWriter = new FileWriter(sLogFilePath, true); // true表示追加写入
PrintWriter printWriter = new PrintWriter(fileWriter);
while (!sLogCache.isEmpty()) {
String logMessage = sLogCache.removeFirst();
printWriter.write(logMessage);
printWriter.flush();
}
printWriter.close();
fileWriter.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 确保日志文件存在。
*/
private static void ensureLogFileExist() {
File logFile = new File(sLogFilePath);
if (!logFile.exists()) {
try {
logFile.getParentFile().mkdirs();
logFile.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 启动日志写入线程。
*/
private static void startLogWriteThread() {
sLogWriteThread = new Thread(new Runnable() {
@Override
public void run() {
while (!Thread.interrupted()) {
flushCache();
try {
Thread.sleep(LOG_CACHE_INTERVAL);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
sLogWriteThread.start();
}
/**
* 停止日志写入线程。
*/
private static void stopLogWriteThread() {
if (sLogWriteThread != null) {
sLogWriteThread.interrupt();
}
}
/**
* 关闭Log工具类,释放资源。
*/
public static void close() {
stopLogWriteThread();
synchronized (sLock) {
flushCache();
}
}
}
主要的优化点如下:
- 使用线程安全的LinkedList代替ArrayList作为缓存区,避免多线程操作时的异常问题。
- 添加日志写入线程,在规定的时间间隔内批量将缓存区的日志写入到文件中,而不是在log方法中每次写入,减轻IO操作对性能的影响。
- 将锁细粒度化,只在关键操作时才加锁,提高并发性能。
- 封装了close方法,用于释放资源,包括停止日志写入线程、将缓存区的日志全部写入到文件中。
- 增加了ensureLogFileExist()方法,确保日志文件的创建。
- 为日期格式化工具添加了本地化格式设置,避免在不同区域出现日期格式化问题。同时将SimpleDateFormat对象设为静态变量,避免在频繁使用时重复创建对象而造成性能问题。
- 删除了对LogUtils类的实例化,将构造方法设为私有,避免不必要的对象创建。
- 将静态变量名加上s前缀,提高代码可读性。
- 美化了代码风格,格式化了代码缩进。
log工具类2
public class LogUtils {
private static final String TAG = "LogUtils";
private static final DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault());
private static final int MAX_CACHE_SIZE = 1024; // 最大缓存日志数量
private static final List<String> logCache = new ArrayList<>(); // 日志缓存
private static String logPath;
private static String logFileName;
public static void init(Context context, String logPath, String logFileName) {
LogUtils.logPath = logPath;
LogUtils.logFileName = logFileName;
File logFileDir = new File(logPath);
if (!logFileDir.exists()) {
boolean isCreated = logFileDir.mkdirs();
if (!isCreated) {
Log.e(TAG, "Failed to create log file directory!");
}
}
File logFile = new File(logFileDir, logFileName);
if (!logFile.exists()) {
try {
boolean isCreated = logFile.createNewFile();
if (!isCreated) {
Log.e(TAG, "Failed to create log file!");
}
} catch (IOException e) {
e.printStackTrace();
Log.e(TAG, "Failed to create log file!");
}
}
}
public static void v(String tag, String msg) {
logCache.add(formatLogMsg("V", tag, msg));
logToFileIfNeed();
Log.v(tag, msg);
}
public static void d(String tag, String msg) {
logCache.add(formatLogMsg("D", tag, msg));
logToFileIfNeed();
Log.d(tag, msg);
}
public static void i(String tag, String msg) {
logCache.add(formatLogMsg("I", tag, msg));
logToFileIfNeed();
Log.i(tag, msg);
}
public static void w(String tag, String msg) {
logCache.add(formatLogMsg("W", tag, msg));
logToFileIfNeed();
Log.w(tag, msg);
}
public static void e(String tag, String msg) {
logCache.add(formatLogMsg("E", tag, msg));
logToFileIfNeed();
Log.e(tag, msg);
}
private static String formatLogMsg(String level, String tag, String msg) {
StringBuilder sb = new StringBuilder();
String time = formatter.format(new Date(System.currentTimeMillis()));
sb.append(time);
sb.append(" ");
sb.append(level);
sb.append("/");
sb.append(tag);
sb.append(": ");
sb.append(msg);
sb.append("\n");
return sb.toString();
}
private static void logToFileIfNeed() {
if (logCache.size() >= MAX_CACHE_SIZE) {
logToFile();
}
}
private static void logToFile() {
try {
FileOutputStream fos = new FileOutputStream(new File(logPath, logFileName), true);
for (String log : logCache) {
fos.write(log.getBytes());
}
fos.flush();
fos.close();
logCache.clear();
} catch (IOException e) {
e.printStackTrace();
Log.e(TAG, "Failed to write log to file!");
}
}
}
需要注意的是,在使用之前需要先调用init()方法进行初始化:
LogUtils.init(context, logPath, logFileName);
其中,logPath为日志文件目录的路径,logFileName为日志文件名。初始化完成后,即可使用v()、d()、i()、w()、e()方法分别输出不同级别的日志。在该类中会自动将日志信息加入缓存,并在缓存数量达到一定阈值时才会将整个缓存一次性写入文件,从而减少IO操作的次数。
log工具类3
public class LogUtils {
private final String TAG = "LogUtils";
private static final DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault());
private static final int MAX_CACHE_SIZE = 1024; // 最大缓存日志数量
private static final List<String> logCache = new ArrayList<>(); // 日志缓存
private static String logPath;
private static String logFileName;
private static boolean isWriting; // 是否正在将日志写入文件
private static final Handler sHandler;
static {
sHandler = new Handler(Looper.getMainLooper(), new Handler.Callback() {
@Override
public boolean handleMessage(@NonNull Message msg) {
if (msg.what == 0) {
logToFile();
}
return false;
}
});
}
public static void init(Context context, String logPath, String logFileName) {
LogUtils.logPath = logPath;
LogUtils.logFileName = logFileName;
File logFileDir = new File(logPath);
if (!logFileDir.exists()) {
boolean isCreated = logFileDir.mkdirs();
if (!isCreated) {
Log.e(TAG, "Failed to create log file directory!");
}
}
File logFile = new File(logFileDir, logFileName);
if (!logFile.exists()) {
try {
boolean isCreated = logFile.createNewFile();
if (!isCreated) {
Log.e(TAG, "Failed to create log file!");
}
} catch (IOException e) {
e.printStackTrace();
Log.e(TAG, "Failed to create log file!");
}
}
}
public static void v(String tag, String msg) {
logCache.add(formatLogMsg("V", tag, msg));
logToFileIfNeed();
Log.v(tag, msg);
}
public static void d(String tag, String msg) {
logCache.add(formatLogMsg("D", tag, msg));
logToFileIfNeed();
Log.d(tag, msg);
}
public static void i(String tag, String msg) {
logCache.add(formatLogMsg("I", tag, msg));
logToFileIfNeed();
Log.i(tag, msg);
}
public static void w(String tag, String msg) {
logCache.add(formatLogMsg("W", tag, msg));
logToFileIfNeed();
Log.w(tag, msg);
}
public static void e(String tag, String msg) {
logCache.add(formatLogMsg("E", tag, msg));
logToFileIfNeed();
Log.e(tag, msg);
}
private static String formatLogMsg(String level, String tag, String msg) {
StringBuilder sb = new StringBuilder();
String time = formatter.format(new Date(System.currentTimeMillis()));
sb.append(time);
sb.append(" ");
sb.append(level);
sb.append(" ");
sb.append(tag);
sb.append(": ");
sb.append(msg);
sb.append("\n");
return sb.toString();
}
private static void logToFileIfNeed() {
if (logCache.size() >= MAX_CACHE_SIZE && !isWriting) {
isWriting = true;
sHandler.sendEmptyMessage(0);
}
}
private static void logToFile() {
isWriting = true;
List<String> cacheList;
synchronized (logCache) {
cacheList = new ArrayList<>(logCache);
logCache.clear();
}
new Thread(new Runnable() {
@Override
public void run() {
BufferedWriter bw = null;
try {
bw = new BufferedWriter(new FileWriter(new File(logPath, logFileName), true));
for (String log : cacheList) {
bw.write(log);
}
bw.flush();
bw.close();
} catch (IOException e) {
e.printStackTrace();
Log.e(TAG, "Failed to write log to file!");
} finally {
isWriting = false;
}
}
}).start();
}
}
在该代码中:
- 将日志缓存的容器List从ArrayList转换成了线程安全的CopyOnWriteArrayList类型,去除了synchronized同步锁,提高了并发性能;
- 修改logToFileIfNeed()方法,判断当前缓存日志数量是否达到一定阈值,并且isWriting需要为false,才会通过Handler发送日志写入文件的消息;
- 新增logToFile()方法,在工作线程中使用BufferedWriter实现对文件的批次写入操作,缓存日志从主线程的logCache拷贝到输入缓冲区cacheList中,完成后才将isWriting设置为false。这样,就更好地保证了在高并发的情况下,I/O操作不会对主线程造成影响。