summaryrefslogtreecommitdiffstats
path: root/modules/log/logger_impl.go
blob: d38c6516ed5cefe8d143078539e26320c1cb9264 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package log

import (
	"context"
	"runtime"
	"strings"
	"sync"
	"sync/atomic"
	"time"

	"code.gitea.io/gitea/modules/json"
	"code.gitea.io/gitea/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 writers
func (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 writers
func (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 shared
func (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 shared
func (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 flushed
func (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 flushed
func (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 exits
func (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 writers
func (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 = 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
}