240 lines · 6.2 KB · Go
RawDownload
// Copyright 2023 The Gitea Authors. All rights reserved.// SPDX-License-Identifier: MIT package log import (	"context"	"runtime"	"strings"	"sync"	"sync/atomic"	"time" 	"forgejo.org/modules/json"	"forgejo.org/modules/util") type LoggerImpl struct {	LevelLogger 	ctx       context.Context	ctxCancel context.CancelFunc 	level           atomic.Int32	stacktraceLevel atomic.Int32 	eventWriterMu sync.RWMutex	eventWriters  map[string]EventWriter} var (	_ BaseLogger  = (*LoggerImpl)(nil)	_ LevelLogger = (*LoggerImpl)(nil)) // SendLogEvent sends a log event to all writersfunc (l *LoggerImpl) SendLogEvent(event *Event) {	l.eventWriterMu.RLock()	defer l.eventWriterMu.RUnlock() 	if len(l.eventWriters) == 0 {		FallbackErrorf("[no logger writer]: %s", event.MsgSimpleText)		return	} 	// the writers have their own goroutines, the message arguments (with Stringer) shouldn't be used in other goroutines	// so the event message must be formatted here	msgFormat, msgArgs := event.msgFormat, event.msgArgs	event.msgFormat, event.msgArgs = "(already processed by formatters)", nil 	for _, w := range l.eventWriters {		if event.Level < w.GetLevel() {			continue		}		formatted := &EventFormatted{			Origin: event,			Msg:    w.Base().FormatMessage(w.Base().Mode, event, msgFormat, msgArgs...),		}		select {		case w.Base().Queue <- formatted:		default:			bs, _ := json.Marshal(event)			FallbackErrorf("log writer %q queue is full, event: %v", w.GetWriterName(), string(bs))		}	}} // syncLevelInternal syncs the level of the logger with the levels of the writersfunc (l *LoggerImpl) syncLevelInternal() {	lowestLevel := NONE	for _, w := range l.eventWriters {		if w.GetLevel() < lowestLevel {			lowestLevel = w.GetLevel()		}	}	l.level.Store(int32(lowestLevel)) 	lowestLevel = NONE	for _, w := range l.eventWriters {		if w.Base().Mode.StacktraceLevel < lowestLevel {			lowestLevel = w.GetLevel()		}	}	l.stacktraceLevel.Store(int32(lowestLevel))} // removeWriterInternal removes a writer from the logger, and stops it if it's not sharedfunc (l *LoggerImpl) removeWriterInternal(w EventWriter) {	if !w.Base().shared {		eventWriterStopWait(w) // only stop non-shared writers, shared writers are managed by the manager	}	delete(l.eventWriters, w.GetWriterName())} // AddWriters adds writers to the logger, and starts them. Existing writers will be replaced by new ones.func (l *LoggerImpl) AddWriters(writer ...EventWriter) {	l.eventWriterMu.Lock()	defer l.eventWriterMu.Unlock()	l.addWritersInternal(writer...)} func (l *LoggerImpl) addWritersInternal(writer ...EventWriter) {	for _, w := range writer {		if old, ok := l.eventWriters[w.GetWriterName()]; ok {			l.removeWriterInternal(old)		}	} 	for _, w := range writer {		l.eventWriters[w.GetWriterName()] = w		eventWriterStartGo(l.ctx, w, false)	} 	l.syncLevelInternal()} // RemoveWriter removes a writer from the logger, and the writer is closed and flushed if it is not sharedfunc (l *LoggerImpl) RemoveWriter(modeName string) error {	l.eventWriterMu.Lock()	defer l.eventWriterMu.Unlock() 	w, ok := l.eventWriters[modeName]	if !ok {		return util.ErrNotExist	} 	l.removeWriterInternal(w)	l.syncLevelInternal()	return nil} // ReplaceAllWriters replaces all writers from the logger, non-shared writers are closed and flushedfunc (l *LoggerImpl) ReplaceAllWriters(writer ...EventWriter) {	l.eventWriterMu.Lock()	defer l.eventWriterMu.Unlock() 	for _, w := range l.eventWriters {		l.removeWriterInternal(w)	}	l.eventWriters = map[string]EventWriter{}	l.addWritersInternal(writer...)} // DumpWriters dumps the writers as a JSON map, it's used for debugging and display purposes.func (l *LoggerImpl) DumpWriters() map[string]any {	l.eventWriterMu.RLock()	defer l.eventWriterMu.RUnlock() 	writers := make(map[string]any, len(l.eventWriters))	for k, w := range l.eventWriters {		bs, err := json.Marshal(w.Base().Mode)		if err != nil {			FallbackErrorf("marshal writer %q to dump failed: %v", k, err)			continue		}		m := map[string]any{}		_ = json.Unmarshal(bs, &m)		m["WriterType"] = w.GetWriterType()		writers[k] = m	}	return writers} // Close closes the logger, non-shared writers are closed and flushedfunc (l *LoggerImpl) Close() {	l.ReplaceAllWriters()	l.ctxCancel()} // IsEnabled returns true if the logger is enabled: it has a working level and has writers// Fatal is not considered as enabled, because it's a special case and the process just exitsfunc (l *LoggerImpl) IsEnabled() bool {	l.eventWriterMu.RLock()	defer l.eventWriterMu.RUnlock()	return l.level.Load() < int32(FATAL) && len(l.eventWriters) > 0} // Log prepares the log event, if the level matches, the event will be sent to the writersfunc (l *LoggerImpl) Log(skip int, level Level, format string, logArgs ...any) {	if Level(l.level.Load()) > level {		return	} 	event := &Event{		Time:   time.Now(),		Level:  level,		Caller: "?()",	} 	pc, filename, line, ok := runtime.Caller(skip + 1)	if ok {		fn := runtime.FuncForPC(pc)		if fn != nil {			event.Caller = strings.TrimSuffix(fn.Name(), "[...]") + "()"		}	}	event.Filename, event.Line = strings.TrimPrefix(filename, projectPackagePrefix), line 	if l.stacktraceLevel.Load() <= int32(level) {		event.Stacktrace = Stack(skip + 1)	} 	labels := getGoroutineLabels()	if labels != nil {		event.GoroutinePid = labels["pid"]	} 	// get a simple text message without color	msgArgs := make([]any, len(logArgs))	copy(msgArgs, logArgs) 	// handle LogStringer values	for i, v := range msgArgs {		if cv, ok := v.(*ColoredValue); ok {			if s, ok := cv.v.(LogStringer); ok {				cv.v = logStringFormatter{v: s}			}		} else if s, ok := v.(LogStringer); ok {			msgArgs[i] = logStringFormatter{v: s}		}	} 	event.MsgSimpleText = colorSprintf(false, format, msgArgs...)	event.msgFormat = format	event.msgArgs = msgArgs	l.SendLogEvent(event)} func (l *LoggerImpl) GetLevel() Level {	return Level(l.level.Load())} func NewLoggerWithWriters(ctx context.Context, name string, writer ...EventWriter) *LoggerImpl {	l := &LoggerImpl{}	l.ctx, l.ctxCancel = newProcessTypedContext(ctx, "Logger: "+name)	l.LevelLogger = BaseLoggerToGeneralLogger(l)	l.eventWriters = map[string]EventWriter{}	l.AddWriters(writer...)	return l}