logstash的lumberjack协议解析
最近在实现一个agent
采集服务器日志,设计agent
需要能够将数据发送给logstash
。当然logstash
支持很多输入协议,其中,logstash技术栈(包括ElasticSearch
)内有一种叫做lumberjack
的协议,可能是专门为传输日志数据设计的。不过网上对于lumberjack
协议没有公开的资料,而且实现上,只有java
、ruby
、golang
版。笔者通过参考golang
版(elastic/go-lumber)和java
版(logstash-forwarder-java),用C
实现了agent
对lumberjack
的支持。本文总结一下lumberjack
协议的协议报文格式。
lumberjack版本
lumberjack总共有两个版本,logstash-forwarder-java只实现了第一版,elastic/go-lumber两个版本都实现了。新版的logstash
作为服务端同时支持两个版本。相比而言,V2在格式定义上支持json
,因此比V1简化很多,而且冗余信息略少于V1。由于json
的引入,使得V2版本支持json
支持的所有类型,而V1却甚至无法表达一个整型类型(只能全部用字符串表达)。
日志对象
一个日志对象
是一个map<string,byte[]>,所以可以用json形式来序列化表达(V2才支持json)。当然这种数据结构不限于传输日志数据。一个完整的lumberjack
报文可以包含多个日志对象,即可以支持批量发送日志。在同一个报文中,每个日志对象用sequence
(uint32_t)来区分,类似数组的索引(index)。
V1版本格式
Window头
Window头包含有协议的版本、W标志、日志对象数量 这3个信息,下面是Window头的报文格式:
0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
+---------------+---------------+---------------------------------------------------------------+
| '1'(0x31) | 'W'(0x57) | window size(uint32 bigendian) |
+---------------+---------------+---------------------------------------------------------------+
'1'(0x31)
是一个ASCII字符1
占用一个字节,表示V1版本'W'(0x57)
是W
标识,表示是Window头window size
是一个uint32_t
的整型(大端存储),这个值表示报文的日志对象有多少个
日志对象格式
一个日志对象包含对象头
和对象体
下面是日志对象头
0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
+---------------+---------------+---------------------------------------------------------------+
| '1'(0x31) | 'D'(0x44) | seq(uint32 bigendian) |
+---------------------------------------------------------------+-------------------------------+
| count of key value pairs(uint32 bigendian) |
+---------------------------------------------------------------+
'1'(0x31)
是一个ASCII字符1
占用一个字节,表示V1版本'D'(0x44)
是D
标识,表示是Dataseq
即为日志对象的序号,一般可以从1开始累加,用来在后面的确认报文中会带上序号,发送端就可以知道接收端究竟确认了哪些对象了count of key value pairs
,由于日志对象的基本数据类型是map<string,bytes[]>所以构成了一系列的key value pair
,这个值就是标识一个日志对象中,究竟包含多少个key value pair
。
下面是日志对象体
既然日志对象是map<string,bytes[]>结构,那么数据体就是要存储这个结构,我们来继续看日志体的格式
0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
+---------------------------------------------------------------+
| 1st key size(in bytes,big endian) |
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
| |
| 1st key payload |
| |
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
| 1st payload size(in bytes,big endian) |
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
| |
| 1st payload payload |
| |
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
| 2nd key size(in bytes,big endian) |
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
| |
| 2nd key payload |
| |
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
| 2nd payload size(in bytes,big endian) |
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
| |
| 2nd payload payload |
| |
: :
可以看到,每个<string,byte[]>分为key和payload两部分,string自然是key,而byte[]自然是payload。
在组包时,先填入key的长度,然后填入key,再填入payload长度,再填入payload,如此往复,就可以完成一个map<string,byte[]>结构的表达。
最终,一个完整的lumberjack请求包,包含:
Window头 + N x 日志对象(日志对象头+日志对象体)
ACK
lumberjack设计了应用层ACK
机制,即接收端可以在一次请求过程中,随时确认收到的每一个日志对象(通过seq标示)。当然也可以全部收完以后,只确认最后一个日志对象。客户端一般可以设计成等待服务端确认最后一个日志对象的ACK后,放心的认为服务端接收全部收完了。
ACK的格式如下:
0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
+---------------+---------------+---------------------------------------------------------------+
| '1'(0x31) | 'A'(0x41) | record seq(uint32 bigendian) |
+---------------+---------------+---------------------------------------------------------------+
压缩报文
lumberjack支持将日志对象数据压缩后发送,不过在一个完成的报文中,不能同时包含压缩和非压缩数据,即要么对所有的日志对象一起压缩,要么都不压缩。压缩形态的报文格式为:
0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
+---------------+---------------+---------------------------------------------------------------+
| '1'(0x31) | 'C'(0x43) | payload length(in bytes,uint32 bigendian) |
+-----------------------------------------------------------------------------------------------+
| |
| compressed record segment(s) |
| |
+-----------------------------------------------------------------------------------------------+
'C'(0x43)
表示Compress数据payload length
表示压缩数据的长度compressed record segment(s)
是对一个或多个日志对象(包括日志头和日志体)进行压缩以后的字节数据
所以一个完整的压缩形态的数据报文应该是:
Window头 + 压缩报文
值得注意的是,尽管使用了压缩报文,Window头中的日志对象数量还是需要指明日志对象的数量的。
关于压缩,是基于deflate进行的,使用默认的压缩级别即可(level6)。各个语言都可以使用zlib进行压缩和解压,以C语言为例,zlib Usage Example。
下面的代码用于压缩一段内存数据,代码大致如下:
#include <zlib.h>z_stream strm;
int ret;
unsigned char output[CHUNK];
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
strm.opaque = Z_NULL;
//Z_DEFAULT_COMPRESSION是-1,是默认的压缩级别,相当于压缩级别6
deflateInit(&strm, Z_DEFAULT_COMPRESSION);strm.next_in = (unsigned char *)input;
strm.avail_in = len;
strm.next_out = output;do {strm.avail_out = CHUNK;ret = deflate(&strm, Z_FINISH);//写如结果memcpy(result,output,CHUNK-strm.avail_out);
} while(strm.avail_out == 0);deflateEnd(&strm);
V2版本格式
V2版本就没有V1版本那么啰嗦了,直接上图:
window header与v1的协议一致,只是version部分填入2。
0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
+---------------+---------------+---------------------------------------------------------------+
| '2'(0x31) | 'W'(0x57) | window size(uint32 bigendian) |
+---------------+---------------+---------------------------------------------------------------+
日志对象的头部用J
代替D
,表示是json格式的数据
0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
+---------------+---------------+---------------------------------------------------------------+
| '2'(0x32) | 'J'(0x4A) | seq(uint32 bigendian) |
+---------------------------------------------------------------+-------------------------------+
| payload length(uint32 bigendian) |
+---------------------------------------------------------------+
| |
| json encode string |
| |
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
除此之外,由于payload部分不需要通过协议本身来表达map<string,byte[]>,直接借助json格式,所以只需增加json字符串的串长就可以了(图中的payload length)。
V2版本的ACK与V1版本完全相同。
logstash的lumberjack协议解析相关推荐
- synopsys PCIE IP协议解析
synopsys PCIE IP协议解析 1.Overview Core支持单个Pcie内核的Loopback功能,该功能主要为了做芯片验证,以及在没有远程接收器件的情况下完成自己的回环.同时,Cor ...
- 详解BLE 空中包格式—兼BLE Link layer协议解析
BLE有几种空中包格式?常见的PDU命令有哪些?PDU和MTU的区别是什么?DLE又是什么?BLE怎么实现重传的?BLE ACK机制原理是什么?希望这篇文章能帮你回答以上问题. 虽然BLE空中包(pa ...
- wireshark协议解析器 源码分析 封装调用
源码分析 Wireshark启动时,所有解析器进行初始化和注册.要注册的信息包括协议名称.各个字段的信息.过滤用的关键字.要关联的下层协议与端口(handoff)等.在解析过程,每个解析器负责解析自己 ...
- wireshark协议解析器原理与插件编写
工作原理 每个解析器解码自己的协议部分, 然后把封装协议的解码传递给后续协议. 因此它可能总是从一个Frame解析器开始, Frame解析器解析捕获文件自己的数据包细节(如:时间戳), 将数据交给一个 ...
- SGS 0.9.7 协议解析
SGS as客户端发送数据规则: sgs 是0.9.7版 协议规则: 包头3个字节: 包大小(2个字节),命令(1个字节) 数据包 注意:包大小=命令+ 数据包 的大小 登陆命令: 命令是: ...
- 简易HTTP协议解析
首先介绍一些必要的知识点. TCP协议为操作系统底层协议,能够保证应用层获取到完整的.顺序一直的包序列.但TCP不提供具体的分包,需要上层协议自己解决.TCP发送给上层协议的数据是一个没有意义的字符串 ...
- 视音频数据处理入门:UDP-RTP协议解析
===================================================== 视音频数据处理入门系列文章: 视音频数据处理入门:RGB.YUV像素数据处理 视音频数据处理 ...
- 公网传输技术之SRT协议解析(上)
点击上方"LiveVideoStack"关注我们 作者:张博力 编辑:Alex ▼扫描下图二维码或点击阅读原文▼ 了解音视频技术大会更多信息 " 摘 要:SRT协议( ...
- 修改wireshark协议解析规则
不同的协议有不同的解码器,wireshark尝试为每个包尝试找到正确的解码器,特定的情况有可能会选择错误的解码器. 1.使用了其它协议的标准端口,被错误解码,使用udp的80端口发送数据被当作QUIC ...
最新文章
- 蓝桥杯 密文搜索(全排列)
- mysql数据库主键自增6_mysql数据库,主键自增主键不连续
- GDCM:gdcm::Directory的测试程序
- Vue+iview实现自定义格式导出Excel文件
- java反射sethaha_Java反射深度测试
- 五个案例让你明白GCD死锁
- 《Java基础学习笔记》JAVA面向对象之封装
- 第45届国际大学生程序设计竞赛(ICPC)银川站太原理工大学收获4枚奖牌
- Matlab Tricks(一)—— figure(1)
- C/C++[codeup 1962]单词替换
- 中国微电网市场趋势报告、技术动态创新及市场预测
- 有关古希腊罗马神话与医学术语的联系的英文文献去哪找?
- 2019最新个税计算_python
- 国际反垃圾邮件组织有哪些?
- 修改php fpm监听端口,怎样修正php fpm监听端口_后端开发
- 何绍华Linux操作系统第3版章节课后答案习题
- 内存溢出常见原因及解决方法
- 教你在MathType中输入空心字和花体字的重要方法
- C语言函数中的3个点 ...有什么作用
- ubuntu 配置本地源
热门文章
- 数字IC设计随笔之一(Verdi自动添加波形脚本应用)
- 真有人去炸公司了...
- 计算机组成原理——微程序控制器
- 海外出货量占比超七成,海外市场决定小米手机的未来
- 手写计算器java_可编程科学计算器app
- c语言编程的扑克牌游戏,扑克牌加减乘除游戏
- 和计算机网络相关的段子,微信幽默段子 没有谁和谁一开始就很配
- android开发环境安装
- 试题 算法训练 逗志芃的危机 (Java实现 通俗易懂)
- Unity初级案例-愤怒的小鸟:三:07猪的受伤+08弹弓划线操作+09死亡和加分特效的制作+10游戏逻辑的判定,实现多只小鸟的飞出