mirror of https://github.com/go-gitea/gitea.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
255 lines
6.3 KiB
255 lines
6.3 KiB
// Copyright 2023 The Gitea Authors. All rights reserved. |
|
// SPDX-License-Identifier: MIT |
|
|
|
package log |
|
|
|
import ( |
|
"bytes" |
|
"fmt" |
|
"strings" |
|
"time" |
|
) |
|
|
|
type Event struct { |
|
Time time.Time |
|
|
|
Caller string |
|
Filename string |
|
Line int |
|
|
|
Level Level |
|
|
|
MsgSimpleText string |
|
|
|
msgFormat string // the format and args is only valid in the caller's goroutine |
|
msgArgs []any // they are discarded before the event is passed to the writer's channel |
|
|
|
Stacktrace string |
|
} |
|
|
|
type EventFormatted struct { |
|
Origin *Event |
|
Msg any // the message formatted by the writer's formatter, the writer knows its type |
|
} |
|
|
|
type EventFormatter func(mode *WriterMode, event *Event, msgFormat string, msgArgs ...any) []byte |
|
|
|
type logStringFormatter struct { |
|
v LogStringer |
|
} |
|
|
|
var _ fmt.Formatter = logStringFormatter{} |
|
|
|
func (l logStringFormatter) Format(f fmt.State, verb rune) { |
|
if f.Flag('#') && verb == 'v' { |
|
_, _ = fmt.Fprintf(f, "%#v", l.v) |
|
return |
|
} |
|
_, _ = f.Write([]byte(l.v.LogString())) |
|
} |
|
|
|
// Copy of cheap integer to fixed-width decimal to ascii from logger. |
|
// TODO: legacy bugs: doesn't support negative number, overflow if wid it too large. |
|
func itoa(buf []byte, i, wid int) []byte { |
|
var s [20]byte |
|
bp := len(s) - 1 |
|
for i >= 10 || wid > 1 { |
|
wid-- |
|
q := i / 10 |
|
s[bp] = byte('0' + i - q*10) |
|
bp-- |
|
i = q |
|
} |
|
// i < 10 |
|
s[bp] = byte('0' + i) |
|
return append(buf, s[bp:]...) |
|
} |
|
|
|
func colorSprintf(colorize bool, format string, args ...any) string { |
|
hasColorValue := false |
|
for _, v := range args { |
|
if _, hasColorValue = v.(*ColoredValue); hasColorValue { |
|
break |
|
} |
|
} |
|
if colorize || !hasColorValue { |
|
return fmt.Sprintf(format, args...) |
|
} |
|
|
|
noColors := make([]any, len(args)) |
|
copy(noColors, args) |
|
for i, v := range args { |
|
if cv, ok := v.(*ColoredValue); ok { |
|
noColors[i] = cv.v |
|
} |
|
} |
|
return fmt.Sprintf(format, noColors...) |
|
} |
|
|
|
// EventFormatTextMessage makes the log message for a writer with its mode. This function is a copy of the original package |
|
func EventFormatTextMessage(mode *WriterMode, event *Event, msgFormat string, msgArgs ...any) []byte { |
|
buf := make([]byte, 0, 1024) |
|
buf = append(buf, mode.Prefix...) |
|
t := event.Time |
|
flags := mode.Flags.Bits() |
|
if flags&(Ldate|Ltime|Lmicroseconds) != 0 { |
|
if mode.Colorize { |
|
buf = append(buf, fgCyanBytes...) |
|
} |
|
if flags&LUTC != 0 { |
|
t = t.UTC() |
|
} |
|
if flags&Ldate != 0 { |
|
year, month, day := t.Date() |
|
buf = itoa(buf, year, 4) |
|
buf = append(buf, '/') |
|
buf = itoa(buf, int(month), 2) |
|
buf = append(buf, '/') |
|
buf = itoa(buf, day, 2) |
|
buf = append(buf, ' ') |
|
} |
|
if flags&(Ltime|Lmicroseconds) != 0 { |
|
hour, minNum, sec := t.Clock() |
|
buf = itoa(buf, hour, 2) |
|
buf = append(buf, ':') |
|
buf = itoa(buf, minNum, 2) |
|
buf = append(buf, ':') |
|
buf = itoa(buf, sec, 2) |
|
if flags&Lmicroseconds != 0 { |
|
buf = append(buf, '.') |
|
buf = itoa(buf, t.Nanosecond()/1e3, 6) |
|
} |
|
buf = append(buf, ' ') |
|
} |
|
if mode.Colorize { |
|
buf = append(buf, resetBytes...) |
|
} |
|
} |
|
if flags&(Lshortfile|Llongfile) != 0 && event.Filename != "" { |
|
if mode.Colorize { |
|
buf = append(buf, fgGreenBytes...) |
|
} |
|
file := event.Filename |
|
if flags&Lmedfile == Lmedfile { |
|
fileLen := len(file) |
|
const softLimit = 20 |
|
if fileLen > softLimit { |
|
slashIndex := strings.LastIndexByte(file[:fileLen-softLimit], '/') |
|
if slashIndex != -1 { |
|
file = ".../" + file[slashIndex+1:] |
|
} |
|
} |
|
} else if flags&Lshortfile != 0 { |
|
startIndex := strings.LastIndexByte(file, '/') |
|
if startIndex > 0 && startIndex < len(file) { |
|
file = file[startIndex+1:] |
|
} |
|
} |
|
buf = append(buf, file...) |
|
buf = append(buf, ':') |
|
buf = itoa(buf, event.Line, -1) |
|
if flags&(Lfuncname|Lshortfuncname) != 0 { |
|
buf = append(buf, ':') |
|
} else { |
|
if mode.Colorize { |
|
buf = append(buf, resetBytes...) |
|
} |
|
buf = append(buf, ' ') |
|
} |
|
} |
|
if flags&(Lfuncname|Lshortfuncname) != 0 { |
|
if mode.Colorize { |
|
buf = append(buf, fgGreenBytes...) |
|
} |
|
funcName := event.Caller |
|
shortFuncName := funcName |
|
if flags&Lshortfuncname != 0 { |
|
// funcName = "code.gitea.io/gitea/modules/foo/bar.MyFunc.func1.2()" |
|
slashPos := strings.LastIndexByte(funcName, '/') |
|
dotPos := strings.IndexByte(funcName[slashPos+1:], '.') |
|
if dotPos > 0 { |
|
// shortFuncName = "MyFunc.func1.2()" |
|
shortFuncName = funcName[slashPos+1+dotPos+1:] |
|
if strings.Contains(shortFuncName, ".") { |
|
shortFuncName = strings.ReplaceAll(shortFuncName, ".func", ".") |
|
} |
|
} |
|
funcName = shortFuncName |
|
} |
|
buf = append(buf, funcName...) |
|
if mode.Colorize { |
|
buf = append(buf, resetBytes...) |
|
} |
|
buf = append(buf, ' ') |
|
} |
|
|
|
if flags&(Llevel|Llevelinitial) != 0 { |
|
level := strings.ToUpper(event.Level.String()) |
|
if mode.Colorize { |
|
buf = append(buf, ColorBytes(levelToColor[event.Level]...)...) |
|
} |
|
buf = append(buf, '[') |
|
if flags&Llevelinitial != 0 { |
|
buf = append(buf, level[0]) |
|
} else { |
|
buf = append(buf, level...) |
|
} |
|
buf = append(buf, ']') |
|
if mode.Colorize { |
|
buf = append(buf, resetBytes...) |
|
} |
|
buf = append(buf, ' ') |
|
} |
|
|
|
var msg []byte |
|
|
|
// if the log needs colorizing, do it |
|
if mode.Colorize && len(msgArgs) > 0 { |
|
hasColorValue := false |
|
for _, v := range msgArgs { |
|
if _, hasColorValue = v.(*ColoredValue); hasColorValue { |
|
break |
|
} |
|
} |
|
if hasColorValue { |
|
msg = fmt.Appendf(nil, msgFormat, msgArgs...) |
|
} |
|
} |
|
// try to re-use the pre-formatted simple text message |
|
if len(msg) == 0 { |
|
msg = []byte(event.MsgSimpleText) |
|
} |
|
// if still no message, do the normal Sprintf for the message |
|
if len(msg) == 0 { |
|
msg = []byte(colorSprintf(mode.Colorize, msgFormat, msgArgs...)) |
|
} |
|
// remove at most one trailing new line |
|
if len(msg) > 0 && msg[len(msg)-1] == '\n' { |
|
msg = msg[:len(msg)-1] |
|
} |
|
|
|
if flags&Lgopid == Lgopid { |
|
deprecatedGoroutinePid := "no-gopid" // use a dummy value to avoid breaking the log format |
|
buf = append(buf, '[') |
|
if mode.Colorize { |
|
buf = append(buf, ColorBytes(FgHiYellow)...) |
|
} |
|
buf = append(buf, deprecatedGoroutinePid...) |
|
if mode.Colorize { |
|
buf = append(buf, resetBytes...) |
|
} |
|
buf = append(buf, ']', ' ') |
|
} |
|
buf = append(buf, msg...) |
|
|
|
if event.Stacktrace != "" && mode.StacktraceLevel <= event.Level { |
|
lines := bytes.SplitSeq([]byte(event.Stacktrace), []byte("\n")) |
|
for line := range lines { |
|
buf = append(buf, "\n\t"...) |
|
buf = append(buf, line...) |
|
} |
|
buf = append(buf, '\n') |
|
} |
|
buf = append(buf, '\n') |
|
return buf |
|
}
|
|
|