117 lines
2.4 KiB
Go
Executable File
117 lines
2.4 KiB
Go
Executable File
package ripsawlogger
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"time"
|
|
)
|
|
|
|
var (
|
|
// A simple syntax that displays the timestamp and the message field. Can
|
|
// be useful when the exact format of the log entry is not known, or when
|
|
// entries of various formats are recorded.
|
|
MessageFormat = HumanSyntax{
|
|
String: "%s: %s",
|
|
Tokens: []string{
|
|
"_timestamp",
|
|
"message",
|
|
},
|
|
}
|
|
|
|
// This represents Common Log Format, similar to how HTTP and nginx record
|
|
// access logs. Defined here for convenience.
|
|
CommonLogFormat = HumanSyntax{
|
|
String: "%s %s %s [%s] \"%s %s HTTP/%s\" %d %d",
|
|
Tokens: []string{
|
|
"host",
|
|
"rfc931",
|
|
"username",
|
|
"_timestamp",
|
|
"method",
|
|
"url",
|
|
"httpVersion",
|
|
"statusCode",
|
|
"bytes",
|
|
},
|
|
}
|
|
)
|
|
|
|
type HumanLoggerLevel struct {
|
|
Color string
|
|
Syntax HumanSyntax
|
|
}
|
|
|
|
type HumanSyntax struct {
|
|
String string
|
|
Tokens []string
|
|
}
|
|
|
|
func (syntax HumanSyntax) Format(entry LogEntry) string {
|
|
|
|
// if we have no syntax string, just try to pull a message
|
|
if syntax.String == "" {
|
|
if msg, ok := entry.Contents["message"]; ok {
|
|
stringified, _ := json.Marshal(msg)
|
|
return string(stringified)
|
|
}
|
|
|
|
// just format the JSON I guess
|
|
stringified, _ := json.Marshal(entry.Contents)
|
|
return string(stringified)
|
|
}
|
|
|
|
// gather tokens
|
|
values := make([]interface{}, 0, len(syntax.Tokens))
|
|
for _, token := range syntax.Tokens {
|
|
|
|
var resolvedToken interface{} = "-"
|
|
if value, err := syntax.extract(token, entry); err == nil {
|
|
resolvedToken = value
|
|
}
|
|
|
|
values = append(values, resolvedToken)
|
|
}
|
|
|
|
return fmt.Sprintf(syntax.String, values...)
|
|
|
|
}
|
|
|
|
func (syntax HumanSyntax) extract(path string, entry LogEntry) (interface{}, error) {
|
|
|
|
switch path {
|
|
case "_timestamp":
|
|
if entry.Time.IsZero() {
|
|
return time.Now().Format(time.RFC3339Nano), nil
|
|
} else {
|
|
return entry.Time.Format(time.RFC3339Nano), nil
|
|
}
|
|
case "_level":
|
|
return entry.Level, nil
|
|
default:
|
|
if value, ok := entry.Contents[path]; ok {
|
|
return value, nil
|
|
}
|
|
}
|
|
|
|
return "", errors.New("Token not found")
|
|
}
|
|
|
|
type humanLogger map[string]HumanLoggerLevel
|
|
|
|
// Creates a logger that formats log data in a human-readable way, and can be configured
|
|
// on a per-log-level basis.
|
|
func NewHumanLogger(cfg map[string]HumanLoggerLevel) Logger {
|
|
return humanLogger(cfg)
|
|
}
|
|
|
|
func (sl humanLogger) Log(entry LogEntry) {
|
|
if cfg, ok := sl[entry.Level]; ok {
|
|
fmt.Printf("[%s] %s\n", entry.Level, cfg.Syntax.Format(entry))
|
|
}
|
|
}
|
|
|
|
func (sl humanLogger) Flush() {
|
|
// ¯\_(ツ)_/¯
|
|
}
|