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