127 lines
		
	
	
		
			3.5 KiB
		
	
	
	
		
			Go
		
	
	
			
		
		
	
	
			127 lines
		
	
	
		
			3.5 KiB
		
	
	
	
		
			Go
		
	
	
| package logrustash
 | |
| 
 | |
| import (
 | |
| 	"io"
 | |
| 	"sync"
 | |
| 
 | |
| 	"github.com/sirupsen/logrus"
 | |
| )
 | |
| 
 | |
| // Hook represents a Logstash hook.
 | |
| // It has two fields: writer to write the entry to Logstash and
 | |
| // formatter to format the entry to a Logstash format before sending.
 | |
| //
 | |
| // To initialize it use the `New` function.
 | |
| //
 | |
| type Hook struct {
 | |
| 	writer    io.Writer
 | |
| 	formatter logrus.Formatter
 | |
| }
 | |
| 
 | |
| // New returns a new logrus.Hook for Logstash.
 | |
| //
 | |
| // To create a new hook that sends logs to `tcp://logstash.corp.io:9999`:
 | |
| //
 | |
| // conn, _ := net.Dial("tcp", "logstash.corp.io:9999")
 | |
| // hook := logrustash.New(conn, logrustash.DefaultFormatter())
 | |
| func New(w io.Writer, f logrus.Formatter) logrus.Hook {
 | |
| 	return Hook{
 | |
| 		writer:    w,
 | |
| 		formatter: f,
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // Fire takes, formats and sends the entry to Logstash.
 | |
| // Hook's formatter is used to format the entry into Logstash format
 | |
| // and Hook's writer is used to write the formatted entry to the Logstash instance.
 | |
| func (h Hook) Fire(e *logrus.Entry) error {
 | |
| 	dataBytes, err := h.formatter.Format(e)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	_, err = h.writer.Write(dataBytes)
 | |
| 	return err
 | |
| }
 | |
| 
 | |
| // Levels returns all logrus levels.
 | |
| func (h Hook) Levels() []logrus.Level {
 | |
| 	return logrus.AllLevels
 | |
| }
 | |
| 
 | |
| // Using a pool to re-use of old entries when formatting Logstash messages.
 | |
| // It is used in the Fire function.
 | |
| var entryPool = sync.Pool{
 | |
| 	New: func() interface{} {
 | |
| 		return &logrus.Entry{}
 | |
| 	},
 | |
| }
 | |
| 
 | |
| // copyEntry copies the entry `e` to a new entry and then adds all the fields in `fields` that are missing in the new entry data.
 | |
| // It uses `entryPool` to re-use allocated entries.
 | |
| func copyEntry(e *logrus.Entry, fields logrus.Fields) *logrus.Entry {
 | |
| 	ne := entryPool.Get().(*logrus.Entry)
 | |
| 	ne.Message = e.Message
 | |
| 	ne.Level = e.Level
 | |
| 	ne.Time = e.Time
 | |
| 	ne.Data = logrus.Fields{}
 | |
| 	for k, v := range fields {
 | |
| 		ne.Data[k] = v
 | |
| 	}
 | |
| 	for k, v := range e.Data {
 | |
| 		ne.Data[k] = v
 | |
| 	}
 | |
| 	return ne
 | |
| }
 | |
| 
 | |
| // releaseEntry puts the given entry back to `entryPool`. It must be called if copyEntry is called.
 | |
| func releaseEntry(e *logrus.Entry) {
 | |
| 	entryPool.Put(e)
 | |
| }
 | |
| 
 | |
| // LogstashFormatter represents a Logstash format.
 | |
| // It has logrus.Formatter which formats the entry and logrus.Fields which
 | |
| // are added to the JSON message if not given in the entry data.
 | |
| //
 | |
| // Note: use the `DefaultFormatter` function to set a default Logstash formatter.
 | |
| type LogstashFormatter struct {
 | |
| 	logrus.Formatter
 | |
| 	logrus.Fields
 | |
| }
 | |
| 
 | |
| var (
 | |
| 	logstashFields   = logrus.Fields{"@version": "1", "type": "log"}
 | |
| 	logstashFieldMap = logrus.FieldMap{
 | |
| 		logrus.FieldKeyTime: "@timestamp",
 | |
| 		logrus.FieldKeyMsg:  "message",
 | |
| 	}
 | |
| )
 | |
| 
 | |
| // DefaultFormatter returns a default Logstash formatter:
 | |
| // A JSON format with "@version" set to "1" (unless set differently in `fields`,
 | |
| // "type" to "log" (unless set differently in `fields`),
 | |
| // "@timestamp" to the log time and "message" to the log message.
 | |
| //
 | |
| // Note: to set a different configuration use the `LogstashFormatter` structure.
 | |
| func DefaultFormatter(fields logrus.Fields) logrus.Formatter {
 | |
| 	for k, v := range logstashFields {
 | |
| 		if _, ok := fields[k]; !ok {
 | |
| 			fields[k] = v
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return LogstashFormatter{
 | |
| 		Formatter: &logrus.JSONFormatter{FieldMap: logstashFieldMap},
 | |
| 		Fields:    fields,
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // Format formats an entry to a Logstash format according to the given Formatter and Fields.
 | |
| //
 | |
| // Note: the given entry is copied and not changed during the formatting process.
 | |
| func (f LogstashFormatter) Format(e *logrus.Entry) ([]byte, error) {
 | |
| 	ne := copyEntry(e, f.Fields)
 | |
| 	dataBytes, err := f.Formatter.Format(ne)
 | |
| 	releaseEntry(ne)
 | |
| 	return dataBytes, err
 | |
| }
 |