glog 的优点
mmap 内存映射实现,支持同步/异步写入模式,采用自定义的二进制文件格式,上层可以自定义序列化方式,兼具灵活,高性能和容错能力。支持增量(按天)归档和全量(按文件)归档 、 日志流式压缩、加密,支持自动清理日志文件,SDK 包含基于 C++ 实现的日志读取功能。
具体背景及原理说明 可看该网址 https://juejin.cn/post/7168662263337861133
1. glog日志框架的依赖
在需要的module 下的build.gradle 下
android{
defaultConfig {
ndk {
abiFilters "armeabi-v7a"//保留一个架构的so
}
}
}
dependencies {
implementation "cn.huolala.glog.android:glog-android-static:1.0.0"//货拉拉的日志库
}
2. Protobuf数据框架的依赖
在项目的根目录下的build.gradle 下
dependencies {
classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.15' //
}
2.1 Protobuf数据框架的依赖
在需要的module 下的build.gradle 下
apply plugin: 'com.google.protobuf'
android{
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
sourceSets {
main {
// 定义proto文件目录 默认就是proto 可以不写改步骤 或者重新定义目录
proto {
srcDir 'src/main/proto'
include '*.proto'
}
java {
srcDir 'src/main/java'
}
}
}
}
dependencies {
implementation 'com.google.protobuf:protobuf-lite:3.0.0'
}
protobuf {
protoc {
artifact = 'com.google.protobuf:protoc:3.7.0'
}
plugins {
javalite {
artifact = 'com.google.protobuf:protoc-gen-javalite:3.0.0'
}
}
// 默认路径为 build/generated/source/proto
// 该目录下会按 buildType 生成 debug /release 目录
generatedFilesBaseDir = "$projectDir/src/main/xxx" //预编译生成的文件
generateProtoTasks {
all().each { task ->
task.builtins {
remove java
}
task.plugins {
javalite {}
}
}
}
}
3. 简单的封装使用
3.1 glog 的封装及说明
日志内容需要我们自己定义 来把要收集的堆栈信息及线程和异常进行整理成msg 写入
import android.content.Context;
import android.os.Process;
import android.text.TextUtils;
import android.util.Log;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Date;
import java.util.concurrent.atomic.AtomicLong;
import glog.android.BuildConfig;
import glog.android.Glog;
/**
* @author: tjf
* @date: 2022-12-31
* @desc: Glog 日志框架简单的封装 先initialize 初始化 后再配置setGlogConfig日志信息
*
*/
public class GlogManager {
private Glog glog;
Context context;
private GlogManager() {
}
public static GlogManager getInstance() {
return SingletonHelper.INSTANCE;
}
private static class SingletonHelper {
private static final GlogManager INSTANCE = new GlogManager();
}
public void initialize() {
Glog.initialize(BuildConfig.DEBUG ? Glog.InternalLogLevel.InternalLogLevelDebug :
Glog.InternalLogLevel.InternalLogLevelInfo);
}
// 初始化实例 配置glog
public void setGlogConfig(Context context) {
this.context = context;
int EXPIRES_SECS = 14 * 24 * 60 * 60; // 14 day //日志的定期删除时间 默认是 7天
final int TOTAL_ARCHIVE_SIZE_LIMIT = 56 * 1024 * 1024; // 56 MB 每个日志文件的大小 限制 默认是16MB大小
glog = new Glog.Builder(context)
.protoName("glog_potbuf") // 实例标识,相同标识的实例只创建一次 文件名的前缀
.rootDirectory(context.getFilesDir().getAbsolutePath() + "/glog")//日志存储的文件目录
// 默认在data/user/0/包名/files/glog/文件
.async(true)//是否异步写入
.expireSeconds(EXPIRES_SECS)//日志的定期删除时间
.compressMode(Glog.CompressMode.Zlib)//压缩格式 默认开启压缩
.totalArchiveSizeLimit(TOTAL_ARCHIVE_SIZE_LIMIT)//每个日志文件的大小限制
.encryptMode(Glog.EncryptMode.AES) // 加密方式 默认无
.key("") // ECDH Server public key 加密的公钥
.incrementalArchive(true) //支持增量(按天)归档 和 全量(按文件)归档;
// 默认 false 重命名缓存文件的方式归档 true 增量归档,当天日志写入同一文件
.build();
}
//写入日志
public void glogWirter(String msg) {
glog.write(msg.getBytes());
}
//写入proto 格式的日志 tag的
public void glogWirtes_Potobuf(String tag, String msg) {
AtomicLong seq = new AtomicLong();
byte[] msgBytes = LogProtos.Log.newBuilder()
.setLogLevel(LogProtos.Log.Level.INFO)
.setSequence(seq.getAndIncrement())
.setTimestamp(String.valueOf(System.currentTimeMillis()))
.setPid(Process.myPid())
.setTid(String.valueOf(Thread.currentThread().getId()))
.setTag(tag)
.setMsg(msg)
.build()
.toByteArray();
glog.write(msgBytes);
}
//写入proto 格式的日志日志 级别 和tag 和内容
public void glogWirtes_Potobuf(LogProtos.Log.Level logLevel, String tag, String msg) {
AtomicLong seq = new AtomicLong();
byte[] msgBytes = LogProtos.Log.newBuilder()
.setLogLevel(logLevel)
.setSequence(seq.getAndIncrement())
.setTimestamp(String.valueOf(System.currentTimeMillis()))
.setPid(Process.myPid())
.setTid(String.valueOf(Thread.currentThread().getId()))
.setTag(tag)
.setMsg(msg)
.build()
.toByteArray();
glog.write(msgBytes);
}
//释放清空
public void destroy() {
if (glog != null) {
glog.flush();
glog.destroy();
}
}
private final String TAG = "GlogDemo";
// 读取当天的日志 type 1 普通格式 2 proto 格式
public void readNewDateLog(int type) {
glog.flush();
String[] logArchiveFiles = glog.getArchivesOfDate(new Date().getTime() / 1000);
byte[] inBuf = new byte[Glog.getSingleLogMaxLength()];
Log.i(TAG, "开始读取当天日志, 文件列表:" + Arrays.toString(logArchiveFiles));
for (int i = 0; i < logArchiveFiles.length; i++) {
String filename = logArchiveFiles[i];
try {
Glog.Reader readers = glog.openReader(filename);
String str;
StringBuilder stb = new StringBuilder();
while (true) {
int count = readers.read(inBuf);
if (count < 0) { // 读取结束
break;
} else if (count == 0) { // 破损恢复
continue;
}
byte[] outBuf = new byte[count];
System.arraycopy(inBuf, 0, outBuf, 0, count);
if (type == 1) {
str = new String(outBuf, "UTF8");
Log.i(TAG, str);
} else if (type == 2) {
Log.i(TAG, LogProtos(LogProtos.Log.parseFrom(outBuf)));
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
Log.i(TAG, "读取完成");
}
// 读取放在assets下的glog 文件
public void readAssetsFile(String fileName) {
glog.flush();
String filePath = copyAssetAndWrite(context, fileName);
if (TextUtils.isEmpty(filePath)) {
Log.i(TAG, "没有该文件 无法解析");
return;
}
byte[] inBuf = new byte[(int) new File(filePath).length()];
Log.i(TAG, "开始读取, 文件名:" + filePath);
try {
Glog.Reader readers = glog.openReader(filePath);
while (true) {
int count = readers.read(inBuf);
if (count < 0) { // 读取结束
break;
} else if (count == 0) { // 破损恢复
continue;
}
byte[] outBuf = new byte[count];
System.arraycopy(inBuf, 0, outBuf, 0, count);
Log.i(TAG, LogProtos(LogProtos.Log.parseFrom(outBuf)));
}
} catch (IOException e) {
e.printStackTrace();
}
Log.i(TAG, "读取完成");
}
//Proto 格式的输出
private String LogProtos(LogProtos.Log logs) {
return "Log{" +
"sequence=" + logs.getSequence() +
", timestamp='" + logs.getTimestamp() + '\'' +
", logLevel=" + logs.getLogLevelValue() +
", pid=" + logs.getPid() +
", tid='" + logs.getTid() + '\'' +
", tag='" + logs.getTag() + '\'' +
", msg='" + logs.getMsg() + '\'' +
'}';
}
/**
* 读取assets 下的文件 到app的缓存目录下
*
* @param context
* @param fileName
* @return
*/
public String copyAssetAndWrite(Context context, String fileName) {
try {
File cacheDir = context.getCacheDir();
if (!cacheDir.exists()) {
cacheDir.mkdirs();
}
File outFile = new File(cacheDir, fileName);
if (!outFile.exists()) {
boolean res = outFile.createNewFile();
if (!res) {
return null;
}
} else {
if (outFile.length() > 10) {//表示已经写入一次
return outFile.getPath();
}
}
InputStream is = context.getResources().getAssets().open(fileName);
FileOutputStream fos = new FileOutputStream(outFile);
byte[] buffer = new byte[1024];
int byteCount;
while ((byteCount = is.read(buffer)) != -1) {
fos.write(buffer, 0, byteCount);
}
fos.flush();
is.close();
fos.close();
return outFile.getPath();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
3.2 protos 的格式文件
在main 的同级 下新建一个proto文件夹 新建一个日志类型的proto 的数据结构 Log.proto
建好后 通过AS 的build 下的 rebuild project 来创建 编译的LogProtos 文件生成
syntax = "proto3";//proto3 版本
package glog;//包
option java_package = "com.tjf.glogpotobufdemo";//包名称
option java_outer_classname = "LogProtos";//别名
message Log {
enum Level {
INFO = 0;
DEBUG = 1;
VERBOSE = 2;
WARN = 3;
ERROR = 4;
}
int64 sequence = 1;
string timestamp = 2;
Level logLevel = 3;
int32 pid = 4;
string tid = 5;
string tag = 6;
string msg = 7;
}
4. 完结
元旦快乐