formatter.go (98 lines of code) (raw):

// Licensed to Elasticsearch B.V. under one or more contributor // license agreements. See the NOTICE file distributed with // this work for additional information regarding copyright // ownership. Elasticsearch B.V. licenses this file to you 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 ecslogrus import ( "runtime" "strconv" "strings" "github.com/sirupsen/logrus" ) const ( // ecsVersion holds the version of ECS with which the formatter is compatible. ecsVersion = "1.6.0" ) var ( ecsFieldMap = logrus.FieldMap{ logrus.FieldKeyTime: "@timestamp", logrus.FieldKeyMsg: "message", logrus.FieldKeyLevel: "log.level", } ) // Formatter is a logrus.Formatter, formatting log entries as ECS-compliant JSON. type Formatter struct { // DisableHTMLEscape allows disabling html escaping in output DisableHTMLEscape bool // DataKey allows users to put all the log entry parameters into a // nested dictionary at a given key. // // DataKey is ignored for well-defined fields, such as "error", // which will instead be stored under the appropriate ECS fields. DataKey string // CallerPrettyfier can be set by the user to modify the content // of the function and file keys in the json data when ReportCaller is // activated. If any of the returned value is the empty string the // corresponding key will be removed from json fields. CallerPrettyfier func(*runtime.Frame) (function string, file string) // PrettyPrint will indent all json logs PrettyPrint bool } // Format formats e as ECS-compliant JSON. func (f *Formatter) Format(e *logrus.Entry) ([]byte, error) { datahint := len(e.Data) if f.DataKey != "" { datahint = 2 } data := make(logrus.Fields, datahint) if len(e.Data) > 0 { extraData := data if f.DataKey != "" { extraData = make(logrus.Fields, len(e.Data)) } for k, v := range e.Data { switch k { case logrus.ErrorKey: err, ok := v.(error) if ok { data["error"] = errorObject{ Message: err.Error(), } break } fallthrough // error has unexpected type default: extraData[k] = v } } if f.DataKey != "" && len(extraData) > 0 { data[f.DataKey] = extraData } } if e.HasCaller() { // Logrus has a single configurable field (logrus.FieldKeyFile) // for storing a combined filename and line number, but we want // to split them apart into two fields. Remove the event's Caller // field, and encode the ECS fields explicitly. var funcVal, fileVal string var lineVal int if f.CallerPrettyfier != nil { var fileLineVal string funcVal, fileLineVal = f.CallerPrettyfier(e.Caller) if sep := strings.IndexRune(fileLineVal, ':'); sep != -1 { fileVal = fileLineVal[:sep] lineVal, _ = strconv.Atoi(fileLineVal[sep+1:]) } else { fileVal = fileLineVal lineVal = 0 } } else { funcVal = e.Caller.Function fileVal = e.Caller.File lineVal = e.Caller.Line } e.Caller = nil if funcVal != "" { data["log.origin.function"] = funcVal } if fileVal != "" { data["log.origin.file.name"] = fileVal } if lineVal > 0 { data["log.origin.file.line"] = lineVal } } data["ecs.version"] = ecsVersion ecopy := *e ecopy.Data = data e = &ecopy jf := logrus.JSONFormatter{ TimestampFormat: "2006-01-02T15:04:05.000Z0700", DisableHTMLEscape: f.DisableHTMLEscape, FieldMap: ecsFieldMap, CallerPrettyfier: f.CallerPrettyfier, PrettyPrint: f.PrettyPrint, } return jf.Format(e) } type errorObject struct { Message string `json:"message,omitempty"` }