log.go raw
1 // Copyright 2024 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 package metadata
16
17 import (
18 "bytes"
19 "context"
20 "encoding/json"
21 "fmt"
22 "log/slog"
23 "net/http"
24 "strings"
25 )
26
27 // Code below this point is copied from github.com/googleapis/gax-go/v2/internallog
28 // to avoid the dependency. The compute/metadata module is used by too many
29 // non-client library modules that can't justify the dependency.
30
31 // The handler returned if logging is not enabled.
32 type noOpHandler struct{}
33
34 func (h noOpHandler) Enabled(_ context.Context, _ slog.Level) bool {
35 return false
36 }
37
38 func (h noOpHandler) Handle(_ context.Context, _ slog.Record) error {
39 return nil
40 }
41
42 func (h noOpHandler) WithAttrs(_ []slog.Attr) slog.Handler {
43 return h
44 }
45
46 func (h noOpHandler) WithGroup(_ string) slog.Handler {
47 return h
48 }
49
50 // httpRequest returns a lazily evaluated [slog.LogValuer] for a
51 // [http.Request] and the associated body.
52 func httpRequest(req *http.Request, body []byte) slog.LogValuer {
53 return &request{
54 req: req,
55 payload: body,
56 }
57 }
58
59 type request struct {
60 req *http.Request
61 payload []byte
62 }
63
64 func (r *request) LogValue() slog.Value {
65 if r == nil || r.req == nil {
66 return slog.Value{}
67 }
68 var groupValueAttrs []slog.Attr
69 groupValueAttrs = append(groupValueAttrs, slog.String("method", r.req.Method))
70 groupValueAttrs = append(groupValueAttrs, slog.String("url", r.req.URL.String()))
71
72 var headerAttr []slog.Attr
73 for k, val := range r.req.Header {
74 headerAttr = append(headerAttr, slog.String(k, strings.Join(val, ",")))
75 }
76 if len(headerAttr) > 0 {
77 groupValueAttrs = append(groupValueAttrs, slog.Any("headers", headerAttr))
78 }
79
80 if len(r.payload) > 0 {
81 if attr, ok := processPayload(r.payload); ok {
82 groupValueAttrs = append(groupValueAttrs, attr)
83 }
84 }
85 return slog.GroupValue(groupValueAttrs...)
86 }
87
88 // httpResponse returns a lazily evaluated [slog.LogValuer] for a
89 // [http.Response] and the associated body.
90 func httpResponse(resp *http.Response, body []byte) slog.LogValuer {
91 return &response{
92 resp: resp,
93 payload: body,
94 }
95 }
96
97 type response struct {
98 resp *http.Response
99 payload []byte
100 }
101
102 func (r *response) LogValue() slog.Value {
103 if r == nil {
104 return slog.Value{}
105 }
106 var groupValueAttrs []slog.Attr
107 groupValueAttrs = append(groupValueAttrs, slog.String("status", fmt.Sprint(r.resp.StatusCode)))
108
109 var headerAttr []slog.Attr
110 for k, val := range r.resp.Header {
111 headerAttr = append(headerAttr, slog.String(k, strings.Join(val, ",")))
112 }
113 if len(headerAttr) > 0 {
114 groupValueAttrs = append(groupValueAttrs, slog.Any("headers", headerAttr))
115 }
116
117 if len(r.payload) > 0 {
118 if attr, ok := processPayload(r.payload); ok {
119 groupValueAttrs = append(groupValueAttrs, attr)
120 }
121 }
122 return slog.GroupValue(groupValueAttrs...)
123 }
124
125 func processPayload(payload []byte) (slog.Attr, bool) {
126 peekChar := payload[0]
127 if peekChar == '{' {
128 // JSON object
129 var m map[string]any
130 if err := json.Unmarshal(payload, &m); err == nil {
131 return slog.Any("payload", m), true
132 }
133 } else if peekChar == '[' {
134 // JSON array
135 var m []any
136 if err := json.Unmarshal(payload, &m); err == nil {
137 return slog.Any("payload", m), true
138 }
139 } else {
140 // Everything else
141 buf := &bytes.Buffer{}
142 if err := json.Compact(buf, payload); err != nil {
143 // Write raw payload incase of error
144 buf.Write(payload)
145 }
146 return slog.String("payload", buf.String()), true
147 }
148 return slog.Attr{}, false
149 }
150