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