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