package example.com.baseknowledge.IO;
import android.os.Environment;
import android.os.Process;
import android.os.SystemClock;
import android.text.TextUtils;
import android.util.Base64;
import android.util.Log;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.security.KeyFactory;
import java.security.MessageDigest;
import java.security.PublicKey;
import java.security.spec.X509EncodedKeySpec;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import timber.log.Timber;
/**
* ================================================
* 作 者:zhoujianan
* 版 本:v1.0
* 创建日期:2020/8/31
* 描 述:
* 修订历史:
* ================================================
*/
public class FileTreeIo extends Timber.Tree {
private static final SimpleDateFormat LOG_TIME_FORMAT = new SimpleDateFormat("MM-dd HH:mm:ss.SSS");
private static final String LOG_FORMAT = "%s %d-%d %c/%s: %s";
private static final char[] PRIORITY = {'V', 'V', 'V', 'D', 'I', 'W', 'E', 'E', 'E'};
private static final String TAG = "FileLoggingTree";
/************************************* 写入文件 ****************************************/
private LinkedBlockingQueue<String> mLogQueue = new LinkedBlockingQueue<>();
private SimpleDateFormat simpleDateFormat = new SimpleDateFormat("MM-dd HH-mm-ss");
private List<String> mLogFiles = new ArrayList<>();
private boolean mIsEncode;
private volatile boolean mUploadNewFile;
private boolean mIsExit;
ExecutorService mZipService;
@Override
protected void log(int priority, String tag, String message, Throwable t) {
String timeStamp = LOG_TIME_FORMAT.format(Calendar.getInstance().getTime());
String formatInfo = String.format(LOG_FORMAT, timeStamp, Process.myPid(), Process.myTid(), PRIORITY[priority], tag, message);
//mTempCache.write(formatInfo);
System.out.println("log is " + formatInfo);
if (mLogQueue != null && !mLogQueue.offer(formatInfo)) {
Log.e(TAG, "File LogQueue is Full.!!!!");
}
}
private void startWriteFile() {
while (true) {
BufferedOutputStream opt = null;
try {
synchronized (this) {
String fileName = "Log-" + simpleDateFormat.format(Calendar.getInstance().getTime())
+ "(" + Process.myPid() + ")" + ".log";
opt = new BufferedOutputStream(new FileOutputStream(mFileDir + fileName));
System.out.println("startWriteFile: mFileDir " + mFileDir + " fileName: " + fileName);
mLogFiles.add(fileName);
}
if (mIsEncode) {
opt.write(("DecodeAESKey:" + mDecodeAESKey + "\n").getBytes("UTF-8"));
}
mUploadNewFile = false;
String logInfo;
int logCount = 0;
while (true) {
logInfo = mLogQueue.take();
if (logInfo != null && !TextUtils.isEmpty(logInfo = logInfo.trim())) {
logInfo = logInfo.replace("\n", "\n ");
if (mIsEncode) {
logInfo = encodeAES(logInfo);
}
System.out.println("write: " + logInfo);
System.out.println("logCount: " + logCount + " 消息剩余 " + mLogQueue.size());
opt.write((logInfo + "\n").getBytes("UTF-8"));
logCount++;
if (mIsExit) {
opt.flush();
}
}
if (mUploadNewFile || logCount > 5 * 10000) {
opt.write(("消息剩余 " + mLogQueue.size()).getBytes("UTF-8"));
opt.flush();
opt.close();
zipLog();
break;
}
}
} catch (IOException e) {
if (opt != null) {
try {
opt.flush();
opt.close();
} catch (IOException e1) {
e1.printStackTrace();
}
}
if (!trySolveException(e)) {
mLogQueue = null;
return;
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void zipLog() {
mZipService.execute(zipRunnable);
}
private Runnable zipRunnable = new Runnable() {
@Override
public void run() {
BufferedOutputStream zipOpt = null;
ZipOutputStream zipOutputStream = null;
System.out.println("zipRunnable");
try {
String zipName = "Log-" + simpleDateFormat.format(Calendar.getInstance().getTime())
+ "(" + Process.myPid() + ")" + ".zip";
zipOutputStream = new ZipOutputStream(new FileOutputStream(mFileDir + zipName));
zipOpt = new BufferedOutputStream(zipOutputStream);
LinkedList<File> LogFileList = new LinkedList<>();
for (int i = mLogFiles.size() - 1; i >= 0; i--) {
File file = new File(mFileDir, mLogFiles.get(i));
if (file.exists() && file.length() > 0) {
LogFileList.addFirst(file);
}
if (LogFileList.size() > 2) {
break;
}
}
int fileCount = 0;
for (File file : LogFileList) {
String fileName = "Log-" + simpleDateFormat.format(Calendar.getInstance().getTime())
+ "(" + Process.myPid() + ")" + "-" + fileCount + ".log";
zipOutputStream.putNextEntry(new ZipEntry(fileName));
byte[] data = new byte[1024];
int read;
BufferedInputStream inputStream = new BufferedInputStream(new FileInputStream(file));
while ((read = inputStream.read(data, 0, data.length)) != -1) {
zipOpt.write(data, 0, read);
}
fileCount++;
file.delete();
}
zipOpt.flush();
zipOpt.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
try {
if (zipOpt != null) {
zipOpt.flush();
zipOpt.close();
}
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
};
private int mtrySolveCount;
private boolean trySolveException(IOException e) {
if (mtrySolveCount++ > 10) {
return false;
}
if (e instanceof FileNotFoundException) {
prepare();
return new File(mFileDir).exists();
}
File[] files = new File(mFileDir).listFiles(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return !mLogFiles.contains(name);
}
});
if (files != null && files.length / 3 > 0) {
int index = files.length / 3;
for (int i = 0; i < index; i++) {
files[i].delete();
}
return true;
}
return false;
}
/************************************* 初始化 ****************************************/
private FileTreeIo(boolean isDebug) {
new Thread(new Runnable() {
@Override
public void run() {
init(isDebug);
prepare();
deleteExpiredlog();
startWriteFile();
mIsEncode = true;
}
}).start();
mZipService = Executors.newSingleThreadExecutor();
}
private static FileTreeIo INSTANCE;
public static FileTreeIo setup(boolean isDebug) {
INSTANCE = new FileTreeIo(isDebug);
return INSTANCE;
}
public static FileTreeIo getInstance() {
return INSTANCE;
}
/************************************* 初始化文件目录 ****************************************/
private String mFileDir;
private void prepare() {
File directory = Environment.getExternalStorageDirectory();
String saveDirectoy = directory + "/xxx/LIVE/Log";
Log.d(TAG, "saveDirectoy =>" + saveDirectoy);
File folder = new File(saveDirectoy);
if (!folder.exists()) {
folder.mkdirs();
}
mFileDir = saveDirectoy + "/";
}
/**
* 删除过期的日志
*/
private void deleteExpiredlog() {
File saveDir = new File(mFileDir);
File[] files = saveDir.listFiles();
if (files == null) {
return;
}
long currentTimeMillis = System.currentTimeMillis();
long lastModified;
for (File file : files) {
lastModified = file.lastModified();
if ((currentTimeMillis - lastModified) > (24 * 60 * 60 * 1000)) {
file.delete();
}
}
}
/************************************* - 收集日志 - ****************************************/
public LinkedList<File> collectLog() {
synchronized (this) {
mUploadNewFile = true;
Timber.tag(TAG).i("collectLog UpNewFile");
SystemClock.sleep(500);
LinkedList<File> collectFiles = new LinkedList<>();
for (int i = mLogFiles.size() - 1; i >= 0; i--) {
File file = new File(mFileDir, mLogFiles.get(i));
if (file.exists() && file.length() > 0) {
collectFiles.addFirst(file);
}
if (collectFiles.size() >= 2) {
break;
}
}
return collectFiles;
}
}
/************************************* 初始化加解密 ****************************************/
private static final String PUBLIC_KEY = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCvXe+pNk/oqMRL5wdjG5CWPxAK0lNoHqanS2NsGyej2SgO6yD6MtUFmrhdNnhe0rlGE9U5zrEEHwjiLPVE+SQ9atmMo0GTZwsI9drBkm0vSYjYIv5c7Uy5c0HZCcjCxGvQDPU6MmhtA4f4GUOD0XWYhqWO+U0spkh8uZGVq7CIXQIDAQAB";
private String mDecodeAESKey;
private Cipher mEnCipher;
private String encodeAES(String content) {
try {
byte[] bytes = mEnCipher.doFinal(content.getBytes("UTF-8"));
return new String(Base64.encode(bytes, Base64.NO_WRAP), "UTF-8");
} catch (Exception e) {
mIsEncode = false;
Timber.tag(TAG).e(e, "encodeAES error");
}
return content;
}
private void init(boolean isDebug) {
if (!isDebug) {
try {
String randomKey = getRandomString(32);
byte[] bytes = encryptByPublicKey(randomKey.getBytes("UTF-8"), Base64.decode(PUBLIC_KEY, Base64.NO_WRAP));
mDecodeAESKey = new String(Base64.encode(bytes, Base64.NO_WRAP), "UTF-8");
Log.d(TAG, "DecodeAESKey:" + mDecodeAESKey);
mEnCipher = generateAESCipher(randomKey);
mIsEncode = true;
} catch (Exception e) {
Timber.tag(TAG).e(e, "init encode error");
}
}
}
/**
* 随机生成字符串
*
* @param length
* @return
*/
private static String getRandomString(int length) {
String str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
Random random = new Random();
StringBuffer sb = new StringBuffer();
for (int i = 0; i < length; i++) {
int number = random.nextInt(62);
sb.append(str.charAt(number));
}
return sb.toString();
}
private static Cipher generateAESCipher(String password) throws Exception {
byte[] rawKey = MessageDigest.getInstance("MD5").digest(password.getBytes("UTF-8"));
SecretKeySpec skeySpec = new SecretKeySpec(rawKey, "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
return cipher;
}
/**
* RSA算法
*/
private static final String RSA_ANDROID = "RSA/ECB/PKCS1Padding";
/**
* 使用公钥加密
*/
private static byte[] encryptByPublicKey(byte[] data, byte[] publicKey) throws Exception {
// 得到公钥对象
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKey);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey pubKey = keyFactory.generatePublic(keySpec);
// 加密数据
Cipher cp = Cipher.getInstance(RSA_ANDROID);
cp.init(Cipher.ENCRYPT_MODE, pubKey);
byte[] bytes = cp.doFinal(data);
return bytes;
}
}
在打印日志的根部:需要调用log方法,加入到队列中