distribution/vendor/github.com/bshuster-repo/logrus-logstash-hook/hook.go
olegburov 907e7be545 Bump Logstash hook for logrus to v1.0.0.
Signed-off-by: olegburov <oleg.burov@outlook.com>
2021-02-11 21:51:13 -08:00

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
}