Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

enhance(logging): add flag to allow logging in ecs format, map custom fields to ecs fields #971

Merged
merged 13 commits into from
Sep 27, 2023
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions cmd/vela-server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@ func main() {
Usage: "set log level - options: (trace|debug|info|warn|error|fatal|panic)",
Value: "info",
},
&cli.StringFlag{
EnvVars: []string{"VELA_LOG_FORMATTER", "LOG_FORMATTER"},
Name: "log-formatter",
Usage: "set log formatter - options: (json|ecs)",
Value: "json",
},
&cli.StringFlag{
EnvVars: []string{"VELA_ADDR", "VELA_HOST"},
Name: "server-addr",
Expand Down Expand Up @@ -247,9 +253,6 @@ func main() {
// Add Source Flags
app.Flags = append(app.Flags, scm.Flags...)

// set logrus to log in JSON format
logrus.SetFormatter(&logrus.JSONFormatter{})

if err = app.Run(os.Args); err != nil {
logrus.Fatal(err)
}
Expand Down
12 changes: 12 additions & 0 deletions cmd/vela-server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,18 @@ import (
)

func server(c *cli.Context) error {
// set log formatter
switch c.String("log-formatter") {
case "json":
// set logrus to log in JSON format
logrus.SetFormatter(&logrus.JSONFormatter{})
case "ecs":
// set logrus to log in Elasticsearch Common Schema (ecs) format
logrus.SetFormatter(&middleware.Formatter{
DataKey: "labels.vela",
})
}

// validate all input
err := validate(c)
if err != nil {
Expand Down
1 change: 1 addition & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ services:
VELA_ADDR: 'http://localhost:8080'
VELA_WEBUI_ADDR: 'http://localhost:8888'
VELA_LOG_LEVEL: trace
VELA_LOG_FORMATTER: json
KellyMerrick marked this conversation as resolved.
Show resolved Hide resolved
# comment the line below to use registration flow
VELA_SECRET: 'zB7mrKDTZqNeNTD8z47yG4DHywspAh'
QUEUE_PUBLIC_KEY: 'DXsJkoTSkHlG26d75LyHJG+KQsXPr8VKPpmH/78zmko='
Expand Down
78 changes: 77 additions & 1 deletion router/middleware/logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,33 @@ import (
"github.com/sirupsen/logrus"
)

// This file, in part, reproduces portions of
// https://github.com/elastic/ecs-logging-go-logrus/blob/v1.0.0/formatter.go
// to handle our custom fields in Format().

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",
}
)
KellyMerrick marked this conversation as resolved.
Show resolved Hide resolved

// Formatter holds ECS parameter information for logging.
type Formatter struct {
KellyMerrick marked this conversation as resolved.
Show resolved Hide resolved
// 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
}

// Logger returns a gin.HandlerFunc (middleware) that logs requests using logrus.
//
// Requests with errors are logged using logrus.Error().
Expand Down Expand Up @@ -98,7 +125,7 @@ func Logger(logger *logrus.Logger, timeFormat string) gin.HandlerFunc {
// Append error field if this is an erroneous request.
entry.Error(c.Errors.String())
} else {
entry.Info()
entry.Infof("%v %v %v %s %s", fields["status"], fields["latency"], fields["ip"], fields["method"], fields["path"])
}
}
}
Expand All @@ -114,3 +141,52 @@ func sanitize(body interface{}) interface{} {

return body
}

// Format formats e as ECS-compliant JSON,
KellyMerrick marked this conversation as resolved.
Show resolved Hide resolved
// mapping our custom fields to ECS fields.
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 "ip":
data["client.ip"] = v
case "latency":
data["event.duration"] = v
case "method":
data["http.request.method"] = v
case "path":
data["url.path"] = v
case "status":
data["http.response.status_code"] = v
case "user-agent":
data["user_agent.name"] = v
case "version":
data["user_agent.version"] = v
default:
extraData[k] = v
}
}
if f.DataKey != "" && len(extraData) > 0 {
data[f.DataKey] = extraData
}
}
data["ecs.version"] = ecsVersion
ecopy := *e
ecopy.Data = data
e = &ecopy

jf := logrus.JSONFormatter{
TimestampFormat: "2006-01-02T15:04:05.000Z0700",
FieldMap: ecsFieldMap,
}
return jf.Format(e)
}
64 changes: 61 additions & 3 deletions router/middleware/logger_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,6 @@ func TestMiddleware_Logger(t *testing.T) {

payload, _ := json.Marshal(`{"foo": "bar"}`)
wantLevel := logrus.InfoLevel
wantMessage := ""

logger, hook := test.NewNullLogger()
defer hook.Reset()
Expand Down Expand Up @@ -106,8 +105,16 @@ func TestMiddleware_Logger(t *testing.T) {
t.Errorf("Logger Level is %v, want %v", gotLevel, wantLevel)
}

if !reflect.DeepEqual(gotMessage, wantMessage) {
t.Errorf("Logger Message is %v, want %v", gotMessage, wantMessage)
if gotMessage == "" {
t.Errorf("Logger Message is %v, want non-empty string", gotMessage)
}

if strings.Contains(gotMessage, "GET") {
t.Errorf("Logger Message is %v, want message to contain GET", gotMessage)
}

if !strings.Contains(gotMessage, "POST") {
t.Errorf("Logger Message is %v, message shouldn't contain POST", gotMessage)
}
}

Expand Down Expand Up @@ -217,3 +224,54 @@ func TestMiddleware_Logger_Sanitize(t *testing.T) {
}
}
}

func TestMiddleware_Format(t *testing.T) {

wantLabels := "labels.vela"

// setup data, fields, and logger
formatter := &Formatter{
DataKey: wantLabels,
}

fields := logrus.Fields{
"ip": "123.4.5.6",
"method": http.MethodGet,
"path": "/foobar",
"latency": 0,
"status": http.StatusOK,
"user-agent": "foobar",
"version": "v1.0.0",
"org": "foo",
}

logger := logrus.NewEntry(logrus.StandardLogger())
entry := logger.WithFields(fields)

got, err := formatter.Format(entry)

fmt.Println("got:", string(got))
// run test
gotLabels := string(formatter.DataKey)

if err != nil {
t.Errorf("Format returned err: %v", err)
}

if got == nil {
t.Errorf("Format returned nothing, want a log")
}

if !reflect.DeepEqual(gotLabels, wantLabels) {
t.Errorf("Logger returned %v, want %v", gotLabels, wantLabels)
}

if !strings.Contains(string(got), "GET") {
t.Errorf("Format returned %v, want to contain GET", string(got))
}

if !strings.Contains(string(got), "/foobar") {
t.Errorf("Format returned %v, want to contain GET", string(got))
}

}