summaryrefslogtreecommitdiffstats
path: root/modules/log/base.go
diff options
context:
space:
mode:
Diffstat (limited to 'modules/log/base.go')
-rw-r--r--modules/log/base.go328
1 files changed, 328 insertions, 0 deletions
diff --git a/modules/log/base.go b/modules/log/base.go
new file mode 100644
index 0000000000..5577737e04
--- /dev/null
+++ b/modules/log/base.go
@@ -0,0 +1,328 @@
+// 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 ""
+}