matterbridge/vendor/github.com/mattermost/logr/v2/formatters/gelf.go
2021-10-16 23:23:24 +02:00

153 lines
3.4 KiB
Go

package formatters
import (
"bytes"
"fmt"
"net"
"os"
"strings"
"github.com/francoispqt/gojay"
"github.com/mattermost/logr/v2"
)
const (
GelfVersion = "1.1"
GelfVersionKey = "version"
GelfHostKey = "host"
GelfShortKey = "short_message"
GelfFullKey = "full_message"
GelfTimestampKey = "timestamp"
GelfLevelKey = "level"
)
// Gelf formats log records as GELF rcords (https://docs.graylog.org/en/4.0/pages/gelf.html).
type Gelf struct {
// Hostname allows a custom hostname, otherwise os.Hostname is used
Hostname string `json:"hostname"`
// EnableCaller enables output of the file and line number that emitted a log record.
EnableCaller bool `json:"enable_caller"`
// FieldSorter allows custom sorting for the context fields.
FieldSorter func(fields []logr.Field) []logr.Field `json:"-"`
}
func (g *Gelf) CheckValid() error {
return nil
}
// IsStacktraceNeeded returns true if a stacktrace is needed so we can output the `Caller` field.
func (g *Gelf) IsStacktraceNeeded() bool {
return g.EnableCaller
}
// Format converts a log record to bytes in GELF format.
func (g *Gelf) Format(rec *logr.LogRec, level logr.Level, buf *bytes.Buffer) (*bytes.Buffer, error) {
if buf == nil {
buf = &bytes.Buffer{}
}
enc := gojay.BorrowEncoder(buf)
defer func() {
enc.Release()
}()
gr := gelfRecord{
LogRec: rec,
Gelf: g,
level: level,
sorter: g.FieldSorter,
}
err := enc.EncodeObject(gr)
if err != nil {
return nil, err
}
buf.WriteByte(0)
return buf, nil
}
type gelfRecord struct {
*logr.LogRec
*Gelf
level logr.Level
sorter func(fields []logr.Field) []logr.Field
}
// MarshalJSONObject encodes the LogRec as JSON.
func (gr gelfRecord) MarshalJSONObject(enc *gojay.Encoder) {
enc.AddStringKey(GelfVersionKey, GelfVersion)
enc.AddStringKey(GelfHostKey, gr.getHostname())
enc.AddStringKey(GelfShortKey, gr.Msg())
if gr.level.Stacktrace {
frames := gr.StackFrames()
if len(frames) != 0 {
var sbuf strings.Builder
for _, frame := range frames {
fmt.Fprintf(&sbuf, "%s\n %s:%d\n", frame.Function, frame.File, frame.Line)
}
enc.AddStringKey(GelfFullKey, sbuf.String())
}
}
secs := float64(gr.Time().UTC().Unix())
millis := float64(gr.Time().Nanosecond() / 1000000)
ts := secs + (millis / 1000)
enc.AddFloat64Key(GelfTimestampKey, ts)
enc.AddUint32Key(GelfLevelKey, uint32(gr.level.ID))
var fields []logr.Field
if gr.EnableCaller {
caller := logr.Field{
Key: "_caller",
Type: logr.StringType,
String: gr.LogRec.Caller(),
}
fields = append(fields, caller)
}
fields = append(fields, gr.Fields()...)
if gr.sorter != nil {
fields = gr.sorter(fields)
}
if len(fields) > 0 {
for _, field := range fields {
if !strings.HasPrefix("_", field.Key) {
field.Key = "_" + field.Key
}
if err := encodeField(enc, field); err != nil {
enc.AddStringKey(field.Key, fmt.Sprintf("<error encoding field: %v>", err))
}
}
}
}
// IsNil returns true if the gelf record pointer is nil.
func (gr gelfRecord) IsNil() bool {
return gr.LogRec == nil
}
func (g *Gelf) getHostname() string {
if g.Hostname != "" {
return g.Hostname
}
h, err := os.Hostname()
if err == nil {
return h
}
// get the egress IP by fake dialing any address. UDP ensures no dial.
conn, err := net.Dial("udp", "8.8.8.8:80")
if err != nil {
return "unknown"
}
defer conn.Close()
local := conn.LocalAddr().(*net.UDPAddr)
return local.IP.String()
}