// Copyright 2017 Unknwon // // Licensed under the Apache License, Version 2.0 (the "License"): you may // not use this file except in compliance with the License. You may obtain // a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the // License for the specific language governing permissions and limitations // under the License. package clog import ( "bytes" "errors" "fmt" "io/ioutil" "net/http" ) const ( SLACK = "slack" _SLACK_ATTACHMENT = `{ "attachments": [ { "text": "%s", "color": "%s" } ] }` ) var slackColors = []string{ "", // Trace "#3aa3e3", // Info "warning", // Warn "danger", // Error "#ff0200", // Fatal } type SlackConfig struct { // Minimum level of messages to be processed. Level LEVEL // Buffer size defines how many messages can be queued before hangs. BufferSize int64 // Slack webhook URL. URL string } type slack struct { Adapter url string } func newSlack() Logger { return &slack{ Adapter: Adapter{ quitChan: make(chan struct{}), }, } } func (s *slack) Level() LEVEL { return s.level } func (s *slack) Init(v interface{}) error { cfg, ok := v.(SlackConfig) if !ok { return ErrConfigObject{"SlackConfig", v} } if !isValidLevel(cfg.Level) { return ErrInvalidLevel{} } s.level = cfg.Level if len(cfg.URL) == 0 { return errors.New("URL cannot be empty") } s.url = cfg.URL s.msgChan = make(chan *Message, cfg.BufferSize) return nil } func (s *slack) ExchangeChans(errorChan chan<- error) chan *Message { s.errorChan = errorChan return s.msgChan } func buildSlackAttachment(msg *Message) string { return fmt.Sprintf(_SLACK_ATTACHMENT, msg.Body, slackColors[msg.Level]) } func (s *slack) write(msg *Message) { attachment := buildSlackAttachment(msg) resp, err := http.Post(s.url, "application/json", bytes.NewReader([]byte(attachment))) if err != nil { s.errorChan <- fmt.Errorf("slack: %v", err) } defer resp.Body.Close() if resp.StatusCode/100 != 2 { data, _ := ioutil.ReadAll(resp.Body) s.errorChan <- fmt.Errorf("slack: %s", data) } } func (s *slack) Start() { LOOP: for { select { case msg := <-s.msgChan: s.write(msg) case <-s.quitChan: break LOOP } } for { if len(s.msgChan) == 0 { break } s.write(<-s.msgChan) } s.quitChan <- struct{}{} // Notify the cleanup is done. } func (s *slack) Destroy() { s.quitChan <- struct{}{} <-s.quitChan close(s.msgChan) close(s.quitChan) } func init() { Register(SLACK, newSlack) }