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
15package metadata
16
17import (
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.
32type noOpHandler struct{}
33
34func (h noOpHandler) Enabled(_ context.Context, _ slog.Level) bool {
35 return false
36}
37
38func (h noOpHandler) Handle(_ context.Context, _ slog.Record) error {
39 return nil
40}
41
42func (h noOpHandler) WithAttrs(_ []slog.Attr) slog.Handler {
43 return h
44}
45
46func (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.
52func httpRequest(req *http.Request, body []byte) slog.LogValuer {
53 return &request{
54 req: req,
55 payload: body,
56 }
57}
58
59type request struct {
60 req *http.Request
61 payload []byte
62}
63
64func (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.
90func httpResponse(resp *http.Response, body []byte) slog.LogValuer {
91 return &response{
92 resp: resp,
93 payload: body,
94 }
95}
96
97type response struct {
98 resp *http.Response
99 payload []byte
100}
101
102func (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
125func 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}