+15
-12
cmd/knotserver/main.go
+15
-12
cmd/knotserver/main.go
···
3
3
import (
4
4
"context"
5
5
"fmt"
6
-
"log"
7
-
"log/slog"
8
6
"net/http"
9
-
"os"
10
7
11
8
"github.com/sotangled/tangled/knotserver"
12
9
"github.com/sotangled/tangled/knotserver/config"
13
10
"github.com/sotangled/tangled/knotserver/db"
11
+
"github.com/sotangled/tangled/log"
14
12
"github.com/sotangled/tangled/rbac"
15
13
)
16
14
···
19
17
// ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
20
18
// defer stop()
21
19
22
-
slog.SetDefault(slog.New(slog.NewTextHandler(os.Stdout, nil)))
20
+
l := log.New("knotserver")
23
21
24
22
c, err := config.Load(ctx)
25
23
if err != nil {
26
-
log.Fatal(err)
24
+
l.Error("failed to load config", "error", err)
25
+
return
27
26
}
28
27
29
28
if c.Server.Dev {
30
-
log.Println("running in dev mode, signature verification is disabled")
29
+
l.Info("running in dev mode, signature verification is disabled")
31
30
}
32
31
33
32
db, err := db.Setup(c.Server.DBPath)
34
33
if err != nil {
35
-
log.Fatalf("failed to setup db: %s", err)
34
+
l.Error("failed to setup db", "error", err)
35
+
return
36
36
}
37
37
38
38
e, err := rbac.NewEnforcer(c.Server.DBPath)
39
39
if err != nil {
40
-
log.Fatalf("failed to setup rbac enforcer: %s", err)
40
+
l.Error("failed to setup rbac enforcer", "error", err)
41
+
return
41
42
}
42
43
43
-
mux, err := knotserver.Setup(ctx, c, db, e)
44
+
mux, err := knotserver.Setup(ctx, c, db, e, l)
44
45
if err != nil {
45
-
log.Fatal(err)
46
+
l.Error("failed to setup server", "error", err)
47
+
return
46
48
}
47
49
48
50
addr := fmt.Sprintf("%s:%d", c.Server.Host, c.Server.Port)
49
51
50
-
log.Println("starting main server on", addr)
51
-
log.Fatal(http.ListenAndServe(addr, mux))
52
+
l.Info("starting main server", "address", addr)
53
+
l.Error("server error", "error", http.ListenAndServe(addr, mux))
54
+
return
52
55
}
+3
-3
knotserver/file.go
+3
-3
knotserver/file.go
···
3
3
import (
4
4
"bytes"
5
5
"io"
6
-
"log"
6
+
"log/slog"
7
7
"net/http"
8
8
"strings"
9
9
···
43
43
}
44
44
}
45
45
46
-
func (h *Handle) showFile(content string, data map[string]any, w http.ResponseWriter) {
46
+
func (h *Handle) showFile(content string, data map[string]any, w http.ResponseWriter, l *slog.Logger) {
47
47
lc, err := countLines(strings.NewReader(content))
48
48
if err != nil {
49
49
// Non-fatal, we'll just skip showing line numbers in the template.
50
-
log.Printf("counting lines: %s", err)
50
+
l.Warn("counting lines", "error", err)
51
51
}
52
52
53
53
lines := make([]int, lc)
+3
-4
knotserver/git.go
+3
-4
knotserver/git.go
···
3
3
import (
4
4
"compress/gzip"
5
5
"io"
6
-
"log"
7
6
"net/http"
8
7
"path/filepath"
9
8
···
26
25
27
26
if err := cmd.InfoRefs(); err != nil {
28
27
http.Error(w, err.Error(), 500)
29
-
log.Printf("git: failed to execute git-upload-pack (info/refs) %s", err)
28
+
d.l.Error("git: failed to execute git-upload-pack (info/refs)", "handler", "InfoRefs", "error", err)
30
29
return
31
30
}
32
31
}
···
53
52
reader, err := gzip.NewReader(r.Body)
54
53
if err != nil {
55
54
http.Error(w, err.Error(), 500)
56
-
log.Printf("git: failed to create gzip reader: %s", err)
55
+
d.l.Error("git: failed to create gzip reader", "handler", "UploadPack", "error", err)
57
56
return
58
57
}
59
58
defer reader.Close()
···
62
61
cmd.Stdin = reader
63
62
if err := cmd.UploadPack(); err != nil {
64
63
http.Error(w, err.Error(), 500)
65
-
log.Printf("git: failed to execute git-upload-pack %s", err)
64
+
d.l.Error("git: failed to execute git-upload-pack", "handler", "UploadPack", "error", err)
66
65
return
67
66
}
68
67
}
+4
-1
knotserver/handler.go
+4
-1
knotserver/handler.go
···
3
3
import (
4
4
"context"
5
5
"fmt"
6
+
"log/slog"
6
7
"net/http"
7
8
8
9
"github.com/go-chi/chi/v5"
···
21
22
db *db.DB
22
23
js *jsclient.JetstreamClient
23
24
e *rbac.Enforcer
25
+
l *slog.Logger
24
26
25
27
// init is a channel that is closed when the knot has been initailized
26
28
// i.e. when the first user (knot owner) has been added.
···
28
30
knotInitialized bool
29
31
}
30
32
31
-
func Setup(ctx context.Context, c *config.Config, db *db.DB, e *rbac.Enforcer) (http.Handler, error) {
33
+
func Setup(ctx context.Context, c *config.Config, db *db.DB, e *rbac.Enforcer, l *slog.Logger) (http.Handler, error) {
32
34
r := chi.NewRouter()
33
35
34
36
h := Handle{
35
37
c: c,
36
38
db: db,
37
39
e: e,
40
+
l: l,
38
41
init: make(chan struct{}),
39
42
}
40
43
+66
-34
knotserver/jetstream.go
+66
-34
knotserver/jetstream.go
···
5
5
"encoding/json"
6
6
"fmt"
7
7
"io"
8
-
"log"
9
8
"net/http"
10
9
"net/url"
11
10
"strings"
···
14
13
"github.com/sotangled/tangled/api/tangled"
15
14
"github.com/sotangled/tangled/knotserver/db"
16
15
"github.com/sotangled/tangled/knotserver/jsclient"
16
+
"github.com/sotangled/tangled/log"
17
17
)
18
18
19
19
func (h *Handle) StartJetstream(ctx context.Context) error {
20
+
l := h.l.With("component", "jetstream")
21
+
ctx = log.IntoContext(ctx, l)
20
22
collections := []string{tangled.PublicKeyNSID, tangled.KnotMemberNSID}
21
23
dids := []string{}
22
24
23
-
lastTimeUs, err := h.getLastTimeUs()
25
+
lastTimeUs, err := h.getLastTimeUs(ctx)
24
26
if err != nil {
25
27
return err
26
28
}
···
31
33
return fmt.Errorf("failed to read from jetstream: %w", err)
32
34
}
33
35
34
-
go h.processMessages(messages)
36
+
go h.processMessages(ctx, messages)
35
37
36
38
return nil
37
39
}
38
40
39
-
func (h *Handle) getLastTimeUs() (int64, error) {
41
+
func (h *Handle) getLastTimeUs(ctx context.Context) (int64, error) {
42
+
l := log.FromContext(ctx)
40
43
lastTimeUs, err := h.db.GetLastTimeUs()
41
44
if err != nil {
42
-
log.Println("couldn't get last time us, starting from now")
45
+
l.Info("couldn't get last time us, starting from now")
43
46
lastTimeUs = time.Now().UnixMicro()
44
47
}
45
48
46
49
// If last time is older than a week, start from now
47
50
if time.Now().UnixMicro()-lastTimeUs > 7*24*60*60*1000*1000 {
48
51
lastTimeUs = time.Now().UnixMicro()
49
-
log.Printf("last time us is older than a week. discarding that and starting from now.")
52
+
l.Info("last time us is older than a week. discarding that and starting from now")
50
53
err = h.db.SaveLastTimeUs(lastTimeUs)
51
54
if err != nil {
52
-
log.Println("failed to save last time us")
55
+
l.Error("failed to save last time us")
53
56
}
54
57
}
55
58
56
-
log.Printf("found last time_us %d", lastTimeUs)
59
+
l.Info("found last time_us", "time_us", lastTimeUs)
57
60
return lastTimeUs, nil
58
61
}
59
62
60
-
func (h *Handle) processPublicKey(did string, record map[string]interface{}) {
63
+
func (h *Handle) processPublicKey(ctx context.Context, did string, record map[string]interface{}) error {
64
+
l := log.FromContext(ctx)
61
65
if err := h.db.AddPublicKeyFromRecord(did, record); err != nil {
62
-
log.Printf("failed to add public key: %v", err)
63
-
} else {
64
-
log.Printf("added public key from firehose: %s", did)
66
+
l.Error("failed to add public key", "error", err)
67
+
return fmt.Errorf("failed to add public key: %w", err)
65
68
}
69
+
l.Info("added public key from firehose", "did", did)
70
+
return nil
66
71
}
67
72
68
-
func (h *Handle) fetchAndAddKeys(did string) {
73
+
func (h *Handle) fetchAndAddKeys(ctx context.Context, did string) error {
74
+
l := log.FromContext(ctx)
75
+
69
76
keysEndpoint, err := url.JoinPath(h.c.AppViewEndpoint, "keys", did)
70
77
if err != nil {
71
-
log.Printf("error building endpoint url: %s: %v", did, err)
72
-
return
78
+
l.Error("error building endpoint url", "did", did, "error", err.Error())
79
+
return fmt.Errorf("error building endpoint url: %w", err)
73
80
}
74
81
75
82
resp, err := http.Get(keysEndpoint)
76
83
if err != nil {
77
-
log.Printf("error getting keys for %s: %v", did, err)
78
-
return
84
+
l.Error("error getting keys", "did", did, "error", err)
85
+
return fmt.Errorf("error getting keys: %w", err)
79
86
}
80
87
defer resp.Body.Close()
81
88
82
89
plaintext, err := io.ReadAll(resp.Body)
83
90
if err != nil {
84
-
log.Printf("error reading response body: %v", err)
85
-
return
91
+
l.Error("error reading response body", "error", err)
92
+
return fmt.Errorf("error reading response body: %w", err)
86
93
}
87
94
88
95
for _, key := range strings.Split(string(plaintext), "\n") {
···
94
101
}
95
102
pk.Key = key
96
103
if err := h.db.AddPublicKey(pk); err != nil {
97
-
log.Printf("failed to add public key: %v", err)
104
+
l.Error("failed to add public key", "error", err)
105
+
return fmt.Errorf("failed to add public key: %w", err)
98
106
}
99
107
}
108
+
return nil
100
109
}
101
110
102
-
func (h *Handle) processKnotMember(did string, record map[string]interface{}) {
111
+
func (h *Handle) processKnotMember(ctx context.Context, did string, record map[string]interface{}) error {
112
+
l := log.FromContext(ctx)
103
113
ok, err := h.e.E.Enforce(did, ThisServer, ThisServer, "server:invite")
104
114
if err != nil || !ok {
105
-
log.Printf("failed to add member from did %s", did)
106
-
return
115
+
l.Error("failed to add member", "did", did)
116
+
return fmt.Errorf("failed to enforce permissions: %w", err)
107
117
}
108
118
109
-
log.Printf("adding member")
119
+
l.Info("adding member")
110
120
if err := h.e.AddMember(ThisServer, record["member"].(string)); err != nil {
111
-
log.Printf("failed to add member: %v", err)
112
-
} else {
113
-
log.Printf("added member from firehose: %s", record["member"])
121
+
l.Error("failed to add member", "error", err)
122
+
return fmt.Errorf("failed to add member: %w", err)
123
+
}
124
+
l.Info("added member from firehose", "member", record["member"])
125
+
126
+
if err := h.db.AddDid(did); err != nil {
127
+
l.Error("failed to add did", "error", err)
128
+
return fmt.Errorf("failed to add did: %w", err)
114
129
}
115
130
116
-
h.fetchAndAddKeys(did)
131
+
if err := h.fetchAndAddKeys(ctx, did); err != nil {
132
+
return fmt.Errorf("failed to fetch and add keys: %w", err)
133
+
}
134
+
117
135
h.js.UpdateDids([]string{did})
136
+
return nil
118
137
}
119
138
120
-
func (h *Handle) processMessages(messages <-chan []byte) {
139
+
func (h *Handle) processMessages(ctx context.Context, messages <-chan []byte) {
140
+
l := log.FromContext(ctx)
141
+
l.Info("waiting for knot to be initialized")
121
142
<-h.init
122
-
log.Println("initalized jetstream watcher")
143
+
l.Info("initialized jetstream watcher")
123
144
124
145
for msg := range messages {
125
146
var data map[string]interface{}
126
147
if err := json.Unmarshal(msg, &data); err != nil {
127
-
log.Printf("error unmarshaling message: %v", err)
148
+
l.Error("error unmarshaling message", "error", err)
128
149
continue
129
150
}
130
151
···
133
154
did := data["did"].(string)
134
155
record := commit["record"].(map[string]interface{})
135
156
157
+
var processErr error
136
158
switch commit["collection"].(string) {
137
159
case tangled.PublicKeyNSID:
138
-
h.processPublicKey(did, record)
160
+
if err := h.processPublicKey(ctx, did, record); err != nil {
161
+
processErr = fmt.Errorf("failed to process public key: %w", err)
162
+
}
139
163
case tangled.KnotMemberNSID:
140
-
h.processKnotMember(did, record)
164
+
if err := h.processKnotMember(ctx, did, record); err != nil {
165
+
processErr = fmt.Errorf("failed to process knot member: %w", err)
166
+
}
167
+
}
168
+
169
+
if processErr != nil {
170
+
l.Error("error processing message", "error", processErr)
171
+
continue
141
172
}
142
173
143
174
lastTimeUs := int64(data["time_us"].(float64))
144
175
if err := h.db.SaveLastTimeUs(lastTimeUs); err != nil {
145
-
log.Printf("failed to save last time us: %v", err)
176
+
l.Error("failed to save last time us", "error", err)
177
+
continue
146
178
}
147
179
}
148
180
}
+45
-22
knotserver/routes.go
+45
-22
knotserver/routes.go
···
9
9
"errors"
10
10
"fmt"
11
11
"html/template"
12
-
"log"
13
12
"net/http"
14
13
"path/filepath"
15
14
"strconv"
···
30
29
31
30
func (h *Handle) RepoIndex(w http.ResponseWriter, r *http.Request) {
32
31
path := filepath.Join(h.c.Repo.ScanPath, didPath(r))
32
+
l := h.l.With("path", path, "handler", "RepoIndex")
33
33
34
34
gr, err := git.Open(path, "")
35
35
if err != nil {
···
37
37
writeMsg(w, "repo empty")
38
38
return
39
39
} else {
40
-
log.Println(err)
40
+
l.Error("opening repo", "error", err.Error())
41
41
notFound(w)
42
42
return
43
43
}
···
45
45
commits, err := gr.Commits()
46
46
if err != nil {
47
47
writeError(w, err.Error(), http.StatusInternalServerError)
48
-
log.Println(err)
48
+
l.Error("fetching commits", "error", err.Error())
49
49
return
50
50
}
51
51
···
73
73
}
74
74
75
75
if readmeContent == "" {
76
-
log.Printf("no readme found for %s", path)
76
+
l.Warn("no readme found")
77
77
}
78
78
79
79
mainBranch, err := gr.FindMainBranch(h.c.Repo.MainBranch)
80
80
if err != nil {
81
81
writeError(w, err.Error(), http.StatusInternalServerError)
82
-
log.Println(err)
82
+
l.Error("finding main branch", "error", err.Error())
83
83
return
84
84
}
85
85
···
100
100
treePath := chi.URLParam(r, "*")
101
101
ref := chi.URLParam(r, "ref")
102
102
103
+
l := h.l.With("handler", "RepoTree", "ref", ref, "treePath", treePath)
104
+
103
105
path := filepath.Join(h.c.Repo.ScanPath, didPath(r))
104
106
gr, err := git.Open(path, ref)
105
107
if err != nil {
···
110
112
files, err := gr.FileTree(treePath)
111
113
if err != nil {
112
114
writeError(w, err.Error(), http.StatusInternalServerError)
113
-
log.Println(err)
115
+
l.Error("file tree", "error", err.Error())
114
116
return
115
117
}
116
118
···
133
135
treePath := chi.URLParam(r, "*")
134
136
ref := chi.URLParam(r, "ref")
135
137
138
+
l := h.l.With("handler", "FileContent", "ref", ref, "treePath", treePath)
139
+
136
140
path := filepath.Join(h.c.Repo.ScanPath, didPath(r))
137
141
gr, err := git.Open(path, ref)
138
142
if err != nil {
···
155
159
if raw {
156
160
h.showRaw(string(safe), w)
157
161
} else {
158
-
h.showFile(string(safe), data, w)
162
+
h.showFile(string(safe), data, w, l)
159
163
}
160
164
}
161
165
162
166
func (h *Handle) Archive(w http.ResponseWriter, r *http.Request) {
163
167
name := chi.URLParam(r, "name")
164
168
file := chi.URLParam(r, "file")
169
+
170
+
l := h.l.With("handler", "Archive", "name", name, "file", file)
165
171
166
172
// TODO: extend this to add more files compression (e.g.: xz)
167
173
if !strings.HasSuffix(file, ".tar.gz") {
···
192
198
if err != nil {
193
199
// once we start writing to the body we can't report error anymore
194
200
// so we are only left with printing the error.
195
-
log.Println(err)
201
+
l.Error("writing tar file", "error", err.Error())
196
202
return
197
203
}
198
204
···
200
206
if err != nil {
201
207
// once we start writing to the body we can't report error anymore
202
208
// so we are only left with printing the error.
203
-
log.Println(err)
209
+
l.Error("flushing?", "error", err.Error())
204
210
return
205
211
}
206
212
}
207
213
208
214
func (h *Handle) Log(w http.ResponseWriter, r *http.Request) {
209
-
fmt.Println(r.URL.Path)
210
215
ref := chi.URLParam(r, "ref")
216
+
path := filepath.Join(h.c.Repo.ScanPath, didPath(r))
211
217
212
-
path := filepath.Join(h.c.Repo.ScanPath, didPath(r))
218
+
l := h.l.With("handler", "Log", "ref", ref, "path", path)
219
+
213
220
gr, err := git.Open(path, ref)
214
221
if err != nil {
215
222
notFound(w)
···
219
226
commits, err := gr.Commits()
220
227
if err != nil {
221
228
writeError(w, err.Error(), http.StatusInternalServerError)
222
-
log.Println(err)
229
+
l.Error("fetching commits", "error", err.Error())
223
230
return
224
231
}
225
232
···
269
276
func (h *Handle) Diff(w http.ResponseWriter, r *http.Request) {
270
277
ref := chi.URLParam(r, "ref")
271
278
279
+
l := h.l.With("handler", "Diff", "ref", ref)
280
+
272
281
path := filepath.Join(h.c.Repo.ScanPath, didPath(r))
273
282
gr, err := git.Open(path, ref)
274
283
if err != nil {
···
279
288
diff, err := gr.Diff()
280
289
if err != nil {
281
290
writeError(w, err.Error(), http.StatusInternalServerError)
282
-
log.Println(err)
291
+
l.Error("getting diff", "error", err.Error())
283
292
return
284
293
}
285
294
···
297
306
298
307
func (h *Handle) Refs(w http.ResponseWriter, r *http.Request) {
299
308
path := filepath.Join(h.c.Repo.ScanPath, didPath(r))
309
+
l := h.l.With("handler", "Refs")
310
+
300
311
gr, err := git.Open(path, "")
301
312
if err != nil {
302
313
notFound(w)
···
306
317
tags, err := gr.Tags()
307
318
if err != nil {
308
319
// Non-fatal, we *should* have at least one branch to show.
309
-
log.Println(err)
320
+
l.Error("getting tags", "error", err.Error())
310
321
}
311
322
312
323
branches, err := gr.Branches()
313
324
if err != nil {
314
-
log.Println(err)
325
+
l.Error("getting branches", "error", err.Error())
315
326
writeError(w, err.Error(), http.StatusInternalServerError)
316
327
return
317
328
}
···
327
338
}
328
339
329
340
func (h *Handle) Keys(w http.ResponseWriter, r *http.Request) {
341
+
l := h.l.With("handler", "Keys")
342
+
330
343
switch r.Method {
331
344
case http.MethodGet:
332
345
keys, err := h.db.GetAllPublicKeys()
333
346
if err != nil {
334
347
writeError(w, err.Error(), http.StatusInternalServerError)
335
-
log.Println(err)
348
+
l.Error("getting public keys", "error", err.Error())
336
349
return
337
350
}
338
351
···
358
371
359
372
if err := h.db.AddPublicKey(pk); err != nil {
360
373
writeError(w, err.Error(), http.StatusInternalServerError)
361
-
log.Printf("adding public key: %s", err)
374
+
l.Error("adding public key", "error", err.Error())
362
375
return
363
376
}
364
377
···
368
381
}
369
382
370
383
func (h *Handle) NewRepo(w http.ResponseWriter, r *http.Request) {
384
+
l := h.l.With("handler", "NewRepo")
385
+
371
386
data := struct {
372
387
Did string `json:"did"`
373
388
Name string `json:"name"`
···
385
400
repoPath := filepath.Join(h.c.Repo.ScanPath, relativeRepoPath)
386
401
err := git.InitBare(repoPath)
387
402
if err != nil {
388
-
log.Println(err)
403
+
l.Error("initializing bare repo", "error", err.Error())
389
404
writeError(w, err.Error(), http.StatusInternalServerError)
390
405
return
391
406
}
···
393
408
// add perms for this user to access the repo
394
409
err = h.e.AddRepo(did, ThisServer, relativeRepoPath)
395
410
if err != nil {
396
-
log.Println(err)
411
+
l.Error("adding repo permissions", "error", err.Error())
397
412
writeError(w, err.Error(), http.StatusInternalServerError)
398
413
return
399
414
}
···
402
417
}
403
418
404
419
func (h *Handle) AddMember(w http.ResponseWriter, r *http.Request) {
420
+
l := h.l.With("handler", "AddMember")
421
+
405
422
data := struct {
406
423
Did string `json:"did"`
407
424
PublicKeys []string `json:"keys"`
···
427
444
428
445
h.js.UpdateDids([]string{did})
429
446
if err := h.e.AddMember(ThisServer, did); err != nil {
430
-
log.Println(err)
447
+
l.Error("adding member", "error", err.Error())
431
448
writeError(w, err.Error(), http.StatusInternalServerError)
432
449
return
433
450
}
···
436
453
}
437
454
438
455
func (h *Handle) Init(w http.ResponseWriter, r *http.Request) {
456
+
l := h.l.With("handler", "Init")
457
+
439
458
if h.knotInitialized {
440
459
writeError(w, "knot already initialized", http.StatusConflict)
441
460
return
···
447
466
}{}
448
467
449
468
if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
469
+
l.Error("failed to decode request body", "error", err.Error())
450
470
writeError(w, "invalid request body", http.StatusBadRequest)
451
471
return
452
472
}
453
473
454
474
if data.Did == "" {
475
+
l.Error("empty DID in request")
455
476
writeError(w, "did is empty", http.StatusBadRequest)
456
477
return
457
478
}
···
464
485
pk.Key = k
465
486
err := h.db.AddPublicKey(pk)
466
487
if err != nil {
488
+
l.Error("failed to add public key", "error", err.Error())
467
489
writeError(w, err.Error(), http.StatusInternalServerError)
468
490
return
469
491
}
470
492
}
471
493
} else {
494
+
l.Error("failed to add DID", "error", err.Error())
472
495
writeError(w, err.Error(), http.StatusInternalServerError)
473
496
return
474
497
}
475
498
476
499
h.js.UpdateDids([]string{data.Did})
477
500
if err := h.e.AddOwner(ThisServer, data.Did); err != nil {
478
-
log.Println(err)
501
+
l.Error("adding owner", "error", err.Error())
479
502
writeError(w, err.Error(), http.StatusInternalServerError)
480
503
return
481
504
}
482
-
// Signal that the knot is ready
505
+
483
506
close(h.init)
484
507
485
508
mac := hmac.New(sha256.New, []byte(h.c.Server.Secret))
+49
log/log.go
+49
log/log.go
···
1
+
package log
2
+
3
+
import (
4
+
"context"
5
+
"log/slog"
6
+
"os"
7
+
)
8
+
9
+
// NewHandler sets up a new slog.Handler with the service name
10
+
// as an attribute
11
+
func NewHandler(name string) slog.Handler {
12
+
handler := slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{})
13
+
14
+
var attrs []slog.Attr
15
+
attrs = append(attrs, slog.Attr{Key: "service", Value: slog.StringValue(name)})
16
+
handler.WithAttrs(attrs)
17
+
return handler
18
+
}
19
+
20
+
func New(name string) *slog.Logger {
21
+
return slog.New(NewHandler(name))
22
+
}
23
+
24
+
func NewContext(ctx context.Context, name string) context.Context {
25
+
return IntoContext(ctx, New(name))
26
+
}
27
+
28
+
type ctxKey struct{}
29
+
30
+
// IntoContext adds a logger to a context. Use FromContext to
31
+
// pull the logger out.
32
+
func IntoContext(ctx context.Context, l *slog.Logger) context.Context {
33
+
return context.WithValue(ctx, ctxKey{}, l)
34
+
}
35
+
36
+
// FromContext returns a logger from a context.Context;
37
+
// if the passed context is nil, we return the default slog
38
+
// logger.
39
+
func FromContext(ctx context.Context) *slog.Logger {
40
+
if ctx != nil {
41
+
v := ctx.Value(ctxKey{})
42
+
if v == nil {
43
+
return slog.Default()
44
+
}
45
+
return v.(*slog.Logger)
46
+
}
47
+
48
+
return slog.Default()
49
+
}