// 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 "" }