File: trace.go

package info (click to toggle)
golang-github-chainguard-dev-clog 1.7.0-3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 424 kB
  • sloc: makefile: 4
file content (124 lines) | stat: -rw-r--r-- 3,448 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
package gcp

import (
	"context"
	"fmt"
	"io"
	"log/slog"
	"net"
	"net/http"
	"os"
	"runtime"
	"strings"
	"sync"
	"time"
)

func insideTest() bool {
	// Ask runtime.Callers for up to 10 PCs, including runtime.Callers itself.
	pc := make([]uintptr, 10)
	n := runtime.Callers(0, pc)
	if n == 0 {
		slog.Debug("WithCloudTraceContext: no PCs available")
		return true
	}
	frames := runtime.CallersFrames(pc[:n])
	for {
		frame, more := frames.Next()
		if !more {
			break
		}
		if strings.HasPrefix(frame.Function, "testing.") &&
			strings.HasSuffix(frame.File, "src/testing/testing.go") {
			slog.Debug("WithCloudTraceContext: inside test", "function", frame.Function, "file", frame.File, "line", frame.Line)
			return true
		}
	}
	return false
}

var (
	projectID  string
	lookupOnce sync.Once
)

// WithCloudTraceContext returns an http.handler that adds the GCP Cloud Trace
// ID to the context. This is used to correlate the structured logs with the
// request log.
func WithCloudTraceContext(h http.Handler) http.Handler {
	// Get the project ID from the environment if specified
	fromEnv := os.Getenv("GOOGLE_CLOUD_PROJECT")
	if fromEnv != "" {
		projectID = fromEnv
	} else {
		lookupOnce.Do(func() {
			if insideTest() {
				slog.Debug("WithCloudTraceContext: inside test, not looking up project ID")
				return
			}

			// By default use the metadata IP; otherwise use the environment variable
			// for consistency with https://pkg.go.dev/cloud.google.com/go/compute/metadata#Client.Get
			host := "169.254.169.254"
			if h := os.Getenv("GCE_METADATA_HOST"); h != "" {
				host = h
			}
			req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("http://%s/computeMetadata/v1/project/project-id", host), nil)
			if err != nil {
				slog.Debug("WithCloudTraceContext: could not get GCP project ID from metadata server", "err", err)
				return
			}
			req.Header.Set("Metadata-Flavor", "Google")
			resp, err := (&http.Client{ // Timeouts copied from https://pkg.go.dev/cloud.google.com/go/compute/metadata#Get
				Transport: &http.Transport{
					Dial: (&net.Dialer{Timeout: 2 * time.Second}).Dial,
				},
				Timeout: 5 * time.Second,
			}).Do(req)
			if err != nil {
				slog.Debug("WithCloudTraceContext: could not get GCP project ID from metadata server", "err", err)
				return
			}
			if resp.StatusCode != http.StatusOK {
				slog.Debug("WithCloudTraceContext: could not get GCP project ID from metadata server", "code", resp.StatusCode, "status", resp.Status)
				return
			}
			defer resp.Body.Close()
			all, err := io.ReadAll(resp.Body)
			if err != nil {
				slog.Debug("WithCloudTraceContext: could not get GCP project ID from metadata server", "err", err)
				return
			}
			projectID = string(all)
		})
	}

	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if projectID != "" {
			var trace string
			traceHeader := r.Header.Get("traceparent")
			traceID := parseTraceFromW3CHeader(traceHeader)
			if traceID != "" {
				trace = fmt.Sprintf("projects/%s/traces/%s", projectID, traceID)
			}
			r = r.WithContext(context.WithValue(r.Context(), "trace", trace))
		}
		h.ServeHTTP(w, r)
	})
}

func traceFromContext(ctx context.Context) string {
	trace := ctx.Value("trace")
	if trace == nil {
		return ""
	}
	return trace.(string)
}

func parseTraceFromW3CHeader(traceparent string) string {
	traceParts := strings.Split(traceparent, "-")
	if len(traceParts) > 1 {
		return traceParts[1]
	}
	return ""
}