From 0a66fbccccb4eb296df2555a007151cb17aaf698 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 15 May 2019 11:03:14 +0800 Subject: [PATCH] change files --- base.go | 328 --------------------------------- colors.go | 106 +++++++++-- colors_router.go | 83 +++++++++ conn.go | 4 +- console.go | 8 +- file.go | 4 +- file_test.go | 2 - flags.go | 64 +++++++ go.mod | 4 +- go.sum | 4 +- log.go | 2 +- logger.go | 6 +- provider.go | 7 +- smtp.go | 4 +- writer.go | 273 +++++++++++++++++++++++++++ base_test.go => writer_test.go | 12 +- 16 files changed, 538 insertions(+), 373 deletions(-) delete mode 100644 base.go create mode 100644 colors_router.go create mode 100644 flags.go create mode 100644 writer.go rename base_test.go => writer_test.go (98%) diff --git a/base.go b/base.go deleted file mode 100644 index 5577737..0000000 --- a/base.go +++ /dev/null @@ -1,328 +0,0 @@ -// Copyright 2019 The Gitea Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package log - -import ( - "bytes" - "fmt" - "io" - "regexp" - "strings" - "sync" -) - -// These flags define which text to prefix to each log entry generated -// by the Logger. Bits are or'ed together to control what's printed. -// There is no control over the order they appear (the order listed -// here) or the format they present (as described in the comments). -// The prefix is followed by a colon only if more than time is stated -// is specified. For example, flags Ldate | Ltime -// produce, 2009/01/23 01:23:23 message. -// The standard is: -// 2009/01/23 01:23:23 ...a/b/c/d.go:23:runtime.Caller() [I]: message -const ( - Ldate = 1 << iota // the date in the local time zone: 2009/01/23 - Ltime // the time in the local time zone: 01:23:23 - Lmicroseconds // microsecond resolution: 01:23:23.123123. assumes Ltime. - Llongfile // full file name and line number: /a/b/c/d.go:23 - Lshortfile // final file name element and line number: d.go:23. overrides Llongfile - Lfuncname // function name of the caller: runtime.Caller() - Lshortfuncname // last part of the function name - LUTC // if Ldate or Ltime is set, use UTC rather than the local time zone - Llevelinitial // Initial character of the provided level in brackets eg. [I] for info - Llevel // Provided level in brackets [INFO] - - // Last 20 characters of the filename - Lmedfile = Lshortfile | Llongfile - - // LstdFlags is the initial value for the standard logger - LstdFlags = Ldate | Ltime | Lmedfile | Lshortfuncname | Llevelinitial -) - -var flagFromString = map[string]int{ - "none": 0, - "date": Ldate, - "time": Ltime, - "microseconds": Lmicroseconds, - "longfile": Llongfile, - "shortfile": Lshortfile, - "funcname": Lfuncname, - "shortfuncname": Lshortfuncname, - "utc": LUTC, - "levelinitial": Llevelinitial, - "level": Llevel, - "medfile": Lmedfile, - "stdflags": LstdFlags, -} - -// FlagsFromString takes a comma separated list of flags and returns -// the flags for this string -func FlagsFromString(from string) int { - flags := 0 - for _, flag := range strings.Split(strings.ToLower(from), ",") { - f, ok := flagFromString[strings.TrimSpace(flag)] - if ok { - flags = flags | f - } - } - return flags -} - -type byteArrayWriter []byte - -func (b *byteArrayWriter) Write(p []byte) (int, error) { - *b = append(*b, p...) - return len(p), nil -} - -// BaseLogger represent a basic logger for Gitea -type BaseLogger struct { - out io.WriteCloser - mu sync.Mutex - - Level Level `json:"level"` - StacktraceLevel Level `json:"stacktraceLevel"` - Flags int `json:"flags"` - Prefix string `json:"prefix"` - Colorize bool `json:"colorize"` - Expression string `json:"expression"` - regexp *regexp.Regexp -} - -func (b *BaseLogger) createLogger(out io.WriteCloser, level ...Level) { - b.mu.Lock() - defer b.mu.Unlock() - b.out = out - switch b.Flags { - case 0: - b.Flags = LstdFlags - case -1: - b.Flags = 0 - } - if len(level) > 0 { - b.Level = level[0] - } - b.createExpression() -} - -func (b *BaseLogger) createExpression() { - if len(b.Expression) > 0 { - var err error - b.regexp, err = regexp.Compile(b.Expression) - if err != nil { - b.regexp = nil - } - } -} - -// GetLevel returns the logging level for this logger -func (b *BaseLogger) GetLevel() Level { - return b.Level -} - -// GetStacktraceLevel returns the stacktrace logging level for this logger -func (b *BaseLogger) GetStacktraceLevel() Level { - return b.StacktraceLevel -} - -// Copy of cheap integer to fixed-width decimal to ascii from logger. -func itoa(buf *[]byte, i int, wid int) { - var b [20]byte - bp := len(b) - 1 - for i >= 10 || wid > 1 { - wid-- - q := i / 10 - b[bp] = byte('0' + i - q*10) - bp-- - i = q - } - // i < 10 - b[bp] = byte('0' + i) - *buf = append(*buf, b[bp:]...) -} - -func (b *BaseLogger) createMsg(buf *[]byte, event *Event) { - *buf = append(*buf, b.Prefix...) - t := event.time - if b.Flags&(Ldate|Ltime|Lmicroseconds) != 0 { - if b.Colorize { - *buf = append(*buf, fgCyanBytes...) - } - if b.Flags&LUTC != 0 { - t = t.UTC() - } - if b.Flags&Ldate != 0 { - year, month, day := t.Date() - itoa(buf, year, 4) - *buf = append(*buf, '/') - itoa(buf, int(month), 2) - *buf = append(*buf, '/') - itoa(buf, day, 2) - *buf = append(*buf, ' ') - } - if b.Flags&(Ltime|Lmicroseconds) != 0 { - hour, min, sec := t.Clock() - itoa(buf, hour, 2) - *buf = append(*buf, ':') - itoa(buf, min, 2) - *buf = append(*buf, ':') - itoa(buf, sec, 2) - if b.Flags&Lmicroseconds != 0 { - *buf = append(*buf, '.') - itoa(buf, t.Nanosecond()/1e3, 6) - } - *buf = append(*buf, ' ') - } - if b.Colorize { - *buf = append(*buf, resetBytes...) - } - - } - if b.Flags&(Lshortfile|Llongfile) != 0 { - if b.Colorize { - *buf = append(*buf, fgGreenBytes...) - } - file := event.filename - if b.Flags&Lmedfile == Lmedfile { - startIndex := len(file) - 20 - if startIndex > 0 { - file = "..." + file[startIndex:] - } - } else if b.Flags&Lshortfile != 0 { - startIndex := strings.LastIndexByte(file, '/') - if startIndex > 0 && startIndex < len(file) { - file = file[startIndex+1:] - } - } - *buf = append(*buf, file...) - *buf = append(*buf, ':') - itoa(buf, event.line, -1) - if b.Flags&(Lfuncname|Lshortfuncname) != 0 { - *buf = append(*buf, ':') - } else { - if b.Colorize { - *buf = append(*buf, resetBytes...) - } - *buf = append(*buf, ' ') - } - } - if b.Flags&(Lfuncname|Lshortfuncname) != 0 { - if b.Colorize { - *buf = append(*buf, fgGreenBytes...) - } - funcname := event.caller - if b.Flags&Lshortfuncname != 0 { - lastIndex := strings.LastIndexByte(funcname, '.') - if lastIndex > 0 && len(funcname) > lastIndex+1 { - funcname = funcname[lastIndex+1:] - } - } - *buf = append(*buf, funcname...) - if b.Colorize { - *buf = append(*buf, resetBytes...) - } - *buf = append(*buf, ' ') - - } - if b.Flags&(Llevel|Llevelinitial) != 0 { - level := strings.ToUpper(event.level.String()) - if b.Colorize { - *buf = append(*buf, levelToColor[event.level]...) - } - *buf = append(*buf, '[') - if b.Flags&Llevelinitial != 0 { - *buf = append(*buf, level[0]) - } else { - *buf = append(*buf, level...) - } - *buf = append(*buf, ']') - if b.Colorize { - *buf = append(*buf, resetBytes...) - } - *buf = append(*buf, ' ') - } - - var msg = []byte(event.msg) - if len(msg) > 0 && msg[len(msg)-1] == '\n' { - msg = msg[:len(msg)-1] - } - - pawMode := allowColor - if !b.Colorize { - pawMode = removeColor - } - - baw := byteArrayWriter(*buf) - (&protectedANSIWriter{ - w: &baw, - mode: pawMode, - }).Write([]byte(msg)) - *buf = baw - - if event.stacktrace != "" && b.StacktraceLevel <= event.level { - lines := bytes.Split([]byte(event.stacktrace), []byte("\n")) - if len(lines) > 1 { - for _, line := range lines { - *buf = append(*buf, "\n\t"...) - *buf = append(*buf, line...) - } - } - *buf = append(*buf, '\n') - } - *buf = append(*buf, '\n') -} - -// LogEvent logs the event to the internal writer -func (b *BaseLogger) LogEvent(event *Event) error { - if b.Level > event.level { - return nil - } - - b.mu.Lock() - defer b.mu.Unlock() - if !b.Match(event) { - return nil - } - var buf []byte - b.createMsg(&buf, event) - _, err := b.out.Write(buf) - return err -} - -// Match checks if the given event matches the logger's regexp expression -func (b *BaseLogger) Match(event *Event) bool { - if b.regexp == nil { - return true - } - if b.regexp.Match([]byte(fmt.Sprintf("%s:%d:%s", event.filename, event.line, event.caller))) { - return true - } - // Match on the non-colored msg - therefore strip out colors - var msg []byte - baw := byteArrayWriter(msg) - (&protectedANSIWriter{ - w: &baw, - mode: removeColor, - }).Write([]byte(event.msg)) - msg = baw - if b.regexp.Match(msg) { - return true - } - return false -} - -// Close the base logger -func (b *BaseLogger) Close() { - b.mu.Lock() - defer b.mu.Unlock() - if b.out != nil { - b.out.Close() - } -} - -// GetName returns empty for these provider loggers -func (b *BaseLogger) GetName() string { - return "" -} diff --git a/colors.go b/colors.go index fa8a664..0ec8ce4 100644 --- a/colors.go +++ b/colors.go @@ -7,6 +7,7 @@ package log import ( "fmt" "io" + "reflect" "strconv" "strings" ) @@ -195,11 +196,12 @@ normalLoop: lasti := i if c.mode == escapeAll { - for i < end && (bytes[i] >= ' ' || bytes[i] == '\n') { + for i < end && (bytes[i] >= ' ' || bytes[i] == '\n' || bytes[i] == '\t') { i++ } } else { - for i < end && bytes[i] >= ' ' { + // Allow tabs if we're not escaping everything + for i < end && (bytes[i] >= ' ' || bytes[i] == '\t') { i++ } } @@ -266,10 +268,43 @@ normalLoop: return totalWritten, nil } +// ColorSprintf returns a colored string from a format and arguments +// arguments will be wrapped in ColoredValues to protect against color spoofing +func ColorSprintf(format string, args ...interface{}) string { + if len(args) > 0 { + v := make([]interface{}, len(args)) + for i := 0; i < len(v); i++ { + v[i] = NewColoredValuePointer(&args[i]) + } + return fmt.Sprintf(format, v...) + } + return fmt.Sprintf(format) +} + +// ColorFprintf will write to the provided writer similar to ColorSprintf +func ColorFprintf(w io.Writer, format string, args ...interface{}) (int, error) { + if len(args) > 0 { + v := make([]interface{}, len(args)) + for i := 0; i < len(v); i++ { + v[i] = NewColoredValuePointer(&args[i]) + } + return fmt.Fprintf(w, format, v...) + } + return fmt.Fprintf(w, format) +} + +// ColorFormatted structs provide their own colored string when formatted with ColorSprintf +type ColorFormatted interface { + // ColorFormat provides the colored representation of the value + ColorFormat(s fmt.State) +} + +var colorFormattedType = reflect.TypeOf((*ColorFormatted)(nil)).Elem() + // ColoredValue will Color the provided value type ColoredValue struct { - ColorBytes *[]byte - ResetBytes *[]byte + colorBytes *[]byte + resetBytes *[]byte Value *interface{} } @@ -290,14 +325,14 @@ func NewColoredValuePointer(value *interface{}, color ...ColorAttribute) *Colore if len(color) > 0 { bytes := ColorBytes(color...) return &ColoredValue{ - ColorBytes: &bytes, - ResetBytes: &resetBytes, + colorBytes: &bytes, + resetBytes: &resetBytes, Value: value, } } return &ColoredValue{ - ColorBytes: &fgBoldBytes, - ResetBytes: &resetBytes, + colorBytes: &fgBoldBytes, + resetBytes: &resetBytes, Value: value, } @@ -310,17 +345,62 @@ func NewColoredValueBytes(value interface{}, colorBytes *[]byte) *ColoredValue { return val } return &ColoredValue{ - ColorBytes: colorBytes, - ResetBytes: &resetBytes, + colorBytes: colorBytes, + resetBytes: &resetBytes, Value: &value, } } -// Format will format the provided value and protect against ANSI spoofing within the value +// NewColoredIDValue is a helper function to create a ColoredValue from a Value +// The Value will be colored with FgCyan +// If a ColoredValue is provided it is not changed +func NewColoredIDValue(value interface{}) *ColoredValue { + return NewColoredValueBytes(&value, &fgCyanBytes) +} + +// Format will format the provided value and protect against ANSI color spoofing within the value +// If the wrapped value is ColorFormatted and the format is "%-v" then its ColorString will +// be used. It is presumed that this ColorString is safe. func (cv *ColoredValue) Format(s fmt.State, c rune) { - s.Write([]byte(*cv.ColorBytes)) + if c == 'v' && s.Flag('-') { + if val, ok := (*cv.Value).(ColorFormatted); ok { + val.ColorFormat(s) + return + } + v := reflect.ValueOf(*cv.Value) + t := v.Type() + + if reflect.PtrTo(t).Implements(colorFormattedType) { + vp := reflect.New(t) + vp.Elem().Set(v) + val := vp.Interface().(ColorFormatted) + val.ColorFormat(s) + return + } + } + s.Write([]byte(*cv.colorBytes)) fmt.Fprintf(&protectedANSIWriter{w: s}, fmtString(s, c), *(cv.Value)) - s.Write([]byte(*cv.ResetBytes)) + s.Write([]byte(*cv.resetBytes)) +} + +// SetColorBytes will allow a user to set the colorBytes of a colored value +func (cv *ColoredValue) SetColorBytes(colorBytes []byte) { + cv.colorBytes = &colorBytes +} + +// SetColorBytesPointer will allow a user to set the colorBytes pointer of a colored value +func (cv *ColoredValue) SetColorBytesPointer(colorBytes *[]byte) { + cv.colorBytes = colorBytes +} + +// SetResetBytes will allow a user to set the resetBytes pointer of a colored value +func (cv *ColoredValue) SetResetBytes(resetBytes []byte) { + cv.resetBytes = &resetBytes +} + +// SetResetBytesPointer will allow a user to set the resetBytes pointer of a colored value +func (cv *ColoredValue) SetResetBytesPointer(resetBytes *[]byte) { + cv.resetBytes = resetBytes } func fmtString(s fmt.State, c rune) string { diff --git a/colors_router.go b/colors_router.go new file mode 100644 index 0000000..e291a0d --- /dev/null +++ b/colors_router.go @@ -0,0 +1,83 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package log + +import ( + "time" +) + +var statusToColor = map[int][]byte{ + 100: ColorBytes(Bold), + 200: ColorBytes(FgGreen), + 300: ColorBytes(FgYellow), + 304: ColorBytes(FgCyan), + 400: ColorBytes(Bold, FgRed), + 401: ColorBytes(Bold, FgMagenta), + 403: ColorBytes(Bold, FgMagenta), + 500: ColorBytes(Bold, BgRed), +} + +// ColoredStatus addes colors for HTTP status +func ColoredStatus(status int, s ...string) *ColoredValue { + color, ok := statusToColor[status] + if !ok { + color, ok = statusToColor[(status/100)*100] + } + if !ok { + color = fgBoldBytes + } + if len(s) > 0 { + return NewColoredValueBytes(s[0], &color) + } + return NewColoredValueBytes(status, &color) +} + +var methodToColor = map[string][]byte{ + "GET": ColorBytes(FgBlue), + "POST": ColorBytes(FgGreen), + "DELETE": ColorBytes(FgRed), + "PATCH": ColorBytes(FgCyan), + "PUT": ColorBytes(FgYellow, Faint), + "HEAD": ColorBytes(FgBlue, Faint), +} + +// ColoredMethod addes colors for HtTP methos on log +func ColoredMethod(method string) *ColoredValue { + color, ok := methodToColor[method] + if !ok { + return NewColoredValueBytes(method, &fgBoldBytes) + } + return NewColoredValueBytes(method, &color) +} + +var ( + durations = []time.Duration{ + 10 * time.Millisecond, + 100 * time.Millisecond, + 1 * time.Second, + 5 * time.Second, + 10 * time.Second, + } + + durationColors = [][]byte{ + ColorBytes(FgGreen), + ColorBytes(Bold), + ColorBytes(FgYellow), + ColorBytes(FgRed, Bold), + ColorBytes(BgRed), + } + + wayTooLong = ColorBytes(BgMagenta) +) + +// ColoredTime addes colors for time on log +func ColoredTime(duration time.Duration) *ColoredValue { + for i, k := range durations { + if duration < k { + return NewColoredValueBytes(duration, &durationColors[i]) + } + } + return NewColoredValueBytes(duration, &wayTooLong) +} diff --git a/conn.go b/conn.go index d48bb4b..bd76855 100644 --- a/conn.go +++ b/conn.go @@ -77,7 +77,7 @@ func (i *connWriter) connect() error { // ConnLogger implements LoggerProvider. // it writes messages in keep-live tcp connection. type ConnLogger struct { - BaseLogger + WriterLogger ReconnectOnMsg bool `json:"reconnectOnMsg"` Reconnect bool `json:"reconnect"` Net string `json:"net"` @@ -98,7 +98,7 @@ func (log *ConnLogger) Init(jsonconfig string) error { if err != nil { return err } - log.createLogger(&connWriter{ + log.NewWriterLogger(&connWriter{ ReconnectOnMsg: log.ReconnectOnMsg, Reconnect: log.Reconnect, Net: log.Net, diff --git a/console.go b/console.go index cf2dfa4..6cfca8a 100644 --- a/console.go +++ b/console.go @@ -34,14 +34,14 @@ func (n *nopWriteCloser) Close() error { // ConsoleLogger implements LoggerProvider and writes messages to terminal. type ConsoleLogger struct { - BaseLogger + WriterLogger Stderr bool `json:"stderr"` } // NewConsoleLogger create ConsoleLogger returning as LoggerProvider. func NewConsoleLogger() LoggerProvider { log := &ConsoleLogger{} - log.createLogger(&nopWriteCloser{ + log.NewWriterLogger(&nopWriteCloser{ w: os.Stdout, }) return log @@ -55,11 +55,11 @@ func (log *ConsoleLogger) Init(config string) error { return err } if log.Stderr { - log.createLogger(&nopWriteCloser{ + log.NewWriterLogger(&nopWriteCloser{ w: os.Stderr, }) } else { - log.createLogger(log.out) + log.NewWriterLogger(log.out) } return nil } diff --git a/file.go b/file.go index 5c219c3..cdda85d 100644 --- a/file.go +++ b/file.go @@ -20,7 +20,7 @@ import ( // FileLogger implements LoggerProvider. // It writes messages by lines limit, file size limit, or time frequency. type FileLogger struct { - BaseLogger + WriterLogger mw *MuxWriter // The opened file Filename string `json:"filename"` @@ -106,7 +106,7 @@ func (log *FileLogger) Init(config string) error { return errors.New("config must have filename") } // set MuxWriter as Logger's io.Writer - log.createLogger(log.mw) + log.NewWriterLogger(log.mw) return log.StartLogger() } diff --git a/file_test.go b/file_test.go index 9527069..648db6f 100644 --- a/file_test.go +++ b/file_test.go @@ -89,7 +89,6 @@ func TestFileLogger(t *testing.T) { assert.Equal(t, expected, string(logData)) event.level = DEBUG - expected = expected + "" fileLogger.LogEvent(&event) fileLogger.Flush() logData, err = ioutil.ReadFile(filename) @@ -97,7 +96,6 @@ func TestFileLogger(t *testing.T) { assert.Equal(t, expected, string(logData)) event.level = TRACE - expected = expected + "" fileLogger.LogEvent(&event) fileLogger.Flush() logData, err = ioutil.ReadFile(filename) diff --git a/flags.go b/flags.go new file mode 100644 index 0000000..928d42b --- /dev/null +++ b/flags.go @@ -0,0 +1,64 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package log + +import "strings" + +// These flags define which text to prefix to each log entry generated +// by the Logger. Bits are or'ed together to control what's printed. +// There is no control over the order they appear (the order listed +// here) or the format they present (as described in the comments). +// The prefix is followed by a colon only if more than time is stated +// is specified. For example, flags Ldate | Ltime +// produce, 2009/01/23 01:23:23 message. +// The standard is: +// 2009/01/23 01:23:23 ...a/logger/c/d.go:23:runtime.Caller() [I]: message +const ( + Ldate = 1 << iota // the date in the local time zone: 2009/01/23 + Ltime // the time in the local time zone: 01:23:23 + Lmicroseconds // microsecond resolution: 01:23:23.123123. assumes Ltime. + Llongfile // full file name and line number: /a/logger/c/d.go:23 + Lshortfile // final file name element and line number: d.go:23. overrides Llongfile + Lfuncname // function name of the caller: runtime.Caller() + Lshortfuncname // last part of the function name + LUTC // if Ldate or Ltime is set, use UTC rather than the local time zone + Llevelinitial // Initial character of the provided level in brackets eg. [I] for info + Llevel // Provided level in brackets [INFO] + + // Last 20 characters of the filename + Lmedfile = Lshortfile | Llongfile + + // LstdFlags is the initial value for the standard logger + LstdFlags = Ldate | Ltime | Lmedfile | Lshortfuncname | Llevelinitial +) + +var flagFromString = map[string]int{ + "none": 0, + "date": Ldate, + "time": Ltime, + "microseconds": Lmicroseconds, + "longfile": Llongfile, + "shortfile": Lshortfile, + "funcname": Lfuncname, + "shortfuncname": Lshortfuncname, + "utc": LUTC, + "levelinitial": Llevelinitial, + "level": Llevel, + "medfile": Lmedfile, + "stdflags": LstdFlags, +} + +// FlagsFromString takes a comma separated list of flags and returns +// the flags for this string +func FlagsFromString(from string) int { + flags := 0 + for _, flag := range strings.Split(strings.ToLower(from), ",") { + f, ok := flagFromString[strings.TrimSpace(flag)] + if ok { + flags = flags | f + } + } + return flags +} diff --git a/go.mod b/go.mod index 1d9a7a4..1021923 100644 --- a/go.mod +++ b/go.mod @@ -1,9 +1,9 @@ -module code.gitea.io/log +module gitea.com/gitea/log go 1.12 require ( github.com/mattn/go-isatty v0.0.7 github.com/stretchr/testify v1.3.0 - golang.org/x/sys v0.0.0-20190318195719-6c81ef8f67ca + golang.org/x/sys v0.0.0-20190429190828-d89cdac9e872 ) diff --git a/go.sum b/go.sum index 81cd0f8..e5c1f8b 100644 --- a/go.sum +++ b/go.sum @@ -8,5 +8,5 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190318195719-6c81ef8f67ca h1:o2TLx1bGN3W+Ei0EMU5fShLupLmTOU95KvJJmfYhAzM= -golang.org/x/sys v0.0.0-20190318195719-6c81ef8f67ca/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190429190828-d89cdac9e872 h1:cGjJzUd8RgBw428LXP65YXni0aiGNA4Bl+ls8SmLOm8= +golang.org/x/sys v0.0.0-20190429190828-d89cdac9e872/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/log.go b/log.go index 85c3361..8698e9e 100644 --- a/log.go +++ b/log.go @@ -224,5 +224,5 @@ func (l *LoggerAsWriter) Log(msg string) { func init() { _, filename, _, _ := runtime.Caller(0) - prefix = strings.TrimSuffix(filename, "log/log.go") + prefix = strings.TrimSuffix(filename, "modules/log/log.go") } diff --git a/logger.go b/logger.go index 925ab02..9704ffd 100644 --- a/logger.go +++ b/logger.go @@ -69,11 +69,7 @@ func (l *Logger) Log(skip int, level Level, format string, v ...interface{}) err } msg := format if len(v) > 0 { - args := make([]interface{}, len(v)) - for i := 0; i < len(args); i++ { - args[i] = NewColoredValuePointer(&v[i]) - } - msg = fmt.Sprintf(format, args...) + msg = ColorSprintf(format, v...) } stack := "" if l.GetStacktraceLevel() <= level { diff --git a/provider.go b/provider.go index cb47b0b..b31bf39 100644 --- a/provider.go +++ b/provider.go @@ -10,13 +10,12 @@ type LoggerProvider interface { EventLogger } -// LoggerProviderFunc represents a func to generate logger provider -type LoggerProviderFunc func() LoggerProvider +type loggerProvider func() LoggerProvider -var providers = make(map[string]LoggerProviderFunc) +var providers = make(map[string]loggerProvider) // Register registers given logger provider to providers. -func Register(name string, log LoggerProviderFunc) { +func Register(name string, log loggerProvider) { if log == nil { panic("log: register provider is nil") } diff --git a/smtp.go b/smtp.go index 2e78d71..f77d716 100644 --- a/smtp.go +++ b/smtp.go @@ -31,7 +31,7 @@ func (s *smtpWriter) Close() error { // SMTPLogger implements LoggerProvider and is used to send emails via given SMTP-server. type SMTPLogger struct { - BaseLogger + WriterLogger Username string `json:"Username"` Password string `json:"password"` Host string `json:"host"` @@ -63,7 +63,7 @@ func (log *SMTPLogger) Init(jsonconfig string) error { if err != nil { return err } - log.createLogger(&smtpWriter{ + log.NewWriterLogger(&smtpWriter{ owner: log, }) log.sendMailFn = smtp.SendMail diff --git a/writer.go b/writer.go new file mode 100644 index 0000000..22ef0b9 --- /dev/null +++ b/writer.go @@ -0,0 +1,273 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package log + +import ( + "bytes" + "fmt" + "io" + "regexp" + "strings" + "sync" +) + +type byteArrayWriter []byte + +func (b *byteArrayWriter) Write(p []byte) (int, error) { + *b = append(*b, p...) + return len(p), nil +} + +// WriterLogger represent a basic logger for Gitea +type WriterLogger struct { + out io.WriteCloser + mu sync.Mutex + + Level Level `json:"level"` + StacktraceLevel Level `json:"stacktraceLevel"` + Flags int `json:"flags"` + Prefix string `json:"prefix"` + Colorize bool `json:"colorize"` + Expression string `json:"expression"` + regexp *regexp.Regexp +} + +// NewWriterLogger creates a new WriterLogger from the provided WriteCloser. +// Optionally the level can be changed at the same time. +func (logger *WriterLogger) NewWriterLogger(out io.WriteCloser, level ...Level) { + logger.mu.Lock() + defer logger.mu.Unlock() + logger.out = out + switch logger.Flags { + case 0: + logger.Flags = LstdFlags + case -1: + logger.Flags = 0 + } + if len(level) > 0 { + logger.Level = level[0] + } + logger.createExpression() +} + +func (logger *WriterLogger) createExpression() { + if len(logger.Expression) > 0 { + var err error + logger.regexp, err = regexp.Compile(logger.Expression) + if err != nil { + logger.regexp = nil + } + } +} + +// GetLevel returns the logging level for this logger +func (logger *WriterLogger) GetLevel() Level { + return logger.Level +} + +// GetStacktraceLevel returns the stacktrace logging level for this logger +func (logger *WriterLogger) GetStacktraceLevel() Level { + return logger.StacktraceLevel +} + +// Copy of cheap integer to fixed-width decimal to ascii from logger. +func itoa(buf *[]byte, i int, wid int) { + var logger [20]byte + bp := len(logger) - 1 + for i >= 10 || wid > 1 { + wid-- + q := i / 10 + logger[bp] = byte('0' + i - q*10) + bp-- + i = q + } + // i < 10 + logger[bp] = byte('0' + i) + *buf = append(*buf, logger[bp:]...) +} + +func (logger *WriterLogger) createMsg(buf *[]byte, event *Event) { + *buf = append(*buf, logger.Prefix...) + t := event.time + if logger.Flags&(Ldate|Ltime|Lmicroseconds) != 0 { + if logger.Colorize { + *buf = append(*buf, fgCyanBytes...) + } + if logger.Flags&LUTC != 0 { + t = t.UTC() + } + if logger.Flags&Ldate != 0 { + year, month, day := t.Date() + itoa(buf, year, 4) + *buf = append(*buf, '/') + itoa(buf, int(month), 2) + *buf = append(*buf, '/') + itoa(buf, day, 2) + *buf = append(*buf, ' ') + } + if logger.Flags&(Ltime|Lmicroseconds) != 0 { + hour, min, sec := t.Clock() + itoa(buf, hour, 2) + *buf = append(*buf, ':') + itoa(buf, min, 2) + *buf = append(*buf, ':') + itoa(buf, sec, 2) + if logger.Flags&Lmicroseconds != 0 { + *buf = append(*buf, '.') + itoa(buf, t.Nanosecond()/1e3, 6) + } + *buf = append(*buf, ' ') + } + if logger.Colorize { + *buf = append(*buf, resetBytes...) + } + + } + if logger.Flags&(Lshortfile|Llongfile) != 0 { + if logger.Colorize { + *buf = append(*buf, fgGreenBytes...) + } + file := event.filename + if logger.Flags&Lmedfile == Lmedfile { + startIndex := len(file) - 20 + if startIndex > 0 { + file = "..." + file[startIndex:] + } + } else if logger.Flags&Lshortfile != 0 { + startIndex := strings.LastIndexByte(file, '/') + if startIndex > 0 && startIndex < len(file) { + file = file[startIndex+1:] + } + } + *buf = append(*buf, file...) + *buf = append(*buf, ':') + itoa(buf, event.line, -1) + if logger.Flags&(Lfuncname|Lshortfuncname) != 0 { + *buf = append(*buf, ':') + } else { + if logger.Colorize { + *buf = append(*buf, resetBytes...) + } + *buf = append(*buf, ' ') + } + } + if logger.Flags&(Lfuncname|Lshortfuncname) != 0 { + if logger.Colorize { + *buf = append(*buf, fgGreenBytes...) + } + funcname := event.caller + if logger.Flags&Lshortfuncname != 0 { + lastIndex := strings.LastIndexByte(funcname, '.') + if lastIndex > 0 && len(funcname) > lastIndex+1 { + funcname = funcname[lastIndex+1:] + } + } + *buf = append(*buf, funcname...) + if logger.Colorize { + *buf = append(*buf, resetBytes...) + } + *buf = append(*buf, ' ') + + } + if logger.Flags&(Llevel|Llevelinitial) != 0 { + level := strings.ToUpper(event.level.String()) + if logger.Colorize { + *buf = append(*buf, levelToColor[event.level]...) + } + *buf = append(*buf, '[') + if logger.Flags&Llevelinitial != 0 { + *buf = append(*buf, level[0]) + } else { + *buf = append(*buf, level...) + } + *buf = append(*buf, ']') + if logger.Colorize { + *buf = append(*buf, resetBytes...) + } + *buf = append(*buf, ' ') + } + + var msg = []byte(event.msg) + if len(msg) > 0 && msg[len(msg)-1] == '\n' { + msg = msg[:len(msg)-1] + } + + pawMode := allowColor + if !logger.Colorize { + pawMode = removeColor + } + + baw := byteArrayWriter(*buf) + (&protectedANSIWriter{ + w: &baw, + mode: pawMode, + }).Write([]byte(msg)) + *buf = baw + + if event.stacktrace != "" && logger.StacktraceLevel <= event.level { + lines := bytes.Split([]byte(event.stacktrace), []byte("\n")) + if len(lines) > 1 { + for _, line := range lines { + *buf = append(*buf, "\n\t"...) + *buf = append(*buf, line...) + } + } + *buf = append(*buf, '\n') + } + *buf = append(*buf, '\n') +} + +// LogEvent logs the event to the internal writer +func (logger *WriterLogger) LogEvent(event *Event) error { + if logger.Level > event.level { + return nil + } + + logger.mu.Lock() + defer logger.mu.Unlock() + if !logger.Match(event) { + return nil + } + var buf []byte + logger.createMsg(&buf, event) + _, err := logger.out.Write(buf) + return err +} + +// Match checks if the given event matches the logger's regexp expression +func (logger *WriterLogger) Match(event *Event) bool { + if logger.regexp == nil { + return true + } + if logger.regexp.Match([]byte(fmt.Sprintf("%s:%d:%s", event.filename, event.line, event.caller))) { + return true + } + // Match on the non-colored msg - therefore strip out colors + var msg []byte + baw := byteArrayWriter(msg) + (&protectedANSIWriter{ + w: &baw, + mode: removeColor, + }).Write([]byte(event.msg)) + msg = baw + if logger.regexp.Match(msg) { + return true + } + return false +} + +// Close the base logger +func (logger *WriterLogger) Close() { + logger.mu.Lock() + defer logger.mu.Unlock() + if logger.out != nil { + logger.out.Close() + } +} + +// GetName returns empty for these provider loggers +func (logger *WriterLogger) GetName() string { + return "" +} diff --git a/base_test.go b/writer_test.go similarity index 98% rename from base_test.go rename to writer_test.go index 3686c26..886dd58 100644 --- a/base_test.go +++ b/writer_test.go @@ -38,7 +38,7 @@ func TestBaseLogger(t *testing.T) { }, } prefix := "TestPrefix " - b := BaseLogger{ + b := WriterLogger{ out: c, Level: INFO, Flags: LstdFlags | LUTC, @@ -115,7 +115,7 @@ func TestBaseLoggerDated(t *testing.T) { }, } prefix := "" - b := BaseLogger{ + b := WriterLogger{ out: c, Level: WARN, Flags: Ldate | Ltime | Lmicroseconds | Lshortfile | Llevel, @@ -195,14 +195,14 @@ func TestBaseLoggerMultiLineNoFlagsRegexp(t *testing.T) { }, } prefix := "" - b := BaseLogger{ + b := WriterLogger{ Level: DEBUG, StacktraceLevel: ERROR, Flags: -1, Prefix: prefix, Expression: "FILENAME", } - b.createLogger(c) + b.NewWriterLogger(c) location, _ := time.LoadLocation("EST") @@ -263,14 +263,14 @@ func TestBrokenRegexp(t *testing.T) { }, } - b := BaseLogger{ + b := WriterLogger{ Level: DEBUG, StacktraceLevel: ERROR, Flags: -1, Prefix: prefix, Expression: "\\", } - b.createLogger(c) + b.NewWriterLogger(c) assert.Empty(t, b.regexp) b.Close() assert.Equal(t, true, closed)