logkit 是 Pandora 开源的一个通用的日志收集工具,可以将不同数据源的数据方便的发送到 Pandora 进行数据分析。除了基本的数据发送功能,logkit 还有容错、并发、监控、删除等功能。具体的 logkit 设计可以参阅《数据收集工具的设计与最佳实践》,本文将重点介绍如何 contribute logkit 。
我们也特别准备了七牛云限量周边作为见面礼。欢迎大家加入我们,成为七牛云 Contributor (详情请见文末) 【Contributor 登记表】
环境准备与运行
下载 go 源码
下载地址:
https://golang.org/dl/
以 linux 为例:
wget https://storage.googleapis.com/golang/go1.8.3.linux-amd64.tar.gz
安装 go 源码
以 linux 为例:
解压并移动到相应的系统目录,假设为 /opt
tar xzf go1.8.3.linux-amd64.tar.gz && mv go /opt/
配置系统的环境变量
以 Linux 为例:
export PATH=$PATH:/opt/go/bin
export GOROOT=/opt/go
export GOPATH=~/gopath
export PATH=$PATH:~/gopath/bin
请加入到 home 目录的.bashrc中, windows 就设置为系统环境变量(全局生效)
运行并生效环境变量
source ~/.bashrc
创建 logkit GOPATH 目录,并进入
以 Linux 为例:
mkdir -p ~/gopath/src/github.com/qiniu/
cd ~/gopath/src/github.com/qiniu/
下载 logkit 源码
在此之前,可能你需要安装一下 git 工具。
以 linux 为例安装 git :
apt-get install git
git clone https://github.com/qiniu/logkit.git
进入 logkit 源码目录
cd ~/gopath/src/github.com/qiniu/logkit
下载 logkit 包管理工具
go get -u github.com/kardianos/govendor
下载并同步所有 logkit 依赖包
govendor sync
运行 logkit
go run logkit.go -f logkit.conf
代码更新
git pull
govender sync
贡献 logkit 数据源插件
logkit 目前提供六种读取日志的方式,分别为 File Reader、ElasticSearch Reader、MongoDB Reader、MySQL Reader、MicroSoft SQL Server Reader、Kafka Reader
- File Reader: 读取文件中的日志数据,包括 csv 格式的文件, kafka-rest 日志文件,nginx 日志文件等,并支持以 grok 的方式解析日志。
- ElasticSearch Reader: 读取 ElasticSearch 中的数据。
- MongoDB Reader: 读取 MongoDB 中的数据。
- MySQL Reader: 读取 MySQL 中的数据。
- MicroSoft SQL Server Reader: 读取 Microsoft SQL Server 中的数据。
- Kafka Reader: 读取 Kafka 中的数据。
而实现一个 logkit 的自定义 reader 只需要实现以下接口即可。
// Reader 是一个通用的行读取 reader 接口
type Reader interface {
Name( ) string
Source( ) string
ReadLine( ) (string, error)
SetMode(mode string, v interface{}) error
Close( ) error
SyncMeta( )
}
- Name( )方法是为了方便的获取 Reader 名称,做出区别。
- Source( )方法返回读取的数据源说明,通常是一个自定义的标记,如读取文件的话就是文件路径,读取 mysql 的话可以是数据库地址、名称、表等。
- ReadLine( ) 就是读取一行数据,这个「行」是一个宽泛的概念,并不一定以\n分隔就是行,那么如何定义行呢? SetMode( )方法解决这个问题。
- SetMode( )方法定义行首的正则表达式,也可以不做这个定义,默认以\n分隔作为一行。
- Close( )方法包括关闭文件句柄、服务连接等常规的结束操作。
- SyncMeta( ) 同步读取的元信息,记录读取位置,以便重启等异常情况下可以继续读取,不会丢失数据也不会重复读取。
从数据来源上分类,数据读取大致可分为从文件读取、从数据存储服务端读取以及从消息队列中读取三类。
每一类 Reader 均在发送成功后通过 SyncMeta( ) 函数记录读取的位置,保证数据不会因为 runner 意外中断而丢失。
从文件读取数据
这一类最为常见,logkit 支持的已经较为完善,可以参阅 file reader 了解相关内容,支持包括多文件编码格式支持、读取限速等多种功能。
从数据存储服务中读取数据
这一类目前 logkit 支持从 MongoDB、MySQL、MSSQL、Elasticsearch 这四个数据源中读取。
- 在 SyncMeta( ) 的策略上,这一类从服务器中读取的数据可以采用时间戳策略,如 MongoDB、MySQL 中记录的数据,应该都包含一个时间戳 (timestamp) 的字段,每次读取数据均按这个时间戳字段排序,以此获得新增的数据或者数据更新(数据有更新也要更新这个时间戳字段)。
- 周期性运行,需要为用户设计类似定时器等策略,方便用户可以周期性运行,不断同步收集服务器中的数据。
- 考虑性能,在数据量大的情况下,要分批次获取数据,可以参阅不同数据源服务器提供的批量获取数据的方式,针对性的设计性能最优的数据拉取模式。
从消息队列中读取数据
从消息队列中读取数据在设计上最为简单,因为消息队列服务器在提供服务的时候就会考虑到读取的各类问题,直接利用好消费队列的 API 即可。注意记录读取的 Offset ,防止数据丢失。
贡献 logkit 解析器插件
Parser 是在数据被 logkit 读取后,用户可以根据需要定制化的解析日志格式,抽取数据中的字段,转化为结构化数据的方式。
目前 logkit 已经内置一部分数据解析方式,他们分别为:
- csv (按行通过分隔符解析): csv parser
- json (通过 json 反序列化解析): json parser
- grok (通过配置 grok pattern 解析): grok parser
- qiniulog (七牛开源的 golang 日志库日志格式解析): Qiniu Log Parser
- kafkarest (Kafkarest 日志解析): KafkaRestLog Parser
- raw (直接按行读取返回): Raw Parser
Parser 典型配置如下
"parser":{
"name":"req_csv",
"type":"csv",
"labels":"machine nb110,team pandora"
},
- name parser 的 name,用来标识
- type parser 的类型
- labels 对于所有的 Parser ,我们均提供了标签功能,用于记录一些额外的信息,添加到结构化的数据字段中,如常见的日志所在机器、业务所述团队等信息,labels 的写法则是标签名称和数值以空格隔开<标签名> <标签内容>,多个标签提供逗号分隔。
根据不同的 Parser ,还需要填写其他特定 Parser 相关的配置文件,详情请参阅各 Parser 的配置页面。
若上述内置的解析器无法满足用户需求, Logkit 也支持用户根据需要自己实现解析逻辑,只需要实现如下两个接口。
type LogParser interface {
Name() string
// parse lines into structured datas
Parse(lines []string) (datas []sender.Data, err error)
}
Parser 的接口非常简单,一共就 2 个方法。
首先 Name( ) 函数表示 Parser 的名称,用于标识;其次就是批量解析数据的 Parse 方法,传入的是一个字符串数组,数组的每一个元素就是 reader 中 readline 函数获取的每一行,为了性能更高传入数组进行批量解析,返回的数据就是按照 sender 中定义的 Data 类型,实际上对应的就是 Go 语言的 map[string]interface{}类型。
所以 Parser 要做 2 件事情。
- 确定最终要解析出的 schema ,即字段 (field 或者 key) 和类型 (type)。像 json 这种数据天生就含有 schema ,无需用户定义,其他类型的解析器可能需要用户在配置文件中写明 schema 是怎么样的。
- 定义规则将传入的字符串按照 schema 解析。 Parser 之间的主要区别实际上就是规则的不同,如 csv parser 就是按照指定的分隔符分隔; json parser 就是按照 json 格式解析 等等。根据你 Parser 对应要解析的字符串内容实现你的解析规则即可。
实现完成 Parser 后,注意在 NewParserRegistry( ) 中注册对应的 new 方法噢,如下所示。
func NewParserRegistry() *ParserRegistry {
ps := &ParserRegistry{
parserTypeMap: map[string]func(conf.MapConf) (LogParser, error){},
}
ps.RegisterParser(TypeCSV, NewCsvParser)
ps.RegisterParser(TypeRaw, NewRawlogParser)
ps.RegisterParser(TypeKafkaRest, NewKafaRestlogParser)
ps.RegisterParser(TypeEmpty, NewEmptyParser)
ps.RegisterParser(TypeGrok, NewGrokParser)
ps.RegisterParser(TypeJson, NewJsonParser)
ps.RegisterParser(TypeNginx, NewNginxParser)
//ps.RegisterParser(TypeMyNewParser, NewMyNewParser)
return ps
}
一个示例的自定义 Parser
package samples
import (
"strings"
"github.com/qiniu/logkit/conf"
"github.com/qiniu/logkit/parser"
"github.com/qiniu/logkit/sender"
)
// 一个自定义parser的示例,将日志放到data中的log字段中
type CustomParser struct {
// parser 的名字
name string
// 每行截断最大字符数
maxLen int
}
func NewMyParser(c conf.MapConf) (parser.LogParser, error) {
// 获取parser配置中的name项,默认myparser
name, _ := c.GetStringOr("name", "myparsername")
// 获取parser配置中的max_len选项,默认1000
maxLen, _ := c.GetIntOr("max_len", 1000)
p := &CustomParser{
name: name,
maxLen: maxLen,
}
return p, nil
}
func (p *CustomParser) Name() string {
return p.name
}
func (p *CustomParser) Parse(lines []string) (datas []sender.Data, err error) {
for _, l := range lines {
d := sender.Data{}
line := strings.TrimSpace(l)
if len(line) > p.maxLen {
line = line[:p.maxLen]
}
d["log"] = line
datas = append(datas, d)
}
return datas, nil
}
贡献更多的 Transformer 插件
Transformer 是 Parser 的一个补充,它针对字段进行数据变换。
在大多数场景下,你可能用 Parser 就解决了全部问题,但是有些场合,你使用的 Parser 可能比较简单,如 json parser ,此时你发现数据里面有一部分字段你希望做一些扩展,比如有个 ip 字符串,你希望将其扩展为 ip 对应的区域、城市、省份、运营商等信息,此时你就可以配置一个 Transformer ,对 IP 字段进行转换。
再比如,当你希望做一些字符串替换的时候,你又不想写一个 Parser ,只希望对某个字段做一个字符串替换处理,那就可以写配置一个 replace transformer 。
Transform 既可以在 Parser 前调用,也可以在 Parser 后调用。同样,也可以连着调用,调用顺序就是你在配置文件中配置的顺序。
一份带有 Transformer 的配置如下:
{
"name":"test2.csv",
"reader":{
"log_path":"./tests/logdir",
"mode":"dir"
},
"parser":{
"name":"jsonps",
"type":"json"
},
"transforms":[{
"type":"replace",
"stage":"before_parser",
"old":"\\x",
"new":"\\\\x"
}],
"senders":[{
"name":"file_sender",
"sender_type":"file",
"file_send_path":"./test2/test2_csv_file.txt"
}]
}
可以看到,在 reader、parser 和 sender 同级别下面,增加一个 transforms 字段,对应的就是不同 transformer 对应的列表。
type 字段是固定的,每个 Transformer 都有,表示 Transformer 的类型名称,其他字段则根据 Transformer 表达的转换行为各不相同。可以参见每个 Transformer 的介绍页。
目前支持的 Transformer 有:
- replace transformer : 针对字段做字符串替换。
- IP transformer: 针对ip做运营商信息字段扩展。
- date transformer: 将字段解析为时间并做一些转换。
- discard transformer: 将指定字段的数据抛弃。
- split transformer: 将指定字段的数据切分为字符串数组。
- convert transformer: 按照 dsl 将数据进行格式转换。
目前 logkit 的 Transformer 接口如下:
// 注意: transform的规则是,出错要把数据原样返回
type Transformer interface {
Description() string
SampleConfig() string
ConfigOptions() []utils.Option
Type() string
Transform([]sender.Data) ([]sender.Data, error)
RawTransform([]string) ([]string, error)
Stage() string
Stats() utils.StatsInfo
}
- Description( ) string : 描述并返回 Transformer 作用的字符串
- SampleConfig( ) string: 描述并返回 Transformer 示例配置的字符串
- ConfigOptions( ) [ ]utils.Option: 描述并返回 Transform 配置选项的方法。
- Type() string: 描述 Transform 类型的方法
- Transform([]sender.Data) ([]sender.Data, error): 转换 Transform 数据,该方法转换的是 Parser 后的数据,接受的是 Data 数组。
- RawTransform([]string) ([]string, error): 转换 Transform 数据,该方法转换的是 Parser 前的数据,接受的是 string 数组
- Stage() string: 表示 Transform 是用于 Parser 前还是 Parser 后
- Stats() utils.StatsInfo: 统计 Transform 成功失败的数据
Transformer 注册
如下所示,每个 Transformer 需要在 init 方法中注册自己,这样只需 import 对应的包就能注册,无需额外的代码侵入。
func init() {
transforms.Add("discard", func() transforms.Transformer {
return &Discarder{}
})
}
注册时返回的实际上是一个 create 方法, create 的就是对应 Transformer 的结构体。
Transformer 个性化配置
Transformer 中最有趣的是如何将各种不同的配置统一的定义到Transformer 中呢? 答案就是利用 json 协议,将配置的 json 字符串反序列化到生成 Transformer 结构体中,即实现了每个具体 Transformer 的赋值。
creater, _ := transforms.Transformers[TransformType] //从注册中获得对应类型的Transformer creator
trans := creater() //调用Transformer creator创建Transformer对象
err = json.Unmarshal(bts, trans) // 将配置的json字符串反序列化到对象的结构体中
通过上述过程,用户的不同配置就能让不同 Transformer 感知了。
所以在定义 Transformer 时需要注意描述好对应的 json 格式 key 名称,并且保证变量名是大写的,如下所示为一个表示字符串替换的 Transformer 定义:
type Replacer struct {
StageTime string `json:"stage"`
Key string `json:"key"`
Old string `json:"old"`
New string `json:"new"`
}
Transformer 的执行顺序
Transformer 可以在 Parser 前使用也可以在 Parser 后使用,其中 Stage()方法表示的就是这个位置。
Parser 前使用即 Reader 刚刚获取的数据就做一次 Transform 转换,针对的就是 reader 中读取的字符串,这里你可以做一些诸如字符串替换、字符串切割的 Transformer ,以便直接使用诸如 json parser 这样通用的 Parser ,省的再去编写复杂的解析过程。
Parser 后使用的 Transform 就多了,因为经过了 Parser ,带有了 schema 信息,可以知道数据的字段名称和类型,所以可以针对某些字段做转换,比如字符串操作、 IP 扩展等等。
多个 Transformer 可以按顺序串联起来依次执行,写配置文件的时候按顺序填写在 json 数组中即可。
Transformer的参数说明
为了使 logkit 可以在前端更好的展示,Transformer 中还需要写ConfigOptions()接口,该接口返回的就是每个 Transformer 的配置对应的字段名称、数据类型以及作用说明。
一个示例的 Transformer
package mutate
import (
"errors"
"github.com/qiniu/logkit/sender"
"github.com/qiniu/logkit/transforms"
"github.com/qiniu/logkit/utils"
)
type Discarder struct {
Key string `json:"key"`
stats utils.StatsInfo
}
func (g *Discarder) RawTransform(datas []string) ([]string, error) {
return datas, errors.New("discard transformer not support rawTransform")
}
func (g *Discarder) Transform(datas []sender.Data) ([]sender.Data, error) {
var ferr error
errnums := 0
for i := range datas {
delete(datas[i], g.Key)
}
g.stats.Errors += int64(errnums)
g.stats.Success += int64(len(datas) - errnums)
return datas, ferr
}
func (g *Discarder) Description() string {
return "discard onefield from data"
}
func (g *Discarder) Type() string {
return "discard"
}
func (g *Discarder) SampleConfig() string {
return `{
"type":"discard",
"key":"DiscardFieldKey"
}`
}
func (g *Discarder) ConfigOptions() []utils.Option {
return []utils.Option{
transforms.KeyStageAfterOnly,
transforms.KeyFieldName,
}
}
func (g *Discarder) Stage() string {
return transforms.StageAfterParser
}
func (g *Discarder) Stats() utils.StatsInfo {
return g.stats
}
func init() {
transforms.Add("discard", func() transforms.Transformer {
return &Discarder{}
})
}
贡献更多的发送端插件
Senders 的主要作用是将 Parser 后的数据发送至 Sender 支持的各类服务,目前支持发送到的服务包括: Pandora、ElasticSearch、File、InfluxDB、MongoDB 五种服务。
- Pandora Sender: 发送到 Pandora (七牛大数据处理平台)。
- ElasticSearch Sender: 发送到 ElasticSearch。
- File Sender: 发送到本地文件。
- InfluxDB Sender: 发送到 InfluxDB。
- MongoDB Accumulate Sender: 聚合后发送到 MongoDB。
除了上述五种发送到服务之外,logkit 还支持一种发送到本地磁盘进行数据发送预处理的模式( fault_tolerant 设置为 true,ft_strategy 设置为 always_save ),进行了 fault_tolerant 模式设置后,数据的 reader/parser 就和发送异步,数据可以持续读取并解析进入磁盘队列, sender 则可以多线程发送,可以有效提升发送效率,并发发送数据。
典型配置如下
"senders":[{
"name":"pandora_sender",
"sender_type":"pandora",
"pandora_ak":"",
"pandora_sk":"",
"pandora_host":"https://pipeline.qiniu.com",
"pandora_repo_name":"yourRepoName",
"pandora_region":"nb",
"pandora_schema":"field1 pandora_field1,field2,field3 pandora_field3",
"fault_tolerant":"true",
"ft_sync_every":"5",
"ft_save_log_path":"./ft_log",
"ft_write_limit":"1",
"ft_strategy":"always_save",
"ft_procs":"2"
}]
- name: 是 sender 的标识
- sender_type: sender 类型,支持file, mongodb_acc, pandora, influxdb
- fault_tolerant: 是否用异步容错方式进行发送,默认为false。
- ft_save_log_path: 当 fault_tolerant 为 true 时候必填。该路径必须为文件夹,该文件夹会作为本地磁盘队列,存放数据,进行异步容错发送。
- ft_sync_every:当 fault_tolerant 为 true 时候必填。多少次发送数据会记录一次本地磁盘队列的 offset 。
- ft_write_limit:选填,为了避免速率太快导致磁盘压力加大,可以根据系统情况自行限定写入本地磁盘的速率,单位 MB/s。默认10MB/s
- ft_strategy: 选填,该选项设置为 backup_only 的时候,数据不经过本地队列直接发送到下游,设为 always_save 时则所有数据会先发送到本地队列,选 concurrent 的时候会直接并发发送,不经过队列。无论该选项设置什么,失败的数据都会加入到重试队列中异步循环重试。默认选项为 always_save。
- ft_procs :该选项表示从本地队列获取数据点并向下游发送的并发数,如果 ft_strategy 设置为 backup_only,则本项设置无效,只有本地队列有数据时,该项配置才有效,默认并发数为 1.
- ft_memory_channel : 选填,默认为 "false" ,不启用。开启该选项会使用 memory channel 作为 fault_tolerant 中 disk queue 的替代,相当于把 fault_tolerant 作为一个队列使用,使得发送和数据读取解析变为异步,加速整个发送的过程。但是使用 memory channel 数据不落磁盘,会有数据丢失的风险。该功能适合与 ft_procs 连用,利用内存队列,异步后,在发送端多并发加速。
- ft_memory_channel_size: 选填,默认为 "100",单位为 batch,也就是 100 代表 100个待发送的批次,当ft_memory_channel 启用时有效,设置 memory channel 的大小。 注意:该选项设置的大小表达的是队列中可存储的元素个数,并不是占用的内存大小。
补充说明
- 设置 fault_tolerant 为 "true" 时,会维持一个本地队列缓存起需要发送的数据。当数据发送失败的时候会在本地队列进行重试,此时如果发送错误,不会影响 logkit 继续收集日志。
- 设置 fault_tolerant 为 "true" 时,可以保证每次服务重启的时候都从上次的发送offset继续进行发送。在 parse 过程中产生中间结果,需要高容错性发送的时候最适合采用此种模式。
- 设置 fault_tolerant 为 "true" 时,一般希望日志收集程序对机器性能影响较小的时候,建议首先考虑将ft_strategy设置为backup_only,配置这个选项会首先尝试发送数据,发送失败的数据才放到备份队列等待下次重试。如果还希望更小的性能影响,并且数据敏感性不高,也可以不使用 fault_tolerant 模式。
- 当日志发送的速度已经赶不上日志生产速度时,设置fault_tolerant为"true",且 ft_strategy 设置为 concurrent,通过设置 ft_procs 加大并发,ft_procs 设置越大并发度越高,发送越快,对机器性能带来的影响也越大。
- 如果 ft_procs 增加已经不能再加大发送日志速度,那么就需要 加大 ft_write_limit 限制,为 logkit 的队列提升磁盘的读写速度。
- senders 支持多个 sender 配置,但是我们不推荐在 senders 中加入多个 sender,因为一旦某个 sender 发送缓慢,就会导致其他 sender 等待这个 sender 发送成功后再发。简单来说,配置多个 sender 会互相影响。
如何添加更多 Sender (自定义 Sender)?
Sender 的主要作用是将队列中的数据发送至 Sender 支持的各类服务,实现 logkit 的 sender 仅需实现如下接口:
type Sender interface {
Name() string
Send([]Data) error
Close() error
}
其中包括三个函数,Name( ) 标识 Sender 名称,Send( ) 发送数据,Close( ) 关闭一些服务连接等常规操作。
实现一个 Sender 的注意事项
- 多线程发送:多线程发送可以充分利用 CPU 多核的能力,提升发送效率,这一点我们已经设计了 ft sender 作为框架解决了该问题。
- 错误处理与等待:服务端偶尔出现一些异常是很正常的事情,此时就要做好不同错误情况的处理,不会因为某个错误而导致程序出错,另外一方面,一旦发现出错应该让 sender 等待一定时间再发送,设定一个对后端友好的变长错误等待机制也非常重要。一般情况下,可以采用随着连续错误出现递增等待时间的方法,直到一个最顶峰(如10s),就不再增加,当服务端恢复后再取消等待。
- 数据压缩发送:带宽是非常珍贵的资源,通常服务端都会提供 gzip 压缩的数据接收接口,而 sender 利用这些接口,将数据压缩后发送,能节省大量带宽成本。
- 带宽限流:通常情况下数据收集工具只是机器上的一个附属程序,主要资源如带宽还是要预留给主服务,所以限制 sender 的带宽用量也是非常重要的功能,限流的方法就可以采用前面 Channel 一节提到的令牌桶算法。
- 字段填充( UUID/timestamp ):通常情况下收集的数据信息可能不是完备的,需要填充一些信息进去,如全局唯一的 UUID 、代表收集时间的 timestamp 等字段,提供这些字段自动填充的功能,有利于用户对其数据做唯一性、时效性等方面的判断。
- 字段别名:解析后的字段名称中经常会出现一些特殊字符,如"$","@"等符号,如果发送的服务端不支持这些特殊字符,就需要提供一些重命名的功能,将这些字段映射到一个别的名称。
- 字段筛选:未必解析后的所有字段数据都需要发送,这时候提供一个字段筛选的功能,可以方便用户选择去掉一些无用的字段,也可以节省传输的成本。也可以在 Transformer 中也提供类似 discard transformer 的功能,将某个字段去掉。
- 类型转换:类型转换是一个说来简单但是做起来非常繁琐的事情,不只是纯粹的整型转换成浮点型,或者字符串转成整型这么简单,还涉及到你发送到的服务端支持的一些特殊类型,如date时间类型等,更多的类型转换实际上相当于最佳实践,能够做好这些类型转换,就会让用户体验得到极大提升。
- 简单、简单、简单:除了上述这些,剩下的就是尽可能的让用户使用简单。比如假设我们要写一个 mysql sender,mysql 的数据库和表如果不存在,可能数据会发送失败,那就可以考虑提前创建;又比如数据如果有更新了,那么就需要将针对这些更新的字段去更新服务的 Schema 等等。
注册Sender
与Parser类似,实现完毕的Sender注意要注册进SenderRegistry中,如下所示:
func NewSenderRegistry() *SenderRegistry {
ret := &SenderRegistry{
senderTypeMap: map[string]func(conf.MapConf) (Sender, error){},
}
ret.RegisterSender(TypeFile, NewFileSender)
ret.RegisterSender(TypePandora, NewPandoraSender)
ret.RegisterSender(TypeMongodbAccumulate, NewMongodbAccSender)
ret.RegisterSender(TypeInfluxdb, NewInfluxdbSender)
ret.RegisterSender(TypeElastic, NewElasticSender)
ret.RegisterSender(TypeDiscard, NewDiscardSender)
// ret.RegisterSender(TypeMyNewSender, NewMyNewSender)
return ret
}
一个示例的自定义Sender
package samples
import (
"fmt"
"github.com/qiniu/log"
"github.com/qiniu/logkit/conf"
"github.com/qiniu/logkit/sender"
)
// CustomSender 仅作为示例,什么都不做,只是把数据打印出来而已
type CustomSender struct {
name string
prefix string
}
func NewMySender(c conf.MapConf) (sender.Sender, error) {
name, _ := c.GetStringOr("name", "my_sender_name")
prefix, _ := c.GetStringOr("prefix", "")
return &CustomSender{
name: name,
prefix: prefix,
}, nil
}
func (c *CustomSender) Name() string {
return c.name
}
func (c *CustomSender) Send(datas []sender.Data) error {
for _, d := range datas {
var line string
for k, v := range d {
line += fmt.Sprintf("%v=%v ", k, v)
}
log.Info(c.prefix, line)
}
return nil
}
func (c *CustomSender) Close() error {
return nil
}
至此,就全部介绍完了,欢迎加入我们一起贡献 logkit 代码!
成为七牛云 Contributor
- 浏览以上内容;
- 点击填写「 Contributor 登记表」;
- 当 PR 经我们 Review 合并后,贡献者即自动成为我们的 New Contributor;
- 我们的小伙伴会主动联系 New Contributor 并随机将以下一件七牛云限量周边作为见面礼,折跃到你的手中。
- 很高兴认识你!
想了解更多?
- 访问我们的Github站:Pandora、Logkit
- 关注我们的知乎专栏:Pandora
- 也欢迎投稿分享大家在大数据领域的开源经历和实践经验