+15
-16
src/auth.go
+15
-16
src/auth.go
···
6
6
"encoding/json"
7
7
"errors"
8
8
"fmt"
9
-
"log"
10
9
"net"
11
10
"net/http"
12
11
"net/url"
···
32
31
return false
33
32
}
34
33
35
-
func authorizeInsecure() *Authorization {
34
+
func authorizeInsecure(r *http.Request) *Authorization {
36
35
if config.Insecure { // for testing only
37
-
log.Println("auth: INSECURE mode")
36
+
logc.Println(r.Context(), "auth: INSECURE mode")
38
37
return &Authorization{
39
38
repoURLs: nil,
40
39
branch: "pages",
···
276
275
}
277
276
278
277
if len(dnsRecords) > 0 {
279
-
log.Printf("auth: %s TXT/CNAME: %q\n", host, dnsRecords)
278
+
logc.Printf(r.Context(), "auth: %s TXT/CNAME: %q\n", host, dnsRecords)
280
279
}
281
280
282
281
for _, dnsRecord := range dnsRecords {
···
324
323
func AuthorizeMetadataRetrieval(r *http.Request) (*Authorization, error) {
325
324
causes := []error{AuthError{http.StatusUnauthorized, "unauthorized"}}
326
325
327
-
auth := authorizeInsecure()
326
+
auth := authorizeInsecure(r)
328
327
if auth != nil {
329
328
return auth, nil
330
329
}
···
335
334
} else if err != nil { // bad request
336
335
return nil, err
337
336
} else {
338
-
log.Println("auth: DNS challenge")
337
+
logc.Println(r.Context(), "auth: DNS challenge")
339
338
return auth, nil
340
339
}
341
340
···
346
345
} else if err != nil { // bad request
347
346
return nil, err
348
347
} else {
349
-
log.Printf("auth: wildcard %s\n", pattern.GetHost())
348
+
logc.Printf(r.Context(), "auth: wildcard %s\n", pattern.GetHost())
350
349
return auth, nil
351
350
}
352
351
}
···
358
357
} else if err != nil { // bad request
359
358
return nil, err
360
359
} else {
361
-
log.Printf("auth: codeberg %s\n", r.Host)
360
+
logc.Printf(r.Context(), "auth: codeberg %s\n", r.Host)
362
361
return auth, nil
363
362
}
364
363
}
···
376
375
return nil, err
377
376
}
378
377
379
-
auth := authorizeInsecure()
378
+
auth := authorizeInsecure(r)
380
379
if auth != nil {
381
380
return auth, nil
382
381
}
···
388
387
} else if err != nil { // bad request
389
388
return nil, err
390
389
} else {
391
-
log.Println("auth: DNS challenge: allow *")
390
+
logc.Println(r.Context(), "auth: DNS challenge: allow *")
392
391
return auth, nil
393
392
}
394
393
···
400
399
} else if err != nil { // bad request
401
400
return nil, err
402
401
} else {
403
-
log.Printf("auth: DNS allowlist: allow %v\n", auth.repoURLs)
402
+
logc.Printf(r.Context(), "auth: DNS allowlist: allow %v\n", auth.repoURLs)
404
403
return auth, nil
405
404
}
406
405
}
···
414
413
} else if err != nil { // bad request
415
414
return nil, err
416
415
} else {
417
-
log.Printf("auth: wildcard %s: allow %v\n", pattern.GetHost(), auth.repoURLs)
416
+
logc.Printf(r.Context(), "auth: wildcard %s: allow %v\n", pattern.GetHost(), auth.repoURLs)
418
417
return auth, nil
419
418
}
420
419
}
···
426
425
} else if err != nil { // bad request
427
426
return nil, err
428
427
} else {
429
-
log.Printf("auth: codeberg %s: allow %v branch %s\n",
428
+
logc.Printf(r.Context(), "auth: codeberg %s: allow %v branch %s\n",
430
429
r.Host, auth.repoURLs, auth.branch)
431
430
return auth, nil
432
431
}
···
643
642
return nil, err
644
643
}
645
644
646
-
auth := authorizeInsecure()
645
+
auth := authorizeInsecure(r)
647
646
if auth != nil {
648
647
return auth, nil
649
648
}
···
655
654
} else if err != nil { // bad request
656
655
return nil, err
657
656
} else {
658
-
log.Printf("auth: forge token: allow\n")
657
+
logc.Printf(r.Context(), "auth: forge token: allow\n")
659
658
return auth, nil
660
659
}
661
660
···
669
668
} else if err != nil { // bad request
670
669
return nil, err
671
670
} else {
672
-
log.Println("auth: DNS challenge")
671
+
logc.Println(r.Context(), "auth: DNS challenge")
673
672
return auth, nil
674
673
}
675
674
}
+16
-17
src/backend_s3.go
+16
-17
src/backend_s3.go
···
6
6
"crypto/sha256"
7
7
"fmt"
8
8
"io"
9
-
"log"
10
9
"net/http"
11
10
"path"
12
11
"strings"
···
170
169
if err != nil {
171
170
return nil, err
172
171
} else if !exists {
173
-
log.Printf("s3: create bucket %s\n", bucket)
172
+
logc.Printf(ctx, "s3: create bucket %s\n", bucket)
174
173
175
174
err = client.MakeBucket(ctx, bucket,
176
175
minio.MakeBucketOptions{Region: config.Region})
···
236
235
minio.StatObjectOptions{})
237
236
if err != nil {
238
237
if errResp := minio.ToErrorResponse(err); errResp.Code == "NoSuchKey" {
239
-
log.Printf("s3 feature %q: disabled", feature)
238
+
logc.Printf(ctx, "s3 feature %q: disabled", feature)
240
239
return false, nil
241
240
} else {
242
241
return false, err
243
242
}
244
243
}
245
-
log.Printf("s3 feature %q: enabled", feature)
244
+
logc.Printf(ctx, "s3 feature %q: enabled", feature)
246
245
return true, nil
247
246
}
248
247
···
250
249
if err != nil {
251
250
err = fmt.Errorf("getting s3 backend feature %q: %w", feature, err)
252
251
ObserveError(err)
253
-
log.Print(err)
252
+
logc.Println(ctx, err)
254
253
return false
255
254
}
256
255
return isOn
···
268
267
reader io.ReadSeeker, size uint64, mtime time.Time, err error,
269
268
) {
270
269
loader := func(ctx context.Context, name string) (*CachedBlob, error) {
271
-
log.Printf("s3: get blob %s\n", name)
270
+
logc.Printf(ctx, "s3: get blob %s\n", name)
272
271
273
272
startTime := time.Now()
274
273
···
313
312
}
314
313
315
314
func (s3 *S3Backend) PutBlob(ctx context.Context, name string, data []byte) error {
316
-
log.Printf("s3: put blob %s (%s)\n", name, datasize.ByteSize(len(data)).HumanReadable())
315
+
logc.Printf(ctx, "s3: put blob %s (%s)\n", name, datasize.ByteSize(len(data)).HumanReadable())
317
316
318
317
_, err := s3.client.StatObject(ctx, s3.bucket, blobObjectName(name),
319
318
minio.GetObjectOptions{})
···
325
324
return err
326
325
} else {
327
326
ObserveData(ctx, "blob.status", "created")
328
-
log.Printf("s3: put blob %s (created)\n", name)
327
+
logc.Printf(ctx, "s3: put blob %s (created)\n", name)
329
328
return nil
330
329
}
331
330
} else {
···
333
332
}
334
333
} else {
335
334
ObserveData(ctx, "blob.status", "exists")
336
-
log.Printf("s3: put blob %s (exists)\n", name)
335
+
logc.Printf(ctx, "s3: put blob %s (exists)\n", name)
337
336
blobsDedupedCount.Inc()
338
337
blobsDedupedBytes.Add(float64(len(data)))
339
338
return nil
···
341
340
}
342
341
343
342
func (s3 *S3Backend) DeleteBlob(ctx context.Context, name string) error {
344
-
log.Printf("s3: delete blob %s\n", name)
343
+
logc.Printf(ctx, "s3: delete blob %s\n", name)
345
344
346
345
return s3.client.RemoveObject(ctx, s3.bucket, blobObjectName(name),
347
346
minio.RemoveObjectOptions{})
···
356
355
}
357
356
358
357
func (s3 *S3Backend) ListManifests(ctx context.Context) (manifests []string, err error) {
359
-
log.Print("s3: list manifests")
358
+
logc.Print(ctx, "s3: list manifests")
360
359
361
360
ctx, cancel := context.WithCancel(ctx)
362
361
defer cancel()
···
396
395
}
397
396
398
397
func (l s3ManifestLoader) load(ctx context.Context, name string, oldManifest *CachedManifest) (*CachedManifest, error) {
399
-
log.Printf("s3: get manifest %s\n", name)
398
+
logc.Printf(ctx, "s3: get manifest %s\n", name)
400
399
401
400
loader := func() (*CachedManifest, error) {
402
401
opts := minio.GetObjectOptions{}
···
475
474
476
475
func (s3 *S3Backend) StageManifest(ctx context.Context, manifest *Manifest) error {
477
476
data := EncodeManifest(manifest)
478
-
log.Printf("s3: stage manifest %x\n", sha256.Sum256(data))
477
+
logc.Printf(ctx, "s3: stage manifest %x\n", sha256.Sum256(data))
479
478
480
479
_, err := s3.client.PutObject(ctx, s3.bucket, stagedManifestObjectName(data),
481
480
bytes.NewReader(data), int64(len(data)), minio.PutObjectOptions{})
···
484
483
485
484
func (s3 *S3Backend) CommitManifest(ctx context.Context, name string, manifest *Manifest) error {
486
485
data := EncodeManifest(manifest)
487
-
log.Printf("s3: commit manifest %x -> %s", sha256.Sum256(data), name)
486
+
logc.Printf(ctx, "s3: commit manifest %x -> %s", sha256.Sum256(data), name)
488
487
489
488
// Remove staged object unconditionally (whether commit succeeded or failed), since
490
489
// the upper layer has to retry the complete operation anyway.
···
503
502
}
504
503
505
504
func (s3 *S3Backend) DeleteManifest(ctx context.Context, name string) error {
506
-
log.Printf("s3: delete manifest %s\n", name)
505
+
logc.Printf(ctx, "s3: delete manifest %s\n", name)
507
506
508
507
err := s3.client.RemoveObject(ctx, s3.bucket, manifestObjectName(name),
509
508
minio.RemoveObjectOptions{})
···
516
515
}
517
516
518
517
func (s3 *S3Backend) CheckDomain(ctx context.Context, domain string) (exists bool, err error) {
519
-
log.Printf("s3: check domain %s\n", domain)
518
+
logc.Printf(ctx, "s3: check domain %s\n", domain)
520
519
521
520
_, err = s3.client.StatObject(ctx, s3.bucket, domainCheckObjectName(domain),
522
521
minio.StatObjectOptions{})
···
547
546
}
548
547
549
548
func (s3 *S3Backend) CreateDomain(ctx context.Context, domain string) error {
550
-
log.Printf("s3: create domain %s\n", domain)
549
+
logc.Printf(ctx, "s3: create domain %s\n", domain)
551
550
552
551
_, err := s3.client.PutObject(ctx, s3.bucket, domainCheckObjectName(domain),
553
552
&bytes.Reader{}, 0, minio.PutObjectOptions{})
+5
-6
src/caddy.go
+5
-6
src/caddy.go
···
3
3
import (
4
4
"crypto/tls"
5
5
"fmt"
6
-
"log"
7
6
"net"
8
7
"net/http"
9
8
"net/url"
···
22
21
// this isn't really what git-pages is designed for, and object store accesses can cost money.
23
22
// [^1]: https://letsencrypt.org/2025/07/01/issuing-our-first-ip-address-certificate
24
23
if ip := net.ParseIP(domain); ip != nil {
25
-
log.Println("caddy:", domain, 404, "(bare IP)")
24
+
logc.Println(r.Context(), "caddy:", domain, 404, "(bare IP)")
26
25
w.WriteHeader(http.StatusNotFound)
27
26
return
28
27
}
···
52
51
} else {
53
52
connectHost += ":443"
54
53
}
55
-
log.Printf("caddy: check TLS %s", fallbackURL)
54
+
logc.Printf(r.Context(), "caddy: check TLS %s", fallbackURL)
56
55
connection, err := tls.Dial("tcp", connectHost, &tls.Config{ServerName: domain})
57
56
if err != nil {
58
57
continue
···
64
63
}
65
64
66
65
if found {
67
-
log.Println("caddy:", domain, 200)
66
+
logc.Println(r.Context(), "caddy:", domain, 200)
68
67
w.WriteHeader(http.StatusOK)
69
68
} else if err == nil {
70
-
log.Println("caddy:", domain, 404)
69
+
logc.Println(r.Context(), "caddy:", domain, 404)
71
70
w.WriteHeader(http.StatusNotFound)
72
71
} else {
73
-
log.Println("caddy:", domain, 500)
72
+
logc.Println(r.Context(), "caddy:", domain, 500)
74
73
w.WriteHeader(http.StatusInternalServerError)
75
74
fmt.Fprintln(w, err)
76
75
}
+42
src/log.go
+42
src/log.go
···
1
+
package git_pages
2
+
3
+
import (
4
+
"context"
5
+
"fmt"
6
+
"log/slog"
7
+
"runtime"
8
+
"time"
9
+
)
10
+
11
+
var logc slogWithCtx
12
+
13
+
type slogWithCtx struct{}
14
+
15
+
func (l slogWithCtx) log(ctx context.Context, level slog.Level, msg string) {
16
+
if ctx == nil {
17
+
ctx = context.Background()
18
+
}
19
+
logger := slog.Default()
20
+
if !logger.Enabled(ctx, level) {
21
+
return
22
+
}
23
+
24
+
var pcs [1]uintptr
25
+
// skip [runtime.Callers, this method, method calling this method]
26
+
runtime.Callers(3, pcs[:])
27
+
28
+
r := slog.NewRecord(time.Now(), level, msg, pcs[0])
29
+
logger.Handler().Handle(ctx, r)
30
+
}
31
+
32
+
func (l slogWithCtx) Print(ctx context.Context, v ...any) {
33
+
l.log(ctx, slog.LevelInfo, fmt.Sprint(v...))
34
+
}
35
+
36
+
func (l slogWithCtx) Printf(ctx context.Context, format string, v ...any) {
37
+
l.log(ctx, slog.LevelInfo, fmt.Sprintf(format, v...))
38
+
}
39
+
40
+
func (l slogWithCtx) Println(ctx context.Context, v ...any) {
41
+
l.log(ctx, slog.LevelInfo, fmt.Sprintln(v...))
42
+
}
+1
-1
src/main.go
+1
-1
src/main.go
···
85
85
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
86
86
defer func() {
87
87
if err := recover(); err != nil {
88
-
log.Printf("panic: %s %s %s: %s\n%s",
88
+
logc.Printf(r.Context(), "panic: %s %s %s: %s\n%s",
89
89
r.Method, r.Host, r.URL.Path, err, string(debug.Stack()))
90
90
http.Error(w,
91
91
fmt.Sprintf("internal server error: %s", err),
+5
-6
src/manifest.go
+5
-6
src/manifest.go
···
8
8
"crypto/sha256"
9
9
"errors"
10
10
"fmt"
11
-
"log"
12
11
"mime"
13
12
"net/http"
14
13
"path"
···
189
188
190
189
if originalSize != 0 {
191
190
spaceSaving := (float64(originalSize) - float64(compressedSize)) / float64(originalSize)
192
-
log.Printf("compress: saved %.2f percent (%s to %s)",
191
+
logc.Printf(ctx, "compress: saved %.2f percent (%s to %s)",
193
192
spaceSaving*100.0,
194
193
datasize.ByteSize(originalSize).HR(),
195
194
datasize.ByteSize(compressedSize).HR(),
···
205
204
func PrepareManifest(ctx context.Context, manifest *Manifest) error {
206
205
// Parse Netlify-style `_redirects`
207
206
if err := ProcessRedirectsFile(manifest); err != nil {
208
-
log.Printf("redirects err: %s\n", err)
207
+
logc.Printf(ctx, "redirects err: %s\n", err)
209
208
} else if len(manifest.Redirects) > 0 {
210
-
log.Printf("redirects ok: %d rules\n", len(manifest.Redirects))
209
+
logc.Printf(ctx, "redirects ok: %d rules\n", len(manifest.Redirects))
211
210
}
212
211
213
212
// Parse Netlify-style `_headers`
214
213
if err := ProcessHeadersFile(manifest); err != nil {
215
-
log.Printf("headers err: %s\n", err)
214
+
logc.Printf(ctx, "headers err: %s\n", err)
216
215
} else if len(manifest.Headers) > 0 {
217
-
log.Printf("headers ok: %d rules\n", len(manifest.Headers))
216
+
logc.Printf(ctx, "headers ok: %d rules\n", len(manifest.Headers))
218
217
}
219
218
220
219
// Sniff content type like `http.ServeContent`
+3
-4
src/migrate.go
+3
-4
src/migrate.go
···
3
3
import (
4
4
"context"
5
5
"fmt"
6
-
"log"
7
6
"slices"
8
7
"strings"
9
8
)
···
19
18
20
19
func createDomainMarkers(ctx context.Context) error {
21
20
if backend.HasFeature(ctx, FeatureCheckDomainMarker) {
22
-
log.Print("store already has domain markers")
21
+
logc.Print(ctx, "store already has domain markers")
23
22
return nil
24
23
}
25
24
···
36
35
}
37
36
}
38
37
for idx, domain := range domains {
39
-
log.Printf("(%d / %d) creating domain %s", idx+1, len(domains), domain)
38
+
logc.Printf(ctx, "(%d / %d) creating domain %s", idx+1, len(domains), domain)
40
39
if err := backend.CreateDomain(ctx, domain); err != nil {
41
40
return fmt.Errorf("creating domain %s: %w", domain, err)
42
41
}
···
44
43
if err := backend.EnableFeature(ctx, FeatureCheckDomainMarker); err != nil {
45
44
return err
46
45
}
47
-
log.Printf("created markers for %d domains", len(domains))
46
+
logc.Printf(ctx, "created markers for %d domains", len(domains))
48
47
return nil
49
48
}
+2
-3
src/pages.go
+2
-3
src/pages.go
···
8
8
"errors"
9
9
"fmt"
10
10
"io"
11
-
"log"
12
11
"maps"
13
12
"net/http"
14
13
"net/url"
···
652
651
// any intentional deviation is an opportunity to miss an issue that will affect our
653
652
// visitors but not our health checks.
654
653
if r.Header.Get("Health-Check") == "" {
655
-
log.Println("pages:", r.Method, r.Host, r.URL, r.Header.Get("Content-Type"))
654
+
logc.Println(r.Context(), "pages:", r.Method, r.Host, r.URL, r.Header.Get("Content-Type"))
656
655
if region := os.Getenv("FLY_REGION"); region != "" {
657
656
machine_id := os.Getenv("FLY_MACHINE_ID")
658
657
w.Header().Add("Server", fmt.Sprintf("git-pages (fly.io; %s; %s)", region, machine_id))
···
695
694
http.Error(w, message, http.StatusRequestEntityTooLarge)
696
695
err = errors.New(message)
697
696
}
698
-
log.Println("pages err:", err)
697
+
logc.Println(r.Context(), "pages err:", err)
699
698
}
700
699
}
+9
-10
src/update.go
+9
-10
src/update.go
···
5
5
"errors"
6
6
"fmt"
7
7
"io"
8
-
"log"
9
8
"strings"
10
9
)
11
10
···
71
70
status = "unchanged"
72
71
}
73
72
if newManifest.Commit != nil {
74
-
log.Printf("update %s ok: %s %s", webRoot, status, *newManifest.Commit)
73
+
logc.Printf(ctx, "update %s ok: %s %s", webRoot, status, *newManifest.Commit)
75
74
} else {
76
-
log.Printf("update %s ok: %s", webRoot, status)
75
+
logc.Printf(ctx, "update %s ok: %s", webRoot, status)
77
76
}
78
77
} else {
79
-
log.Printf("update %s err: %s", webRoot, err)
78
+
logc.Printf(ctx, "update %s err: %s", webRoot, err)
80
79
}
81
80
82
81
return UpdateResult{outcome, newManifest, err}
···
91
90
span, ctx := ObserveFunction(ctx, "UpdateFromRepository", "repo.url", repoURL)
92
91
defer span.Finish()
93
92
94
-
log.Printf("update %s: %s %s\n", webRoot, repoURL, branch)
93
+
logc.Printf(ctx, "update %s: %s %s\n", webRoot, repoURL, branch)
95
94
96
95
manifest, err := FetchRepository(ctx, repoURL, branch)
97
96
if errors.Is(err, context.DeadlineExceeded) {
···
119
118
120
119
switch contentType {
121
120
case "application/x-tar":
122
-
log.Printf("update %s: (tar)", webRoot)
121
+
logc.Printf(ctx, "update %s: (tar)", webRoot)
123
122
manifest, err = ExtractTar(reader) // yellow?
124
123
case "application/x-tar+gzip":
125
-
log.Printf("update %s: (tar.gz)", webRoot)
124
+
logc.Printf(ctx, "update %s: (tar.gz)", webRoot)
126
125
manifest, err = ExtractTarGzip(reader) // definitely yellow.
127
126
case "application/x-tar+zstd":
128
-
log.Printf("update %s: (tar.zst)", webRoot)
127
+
logc.Printf(ctx, "update %s: (tar.zst)", webRoot)
129
128
manifest, err = ExtractTarZstd(reader)
130
129
case "application/zip":
131
-
log.Printf("update %s: (zip)", webRoot)
130
+
logc.Printf(ctx, "update %s: (zip)", webRoot)
132
131
manifest, err = ExtractZip(reader)
133
132
default:
134
133
err = errArchiveFormat
135
134
}
136
135
137
136
if err != nil {
138
-
log.Printf("update %s err: %s", webRoot, err)
137
+
logc.Printf(ctx, "update %s err: %s", webRoot, err)
139
138
result = UpdateResult{UpdateError, nil, err}
140
139
} else {
141
140
result = Update(ctx, webRoot, manifest)
+1
-2
src/wildcard.go
+1
-2
src/wildcard.go
···
3
3
import (
4
4
"crypto/tls"
5
5
"fmt"
6
-
"log"
7
6
"net/http"
8
7
"net/http/httputil"
9
8
"net/url"
···
95
94
96
95
for _, pattern := range wildcards {
97
96
if pattern.IsFallbackFor(host) {
98
-
log.Printf("proxy: %s via %s", pattern.GetHost(), pattern.FallbackURL)
97
+
logc.Printf(r.Context(), "proxy: %s via %s", pattern.GetHost(), pattern.FallbackURL)
99
98
pattern.Fallback.ServeHTTP(w, r)
100
99
return true, nil
101
100
}