Adds Advanced Logging to server. Advanced Logging is an optional logging capability that allows customers to send log records to any number of destinations. Supported destinations: - file - syslog (with out without TLS) - raw TCP socket (with out without TLS) Allows developers to specify discrete log levels as well as the standard trace, debug, info, ... panic. Existing code and logging API usage is unchanged. Log records are emitted asynchronously to reduce latency to the caller. Supports hot-reloading of logger config, including adding removing targets. Advanced Logging is configured within config.json via "LogSettings.AdvancedLoggingConfig" which can contain a filespec to another config file, a database DSN, or JSON.
120 строки
2.8 KiB
Go
120 строки
2.8 KiB
Go
package logr
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"runtime"
|
|
"sort"
|
|
)
|
|
|
|
// Formatter turns a LogRec into a formatted string.
|
|
type Formatter interface {
|
|
// Format converts a log record to bytes. If buf is not nil then it will be
|
|
// be filled with the formatted results, otherwise a new buffer will be allocated.
|
|
Format(rec *LogRec, stacktrace bool, buf *bytes.Buffer) (*bytes.Buffer, error)
|
|
}
|
|
|
|
const (
|
|
// DefTimestampFormat is the default time stamp format used by
|
|
// Plain formatter and others.
|
|
DefTimestampFormat = "2006-01-02 15:04:05.000 Z07:00"
|
|
)
|
|
|
|
// DefaultFormatter is the default formatter, outputting only text with
|
|
// no colors and a space delimiter. Use `format.Plain` instead.
|
|
type DefaultFormatter struct {
|
|
}
|
|
|
|
// Format converts a log record to bytes.
|
|
func (p *DefaultFormatter) Format(rec *LogRec, stacktrace bool, buf *bytes.Buffer) (*bytes.Buffer, error) {
|
|
if buf == nil {
|
|
buf = &bytes.Buffer{}
|
|
}
|
|
delim := " "
|
|
timestampFmt := DefTimestampFormat
|
|
|
|
fmt.Fprintf(buf, "%s%s", rec.Time().Format(timestampFmt), delim)
|
|
fmt.Fprintf(buf, "%v%s", rec.Level(), delim)
|
|
fmt.Fprint(buf, rec.Msg(), delim)
|
|
|
|
ctx := rec.Fields()
|
|
if len(ctx) > 0 {
|
|
WriteFields(buf, ctx, " ")
|
|
}
|
|
|
|
if stacktrace {
|
|
frames := rec.StackFrames()
|
|
if len(frames) > 0 {
|
|
buf.WriteString("\n")
|
|
WriteStacktrace(buf, rec.StackFrames())
|
|
}
|
|
}
|
|
buf.WriteString("\n")
|
|
|
|
return buf, nil
|
|
}
|
|
|
|
// WriteFields writes zero or more name value pairs to the io.Writer.
|
|
// The pairs are sorted by key name and output in key=value format
|
|
// with optional separator between fields.
|
|
func WriteFields(w io.Writer, flds Fields, separator string) {
|
|
keys := make([]string, 0, len(flds))
|
|
for k := range flds {
|
|
keys = append(keys, k)
|
|
}
|
|
sort.Strings(keys)
|
|
sep := ""
|
|
for _, key := range keys {
|
|
writeField(w, key, flds[key], sep)
|
|
sep = separator
|
|
}
|
|
}
|
|
|
|
func writeField(w io.Writer, key string, val interface{}, sep string) {
|
|
var template string
|
|
switch v := val.(type) {
|
|
case error:
|
|
val := v.Error()
|
|
if shouldQuote(val) {
|
|
template = "%s%s=%q"
|
|
} else {
|
|
template = "%s%s=%s"
|
|
}
|
|
case string:
|
|
if shouldQuote(v) {
|
|
template = "%s%s=%q"
|
|
} else {
|
|
template = "%s%s=%s"
|
|
}
|
|
default:
|
|
template = "%s%s=%v"
|
|
}
|
|
fmt.Fprintf(w, template, sep, key, val)
|
|
}
|
|
|
|
// shouldQuote returns true if val contains any characters that might be unsafe
|
|
// when injecting log output into an aggregator, viewer or report.
|
|
func shouldQuote(val string) bool {
|
|
for _, c := range val {
|
|
if !((c >= '0' && c <= '9') ||
|
|
(c >= 'a' && c <= 'z') ||
|
|
(c >= 'A' && c <= 'Z')) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// WriteStacktrace formats and outputs a stack trace to an io.Writer.
|
|
func WriteStacktrace(w io.Writer, frames []runtime.Frame) {
|
|
for _, frame := range frames {
|
|
if frame.Function != "" {
|
|
fmt.Fprintf(w, " %s\n", frame.Function)
|
|
}
|
|
if frame.File != "" {
|
|
fmt.Fprintf(w, " %s:%d\n", frame.File, frame.Line)
|
|
}
|
|
}
|
|
}
|