1、插件接口实现:
注册插件实例
func init() { pipeline.RegisterPlugin("HttpOutput", func() interface{} { return new(HttpOutput) }) }
内部数据初始化
func (o *HttpOutput) ConfigStruct() interface{} { return &HttpOutputConfig{ HttpTimeout: 0, Headers: make(http.Header), Method: "POST", } } func (o *HttpOutput) Init(config interface{}) (err error) { o.HttpOutputConfig = config.(*HttpOutputConfig) if o.url, err = url.Parse(o.Address); err != nil { return fmt.Errorf("Can't parse URL '%s': %s", o.Address, err.Error()) } if o.url.Scheme != "http" && o.url.Scheme != "https" { return errors.New("`address` must contain an absolute http or https URL.") } o.Method = strings.ToUpper(o.Method) if o.Method != "POST" && o.Method != "GET" && o.Method != "PUT" { return errors.New("HTTP Method must be POST, GET, or PUT.") } if o.Method != "GET" { o.sendBody = true } o.client = new(http.Client) if o.HttpTimeout > 0 { o.client.Timeout = time.Duration(o.HttpTimeout) * time.Millisecond } if o.Username != "" || o.Password != "" { o.useBasicAuth = true } if o.url.Scheme == "https" { transport := &http.Transport{} if transport.TLSClientConfig, err = tcp.CreateGoTlsConfig(&o.Tls); err != nil { return fmt.Errorf("TLS init error: %s", err.Error()) } o.client.Transport = transport } return }
实例运行
func (o *HttpOutput) Run(or pipeline.OutputRunner, h pipeline.PluginHelper) (err error) { if or.Encoder() == nil { return errors.New("Encoder must be specified.") } var ( e error outBytes []byte ) inChan := or.InChan() for pack := range inChan { // tob, _ := json.Marshal(pack.Message) // fmt.Println(string(tob)) outBytes, e = or.Encode(pack) pack.Recycle() if e != nil { or.LogError(e) continue } if outBytes == nil { continue } if e = o.request(or, outBytes); e != nil { or.LogError(e) } } return }
一般实例Run接口中的实现基本类似,获取or.InChan,循环读取,调用Encode,回收pack,然后将encode的数据发送到输出源;
2、以Mongodb作为输出源为例,代码如下:
package mongo import ( "encoding/json" "errors" "fmt" "github.com/mozilla-services/heka/pipeline" "labix.org/v2/mgo" "log" "runtime" "runtime/debug" "strings" "time" ) type MongoOutput struct { *MongoOutputConfig mgoSession *mgo.Session logMsgChan chan *logMessage } type ServerId map[string]string type MongoOutputConfig struct { Address []string LogMsgChSize int MgoWriterCount int SrvIds ServerId Username string `toml:"username"` Password string `toml:"password"` } type logMessage struct { ServerId string `json:"Hostname"` ClientIp string `json:"remote_addr"` Host string `json:"host"` Cookie string `json:"http_cookie"` Referer string `json:"http_referer"` URI string `json:"request_uri"` Timestamp int64 `json:"timestamp"` UserAgent string `json:"http_user_agent"` } //data transferred from agent. type mgoMessage struct { Url string `json:"url"` UA string `json:"ua"` Referer string `json:"referer"` Cookie string `json:"cookie"` Ip string `json:"ip"` TimeStamp int64 `json:"timestamp"` } func (o *MongoOutput) ConfigStruct() interface{} { return &MongoOutputConfig{ Address: []string{"127.0.0.1:27017"}, LogMsgChSize: 10000, MgoWriterCount: runtime.NumCPU(), SrvIds: make(ServerId), } } func (o *MongoOutput) Init(config interface{}) (err error) { o.MongoOutputConfig = config.(*MongoOutputConfig) //todo: check address validity // if o.url, err = url.Parse(o.Address); err != nil { // return fmt.Errorf("Can't parse URL '%s': %s", o.Address, err.Error()) // } // o.logMsgChan = make(chan *logMessage, o.LogMsgChSize) mgoInfo := mgo.DialInfo{Addrs: o.Address, Timeout: time.Second} o.mgoSession, err = mgo.DialWithInfo(&mgoInfo) if err != nil { log.Printf("initialize MongoOutput failed, %s", err.Error()) return err } return } func WriteDataToMgo(mo *MongoOutput) { defer func() { if err, ok := recover().(error); ok { log.Println("WARN: panic in %v", err) log.Println(string(debug.Stack())) } }() //srvlog.Printf("WriteDataToMgo key:%s", key) sessionCopy := mo.mgoSession.Copy() defer sessionCopy.Close() db := sessionCopy.DB("admin") var msg mgoMessage var err error for logMsg := range mo.logMsgChan { if logMsg.ClientIp == "" || logMsg.UserAgent == "" { continue } keyName := mo.SrvIds[logMsg.ServerId] //fmt.Printf("%s, %s", logMsg.ServerId, keyName) if keyName == "" { log.Printf("no invalid mongo key for %s", logMsg.ServerId) continue } year, month, day := time.Now().Date() collName := fmt.Sprintf("%s_%d_%d_%d", keyName, year, month, day) coll := db.C(collName) msg.Url = fmt.Sprintf("http://%s%s", logMsg.Host, logMsg.URI) if logMsg.UserAgent != "-" { msg.UA = logMsg.UserAgent } else { msg.UA = "" } if logMsg.Referer != "-" { msg.Referer = logMsg.Referer } else { msg.Referer = "" } if logMsg.Cookie != "-" { msg.Cookie = logMsg.Cookie } else { msg.Cookie = "" } msg.Ip = logMsg.ClientIp msg.TimeStamp = logMsg.Timestamp if err != nil { log.Println(err.Error()) continue } //fmt.Println("MongoOutput-119-%v", msg) err = coll.Insert(msg) if err != nil { log.Println(err.Error()) continue } } } func (o *MongoOutput) Run(or pipeline.OutputRunner, h pipeline.PluginHelper) (err error) { if or.Encoder() == nil { return errors.New("Encoder must be specified.") } var ( e error outBytes []byte ) inChan := or.InChan() for i := 0; i < o.MgoWriterCount; i++ { go WriteDataToMgo(o) } for pack := range inChan { outBytes, e = or.Encode(pack) pack.Recycle() if e != nil { or.LogError(e) continue } if outBytes == nil { continue } subStrs := strings.Split(string(outBytes), "\n") //fmt.Printf("MongoOutput:len of subStrs:%d", len(subStrs)) if len(subStrs) == 3 { logMsg := &logMessage{} //fmt.Printf("MongoOutput:%s\n", subStrs[1]) e = json.Unmarshal([]byte(subStrs[1]), logMsg) if e != nil { log.Printf("MongoOutput-%s", err.Error()) continue } //fmt.Printf("MongoOutput-165-%v", logMsg) o.logMsgChan <- logMsg } //fmt.Println("MongoOutput:", string(outBytes)) } return } func init() { pipeline.RegisterPlugin("MongoOutput", func() interface{} { return new(MongoOutput) }) }
以上代码已经运行在实际环境中,核心逻辑是将pack Encode后,解析返回数据,转换为Mongo存储的格式,然后将数据传递到mo.logMsgChan,Mongo写routine持续从该channel缓冲中读取数据,将数据写入mongodb中;
3、编译插件到Heka系统,介绍两种方式:
1)参考官网: http://hekad.readthedocs.org/en/v0.8.2/installing.html#build-include-externals
2)在调用Heka系统的build.sh后,会产生一个build目录,cd build/heka目录,发现目录结构符合标准的go工程目录,在该目录下建立install文件,install文件如下:
#!/usr/bin/env bash if [ ! -f install ]; then echo 'install must be run within its container folder' 1>&2 exit 1 fi CURDIR=`pwd` OLDGOPATH="$GOPATH" export GOPATH="$OLDGOPATH:$CURDIR" gofmt -w src #go install hekad go install github.com/mozilla-services/heka/cmd/hekad #go install github.com/mozilla-services/heka/cmd/heka-flood #go install github.com/mozilla-services/heka/cmd/heka-inject #go install github.com/mozilla-services/heka/cmd/heka-logstreamer #go install github.com/mozilla-services/heka/cmd/heka-cat #go install github.com/mozilla-services/heka/cmd/heka-sbmgr #go install github.com/mozilla-services/heka/cmd/heka-sbmgrload #go test github.com/mozilla-services/heka/plugins/mongo export GOPATH="$OLDGOPATH" echo 'finished'
每次在添加完插件后,需要在hekad/main.go文件import中添加一行代码:
_ "github.com/mozilla-services/heka/plugins/mongo"
ok之后,编译./install即可!
以上如有疑问,请email到[email protected]