C++ sentry 如何压缩日志文件
项目中在使用 sentry 上传事件的 attachment 函数过程中发现,附带的 log 文件是未压缩的,于是有了需求,即需要在 sentry 内部将未压缩的文件流压缩后再上传给服务器
这个需求看似挺简单的,其实过程挺坎坷的,因为要看 sentry 的源码,并对 zlib 的库有一定的了解才行。
这个文章为了以后同样有此需求的人作参考。
sentry 分为 sentry 源码和 crashpad 源码两部分,日常使用中,我们可以利用 sentry 自带的崩溃自动上传机制查看崩溃时的堆栈,不过堆栈只能看见崩溃的地方(局部的几个函数),并不能知晓
用户上下文的行为,复现一个崩溃要了解用户是以什么样的“动作”触发的,所以在崩溃时能够看到调试日志,对于开发人员来说是很重要的,不过在阅读 sentry 源码发现,sentry 附加的文件是从本地读取的,
也就是说 sentry 内部拿到本地文件的路径,传给 fopen 和 fread,并且内部没有集成压缩的 api,需要我们修改源码来实现压缩。
在不大面积修改 sentry 源码的前提下,我打算在本地项目中把日志先压缩到 buffer 内,再把 buffer 传给 sentry,这样 sentry 只需要一个接受 buffer 参数的接口即可,正好 sentry 内的 sentry_attachment_s 的结构体有 buf 参数,
struct sentry_attachment_s {
sentry_path_t *path;
sentry_attachment_t *next;
char *buf;
size_t buf_len;
};
这样我们可以手动创建一个接受 buffer 的接口,比如
static void
add_attachment_buf(sentry_options_t *opts, const char *buf, size_t buf_len)
{
if (!buf) {
return;
}
sentry_attachment_t *attachment = SENTRY_MAKE(sentry_attachment_t);
if (!attachment) {
sentry__path_free(path);
return;
}
attachment->buf = buf;
attachment->buf_len = buf_len;
attachment->next = opts->attachments;
opts->attachments = attachment;
}
#ifdef SENTRY_PLATFORM_WINDOWS
void
sentry_options_add_attachment_buf(
sentry_options_t *opts, const char *buf, size_t buf_len)
{
add_attachment_buf(opts, buf, buf_len);
}
说明一下,sentry 内部是链表读取文件路径的,可以使用这个接口替换已有接受文件路径的接口调用
之后,我们还需要提前压缩文件,即输入未压缩的文件数据,输出压缩后的文件数据,这里我们可以使用 zlib 库,如下
int CompressToGzip(const char* input, int inputSize, char* output) {
z_stream zs;
zs.zalloc = Z_NULL;
zs.zfree = Z_NULL;
zs.opaque = Z_NULL;
zs.avail_in = (uInt)inputSize;
zs.next_in = (Bytef*)input;
// zs.avail_out = (uInt)outputSize;
zs.next_out = (Bytef*)output; // hard to believe they don't have a macro for gzip encoding, "Add 16" is the
// best thing zlib can do: "Add 16 to windowBits to write a simple gzip header
// and trailer around the compressed data instead of a zlib wrapper"
deflateInit2(&zs, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 15 | 16, 8,
Z_DEFAULT_STRATEGY);
deflate(&zs, Z_FINISH);
deflateEnd(&zs);
return zs.total_out;
}
这个函数接受未压缩文件的输入和大小,输出压缩后的数据,返回压缩后的大小,压缩格式是 .gz
调用的话就很简单了,
void Send(std::string log, int severity, sentry_extra_data extra_data) {
int input_size;
const char* input = file_size(L"D:\\project\\bin\\Debug\\debug.log", &input_size);
int output_size = input_size;
char* output = new char[input_size];
int size = CompressToGzip(input, input_size, output);
sentry_options_add_attachment_buf(options, output, size);
delete output;
if (!extra_data.extra_data_key.empty()) {
sentry_set_extra(extra_data.extra_data_key.c_str(),
extra_data.extra_data_value);
} auto event = sentry_value_new_message_event(sentry_level_e(severity), nullptr,
log.c_str());
sentry_capture_event(event);
}
未压缩文件的输入和大小的函数如下,
char* file_size(const wchar_t* filename, int* filesize) {
FILE* fp = _wfopen(filename, L"rb");
fseek(fp, 0L, SEEK_END); *filesize = ftell(fp); file_buffer = new char[*filesize]; rewind(fp);
fread(file_buffer, 1, *filesize, fp); fclose(fp);
return file_buffer;
}
另外我们还需要在 sentry 内部修改上传日志的文件名,可以参考下面这个函数,不使用 path 参数,而是手动赋值一个文件名给 *s
const sentry_pathchar_t *
sentry__path_filename(const sentry_path_t *path)
{
const wchar_t *s = L"debug.log.gz";
const wchar_t *ptr = s;
size_t idx = wcslen(s); while (true) {
if (s[idx] == L'/' || s[idx] == L'\\') {
ptr = s + idx + 1;
break;
}
if (idx > 0) {
idx -= 1;
} else {
break;
}
} return ptr;
}
这些步骤都完成后,上传日志给 sentry 后应该就可以看到压缩的文件了
在这个需求进行到这边时,我以为差不多要完成了,发现压缩日志只在正常上传日志的事件中出现,而在崩溃时,sentry 自动上传的崩溃事件中并没有附加压缩文件,嗯,果然没那么简单
重新梳理了 sentry 源码,了解了 crash 的机制,并结合使用 sentry 过程时,需要一个 crashpad_handler.exe 程序才能完成崩溃自动上传事件,差不多摸透崩溃自动上传的工作原理。
CreateProcess 可以在父进程中打开子进程,is_embedded_in_dll 是判断是否有 crashpad_handler.exe,没有的话就 rundll32.exe 代替,并和平结束进程,有的话,就使用 command_line 参数,command_line 是个长字符串,
类型为 wstring,里面包括了 sentry 上报日志所需要的各种变量,比如 dsn,user_key,attachments 文件路径等等
crashpad_handler.exe 子进程就是程序崩溃后执行的,故要压缩其中的日志文件也要在 crashpad 代码模块中进行,如下,
CopyFileContent 即是读取本地文件以及写入 sentry 日志的操作,我们不需要改动它,而是新写一个 CopyCompressFileContent 函数,这个函数只需要筛选出对应的日志名字,再压缩它
注意添加头文件
#include "third_party/zlib/zlib/zlib.h"
void CopyCompressFileContent(FileReaderInterface* file_reader,
FileWriterInterface* file_writer) {
char buf[4096];
char output[4096];
FileOperationResult read_result;
z_stream zs;
zs.zalloc = Z_NULL;
zs.zfree = Z_NULL;
zs.opaque = Z_NULL;
do {
read_result = file_reader->Read(buf, sizeof(buf));
if (read_result < 0) {
break;
}
zs.avail_in = (uInt)read_result;
zs.next_in = (Bytef*)buf;
zs.avail_out = (uInt)sizeof(output);
zs.next_out = (Bytef*)output;
deflateInit2(
&zs, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 15 | 16, 8, Z_DEFAULT_STRATEGY);
deflate(&zs, Z_FINISH);
deflateEnd(&zs);
if (read_result > 0 && !file_writer->Write(output, zs.total_out)) {
break;
}
} while (read_result > 0);
}
有了压缩之后,同时把生成的最终文件名字改成 .gz,比如,
在程序崩溃后,我们在 sentry 不仅能够看到崩溃堆栈,也能查看调试日志以复现问题并解决掉
一些注意的地方,zlib 库只能用于 .gz 的解压缩,不能用于 zip 的解压缩
参考:https://www.zlib.net/zlib_faq.html#faq11
在对 z_stream 赋值时,一定要确保 avail_in 和 avail_out 都有足够的大小,不然 deflate 容易出现 Z_BUF_ERROR 错误,这个错误只是个提示,并不是致命的,它告诉我们需要足够大的缓冲区才行
参考:zlib Z_BUF_ERROR with specific file and specific buffer sizes
PS: 在一开始我将 zs.avail_out 注释了,导致我花了很久才找到错误的地方(注释的原因是网上某篇教程提供的例子中这样写的,没多想)
并且我习惯新创建一个工程来测试例子的可靠性,如果没问题我再集成到项目中,在注释 zs.avail_out 的工程中,可以正常压缩文件,这使我在很长一段时间内忽略 avail_out 的赋值,而在 crashpad 工程中集成压缩代码时,
zs.total_out 会一直返回 0,导致压缩文件失败。并且 deflate 报 Z_BUF_ERROR,在对 zs.avail_out 赋值后,压缩成功,至于为何在不同项目中会成功或失败,我们要在后面花时间去研究它。
如果想要压缩为 zip 文件,则需要使用其他库,比如 minizip 库,参考:
嗯,就写到这,后续想到更多的细节会继续补充,希望能帮助到有类似需求的人
C++ sentry 如何压缩日志文件的更多相关文章
- sqlserver中压缩日志文件
最近在转移数据,sqlserver的日志文件ldf,占用空间特别大,为了还原库,节省空间,所以压缩日志文件迫在眉睫.在网上找了一段代码: USE [master] GO ALTER DATABASE ...
- SQL2005、SQL2008如何压缩日志文件(log) 如何清除日志
原文发布时间为:2010-11-01 -- 来源于本人的百度文章 [由搬家工具导入] ALTER DATABASE [DataBaseName] SET ...
- sql server 压缩日志文件
USE [master] GO ALTER DATABASE TestDB SET RECOVERY SIMPLE WITH NO_WAIT GO ALTER DATABASE TestDB SET ...
- shell脚本----周期压缩备份日志文件
一.日志文件样式 二.目标 1.备份压缩.log结尾&&时间样式为“date +%Y%m%d”的日志文件(如:20170912.20160311等) 2.可指定压缩范围(N天前至当天) ...
- 日志文件 清理or压缩
1.操作前请断开所有数据库连接. 2.分离数据库 分离数据库:企业管理器->服务器->数据库->cwbase1->右键->分离数据库 分离后,cwbase1数据库被删除, ...
- linux之使用cron,logrotate管理日志文件
1) logrotate配置 logrotate 程序是一个日志文件管理工具.用来把旧的日志文件删除,并创建新的日志文件,我们把它叫做“转储”. 我们可以根据日志文件的大小,也可以根据其天数来 ...
- 日志文件 的管理 logrotate 配置
于Linux 的系统安全来说,日志文件是极其重要的工具.系统管理员可以使用logrotate 程序用来管理系统中的最新的事件, 对于Linux 的系统安全来说,日志文件是极其重要的工具.系统管理员可以 ...
- logrotate: 管理日志文件
Erik Troan提供了一种优秀的工具logrotate,它实现了多种多样的日志管理策略,而且在我们举例的所有发行版本上都是标准应用. logrotate的配置文件由一系列规范组成,它们说明了要管理 ...
- sql server压缩数据库和日志文件
DBCC SHRINKDATABASE 功能:压缩数据库 用法:DBCC SHRINKDATABASE tb_115sou_com 注意:只有产生许多未使用空间的操作(如截断表或删除表操作)后,执行收 ...
- shell脚本实现自动压缩一天前的日志文件 ,并传到ftp服务器上
shell脚本实现自动压缩一天前的日志文件 ,并传到ftp服务器上 naonao_127关注2人评论19401人阅读2012-06-08 11:26:16 生产环境下脚本自动备份脚本是 ...
随机推荐
- [转帖]关于面试时HA(RAC)会问到的一些问题
1.什么是RAC(Real Application Cluster)? RAC(Real Application Cluster)是Oracle数据库的一种部署架构,它将多个数据库服务器连接在一起,共 ...
- [转帖]Debian开启SSH
一.Debian开启SSH 参考链接: https://blog.csdn.net/zzpzheng/article/details/71170572 https://help.aliyun.com/ ...
- [转帖]kafka指定topic设置消息留存时间
背景 单个主题消息量庞大,需要指定这个主题的消息留存时间缩小点 执行命令 ./bin/kafka-configs.sh --bootstrap-server node1:9092 --entity-t ...
- [转帖]rclone将本地文件或文件夹导入minio中
1.背景:公司数据迁移涉及到文件迁移,原有文件服务器没有使用minio,但是现在的新系统使用了minio.所以这就需要我们将文件上传到minio文件服务器中:由于历史文件数据量大,甲方要求可以通过服务 ...
- [转帖]【InfluxDB V2.0】介绍与使用,flux查询、数据可视化
目录 一.关键概念 二.系统结构 三.配置文件 四.Flux查询语句 五.可视化数据 附录 一.关键概念 相比V1 移除了database 和 RP,增加了bucket. V2具有以下几个概念: ti ...
- [转帖]Arm发布CortexX4,功耗可降低40%
https://www.eet-china.com/mp/a224124.html ARM 发布了新一代的移动处理器内核,包括 Cortex-X4.Cortex-A720.Cortex-A520,预计 ...
- [转帖]适用于 Azure VM 的 TCP/IP 性能优化
https://learn.microsoft.com/zh-cn/azure/virtual-network/virtual-network-tcpip-performance-tuning?con ...
- 【字符串,哈希】【Yandex】Yandex7736
2023.6.30 Problem Link 定义一个串 \(S\) 是好的,当且仅当 \(S\) 可以不断消去相邻两个相同字符直至消空.给定一个长为 \(n\) 的字符串 \(s\),求有多少个有序 ...
- ST 表并查集小记🐤
ST 表维护并查集,在 $O(n \log n)$ 时间内处理 $[l_1,r_1]$ 内每个点依次向 $[l_2,r_2]$ 中的点连边(共连 $r_1-l_1+1$ 条边) 首先变成对于 $l_1 ...
- 你不知道的Linux shell操作
Linux Shell 脚本入门教程 Linux Shell 脚本是一种强大的工具,它允许您自动化日常任务和复杂操作.在本教程中,我们将逐步介绍几个实用的 Shell 脚本示例.每个示例都将详细说明, ...