成為七牛雲 Contributor -如何貢獻 logkit 程式碼

weixin_33890499發表於2017-11-27
311249-619551fdd10e50b1
image

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"
   },
  1. name parser 的 name,用來標識
  2. type parser 的型別
  3. 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 件事情。

  1. 確定最終要解析出的 schema ,即欄位 (field 或者 key) 和型別 (type)。像 json 這種資料天生就含有 schema ,無需使用者定義,其他型別的解析器可能需要使用者在配置檔案中寫明 schema 是怎麼樣的。
  2. 定義規則將傳入的字串按照 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 五種服務。

  1. Pandora Sender: 傳送到 Pandora (七牛大資料處理平臺)。
  2. ElasticSearch Sender: 傳送到 ElasticSearch。
  3. File Sender: 傳送到本地檔案。
  4. InfluxDB Sender: 傳送到 InfluxDB。
  5. 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"
}]
  1. name: 是 sender 的標識
  2. sender_type: sender 型別,支援file, mongodb_acc, pandora, influxdb
  3. fault_tolerant: 是否用非同步容錯方式進行傳送,預設為false。
  4. ft_save_log_path: 當 fault_tolerant 為 true 時候必填。該路徑必須為資料夾,該資料夾會作為本地磁碟佇列,存放資料,進行非同步容錯傳送。
  5. ft_sync_every:當 fault_tolerant 為 true 時候必填。多少次傳送資料會記錄一次本地磁碟佇列的 offset 。
  6. ft_write_limit:選填,為了避免速率太快導致磁碟壓力加大,可以根據系統情況自行限定寫入本地磁碟的速率,單位 MB/s。預設10MB/s
  7. ft_strategy: 選填,該選項設定為 backup_only 的時候,資料不經過本地佇列直接傳送到下游,設為 always_save 時則所有資料會先傳送到本地佇列,選 concurrent 的時候會直接併發傳送,不經過佇列。無論該選項設定什麼,失敗的資料都會加入到重試佇列中非同步迴圈重試。預設選項為 always_save。
  8. ft_procs :該選項表示從本地佇列獲取資料點並向下遊傳送的併發數,如果 ft_strategy 設定為 backup_only,則本項設定無效,只有本地佇列有資料時,該項配置才有效,預設併發數為 1.
  9. ft_memory_channel : 選填,預設為 "false" ,不啟用。開啟該選項會使用 memory channel 作為 fault_tolerant 中 disk queue 的替代,相當於把 fault_tolerant 作為一個佇列使用,使得傳送和資料讀取解析變為非同步,加速整個傳送的過程。但是使用 memory channel 資料不落磁碟,會有資料丟失的風險。該功能適合與 ft_procs 連用,利用記憶體佇列,非同步後,在傳送端多併發加速。
  10. 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 的注意事項

  1. 多執行緒傳送:多執行緒傳送可以充分利用 CPU 多核的能力,提升傳送效率,這一點我們已經設計了 ft sender 作為框架解決了該問題。
  2. 錯誤處理與等待:服務端偶爾出現一些異常是很正常的事情,此時就要做好不同錯誤情況的處理,不會因為某個錯誤而導致程式出錯,另外一方面,一旦發現出錯應該讓 sender 等待一定時間再傳送,設定一個對後端友好的變長錯誤等待機制也非常重要。一般情況下,可以採用隨著連續錯誤出現遞增等待時間的方法,直到一個最頂峰(如10s),就不再增加,當服務端恢復後再取消等待。
  3. 資料壓縮傳送:頻寬是非常珍貴的資源,通常服務端都會提供 gzip 壓縮的資料接收介面,而 sender 利用這些介面,將資料壓縮後傳送,能節省大量頻寬成本。
  4. 頻寬限流:通常情況下資料收集工具只是機器上的一個附屬程式,主要資源如頻寬還是要預留給主服務,所以限制 sender 的頻寬用量也是非常重要的功能,限流的方法就可以採用前面 Channel 一節提到的令牌桶演算法。
  5. 欄位填充( UUID/timestamp ):通常情況下收集的資料資訊可能不是完備的,需要填充一些資訊進去,如全域性唯一的 UUID 、代表收集時間的 timestamp 等欄位,提供這些欄位自動填充的功能,有利於使用者對其資料做唯一性、時效性等方面的判斷。
  6. 欄位別名:解析後的欄位名稱中經常會出現一些特殊字元,如"$","@"等符號,如果傳送的服務端不支援這些特殊字元,就需要提供一些重新命名的功能,將這些欄位對映到一個別的名稱。
  7. 欄位篩選:未必解析後的所有欄位資料都需要傳送,這時候提供一個欄位篩選的功能,可以方便使用者選擇去掉一些無用的欄位,也可以節省傳輸的成本。也可以在 Transformer 中也提供類似 discard transformer 的功能,將某個欄位去掉。
  8. 型別轉換:型別轉換是一個說來簡單但是做起來非常繁瑣的事情,不只是純粹的整型轉換成浮點型,或者字串轉成整型這麼簡單,還涉及到你傳送到的服務端支援的一些特殊型別,如date時間型別等,更多的型別轉換實際上相當於最佳實踐,能夠做好這些型別轉換,就會讓使用者體驗得到極大提升。
  9. 簡單、簡單、簡單:除了上述這些,剩下的就是儘可能的讓使用者使用簡單。比如假設我們要寫一個 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

  1. 瀏覽以上內容;
  2. 點選填寫「 Contributor 登記表」
  3. 當 PR 經我們 Review 合併後,貢獻者即自動成為我們的 New Contributor;
  4. 我們的小夥伴會主動聯絡 New Contributor 並隨機將以下一件七牛雲限量周邊作為見面禮,折躍到你的手中。
  5. 很高興認識你!
311249-6fcf27423fac09f0
image

311249-aeb55ed7a4587f21
image

311249-be5d8224b67f6138
image

想了解更多?

  • 訪問我們的Github站:PandoraLogkit
  • 關注我們的知乎專欄:Pandora
  • 也歡迎投稿分享大家在大資料領域的開源經歷和實踐經驗

相關文章