GitRoot
Craft your forge, Build your project, Grow your community freely
1// SPDX-FileCopyrightText: 2025 Romain Maneschi <romain@gitroot.dev>
2//
3// SPDX-License-Identifier: EUPL-1.2
4
5package logger
6
7import (
8 "context"
9 "crypto/rand"
10 "encoding/hex"
11 "fmt"
12 "log"
13 "log/slog"
14 "os"
15)
16
17const (
18 FS_LOGGER_NAME LoggerName = "GitRootFs"
19 SSH_SERVER_LOGGER_NAME LoggerName = "ServerSsh"
20 HTTP_SERVER_LOGGER_NAME LoggerName = "ServerHttp"
21 FS_STORER_LOGGER_NAME LoggerName = "GitRootFsStorer"
22 FS_PLUGIN LoggerName = "FsPlugin"
23 REPOSITORY_MANAGER LoggerName = "RepositoryManager"
24 PLUGIN_MANAGER LoggerName = "PluginManager"
25 BACKGROUND_MANAGER LoggerName = "BackgroundManager"
26 WASM LoggerName = "Wasm"
27 EXEC LoggerName = "ExecManager"
28 REPOSITORY_LOCK LoggerName = "RepositoryLock"
29 SCHEDULER_MANAGER LoggerName = "SchedulerManager"
30 SFTP LoggerName = "Sftp"
31 ITERATOR LoggerName = "Iterator"
32)
33
34var levelByName = map[LoggerName]slog.Level{
35 FS_LOGGER_NAME: slog.LevelWarn,
36 SSH_SERVER_LOGGER_NAME: slog.LevelWarn,
37 HTTP_SERVER_LOGGER_NAME: slog.LevelWarn,
38 FS_STORER_LOGGER_NAME: slog.LevelWarn,
39 FS_PLUGIN: slog.LevelWarn,
40 REPOSITORY_MANAGER: slog.LevelWarn,
41 PLUGIN_MANAGER: slog.LevelWarn,
42 BACKGROUND_MANAGER: slog.LevelWarn,
43 WASM: slog.LevelWarn,
44 EXEC: slog.LevelWarn,
45 REPOSITORY_LOCK: slog.LevelWarn,
46 SCHEDULER_MANAGER: slog.LevelWarn,
47 SFTP: slog.LevelWarn,
48 ITERATOR: slog.LevelWarn,
49}
50
51type LoggerName string
52
53type Logger struct {
54 c context.Context
55 l *slog.Logger
56 level slog.Level
57 name LoggerName
58}
59
60type LoggerPair struct {
61 key string
62 value any
63}
64
65func NewLogger(name LoggerName) *Logger {
66 return NewLoggerCtx(name, context.Background())
67}
68
69func NewLoggerCtx(name LoggerName, ctx context.Context) *Logger {
70 var level = slog.LevelWarn
71 if l, ok := levelByName[name]; ok {
72 level = l
73 }
74 log := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: level}))
75 l := &Logger{
76 c: ctx,
77 l: log,
78 level: level,
79 name: name,
80 }
81 return l.With("name", string(name))
82}
83
84func GenerateRandomID() string {
85 bytes := make([]byte, 16)
86 if _, err := rand.Read(bytes); err != nil {
87 panic(err)
88 }
89 return hex.EncodeToString(bytes)
90}
91
92func NewLoggerPair(key string, value any) LoggerPair {
93 return LoggerPair{
94 key: key,
95 value: value,
96 }
97}
98
99type Loggable interface {
100 Log() string
101}
102
103func NewLoggerPairNamedSlice[T Loggable](key string, value []T) LoggerPair {
104 names := make([]string, len(value))
105 for i, n := range value {
106 names[i] = n.Log()
107 }
108 return LoggerPair{
109 key: key,
110 value: names,
111 }
112}
113
114func (l *Logger) NewSubLogger(name string) *Logger {
115 return l.NewSubLoggerCtx(name, l.c)
116}
117
118func (l *Logger) NewSubLoggerCtx(name string, ctx context.Context) *Logger {
119 subName := fmt.Sprintf("%s::%s", l.name, name)
120 log := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: l.level}))
121 sub := &Logger{
122 c: ctx,
123 l: log,
124 level: l.level,
125 name: LoggerName(subName),
126 }
127 return sub.With("name", subName)
128}
129
130func (l *Logger) With(key string, value string) *Logger {
131 l.l = slog.New(l.l.Handler().WithAttrs([]slog.Attr{{Key: key, Value: slog.StringValue(value)}}))
132 return l
133}
134
135func (l *Logger) Debug(msg string, attr ...LoggerPair) *Logger {
136 attrs := make([]slog.Attr, len(attr))
137 for i, a := range attr {
138 attrs[i] = slog.Attr{Key: a.key, Value: slog.AnyValue(a.value)}
139 }
140 if caller, ok := extractCaller(l.c); ok {
141 attrs = append(attrs, slog.Attr{Key: "caller", Value: slog.StringValue(caller)})
142 }
143 l.l.LogAttrs(l.c, slog.LevelDebug, msg, attrs...)
144 return l
145}
146
147func (l *Logger) Info(msg string, attr ...LoggerPair) *Logger {
148 attrs := make([]slog.Attr, len(attr))
149 for i, a := range attr {
150 attrs[i] = slog.Attr{Key: a.key, Value: slog.AnyValue(a.value)}
151 }
152 if caller, ok := extractCaller(l.c); ok {
153 attrs = append(attrs, slog.Attr{Key: "caller", Value: slog.StringValue(caller)})
154 }
155 l.l.LogAttrs(l.c, slog.LevelInfo, msg, attrs...)
156 return l
157}
158
159func (l *Logger) Warn(msg string, attr ...LoggerPair) *Logger {
160 attrs := make([]slog.Attr, len(attr))
161 for i, a := range attr {
162 attrs[i] = slog.Attr{Key: a.key, Value: slog.AnyValue(a.value)}
163 }
164 l.l.LogAttrs(l.c, slog.LevelWarn, msg, attrs...)
165 return l
166}
167
168func (l *Logger) Error(msg string, err error, attr ...LoggerPair) *Logger {
169 attrs := make([]slog.Attr, len(attr)+1)
170 attrs[0] = slog.Attr{Key: "err", Value: slog.AnyValue(err)}
171 for i, a := range attr {
172 attrs[i+1] = slog.Attr{Key: a.key, Value: slog.AnyValue(a.value)}
173 }
174 l.l.LogAttrs(l.c, slog.LevelError, msg, attrs...)
175 return l
176}
177
178func (l *Logger) LogCaller(ctx context.Context, msg string) *Logger {
179 attr := slog.Attr{Key: "caller", Value: slog.StringValue("NO")}
180 if caller, ok := extractCaller(ctx); ok {
181 attr = slog.Attr{Key: "caller", Value: slog.StringValue(caller)}
182 }
183 l.l.LogAttrs(l.c, slog.LevelInfo, msg, attr)
184 return l
185}
186
187func (l *Logger) AsStdLogger() *log.Logger {
188 return slog.NewLogLogger(l.l.Handler(), slog.LevelInfo)
189}