项目中在使用 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 如何压缩日志文件的更多相关文章

  1. sqlserver中压缩日志文件

    最近在转移数据,sqlserver的日志文件ldf,占用空间特别大,为了还原库,节省空间,所以压缩日志文件迫在眉睫.在网上找了一段代码: USE [master] GO ALTER DATABASE ...

  2. SQL2005、SQL2008如何压缩日志文件(log) 如何清除日志

    原文发布时间为:2010-11-01 -- 来源于本人的百度文章 [由搬家工具导入]            ALTER DATABASE [DataBaseName]             SET ...

  3. sql server 压缩日志文件

    USE [master] GO ALTER DATABASE TestDB SET RECOVERY SIMPLE WITH NO_WAIT GO ALTER DATABASE TestDB SET ...

  4. shell脚本----周期压缩备份日志文件

    一.日志文件样式 二.目标 1.备份压缩.log结尾&&时间样式为“date +%Y%m%d”的日志文件(如:20170912.20160311等) 2.可指定压缩范围(N天前至当天) ...

  5. 日志文件 清理or压缩

    1.操作前请断开所有数据库连接. 2.分离数据库 分离数据库:企业管理器->服务器->数据库->cwbase1->右键->分离数据库 分离后,cwbase1数据库被删除, ...

  6. linux之使用cron,logrotate管理日志文件

    1) logrotate配置   logrotate 程序是一个日志文件管理工具.用来把旧的日志文件删除,并创建新的日志文件,我们把它叫做“转储”.   我们可以根据日志文件的大小,也可以根据其天数来 ...

  7. 日志文件 的管理 logrotate 配置

    于Linux 的系统安全来说,日志文件是极其重要的工具.系统管理员可以使用logrotate 程序用来管理系统中的最新的事件, 对于Linux 的系统安全来说,日志文件是极其重要的工具.系统管理员可以 ...

  8. logrotate: 管理日志文件

    Erik Troan提供了一种优秀的工具logrotate,它实现了多种多样的日志管理策略,而且在我们举例的所有发行版本上都是标准应用. logrotate的配置文件由一系列规范组成,它们说明了要管理 ...

  9. sql server压缩数据库和日志文件

    DBCC SHRINKDATABASE 功能:压缩数据库 用法:DBCC SHRINKDATABASE tb_115sou_com 注意:只有产生许多未使用空间的操作(如截断表或删除表操作)后,执行收 ...

  10. shell脚本实现自动压缩一天前的日志文件 ,并传到ftp服务器上

    shell脚本实现自动压缩一天前的日志文件 ,并传到ftp服务器上 naonao_127关注2人评论19401人阅读2012-06-08 11:26:16         生产环境下脚本自动备份脚本是 ...

随机推荐

  1. [转帖]7.5 TiKV 磁盘空间占用与回收常见问题

    https://book.tidb.io/session4/chapter7/compact.html TiKV 作为 TiDB 的存储节点,用户通过 SQL 导入或更改的所有数据都存储在 TiKV. ...

  2. [转帖]一文快速入门 ClickHouse

    https://zhuanlan.zhihu.com/p/621480049 什么是clickhouse ClickHouse是一种OLAP类型的列式数据库管理系统,这里有两个概念:OLAP.列式数据 ...

  3. [转帖]elasticsearch8.0以上版本修改内置用户密码

    https://www.cnblogs.com/zhang-ding-1314/p/16199682.html 修改密码需要在es启动,并cd到es的bin目录下执行: 1.重置密码并在控制台显示新密 ...

  4. [转帖]JMeter学习(二)搭建骨架--JMeter重要组件

    https://www.cnblogs.com/tian-yong/p/4460665.html JMeter的属性和变量 JMeter属性统一定义在jmeter.properties文件中.JMet ...

  5. [转帖]Jmeter压力测试工具安装及使用教程

    https://www.cnblogs.com/monjeo/p/9330464.html 一.Jmeter下载 进入官网:http://jmeter.apache.org/ 1.第一步进入官网如下图 ...

  6. [转帖]线上Java 高CPU占用、高内存占用排查思路

    一.前言 处理过线上问题的同学基本上都会遇到系统突然运行缓慢,CPU 100%,以及Full GC次数过多的问题.当然,这些问题的最终导致的直观现象就是系统运行缓慢,并且有大量的报警.本文主要针对系统 ...

  7. [转帖] Linux命令拾遗-剖析工具

    https://www.cnblogs.com/codelogs/p/16060472.html 简介# 这是Linux命令拾遗系列的第五篇,本篇主要介绍Linux中常用的线程与内存剖析工具,以及更高 ...

  8. Oracle12c(未更新任何补丁) 使用compression=all 参数导出之后导入失败

    1. 最近使用Oracle12c 进行相关的测试工作, 平台linux 和 windows 都有一个问题 备份恢复使用的 compression=all 时导入数据库不管是oracle12c还是 or ...

  9. 【计数,DP】CF1081G Mergesort Strikes Back

    Problem Link 现有一归并排序算法,但是算法很天才,设了个递归深度上限,如果递归深度到达 \(k\) 则立即返回.其它部分都和正常归并排序一样,递归中点是 \(\lfloor (l+r)/2 ...

  10. 【小分享】vm-storage中,计算metric的平均长度

    作者:张富春(ahfuzhang),转载时请注明作者和引用链接,谢谢! cnblogs博客 zhihu Github 公众号:一本正经的瞎扯 表达式如下: sum by (type) (vm_cach ...