Initial rewrite of revel/cmd to provide vendor support and enhanced CLI options

This commit is contained in:
NotZippy
2018-09-13 13:21:10 -07:00
parent d2ac018544
commit d0baaeb9e9
40 changed files with 4435 additions and 1125 deletions

11
logger/doc.go Normal file
View File

@@ -0,0 +1,11 @@
/*
Package logger contains filters and handles for the logging utilities in Revel.
These facilities all currently use the logging library called log15 at
https://github.com/inconshreveable/log15
Wrappers for the handlers are written here to provide a kind of isolation layer for Revel
in case sometime in the future we would like to switch to another source to implement logging
*/
package logger

206
logger/format.go Normal file
View File

@@ -0,0 +1,206 @@
package logger
import (
"bytes"
"fmt"
"github.com/revel/log15"
"reflect"
"strconv"
"sync"
"time"
)
const (
timeFormat = "2006-01-02T15:04:05-0700"
termTimeFormat = "2006/01/02 15:04:05"
termSmallTimeFormat = "15:04:05"
floatFormat = 'f'
errorKey = "REVEL_ERROR"
)
var (
// Name the log level
toRevel = map[log15.Lvl]string{log15.LvlDebug: "DEBUG",
log15.LvlInfo: "INFO", log15.LvlWarn: "WARN", log15.LvlError: "ERROR", log15.LvlCrit: "CRIT"}
)
// Outputs to the terminal in a format like below
// INFO 09:11:32 server-engine.go:169: Request Stats
func TerminalFormatHandler(noColor bool, smallDate bool) LogFormat {
dateFormat := termTimeFormat
if smallDate {
dateFormat = termSmallTimeFormat
}
return log15.FormatFunc(func(r *log15.Record) []byte {
// Bash coloring http://misc.flogisoft.com/bash/tip_colors_and_formatting
var color = 0
switch r.Lvl {
case log15.LvlCrit:
// Magenta
color = 35
case log15.LvlError:
// Red
color = 31
case log15.LvlWarn:
// Yellow
color = 33
case log15.LvlInfo:
// Green
color = 32
case log15.LvlDebug:
// Cyan
color = 36
}
b := &bytes.Buffer{}
caller := findInContext("caller", r.Ctx)
module := findInContext("module", r.Ctx)
if noColor == false && color > 0 {
if len(module) > 0 {
fmt.Fprintf(b, "\x1b[%dm%-5s\x1b[0m %s %6s %13s: %-40s ", color, toRevel[r.Lvl], r.Time.Format(dateFormat), module, caller, r.Msg)
} else {
fmt.Fprintf(b, "\x1b[%dm%-5s\x1b[0m %s %13s: %-40s ", color, toRevel[r.Lvl], r.Time.Format(dateFormat), caller, r.Msg)
}
} else {
fmt.Fprintf(b, "%-5s %s %6s %13s: %-40s", toRevel[r.Lvl], r.Time.Format(dateFormat), module, caller, r.Msg)
}
for i := 0; i < len(r.Ctx); i += 2 {
if i != 0 {
b.WriteByte(' ')
}
k, ok := r.Ctx[i].(string)
if k == "caller" || k == "fn" || k == "module" {
continue
}
v := formatLogfmtValue(r.Ctx[i+1])
if !ok {
k, v = errorKey, formatLogfmtValue(k)
}
// TODO: we should probably check that all of your key bytes aren't invalid
if noColor == false && color > 0 {
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m=%s", color, k, v)
} else {
b.WriteString(k)
b.WriteByte('=')
b.WriteString(v)
}
}
b.WriteByte('\n')
return b.Bytes()
})
}
func findInContext(key string, ctx []interface{}) string {
for i := 0; i < len(ctx); i += 2 {
k := ctx[i].(string)
if key == k {
return formatLogfmtValue(ctx[i+1])
}
}
return ""
}
// formatValue formats a value for serialization
func formatLogfmtValue(value interface{}) string {
if value == nil {
return "nil"
}
if t, ok := value.(time.Time); ok {
// Performance optimization: No need for escaping since the provided
// timeFormat doesn't have any escape characters, and escaping is
// expensive.
return t.Format(termTimeFormat)
}
value = formatShared(value)
switch v := value.(type) {
case bool:
return strconv.FormatBool(v)
case float32:
return strconv.FormatFloat(float64(v), floatFormat, 3, 64)
case float64:
return strconv.FormatFloat(v, floatFormat, 7, 64)
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
return fmt.Sprintf("%d", value)
case string:
return escapeString(v)
default:
return escapeString(fmt.Sprintf("%+v", value))
}
}
func formatShared(value interface{}) (result interface{}) {
defer func() {
if err := recover(); err != nil {
if v := reflect.ValueOf(value); v.Kind() == reflect.Ptr && v.IsNil() {
result = "nil"
} else {
panic(err)
}
}
}()
switch v := value.(type) {
case time.Time:
return v.Format(timeFormat)
case error:
return v.Error()
case fmt.Stringer:
return v.String()
default:
return v
}
}
var stringBufPool = sync.Pool{
New: func() interface{} { return new(bytes.Buffer) },
}
func escapeString(s string) string {
needsQuotes := false
needsEscape := false
for _, r := range s {
if r <= ' ' || r == '=' || r == '"' {
needsQuotes = true
}
if r == '\\' || r == '"' || r == '\n' || r == '\r' || r == '\t' {
needsEscape = true
}
}
if needsEscape == false && needsQuotes == false {
return s
}
e := stringBufPool.Get().(*bytes.Buffer)
e.WriteByte('"')
for _, r := range s {
switch r {
case '\\', '"':
e.WriteByte('\\')
e.WriteByte(byte(r))
case '\n':
e.WriteString("\\n")
case '\r':
e.WriteString("\\r")
case '\t':
e.WriteString("\\t")
default:
e.WriteRune(r)
}
}
e.WriteByte('"')
var ret string
if needsQuotes {
ret = e.String()
} else {
ret = string(e.Bytes()[1 : e.Len()-1])
}
e.Reset()
stringBufPool.Put(e)
return ret
}

350
logger/handlers.go Normal file
View File

@@ -0,0 +1,350 @@
package logger
import (
"io"
"os"
colorable "github.com/mattn/go-colorable"
"github.com/revel/log15"
"gopkg.in/natefinch/lumberjack.v2"
)
// Filters out records which do not match the level
// Uses the `log15.FilterHandler` to perform this task
func LevelHandler(lvl LogLevel, h LogHandler) LogHandler {
l15Lvl := log15.Lvl(lvl)
return log15.FilterHandler(func(r *log15.Record) (pass bool) {
return r.Lvl == l15Lvl
}, h)
}
// Filters out records which do not match the level
// Uses the `log15.FilterHandler` to perform this task
func MinLevelHandler(lvl LogLevel, h LogHandler) LogHandler {
l15Lvl := log15.Lvl(lvl)
return log15.FilterHandler(func(r *log15.Record) (pass bool) {
return r.Lvl <= l15Lvl
}, h)
}
// Filters out records which match the level
// Uses the `log15.FilterHandler` to perform this task
func NotLevelHandler(lvl LogLevel, h LogHandler) LogHandler {
l15Lvl := log15.Lvl(lvl)
return log15.FilterHandler(func(r *log15.Record) (pass bool) {
return r.Lvl != l15Lvl
}, h)
}
// Adds in a context called `caller` to the record (contains file name and line number like `foo.go:12`)
// Uses the `log15.CallerFileHandler` to perform this task
func CallerFileHandler(h LogHandler) LogHandler {
return log15.CallerFileHandler(h)
}
// Adds in a context called `caller` to the record (contains file name and line number like `foo.go:12`)
// Uses the `log15.CallerFuncHandler` to perform this task
func CallerFuncHandler(h LogHandler) LogHandler {
return log15.CallerFuncHandler(h)
}
// Filters out records which match the key value pair
// Uses the `log15.MatchFilterHandler` to perform this task
func MatchHandler(key string, value interface{}, h LogHandler) LogHandler {
return log15.MatchFilterHandler(key, value, h)
}
// If match then A handler is called otherwise B handler is called
func MatchAbHandler(key string, value interface{}, a, b LogHandler) LogHandler {
return log15.FuncHandler(func(r *log15.Record) error {
for i := 0; i < len(r.Ctx); i += 2 {
if r.Ctx[i] == key {
if r.Ctx[i+1] == value {
if a != nil {
return a.Log(r)
}
return nil
}
}
}
if b != nil {
return b.Log(r)
}
return nil
})
}
// The nil handler is used if logging for a specific request needs to be turned off
func NilHandler() LogHandler {
return log15.FuncHandler(func(r *log15.Record) error {
return nil
})
}
// Match all values in map to log
func MatchMapHandler(matchMap map[string]interface{}, a LogHandler) LogHandler {
return matchMapHandler(matchMap, false, a)
}
// Match !(Match all values in map to log) The inverse of MatchMapHandler
func NotMatchMapHandler(matchMap map[string]interface{}, a LogHandler) LogHandler {
return matchMapHandler(matchMap, true, a)
}
// Rather then chaining multiple filter handlers, process all here
func matchMapHandler(matchMap map[string]interface{}, inverse bool, a LogHandler) LogHandler {
return log15.FuncHandler(func(r *log15.Record) error {
checkMap := map[string]bool{}
// Copy the map to a bool
for i := 0; i < len(r.Ctx); i += 2 {
if value, found := matchMap[r.Ctx[i].(string)]; found && value == r.Ctx[i+1] {
checkMap[r.Ctx[i].(string)] = true
}
}
if len(checkMap) == len(matchMap) {
if !inverse {
return a.Log(r)
}
} else if inverse {
return a.Log(r)
}
return nil
})
}
// Filters out records which do not match the key value pair
// Uses the `log15.FilterHandler` to perform this task
func NotMatchHandler(key string, value interface{}, h LogHandler) LogHandler {
return log15.FilterHandler(func(r *log15.Record) (pass bool) {
switch key {
case r.KeyNames.Lvl:
return r.Lvl != value
case r.KeyNames.Time:
return r.Time != value
case r.KeyNames.Msg:
return r.Msg != value
}
for i := 0; i < len(r.Ctx); i += 2 {
if r.Ctx[i] == key {
return r.Ctx[i+1] == value
}
}
return true
}, h)
}
func MultiHandler(hs ...LogHandler) LogHandler {
// Convert the log handlers to log15.Handlers
handlers := []log15.Handler{}
for _, h := range hs {
if h != nil {
handlers = append(handlers, h)
}
}
return log15.MultiHandler(handlers...)
}
// Outputs the records to the passed in stream
// Uses the `log15.StreamHandler` to perform this task
func StreamHandler(wr io.Writer, fmtr LogFormat) LogHandler {
return log15.StreamHandler(wr, fmtr)
}
// Filter handler, this is the only
// Uses the `log15.FilterHandler` to perform this task
func FilterHandler(fn func(r *log15.Record) bool, h LogHandler) LogHandler {
return log15.FilterHandler(fn, h)
}
type ListLogHandler struct {
handlers []LogHandler
}
func NewListLogHandler(h1, h2 LogHandler) *ListLogHandler {
ll := &ListLogHandler{handlers: []LogHandler{h1, h2}}
return ll
}
func (ll *ListLogHandler) Log(r *log15.Record) (err error) {
for _, handler := range ll.handlers {
if err == nil {
err = handler.Log(r)
} else {
handler.Log(r)
}
}
return
}
func (ll *ListLogHandler) Add(h LogHandler) {
if h != nil {
ll.handlers = append(ll.handlers, h)
}
}
func (ll *ListLogHandler) Del(h LogHandler) {
if h != nil {
for i, handler := range ll.handlers {
if handler == h {
ll.handlers = append(ll.handlers[:i], ll.handlers[i+1:]...)
}
}
}
}
type CompositeMultiHandler struct {
DebugHandler LogHandler
InfoHandler LogHandler
WarnHandler LogHandler
ErrorHandler LogHandler
CriticalHandler LogHandler
}
func NewCompositeMultiHandler() (*CompositeMultiHandler, LogHandler) {
cw := &CompositeMultiHandler{}
return cw, cw
}
func (h *CompositeMultiHandler) Log(r *log15.Record) (err error) {
var handler LogHandler
switch r.Lvl {
case log15.LvlInfo:
handler = h.InfoHandler
case log15.LvlDebug:
handler = h.DebugHandler
case log15.LvlWarn:
handler = h.WarnHandler
case log15.LvlError:
handler = h.ErrorHandler
case log15.LvlCrit:
handler = h.CriticalHandler
}
// Embed the caller function in the context
if handler != nil {
handler.Log(r)
}
return
}
func (h *CompositeMultiHandler) SetHandler(handler LogHandler, replace bool, level LogLevel) {
if handler == nil {
// Ignore empty handler
return
}
source := &h.DebugHandler
switch level {
case LvlDebug:
source = &h.DebugHandler
case LvlInfo:
source = &h.InfoHandler
case LvlWarn:
source = &h.WarnHandler
case LvlError:
source = &h.ErrorHandler
case LvlCrit:
source = &h.CriticalHandler
}
if !replace && *source != nil {
// If this already was a list add a new logger to it
if ll, found := (*source).(*ListLogHandler); found {
ll.Add(handler)
} else {
*source = NewListLogHandler(*source, handler)
}
} else {
*source = handler
}
}
func (h *CompositeMultiHandler) SetHandlers(handler LogHandler, options *LogOptions) {
if len(options.Levels) == 0 {
options.Levels = LvlAllList
}
// Set all levels
for _, lvl := range options.Levels {
h.SetHandler(handler, options.ReplaceExistingHandler, lvl)
}
}
func (h *CompositeMultiHandler) SetJson(writer io.Writer, options *LogOptions) {
handler := CallerFileHandler(StreamHandler(writer, log15.JsonFormatEx(
options.GetBoolDefault("pretty", false),
options.GetBoolDefault("lineSeparated", true),
)))
if options.HandlerWrap != nil {
handler = options.HandlerWrap.SetChild(handler)
}
h.SetHandlers(handler, options)
}
// Use built in rolling function
func (h *CompositeMultiHandler) SetJsonFile(filePath string, options *LogOptions) {
writer := &lumberjack.Logger{
Filename: filePath,
MaxSize: options.GetIntDefault("maxSizeMB", 1024), // megabytes
MaxAge: options.GetIntDefault("maxAgeDays", 7), //days
MaxBackups: options.GetIntDefault("maxBackups", 7),
Compress: options.GetBoolDefault("compress", true),
}
h.SetJson(writer, options)
}
func (h *CompositeMultiHandler) SetTerminal(writer io.Writer, options *LogOptions) {
streamHandler := StreamHandler(
writer,
TerminalFormatHandler(
options.GetBoolDefault("noColor", false),
options.GetBoolDefault("smallDate", true)))
if os.Stdout == writer {
streamHandler = StreamHandler(
colorable.NewColorableStdout(),
TerminalFormatHandler(
options.GetBoolDefault("noColor", false),
options.GetBoolDefault("smallDate", true)))
} else if os.Stderr == writer {
streamHandler = StreamHandler(
colorable.NewColorableStderr(),
TerminalFormatHandler(
options.GetBoolDefault("noColor", false),
options.GetBoolDefault("smallDate", true)))
}
handler := CallerFileHandler(streamHandler)
if options.HandlerWrap != nil {
handler = options.HandlerWrap.SetChild(handler)
}
h.SetHandlers(handler, options)
}
// Use built in rolling function
func (h *CompositeMultiHandler) SetTerminalFile(filePath string, options *LogOptions) {
writer := &lumberjack.Logger{
Filename: filePath,
MaxSize: options.GetIntDefault("maxSizeMB", 1024), // megabytes
MaxAge: options.GetIntDefault("maxAgeDays", 7), //days
MaxBackups: options.GetIntDefault("maxBackups", 7),
Compress: options.GetBoolDefault("compress", true),
}
h.SetTerminal(writer, options)
}
func (h *CompositeMultiHandler) Disable(levels ...LogLevel) {
if len(levels) == 0 {
levels = LvlAllList
}
for _, level := range levels {
switch level {
case LvlDebug:
h.DebugHandler = nil
case LvlInfo:
h.InfoHandler = nil
case LvlWarn:
h.WarnHandler = nil
case LvlError:
h.ErrorHandler = nil
case LvlCrit:
h.CriticalHandler = nil
}
}
}

656
logger/log.go.old Normal file
View File

@@ -0,0 +1,656 @@
package logger
// LoggedError is wrapper to differentiate logged panics from unexpected ones.
import (
"os"
"fmt"
"strings"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"sync"
"go.uber.org/zap/buffer"
"time"
"encoding/base64"
"unicode/utf8"
"encoding/json"
"math"
"io"
)
type (
MultiLogger interface {
//log15.Logger
//// New returns a new Logger that has this logger's context plus the given context
New(ctx ...interface{}) MultiLogger
//
// The encoders job is to encode the
SetHandler(h LogHandler)
SetStackDepth(int) MultiLogger
//
//// Log a message at the given level with context key/value pairs
Debug(msg string, ctx ...interface{})
Debugf(msg string, params ...interface{})
Info(msg string, ctx ...interface{})
Infof(msg string, params ...interface{})
Warn(msg string, ctx ...interface{})
Warnf(msg string, params ...interface{})
Error(msg string, ctx ...interface{})
Errorf(msg string, params ...interface{})
Crit(msg string, ctx ...interface{})
Critf(msg string, params ...interface{})
//// Logs a message as an Crit and exits
Fatal(msg string, ctx ...interface{})
Fatalf(msg string, params ...interface{})
//// Logs a message as an Crit and panics
Panic(msg string, ctx ...interface{})
Panicf(msg string, params ...interface{})
}
// The log han
LogHandler interface {
Encode(Record) ([]byte, error)
GetLevel() Level
GetWriter() io.Writer
}
// The Record
Record struct {
Level Level
Time time.Time
LoggerName string
Message string
Caller EntryCaller
Stack string
Context []Field
}
// The fields passed in
Field interface {
GetKey() string
GetValueAsString() string
GetValue() interface{}
}
EntryCaller interface {
IsDefined() bool
GetPC() uintptr
GetFile() string
GetLine() int
}
// Called only if the logger needs
ResolveLaterLogger func() interface{}
FieldType int
Level int
)
type (
zapLogger struct {
logger *zap.SugaredLogger
coreList []*zapcore.Core
}
zapField struct {
Key string
Type FieldType
Integer int64
String string
Interface interface{}
}
zapEntryCaller struct {
Defined bool
PC uintptr
File string
Line int
}
zapEncoder struct {
lh LogHandler
}
)
func newLogger(addCaller bool) MultiLogger {
logger := zap.New(nil).WithOptions(zap.AddCaller())
l := &zapLogger{logger:logger.Sugar()}
return l
}
// It is up to the handler to determine the synchronization to the output
// streams
func (z *zapLogger) SetHandler(lh LogHandler) {
// Swap out the logger when a new handler is attached
encoder := &zapEncoder{lh}
levelHandler := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
return lvl >= zapcore.Level(lh.GetLevel())
})
logger := zap.New(zapcore.NewCore(encoder, nil, levelHandler)).WithOptions(zap.AddCaller())
Logger.With("foo","bar").Desugar().Core()
}
var Logger *zap.SugaredLogger
func InitLogger(logLevel zapcore.Level) {
config :=zap.NewDevelopmentEncoderConfig()
config.EncodeLevel = zapcore.CapitalColorLevelEncoder
consoleEncoder := NewConsoleEncoder(config)
lowPriority := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
return lvl >= logLevel
})
consoleDebugging := zapcore.Lock(os.Stdout)
core := zapcore.NewTee(
zapcore.NewCore(consoleEncoder, consoleDebugging, lowPriority),
)
logger := zap.New(core).WithOptions(zap.AddCaller())
Logger = logger.Sugar()
}
type LoggedError struct{ error }
func NewLoggedError(err error) *LoggedError {
return &LoggedError{err}
}
func Errorf(format string, args ...interface{}) {
// Ensure the user's command prompt starts on the next line.
if !strings.HasSuffix(format, "\n") {
format += "\n"
}
fmt.Fprintf(os.Stderr, format, args...)
panic(format) // Panic instead of os.Exit so that deferred will run.
}
// This is all for the Console logger - a little wordy but it works
var _sliceEncoderPool = sync.Pool{
New: func() interface{} {
return &sliceArrayEncoder{elems: make([]interface{}, 0, 2)}
},
}
func getSliceEncoder() *sliceArrayEncoder {
return _sliceEncoderPool.Get().(*sliceArrayEncoder)
}
func putSliceEncoder(e *sliceArrayEncoder) {
e.elems = e.elems[:0]
_sliceEncoderPool.Put(e)
}
type consoleEncoder struct {
*zapcore.EncoderConfig
openNamespaces int
buf *buffer.Buffer
reflectBuf *buffer.Buffer
reflectEnc *json.Encoder
}
var (
_pool = buffer.NewPool()
// Get retrieves a buffer from the pool, creating one if necessary.
Get = _pool.Get
)
// NewConsoleEncoder creates an encoder whose output is designed for human -
// rather than machine - consumption. It serializes the core log entry data
// (message, level, timestamp, etc.) in a plain-text format and leaves the
// structured context as JSON.
//
// Note that although the console encoder doesn't use the keys specified in the
// encoder configuration, it will omit any element whose key is set to the empty
// string.
func NewConsoleEncoder(cfg zapcore.EncoderConfig) zapcore.Encoder {
ec := &consoleEncoder{buf : Get(), reflectBuf: Get()}
ec.EncoderConfig = &cfg
return ec
}
func (c consoleEncoder) Clone() zapcore.Encoder {
return &consoleEncoder{buf : Get(), reflectBuf: Get()}
}
func (c consoleEncoder) EncodeEntry(ent zapcore.Entry, fields []zapcore.Field) (*buffer.Buffer, error) {
line := Get()
var color = 0
switch ent.Level {
case zap.PanicLevel:
// Magenta
color = 35
case zap.ErrorLevel:
// Red
color = 31
case zap.WarnLevel:
// Yellow
color = 33
case zap.InfoLevel:
// Green
color = 32
case zap.DebugLevel:
// Cyan
color = 36
}
// We don't want the entry's metadata to be quoted and escaped (if it's
// encoded as strings), which means that we can't use the JSON encoder. The
// simplest option is to use the memory encoder and fmt.Fprint.
//
// If this ever becomes a performance bottleneck, we can implement
// ArrayEncoder for our plain-text format.
arr := getSliceEncoder()
if c.LevelKey != "" && c.EncodeLevel != nil {
arr.AppendString(fmt.Sprintf("\x1b[%dm%-5s\x1b[0m",color,ent.Level.CapitalString()))
}
if ent.LoggerName != "" && c.NameKey != "" {
nameEncoder := c.EncodeName
if nameEncoder == nil {
// Fall back to FullNameEncoder for backward compatibility.
nameEncoder = zapcore.FullNameEncoder
}
nameEncoder(ent.LoggerName, arr)
}
if c.TimeKey != "" && c.EncodeTime != nil {
arr.AppendString(ent.Time.Format("15:04:05"))
}
if ent.Caller.Defined && c.CallerKey != "" && c.EncodeCaller != nil {
c.EncodeCaller(ent.Caller, arr)
}
for i := range arr.elems {
if i > 0 {
line.AppendByte(' ')
}
fmt.Fprint(line, arr.elems[i])
}
putSliceEncoder(arr)
// Add the message itself.
if c.MessageKey != "" {
c.addTabIfNecessary(line)
line.AppendString(ent.Message)
}
// Add any structured context.
c.writeContext(line, fields)
// If there's no stacktrace key, honor that; this allows users to force
// single-line output.
if ent.Stack != "" && c.StacktraceKey != "" {
line.AppendByte('\n')
line.AppendString(ent.Stack)
}
if c.LineEnding != "" {
line.AppendString(c.LineEnding)
} else {
line.AppendString(zapcore.DefaultLineEnding)
}
return line, nil
}
func (c consoleEncoder) writeContext(line *buffer.Buffer, extra []zapcore.Field) {
context := c.Clone().(*consoleEncoder)
defer context.buf.Free()
//
addFields(context, extra)
context.closeOpenNamespaces()
if context.buf.Len() == 0 {
return
}
//
line.Write(context.buf.Bytes())
}
func addFields(enc zapcore.ObjectEncoder, fields []zapcore.Field) {
for i := range fields {
fields[i].AddTo(enc)
}
}
func (c consoleEncoder) addTabIfNecessary(line *buffer.Buffer) {
if line.Len() > 0 {
line.AppendByte('\t')
}
}
func (enc *consoleEncoder) AddArray(key string, arr zapcore.ArrayMarshaler) error {
enc.addKey(key)
return enc.AppendArray(arr)
}
func (enc *consoleEncoder) AddObject(key string, obj zapcore.ObjectMarshaler) error {
enc.addKey(key)
return enc.AppendObject(obj)
}
func (enc *consoleEncoder) AddBinary(key string, val []byte) {
enc.AddString(key, base64.StdEncoding.EncodeToString(val))
}
func (enc *consoleEncoder) AddByteString(key string, val []byte) {
enc.addKey(key)
enc.AppendByteString(val)
}
func (enc *consoleEncoder) AddBool(key string, val bool) {
enc.addKey(key)
enc.AppendBool(val)
}
func (enc *consoleEncoder) AddComplex128(key string, val complex128) {
enc.addKey(key)
enc.AppendComplex128(val)
}
func (enc *consoleEncoder) AddDuration(key string, val time.Duration) {
enc.addKey(key)
enc.AppendDuration(val)
}
func (enc *consoleEncoder) AddFloat64(key string, val float64) {
enc.addKey(key)
enc.AppendFloat64(val)
}
func (enc *consoleEncoder) AddInt64(key string, val int64) {
enc.addKey(key)
enc.AppendInt64(val)
}
func (enc *consoleEncoder) AddReflected(key string, obj interface{}) error {
enc.resetReflectBuf()
err := enc.reflectEnc.Encode(obj)
if err != nil {
return err
}
enc.reflectBuf.TrimNewline()
enc.addKey(key)
_, err = enc.buf.Write(enc.reflectBuf.Bytes())
return err
}
func (enc *consoleEncoder) OpenNamespace(key string) {
enc.addKey(key)
enc.buf.AppendByte('{')
enc.openNamespaces++
}
func (enc *consoleEncoder) AddString(key, val string) {
enc.addKey(key)
enc.AppendString(val)
}
func (enc *consoleEncoder) AddTime(key string, val time.Time) {
enc.addKey(key)
enc.AppendTime(val)
}
func (enc *consoleEncoder) AddUint64(key string, val uint64) {
enc.addKey(key)
enc.AppendUint64(val)
}
func (enc *consoleEncoder) addKey(key string) {
// Print key in different color
enc.buf.AppendString(fmt.Sprintf(" \x1b[%dm%s\x1b[0m",36,key))
enc.buf.AppendByte('=')
}
func (enc *consoleEncoder) AppendArray(arr zapcore.ArrayMarshaler) error {
enc.buf.AppendByte('[')
err := arr.MarshalLogArray(enc)
enc.buf.AppendByte(']')
return err
}
func (enc *consoleEncoder) AppendObject(obj zapcore.ObjectMarshaler) error {
enc.buf.AppendByte('{')
err := obj.MarshalLogObject(enc)
enc.buf.AppendByte('}')
return err
}
func (enc *consoleEncoder) AppendBool(val bool) {
enc.buf.AppendBool(val)
}
func (enc *consoleEncoder) AppendByteString(val []byte) {
enc.buf.AppendByte('"')
enc.safeAddByteString(val)
enc.buf.AppendByte('"')
}
func (enc *consoleEncoder) AppendComplex128(val complex128) {
// Cast to a platform-independent, fixed-size type.
r, i := float64(real(val)), float64(imag(val))
enc.buf.AppendByte('"')
// Because we're always in a quoted string, we can use strconv without
// special-casing NaN and +/-Inf.
enc.buf.AppendFloat(r, 64)
enc.buf.AppendByte('+')
enc.buf.AppendFloat(i, 64)
enc.buf.AppendByte('i')
enc.buf.AppendByte('"')
}
func (enc *consoleEncoder) AppendDuration(val time.Duration) {
cur := enc.buf.Len()
enc.EncodeDuration(val, enc)
if cur == enc.buf.Len() {
// User-supplied EncodeDuration is a no-op. Fall back to nanoseconds to keep
// JSON valid.
enc.AppendInt64(int64(val))
}
}
func (enc *consoleEncoder) AppendInt64(val int64) {
enc.buf.AppendInt(val)
}
func (enc *consoleEncoder) resetReflectBuf() {
if enc.reflectBuf == nil {
enc.reflectBuf = Get()
enc.reflectEnc = json.NewEncoder(enc.reflectBuf)
} else {
enc.reflectBuf.Reset()
}
}
func (enc *consoleEncoder) AppendReflected(val interface{}) error {
enc.resetReflectBuf()
err := enc.reflectEnc.Encode(val)
if err != nil {
return err
}
enc.reflectBuf.TrimNewline()
_, err = enc.buf.Write(enc.reflectBuf.Bytes())
return err
}
func (enc *consoleEncoder) AppendString(val string) {
enc.safeAddString(val)
}
func (enc *consoleEncoder) AppendTime(val time.Time) {
cur := enc.buf.Len()
enc.EncodeTime(val, enc)
if cur == enc.buf.Len() {
// User-supplied EncodeTime is a no-op. Fall back to nanos since epoch to keep
// output JSON valid.
enc.AppendInt64(val.UnixNano())
}
}
func (enc *consoleEncoder) AppendUint64(val uint64) {
enc.buf.AppendUint(val)
}
func (enc *consoleEncoder) appendFloat(val float64, bitSize int) {
switch {
case math.IsNaN(val):
enc.buf.AppendString(`"NaN"`)
case math.IsInf(val, 1):
enc.buf.AppendString(`"+Inf"`)
case math.IsInf(val, -1):
enc.buf.AppendString(`"-Inf"`)
default:
enc.buf.AppendFloat(val, bitSize)
}
}
// safeAddString JSON-escapes a string and appends it to the internal buffer.
// Unlike the standard library's encoder, it doesn't attempt to protect the
// user from browser vulnerabilities or JSONP-related problems.
func (enc *consoleEncoder) safeAddString(s string) {
for i := 0; i < len(s); {
if enc.tryAddRuneSelf(s[i]) {
i++
continue
}
r, size := utf8.DecodeRuneInString(s[i:])
if enc.tryAddRuneError(r, size) {
i++
continue
}
enc.buf.AppendString(s[i : i+size])
i += size
}
}
// safeAddByteString is no-alloc equivalent of safeAddString(string(s)) for s []byte.
func (enc *consoleEncoder) safeAddByteString(s []byte) {
for i := 0; i < len(s); {
if enc.tryAddRuneSelf(s[i]) {
i++
continue
}
r, size := utf8.DecodeRune(s[i:])
if enc.tryAddRuneError(r, size) {
i++
continue
}
enc.buf.Write(s[i : i+size])
i += size
}
}
// tryAddRuneSelf appends b if it is valid UTF-8 character represented in a single byte.
func (enc *consoleEncoder) tryAddRuneSelf(b byte) bool {
if b >= utf8.RuneSelf {
return false
}
if 0x20 <= b && b != '\\' && b != '"' {
enc.buf.AppendByte(b)
return true
}
switch b {
case '\\', '"':
enc.buf.AppendByte('\\')
enc.buf.AppendByte(b)
case '\n':
enc.buf.AppendByte('\\')
enc.buf.AppendByte('n')
case '\r':
enc.buf.AppendByte('\\')
enc.buf.AppendByte('r')
case '\t':
enc.buf.AppendByte('\\')
enc.buf.AppendByte('t')
default:
// Encode bytes < 0x20, except for the escape sequences above.
enc.buf.AppendString(`\u00`)
enc.buf.AppendByte(_hex[b>>4])
enc.buf.AppendByte(_hex[b&0xF])
}
return true
}
func (enc *consoleEncoder) closeOpenNamespaces() {
for i := 0; i < enc.openNamespaces; i++ {
enc.buf.AppendByte('}')
}
}
func (enc *consoleEncoder) tryAddRuneError(r rune, size int) bool {
if r == utf8.RuneError && size == 1 {
enc.buf.AppendString(`\ufffd`)
return true
}
return false
}
func (enc *consoleEncoder) AddComplex64(k string, v complex64) { enc.AddComplex128(k, complex128(v)) }
func (enc *consoleEncoder) AddFloat32(k string, v float32) { enc.AddFloat64(k, float64(v)) }
func (enc *consoleEncoder) AddInt(k string, v int) { enc.AddInt64(k, int64(v)) }
func (enc *consoleEncoder) AddInt32(k string, v int32) { enc.AddInt64(k, int64(v)) }
func (enc *consoleEncoder) AddInt16(k string, v int16) { enc.AddInt64(k, int64(v)) }
func (enc *consoleEncoder) AddInt8(k string, v int8) { enc.AddInt64(k, int64(v)) }
func (enc *consoleEncoder) AddUint(k string, v uint) { enc.AddUint64(k, uint64(v)) }
func (enc *consoleEncoder) AddUint32(k string, v uint32) { enc.AddUint64(k, uint64(v)) }
func (enc *consoleEncoder) AddUint16(k string, v uint16) { enc.AddUint64(k, uint64(v)) }
func (enc *consoleEncoder) AddUint8(k string, v uint8) { enc.AddUint64(k, uint64(v)) }
func (enc *consoleEncoder) AddUintptr(k string, v uintptr) { enc.AddUint64(k, uint64(v)) }
func (enc *consoleEncoder) AppendComplex64(v complex64) { enc.AppendComplex128(complex128(v)) }
func (enc *consoleEncoder) AppendFloat64(v float64) { enc.appendFloat(v, 64) }
func (enc *consoleEncoder) AppendFloat32(v float32) { enc.appendFloat(float64(v), 32) }
func (enc *consoleEncoder) AppendInt(v int) { enc.AppendInt64(int64(v)) }
func (enc *consoleEncoder) AppendInt32(v int32) { enc.AppendInt64(int64(v)) }
func (enc *consoleEncoder) AppendInt16(v int16) { enc.AppendInt64(int64(v)) }
func (enc *consoleEncoder) AppendInt8(v int8) { enc.AppendInt64(int64(v)) }
func (enc *consoleEncoder) AppendUint(v uint) { enc.AppendUint64(uint64(v)) }
func (enc *consoleEncoder) AppendUint32(v uint32) { enc.AppendUint64(uint64(v)) }
func (enc *consoleEncoder) AppendUint16(v uint16) { enc.AppendUint64(uint64(v)) }
func (enc *consoleEncoder) AppendUint8(v uint8) { enc.AppendUint64(uint64(v)) }
func (enc *consoleEncoder) AppendUintptr(v uintptr) { enc.AppendUint64(uint64(v)) }
const _hex = "0123456789abcdef"
type sliceArrayEncoder struct {
elems []interface{}
}
func (s *sliceArrayEncoder) AppendArray(v zapcore.ArrayMarshaler) error {
enc := &sliceArrayEncoder{}
err := v.MarshalLogArray(enc)
s.elems = append(s.elems, enc.elems)
return err
}
func (s *sliceArrayEncoder) AppendObject(v zapcore.ObjectMarshaler) error {
m := zapcore.NewMapObjectEncoder()
err := v.MarshalLogObject(m)
s.elems = append(s.elems, m.Fields)
return err
}
func (s *sliceArrayEncoder) AppendReflected(v interface{}) error {
s.elems = append(s.elems, v)
return nil
}
func (s *sliceArrayEncoder) AppendBool(v bool) { s.elems = append(s.elems, v) }
func (s *sliceArrayEncoder) AppendByteString(v []byte) { s.elems = append(s.elems, v) }
func (s *sliceArrayEncoder) AppendComplex128(v complex128) { s.elems = append(s.elems, v) }
func (s *sliceArrayEncoder) AppendComplex64(v complex64) { s.elems = append(s.elems, v) }
func (s *sliceArrayEncoder) AppendDuration(v time.Duration) { s.elems = append(s.elems, v) }
func (s *sliceArrayEncoder) AppendFloat64(v float64) { s.elems = append(s.elems, v) }
func (s *sliceArrayEncoder) AppendFloat32(v float32) { s.elems = append(s.elems, v) }
func (s *sliceArrayEncoder) AppendInt(v int) { s.elems = append(s.elems, v) }
func (s *sliceArrayEncoder) AppendInt64(v int64) { s.elems = append(s.elems, v) }
func (s *sliceArrayEncoder) AppendInt32(v int32) { s.elems = append(s.elems, v) }
func (s *sliceArrayEncoder) AppendInt16(v int16) { s.elems = append(s.elems, v) }
func (s *sliceArrayEncoder) AppendInt8(v int8) { s.elems = append(s.elems, v) }
func (s *sliceArrayEncoder) AppendString(v string) { s.elems = append(s.elems, v) }
func (s *sliceArrayEncoder) AppendTime(v time.Time) { s.elems = append(s.elems, v) }
func (s *sliceArrayEncoder) AppendUint(v uint) { s.elems = append(s.elems, v) }
func (s *sliceArrayEncoder) AppendUint64(v uint64) { s.elems = append(s.elems, v) }
func (s *sliceArrayEncoder) AppendUint32(v uint32) { s.elems = append(s.elems, v) }
func (s *sliceArrayEncoder) AppendUint16(v uint16) { s.elems = append(s.elems, v) }
func (s *sliceArrayEncoder) AppendUint8(v uint8) { s.elems = append(s.elems, v) }
func (s *sliceArrayEncoder) AppendUintptr(v uintptr) { s.elems = append(s.elems, v) }

227
logger/logger.go Normal file
View File

@@ -0,0 +1,227 @@
package logger
import (
"fmt"
"github.com/revel/config"
"github.com/revel/log15"
"log"
"os"
)
// The LogHandler defines the interface to handle the log records
type (
// The Multilogger reduces the number of exposed defined logging variables,
// and allows the output to be easily refined
MultiLogger interface {
//log15.Logger
//// New returns a new Logger that has this logger's context plus the given context
New(ctx ...interface{}) MultiLogger
//
//// SetHandler updates the logger to write records to the specified handler.
SetHandler(h LogHandler)
SetStackDepth(int) MultiLogger
//
//// Log a message at the given level with context key/value pairs
Debug(msg string, ctx ...interface{})
Debugf(msg string, params ...interface{})
Info(msg string, ctx ...interface{})
Infof(msg string, params ...interface{})
Warn(msg string, ctx ...interface{})
Warnf(msg string, params ...interface{})
Error(msg string, ctx ...interface{})
Errorf(msg string, params ...interface{})
Crit(msg string, ctx ...interface{})
Critf(msg string, params ...interface{})
//// Logs a message as an Crit and exits
Fatal(msg string, ctx ...interface{})
Fatalf(msg string, params ...interface{})
//// Logs a message as an Crit and panics
Panic(msg string, ctx ...interface{})
Panicf(msg string, params ...interface{})
}
LogHandler interface {
log15.Handler
}
LogStackHandler interface {
LogHandler
GetStack() int
}
ParentLogHandler interface {
SetChild(handler LogHandler) LogHandler
}
LogFormat interface {
log15.Format
}
LogLevel log15.Lvl
RevelLogger struct {
log15.Logger
}
// Used for the callback to LogFunctionMap
LogOptions struct {
Ctx *config.Context
ReplaceExistingHandler bool
HandlerWrap ParentLogHandler
Levels []LogLevel
ExtendedOptions map[string]interface{}
}
)
const (
LvlDebug = LogLevel(log15.LvlDebug)
LvlInfo = LogLevel(log15.LvlInfo)
LvlWarn = LogLevel(log15.LvlWarn)
LvlError = LogLevel(log15.LvlError)
LvlCrit = LogLevel(log15.LvlCrit)
)
// A list of all the log levels
var LvlAllList = []LogLevel{LvlDebug, LvlInfo, LvlWarn, LvlError, LvlCrit}
// The log function map can be added to, so that you can specify your own logging mechanism
var LogFunctionMap = map[string]func(*CompositeMultiHandler, *LogOptions){
// Do nothing - set the logger off
"off": func(c *CompositeMultiHandler, logOptions *LogOptions) {
// Only drop the results if there is a parent handler defined
if logOptions.HandlerWrap != nil {
for _, l := range logOptions.Levels {
c.SetHandler(logOptions.HandlerWrap.SetChild(NilHandler()), logOptions.ReplaceExistingHandler, l)
}
}
},
// Do nothing - set the logger off
"": func(*CompositeMultiHandler, *LogOptions) {},
// Set the levels to stdout, replace existing
"stdout": func(c *CompositeMultiHandler, logOptions *LogOptions) {
if logOptions.Ctx != nil {
logOptions.SetExtendedOptions(
"noColor", !logOptions.Ctx.BoolDefault("log.colorize", true),
"smallDate", logOptions.Ctx.BoolDefault("log.smallDate", true))
}
c.SetTerminal(os.Stdout, logOptions)
},
// Set the levels to stderr output to terminal
"stderr": func(c *CompositeMultiHandler, logOptions *LogOptions) {
c.SetTerminal(os.Stderr, logOptions)
},
}
// Set the systems default logger
// Default logs will be captured and handled by revel at level info
func SetDefaultLog(fromLog MultiLogger) {
log.SetOutput(loggerRewrite{Logger: fromLog, Level: log15.LvlInfo, hideDeprecated: true})
// No need to show date and time, that will be logged with revel
log.SetFlags(0)
}
// Formatted debug call
func (rl *RevelLogger) Debugf(msg string, param ...interface{}) {
rl.Debug(fmt.Sprintf(msg, param...))
}
// Formatted info call
func (rl *RevelLogger) Infof(msg string, param ...interface{}) {
rl.Info(fmt.Sprintf(msg, param...))
}
func (rl *RevelLogger) Warnf(msg string, param ...interface{}) {
rl.Warn(fmt.Sprintf(msg, param...))
}
func (rl *RevelLogger) Errorf(msg string, param ...interface{}) {
rl.Error(fmt.Sprintf(msg, param...))
}
func (rl *RevelLogger) Critf(msg string, param ...interface{}) {
rl.Crit(fmt.Sprintf(msg, param...))
}
func (rl *RevelLogger) Fatalf(msg string, param ...interface{}) {
rl.Crit(fmt.Sprintf(msg, param...))
os.Exit(1)
}
func (rl *RevelLogger) Panicf(msg string, param ...interface{}) {
rl.Crit(fmt.Sprintf(msg, param...))
panic(msg)
}
func (rl *RevelLogger) Fatal(msg string, ctx ...interface{}) {
rl.Crit(msg, ctx...)
os.Exit(1)
}
func (rl *RevelLogger) Panic(msg string, ctx ...interface{}) {
rl.Crit(msg, ctx...)
panic(msg)
}
// Override log15 method
func (rl *RevelLogger) New(ctx ...interface{}) MultiLogger {
old := &RevelLogger{Logger: rl.Logger.New(ctx...)}
return old
}
// Set the stack level to check for the caller
func (rl *RevelLogger) SetStackDepth(amount int) MultiLogger {
rl.Logger.SetStackDepth(amount) // Ignore the logger returned
return rl
}
// Create a new logger
func New(ctx ...interface{}) MultiLogger {
r := &RevelLogger{Logger: log15.New(ctx...)}
r.SetStackDepth(1)
return r
}
// Set the handler in the Logger
func (rl *RevelLogger) SetHandler(h LogHandler) {
rl.Logger.SetHandler(h)
}
type parentLogHandler struct {
setChild func(handler LogHandler) LogHandler
}
func NewParentLogHandler(callBack func(child LogHandler) LogHandler) ParentLogHandler {
return &parentLogHandler{callBack}
}
func (p *parentLogHandler) SetChild(child LogHandler) LogHandler {
return p.setChild(child)
}
// Create a new log options
func NewLogOptions(cfg *config.Context, replaceHandler bool, phandler ParentLogHandler, lvl ...LogLevel) (logOptions *LogOptions) {
logOptions = &LogOptions{
Ctx: cfg,
ReplaceExistingHandler: replaceHandler,
HandlerWrap: phandler,
Levels: lvl,
ExtendedOptions: map[string]interface{}{},
}
return
}
// Assumes options will be an even number and have a string, value syntax
func (l *LogOptions) SetExtendedOptions(options ...interface{}) {
for x := 0; x < len(options); x += 2 {
l.ExtendedOptions[options[x].(string)] = options[x+1]
}
}
func (l *LogOptions) GetStringDefault(option, value string) string {
if v, found := l.ExtendedOptions[option]; found {
return v.(string)
}
return value
}
func (l *LogOptions) GetIntDefault(option string, value int) int {
if v, found := l.ExtendedOptions[option]; found {
return v.(int)
}
return value
}
func (l *LogOptions) GetBoolDefault(option string, value bool) bool {
if v, found := l.ExtendedOptions[option]; found {
return v.(bool)
}
return value
}

277
logger/utils.go Normal file
View File

@@ -0,0 +1,277 @@
package logger
import (
"gopkg.in/stack.v0"
"github.com/revel/config"
"github.com/revel/log15"
"log"
"os"
"path/filepath"
"strings"
)
// Utility package to make existing logging backwards compatible
var (
// Convert the string to LogLevel
toLevel = map[string]LogLevel{"debug": LogLevel(log15.LvlDebug),
"info": LogLevel(log15.LvlInfo), "request": LogLevel(log15.LvlInfo), "warn": LogLevel(log15.LvlWarn),
"error": LogLevel(log15.LvlError), "crit": LogLevel(log15.LvlCrit),
"trace": LogLevel(log15.LvlDebug), // TODO trace is deprecated, replaced by debug
}
)
func GetLogger(name string, logger MultiLogger) (l *log.Logger) {
switch name {
case "trace": // TODO trace is deprecated, replaced by debug
l = log.New(loggerRewrite{Logger: logger, Level: log15.LvlDebug}, "", 0)
case "debug":
l = log.New(loggerRewrite{Logger: logger, Level: log15.LvlDebug}, "", 0)
case "info":
l = log.New(loggerRewrite{Logger: logger, Level: log15.LvlInfo}, "", 0)
case "warn":
l = log.New(loggerRewrite{Logger: logger, Level: log15.LvlWarn}, "", 0)
case "error":
l = log.New(loggerRewrite{Logger: logger, Level: log15.LvlError}, "", 0)
case "request":
l = log.New(loggerRewrite{Logger: logger, Level: log15.LvlInfo}, "", 0)
}
return l
}
// Get all handlers based on the Config (if available)
func InitializeFromConfig(basePath string, config *config.Context) (c *CompositeMultiHandler) {
// If running in test mode suppress anything that is not an error
if config!=nil && config.BoolDefault("testModeFlag",false) {
config.SetOption("log.info.output","none")
config.SetOption("log.debug.output","none")
config.SetOption("log.warn.output","none")
config.SetOption("log.error.output","stderr")
config.SetOption("log.crit.output","stderr")
}
// If the configuration has an all option we can skip some
c, _ = NewCompositeMultiHandler()
// Filters are assigned first, non filtered items override filters
initAllLog(c, basePath, config)
initLogLevels(c, basePath, config)
if c.CriticalHandler == nil && c.ErrorHandler != nil {
c.CriticalHandler = c.ErrorHandler
}
initFilterLog(c, basePath, config)
if c.CriticalHandler == nil && c.ErrorHandler != nil {
c.CriticalHandler = c.ErrorHandler
}
initRequestLog(c, basePath, config)
return c
}
// Init the log.all configuration options
func initAllLog(c *CompositeMultiHandler, basePath string, config *config.Context) {
if config != nil {
extraLogFlag := config.BoolDefault("specialUseFlag", false)
if output, found := config.String("log.all.output"); found {
// Set all output for the specified handler
if extraLogFlag {
log.Printf("Adding standard handler for levels to >%s< ", output)
}
initHandlerFor(c, output, basePath, NewLogOptions(config, true, nil, LvlAllList...))
}
}
}
// Init the filter options
// log.all.filter ....
// log.error.filter ....
func initFilterLog(c *CompositeMultiHandler, basePath string, config *config.Context) {
if config != nil {
extraLogFlag := config.BoolDefault("specialUseFlag", false)
// The commands to use
logFilterList := []struct {
LogPrefix, LogSuffix string
parentHandler func(map[string]interface{}) ParentLogHandler
}{{
"log.", ".filter",
func(keyMap map[string]interface{}) ParentLogHandler {
return NewParentLogHandler(func(child LogHandler) LogHandler {
return MatchMapHandler(keyMap, child)
})
},
}, {
"log.", ".nfilter",
func(keyMap map[string]interface{}) ParentLogHandler {
return NewParentLogHandler(func(child LogHandler) LogHandler {
return NotMatchMapHandler(keyMap, child)
})
},
}}
for _, logFilter := range logFilterList {
// Init for all filters
for _, name := range []string{"all", "debug", "info", "warn", "error", "crit",
"trace", // TODO trace is deprecated
} {
optionList := config.Options(logFilter.LogPrefix + name + logFilter.LogSuffix)
for _, option := range optionList {
splitOptions := strings.Split(option, ".")
keyMap := map[string]interface{}{}
for x := 3; x < len(splitOptions); x += 2 {
keyMap[splitOptions[x]] = splitOptions[x+1]
}
phandler := logFilter.parentHandler(keyMap)
if extraLogFlag {
log.Printf("Adding key map handler %s %s output %s", option, name, config.StringDefault(option, ""))
}
if name == "all" {
initHandlerFor(c, config.StringDefault(option, ""), basePath, NewLogOptions(config, false, phandler))
} else {
initHandlerFor(c, config.StringDefault(option, ""), basePath, NewLogOptions(config, false, phandler, toLevel[name]))
}
}
}
}
}
}
// Init the log.error, log.warn etc configuration options
func initLogLevels(c *CompositeMultiHandler, basePath string, config *config.Context) {
for _, name := range []string{"debug", "info", "warn", "error", "crit",
"trace", // TODO trace is deprecated
} {
if config != nil {
extraLogFlag := config.BoolDefault("specialUseFlag", false)
output, found := config.String("log." + name + ".output")
if found {
if extraLogFlag {
log.Printf("Adding standard handler %s output %s", name, output)
}
initHandlerFor(c, output, basePath, NewLogOptions(config, true, nil, toLevel[name]))
}
// Gets the list of options with said prefix
} else {
initHandlerFor(c, "stderr", basePath, NewLogOptions(config, true, nil, toLevel[name]))
}
}
}
// Init the request log options
func initRequestLog(c *CompositeMultiHandler, basePath string, config *config.Context) {
// Request logging to a separate output handler
// This takes the InfoHandlers and adds a MatchAbHandler handler to it to direct
// context with the word "section=requestlog" to that handler.
// Note if request logging is not enabled the MatchAbHandler will not be added and the
// request log messages will be sent out the INFO handler
outputRequest := "stdout"
if config != nil {
outputRequest = config.StringDefault("log.request.output", "")
}
oldInfo := c.InfoHandler
c.InfoHandler = nil
if outputRequest != "" {
initHandlerFor(c, outputRequest, basePath, NewLogOptions(config, false, nil, LvlInfo))
}
if c.InfoHandler != nil || oldInfo != nil {
if c.InfoHandler == nil {
c.InfoHandler = oldInfo
} else {
c.InfoHandler = MatchAbHandler("section", "requestlog", c.InfoHandler, oldInfo)
}
}
}
// Returns a handler for the level using the output string
// Accept formats for output string are
// LogFunctionMap[value] callback function
// `stdout` `stderr` `full/file/path/to/location/app.log` `full/file/path/to/location/app.json`
func initHandlerFor(c *CompositeMultiHandler, output, basePath string, options *LogOptions) {
if options.Ctx != nil {
options.SetExtendedOptions(
"noColor", !options.Ctx.BoolDefault("log.colorize", true),
"smallDate", options.Ctx.BoolDefault("log.smallDate", true),
"maxSize", options.Ctx.IntDefault("log.maxsize", 1024*10),
"maxAge", options.Ctx.IntDefault("log.maxage", 14),
"maxBackups", options.Ctx.IntDefault("log.maxbackups", 14),
"compressBackups", !options.Ctx.BoolDefault("log.compressBackups", true),
)
}
output = strings.TrimSpace(output)
if funcHandler, found := LogFunctionMap[output]; found {
funcHandler(c, options)
} else {
switch output {
case "":
fallthrough
case "off":
// No handler, discard data
default:
// Write to file specified
if !filepath.IsAbs(output) {
output = filepath.Join(basePath, output)
}
if err := os.MkdirAll(filepath.Dir(output), 0755); err != nil {
log.Panic(err)
}
if strings.HasSuffix(output, "json") {
c.SetJsonFile(output, options)
} else {
// Override defaults for a terminal file
options.SetExtendedOptions("noColor", true)
options.SetExtendedOptions("smallDate", false)
c.SetTerminalFile(output, options)
}
}
}
return
}
// This structure and method will handle the old output format and log it to the new format
type loggerRewrite struct {
Logger MultiLogger
Level log15.Lvl
hideDeprecated bool
}
var log_deprecated = []byte("* LOG DEPRECATED * ")
func (lr loggerRewrite) Write(p []byte) (n int, err error) {
if !lr.hideDeprecated {
p = append(log_deprecated, p...)
}
n = len(p)
if len(p) > 0 && p[n-1] == '\n' {
p = p[:n-1]
n--
}
switch lr.Level {
case log15.LvlInfo:
lr.Logger.Info(string(p))
case log15.LvlDebug:
lr.Logger.Debug(string(p))
case log15.LvlWarn:
lr.Logger.Warn(string(p))
case log15.LvlError:
lr.Logger.Error(string(p))
case log15.LvlCrit:
lr.Logger.Crit(string(p))
}
return
}
// For logging purposes the call stack can be used to record the stack trace of a bad error
// simply pass it as a context field in your log statement like
// `controller.Log.Critc("This should not occur","stack",revel.NewCallStack())`
func NewCallStack() interface{} {
return stack.Trace()
}