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