gitea
Зеркало из https://github.com/go-gitea/gitea
1// Copyright 2023 The Gitea Authors. All rights reserved.
2// SPDX-License-Identifier: MIT
3
4package log
5
6import (
7"context"
8"runtime"
9"strings"
10"sync"
11"sync/atomic"
12"time"
13
14"code.gitea.io/gitea/modules/json"
15"code.gitea.io/gitea/modules/util"
16)
17
18type LoggerImpl struct {
19LevelLogger
20
21ctx context.Context
22ctxCancel context.CancelFunc
23
24level atomic.Int32
25stacktraceLevel atomic.Int32
26
27eventWriterMu sync.RWMutex
28eventWriters map[string]EventWriter
29}
30
31var (
32_ BaseLogger = (*LoggerImpl)(nil)
33_ LevelLogger = (*LoggerImpl)(nil)
34)
35
36// SendLogEvent sends a log event to all writers
37func (l *LoggerImpl) SendLogEvent(event *Event) {
38l.eventWriterMu.RLock()
39defer l.eventWriterMu.RUnlock()
40
41if len(l.eventWriters) == 0 {
42FallbackErrorf("[no logger writer]: %s", event.MsgSimpleText)
43return
44}
45
46// the writers have their own goroutines, the message arguments (with Stringer) shouldn't be used in other goroutines
47// so the event message must be formatted here
48msgFormat, msgArgs := event.msgFormat, event.msgArgs
49event.msgFormat, event.msgArgs = "(already processed by formatters)", nil
50
51for _, w := range l.eventWriters {
52if event.Level < w.GetLevel() {
53continue
54}
55formatted := &EventFormatted{
56Origin: event,
57Msg: w.Base().FormatMessage(w.Base().Mode, event, msgFormat, msgArgs...),
58}
59select {
60case w.Base().Queue <- formatted:
61default:
62bs, _ := json.Marshal(event)
63FallbackErrorf("log writer %q queue is full, event: %v", w.GetWriterName(), string(bs))
64}
65}
66}
67
68// syncLevelInternal syncs the level of the logger with the levels of the writers
69func (l *LoggerImpl) syncLevelInternal() {
70lowestLevel := NONE
71for _, w := range l.eventWriters {
72if w.GetLevel() < lowestLevel {
73lowestLevel = w.GetLevel()
74}
75}
76l.level.Store(int32(lowestLevel))
77
78lowestLevel = NONE
79for _, w := range l.eventWriters {
80if w.Base().Mode.StacktraceLevel < lowestLevel {
81lowestLevel = w.GetLevel()
82}
83}
84l.stacktraceLevel.Store(int32(lowestLevel))
85}
86
87// removeWriterInternal removes a writer from the logger, and stops it if it's not shared
88func (l *LoggerImpl) removeWriterInternal(w EventWriter) {
89if !w.Base().shared {
90eventWriterStopWait(w) // only stop non-shared writers, shared writers are managed by the manager
91}
92delete(l.eventWriters, w.GetWriterName())
93}
94
95// AddWriters adds writers to the logger, and starts them. Existing writers will be replaced by new ones.
96func (l *LoggerImpl) AddWriters(writer ...EventWriter) {
97l.eventWriterMu.Lock()
98defer l.eventWriterMu.Unlock()
99l.addWritersInternal(writer...)
100}
101
102func (l *LoggerImpl) addWritersInternal(writer ...EventWriter) {
103for _, w := range writer {
104if old, ok := l.eventWriters[w.GetWriterName()]; ok {
105l.removeWriterInternal(old)
106}
107}
108
109for _, w := range writer {
110l.eventWriters[w.GetWriterName()] = w
111eventWriterStartGo(l.ctx, w, false)
112}
113
114l.syncLevelInternal()
115}
116
117// RemoveWriter removes a writer from the logger, and the writer is closed and flushed if it is not shared
118func (l *LoggerImpl) RemoveWriter(modeName string) error {
119l.eventWriterMu.Lock()
120defer l.eventWriterMu.Unlock()
121
122w, ok := l.eventWriters[modeName]
123if !ok {
124return util.ErrNotExist
125}
126
127l.removeWriterInternal(w)
128l.syncLevelInternal()
129return nil
130}
131
132// ReplaceAllWriters replaces all writers from the logger, non-shared writers are closed and flushed
133func (l *LoggerImpl) ReplaceAllWriters(writer ...EventWriter) {
134l.eventWriterMu.Lock()
135defer l.eventWriterMu.Unlock()
136
137for _, w := range l.eventWriters {
138l.removeWriterInternal(w)
139}
140l.eventWriters = map[string]EventWriter{}
141l.addWritersInternal(writer...)
142}
143
144// DumpWriters dumps the writers as a JSON map, it's used for debugging and display purposes.
145func (l *LoggerImpl) DumpWriters() map[string]any {
146l.eventWriterMu.RLock()
147defer l.eventWriterMu.RUnlock()
148
149writers := make(map[string]any, len(l.eventWriters))
150for k, w := range l.eventWriters {
151bs, err := json.Marshal(w.Base().Mode)
152if err != nil {
153FallbackErrorf("marshal writer %q to dump failed: %v", k, err)
154continue
155}
156m := map[string]any{}
157_ = json.Unmarshal(bs, &m)
158m["WriterType"] = w.GetWriterType()
159writers[k] = m
160}
161return writers
162}
163
164// Close closes the logger, non-shared writers are closed and flushed
165func (l *LoggerImpl) Close() {
166l.ReplaceAllWriters()
167l.ctxCancel()
168}
169
170// IsEnabled returns true if the logger is enabled: it has a working level and has writers
171// Fatal is not considered as enabled, because it's a special case and the process just exits
172func (l *LoggerImpl) IsEnabled() bool {
173l.eventWriterMu.RLock()
174defer l.eventWriterMu.RUnlock()
175return l.level.Load() < int32(FATAL) && len(l.eventWriters) > 0
176}
177
178// Log prepares the log event, if the level matches, the event will be sent to the writers
179func (l *LoggerImpl) Log(skip int, level Level, format string, logArgs ...any) {
180if Level(l.level.Load()) > level {
181return
182}
183
184event := &Event{
185Time: time.Now(),
186Level: level,
187Caller: "?()",
188}
189
190pc, filename, line, ok := runtime.Caller(skip + 1)
191if ok {
192fn := runtime.FuncForPC(pc)
193if fn != nil {
194event.Caller = fn.Name() + "()"
195}
196}
197event.Filename, event.Line = strings.TrimPrefix(filename, projectPackagePrefix), line
198
199if l.stacktraceLevel.Load() <= int32(level) {
200event.Stacktrace = Stack(skip + 1)
201}
202
203labels := getGoroutineLabels()
204if labels != nil {
205event.GoroutinePid = labels["pid"]
206}
207
208// get a simple text message without color
209msgArgs := make([]any, len(logArgs))
210copy(msgArgs, logArgs)
211
212// handle LogStringer values
213for i, v := range msgArgs {
214if cv, ok := v.(*ColoredValue); ok {
215if s, ok := cv.v.(LogStringer); ok {
216cv.v = logStringFormatter{v: s}
217}
218} else if s, ok := v.(LogStringer); ok {
219msgArgs[i] = logStringFormatter{v: s}
220}
221}
222
223event.MsgSimpleText = colorSprintf(false, format, msgArgs...)
224event.msgFormat = format
225event.msgArgs = msgArgs
226l.SendLogEvent(event)
227}
228
229func (l *LoggerImpl) GetLevel() Level {
230return Level(l.level.Load())
231}
232
233func NewLoggerWithWriters(ctx context.Context, name string, writer ...EventWriter) *LoggerImpl {
234l := &LoggerImpl{}
235l.ctx, l.ctxCancel = newProcessTypedContext(ctx, "Logger: "+name)
236l.LevelLogger = BaseLoggerToGeneralLogger(l)
237l.eventWriters = map[string]EventWriter{}
238l.AddWriters(writer...)
239return l
240}
241