mirror of
https://github.com/kevin-DL/revel-cmd.git
synced 2026-01-11 18:54:31 +00:00
Initial rewrite of revel/cmd to provide vendor support and enhanced CLI options
This commit is contained in:
11
logger/doc.go
Normal file
11
logger/doc.go
Normal 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
206
logger/format.go
Normal 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
350
logger/handlers.go
Normal 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
656
logger/log.go.old
Normal 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
227
logger/logger.go
Normal 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
277
logger/utils.go
Normal 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()
|
||||
}
|
||||
Reference in New Issue
Block a user