Graylog处理docker容器的多行日志之过程记录
docker容器虽然支持gelf日志驱动,却不支持合并多行日志为1个message,详情见 log driver should support multiline · Issue #22920 · moby/moby · GitHub
,这导致在graylog查看java应用的报错日志时非常不方便。
解决思路:用logstash处理后再发给graylog。
docker安装logstash
将 /usr/share/logstash/conf.d/ 目录映射出来,方便编辑配置文件
mkdir -p /opt/logstash/conf.d/
vi /opt/logstash/logstash.yml
logstash.yml内容如下:
path.config: /usr/share/logstash/conf.d/*.conf
path.logs: /var/log/logstash
vi /opt/logstash/conf.d/test.conf
input { file{ path => "/usr/share/logstash/conf.d/test.log" start_position => "beginning" type=>"runtimelog" codec=> multiline { pattern => "^%{TIMESTAMP_ISO8601} " negate => true what => "previous" } } } filter {} output { stdout { codec => rubydebug } }
docker run -d -p 5044:5044 -p 5045:5045 -p 12200:12200/udp --name logstash -v /opt/logstash/logstash.yml:/usr/share/logstash/config/logstash.yml -v /opt/logstash/conf.d/:/usr/share/logstash/conf.d/ logstash:7.16.1
进入容器内安装插件
logstash-plugin install logstash-output-gelf logstash-plugin install logstash-input-gelf
安装完插件再添加相关conf
vi /opt/logstash/conf.d/app.conf
input { gelf { port =>12200 host => "0.0.0.0" codec => multiline { pattern => "^%{TIMESTAMP_ISO8601} " negate => true what => "previous" } } } filter {} output { gelf { host => "172.17.0.1" port => 12201 protocol => "UDP" } }
测试结果:input类型为file时,multiline编码正常,input类型为gelf时,无效...
参考链接:docker - logstash-5.x gelf input multiline codec doesn't work - Stack Overflow
既然logstash行不通,换成fluent-bit试试:
mkdir -p /opt/fluent-bit/
vi /opt/fluent-bit/fluent-bit.conf
[INPUT] name forward Listen 0.0.0.0 Port 24224 Buffer_Chunk_Size 1M Buffer_Max_Size 6M #Multiline On #Parser_Firstline multiline_pattern [OUTPUT] Name gelf Match * Host 172.17.0.1 Port 12201 Mode udp Gelf_Short_Message_Key log
docker run -d --name fluent -p 24224:24224 -p 24224:24224/udp -v /opt/fluent-bit/fluent-bit.conf:/fluent-bit/etc/fluent-bit.conf fluent/fluent-bit:1.8
很遗憾,fluent-bit的input类型为forward时,也不支持Multiline处理... unknown configuration property 'Multiline'. The following properties are allowed: unix_path, buffer_chunk_size, and buffer_max_size.
既然logstash和fluent都不能很好的实现,又有了以下想法:
fluentd将接收到的udp数据保存到文本文件,Logstash处理后再发给Graylog服务器。
然而且不说这个方法有多低效,实际操作后发现fluentd保存下来的文件也不是日至原始的格式了,Logstash并不方便处理。
又想到一个方法:自行用golang开发一个软件,实时接收udp数据包,判断是否一个完整的日志,是的话直接转发给Graylog,不是的话存在内存里等待接收完全再将不完整的日志拼接起来,然后发给Graylog。
然而,测试发现golang程序接收到的udp数据包全都是乱码的,一开始以为是不是gelf用的不是utf-8编码,但是看到英文字母都成了乱码,应该跟编码无关。
最终发现了-log-opt有一项是关于是否开启压缩的,-log-opt gelf-compression-type=none 设置完毕,终于不再乱码了...
随手敲了几行golang代码,测试发现基本能满足需求,不过没有区分不同tag的消息,不同容器同时发来消息可能将不同容器的gelf信息合并成同一条。代码待完善...
package main import ( "encoding/json" "fmt" "net" "time" ) //用于定义是否有未发的消息 var HasOld bool type GelfModel struct { Version string `json:"version"` Host string `json:"host"` ShortMessage string `json:"short_message"` Timestamp float64 `json:"timestamp"` Level int `json:"level"` Command string `json:"_command"` ContainerID string `json:"_container_id"` ContainerName string `json:"_container_name"` Created time.Time `json:"_created"` ImageID string `json:"_image_id"` ImageName string `json:"_image_name"` Tag string `json:"_tag"` } func SendToGelf(data []byte) { conn, err := net.Dial("udp", "127.0.0.1:12201") if err != nil { fmt.Println("net.Dial err:", err) return } defer conn.Close() conn.Write(data) //fmt.Println(len(data)) } func main() { HasOld = false udpConn, _ := net.ListenUDP("udp", &net.UDPAddr{ IP: net.IPv4(0, 0, 0, 0), Port: 12200, }) defer udpConn.Close() fmt.Println("udp listening ... ") gelfModel := &GelfModel{} for { var data [102400]byte n, _, _ := udpConn.ReadFromUDP(data[:]) // 接收数据 gelfModel_ := &GelfModel{} json.Unmarshal(data[:n], &gelfModel_) shortMessage := gelfModel_.ShortMessage if n > 10 && shortMessage[2] == ':' && shortMessage[4] == ':' && shortMessage[6] == '.' { //判断是否xx:xx:xx.xxx开头 if !HasOld { //没有的话直接发当前消息 SendToGelf(data[:]) HasOld = false } else { //有的话发之前的消息再发当前消息 jsons, _ := json.Marshal(gelfModel) //转换成JSON返回的是byte[] SendToGelf(jsons) //fmt.Println(string(jsons)) HasOld = false SendToGelf(data[:]) gelfModel.ShortMessage = "" } } else { HasOld = true gelfModel.ShortMessage += shortMessage + "\n" gelfModel.Version = gelfModel_.Version gelfModel.Host = gelfModel_.Host gelfModel.Timestamp = gelfModel_.Timestamp gelfModel.Level = gelfModel_.Level gelfModel.Command = gelfModel_.Command gelfModel.ContainerID = gelfModel_.ContainerID gelfModel.ContainerName = gelfModel_.ContainerName gelfModel.Created = gelfModel_.Created gelfModel.ImageID = gelfModel_.ImageID gelfModel.ImageName = gelfModel_.ImageName gelfModel.Tag = gelfModel_.Tag } } }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架