// Copyright 2024 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package metadata import ( "bytes" "context" "encoding/json" "fmt" "log/slog" "net/http" "strings" ) // Code below this point is copied from github.com/googleapis/gax-go/v2/internallog // to avoid the dependency. The compute/metadata module is used by too many // non-client library modules that can't justify the dependency. // The handler returned if logging is not enabled. type noOpHandler struct{} func (h noOpHandler) Enabled(_ context.Context, _ slog.Level) bool { return false } func (h noOpHandler) Handle(_ context.Context, _ slog.Record) error { return nil } func (h noOpHandler) WithAttrs(_ []slog.Attr) slog.Handler { return h } func (h noOpHandler) WithGroup(_ string) slog.Handler { return h } // httpRequest returns a lazily evaluated [slog.LogValuer] for a // [http.Request] and the associated body. func httpRequest(req *http.Request, body []byte) slog.LogValuer { return &request{ req: req, payload: body, } } type request struct { req *http.Request payload []byte } func (r *request) LogValue() slog.Value { if r == nil || r.req == nil { return slog.Value{} } var groupValueAttrs []slog.Attr groupValueAttrs = append(groupValueAttrs, slog.String("method", r.req.Method)) groupValueAttrs = append(groupValueAttrs, slog.String("url", r.req.URL.String())) var headerAttr []slog.Attr for k, val := range r.req.Header { headerAttr = append(headerAttr, slog.String(k, strings.Join(val, ","))) } if len(headerAttr) > 0 { groupValueAttrs = append(groupValueAttrs, slog.Any("headers", headerAttr)) } if len(r.payload) > 0 { if attr, ok := processPayload(r.payload); ok { groupValueAttrs = append(groupValueAttrs, attr) } } return slog.GroupValue(groupValueAttrs...) } // httpResponse returns a lazily evaluated [slog.LogValuer] for a // [http.Response] and the associated body. func httpResponse(resp *http.Response, body []byte) slog.LogValuer { return &response{ resp: resp, payload: body, } } type response struct { resp *http.Response payload []byte } func (r *response) LogValue() slog.Value { if r == nil { return slog.Value{} } var groupValueAttrs []slog.Attr groupValueAttrs = append(groupValueAttrs, slog.String("status", fmt.Sprint(r.resp.StatusCode))) var headerAttr []slog.Attr for k, val := range r.resp.Header { headerAttr = append(headerAttr, slog.String(k, strings.Join(val, ","))) } if len(headerAttr) > 0 { groupValueAttrs = append(groupValueAttrs, slog.Any("headers", headerAttr)) } if len(r.payload) > 0 { if attr, ok := processPayload(r.payload); ok { groupValueAttrs = append(groupValueAttrs, attr) } } return slog.GroupValue(groupValueAttrs...) } func processPayload(payload []byte) (slog.Attr, bool) { peekChar := payload[0] if peekChar == '{' { // JSON object var m map[string]any if err := json.Unmarshal(payload, &m); err == nil { return slog.Any("payload", m), true } } else if peekChar == '[' { // JSON array var m []any if err := json.Unmarshal(payload, &m); err == nil { return slog.Any("payload", m), true } } else { // Everything else buf := &bytes.Buffer{} if err := json.Compact(buf, payload); err != nil { // Write raw payload incase of error buf.Write(payload) } return slog.String("payload", buf.String()), true } return slog.Attr{}, false }