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
 | 
						|
}
 |