diff --git a/domain/apiresponses/failure_responses.go b/domain/apiresponses/failure_responses.go index 8d171225..b657defc 100644 --- a/domain/apiresponses/failure_responses.go +++ b/domain/apiresponses/failure_responses.go @@ -1,11 +1,9 @@ package apiresponses import ( + "errors" "fmt" - "log/slog" "net/http" - - "github.com/pivotal-cf/brokerapi/v10/internal/logutil" ) // FailureResponse can be returned from any of the `ServiceBroker` interface methods @@ -44,10 +42,10 @@ func (f *FailureResponse) ErrorResponse() interface{} { } } -func (f *FailureResponse) ValidatedStatusCode(prefix string, logger *slog.Logger) int { +func (f *FailureResponse) ValidatedStatusCode(logger interface{ Error(string, error) }) int { if f.statusCode < 400 || 600 <= f.statusCode { if logger != nil { - logger.Error(logutil.Join(prefix, "validating-status-code"), slog.String("error", "Invalid failure http response code: 600, expected 4xx or 5xx, returning internal server error: 500.")) + logger.Error("validating-status-code", errors.New("Invalid failure http response code: 600, expected 4xx or 5xx, returning internal server error: 500.")) } return http.StatusInternalServerError } diff --git a/domain/apiresponses/failure_responses_test.go b/domain/apiresponses/failure_responses_test.go index 738dc314..edc6b3fc 100644 --- a/domain/apiresponses/failure_responses_test.go +++ b/domain/apiresponses/failure_responses_test.go @@ -1,6 +1,7 @@ package apiresponses_test import ( + "context" "errors" "log/slog" "net/http" @@ -9,6 +10,7 @@ import ( . "github.com/onsi/gomega" "github.com/onsi/gomega/gbytes" "github.com/pivotal-cf/brokerapi/v10/domain/apiresponses" + "github.com/pivotal-cf/brokerapi/v10/internal/blog" ) var _ = Describe("FailureResponse", func() { @@ -46,7 +48,7 @@ var _ = Describe("FailureResponse", func() { newError := failureResponse.AppendErrorMessage("and some more details") Expect(newError.Error()).To(Equal("my error message and some more details")) - Expect(newError.ValidatedStatusCode("", nil)).To(Equal(http.StatusForbidden)) + Expect(newError.ValidatedStatusCode(nil)).To(Equal(http.StatusForbidden)) Expect(newError.LoggerAction()).To(Equal(failureResponse.LoggerAction())) errorResponse, typeCast := newError.ErrorResponse().(apiresponses.ErrorResponse) @@ -62,7 +64,7 @@ var _ = Describe("FailureResponse", func() { newError := failureResponse.AppendErrorMessage("and some more details") Expect(newError.Error()).To(Equal("my error message and some more details")) - Expect(newError.ValidatedStatusCode("", nil)).To(Equal(http.StatusForbidden)) + Expect(newError.ValidatedStatusCode(nil)).To(Equal(http.StatusForbidden)) Expect(newError.LoggerAction()).To(Equal(failureResponse.LoggerAction())) Expect(newError.ErrorResponse()).To(Equal(failureResponse.ErrorResponse())) }) @@ -71,25 +73,25 @@ var _ = Describe("FailureResponse", func() { Describe("ValidatedStatusCode", func() { It("returns the status code that was passed in", func() { failureResponse := asFailureResponse(apiresponses.NewFailureResponse(errors.New("my error message"), http.StatusForbidden, "log-key")) - Expect(failureResponse.ValidatedStatusCode("", nil)).To(Equal(http.StatusForbidden)) + Expect(failureResponse.ValidatedStatusCode(nil)).To(Equal(http.StatusForbidden)) }) It("when error key is provided it returns the status code that was passed in", func() { failureResponse := apiresponses.NewFailureResponseBuilder(errors.New("my error message"), http.StatusForbidden, "log-key").WithErrorKey("error key").Build() - Expect(failureResponse.ValidatedStatusCode("", nil)).To(Equal(http.StatusForbidden)) + Expect(failureResponse.ValidatedStatusCode(nil)).To(Equal(http.StatusForbidden)) }) Context("when the status code is invalid", func() { It("returns 500", func() { failureResponse := asFailureResponse(apiresponses.NewFailureResponse(errors.New("my error message"), 600, "log-key")) - Expect(failureResponse.ValidatedStatusCode("", nil)).To(Equal(http.StatusInternalServerError)) + Expect(failureResponse.ValidatedStatusCode(nil)).To(Equal(http.StatusInternalServerError)) }) It("logs that the status has been changed", func() { log := gbytes.NewBuffer() - logger := slog.New(slog.NewJSONHandler(log, nil)) + logger := blog.New(context.TODO(), slog.New(slog.NewJSONHandler(log, nil)), "prefix") failureResponse := asFailureResponse(apiresponses.NewFailureResponse(errors.New("my error message"), 600, "log-key")) - failureResponse.ValidatedStatusCode("", logger) + failureResponse.ValidatedStatusCode(logger) Expect(log).To(gbytes.Say("Invalid failure http response code: 600, expected 4xx or 5xx, returning internal server error: 500.")) }) }) diff --git a/failure_response_test.go b/failure_response_test.go index 3a78044b..4c2ee8f1 100644 --- a/failure_response_test.go +++ b/failure_response_test.go @@ -16,6 +16,7 @@ package brokerapi_test import ( + "context" "errors" "log/slog" "net/http" @@ -25,6 +26,7 @@ import ( "github.com/onsi/gomega/gbytes" "github.com/pivotal-cf/brokerapi/v10" "github.com/pivotal-cf/brokerapi/v10/domain/apiresponses" + "github.com/pivotal-cf/brokerapi/v10/internal/blog" ) var _ = Describe("FailureResponse", func() { @@ -62,7 +64,7 @@ var _ = Describe("FailureResponse", func() { newError := failureResponse.AppendErrorMessage("and some more details") Expect(newError.Error()).To(Equal("my error message and some more details")) - Expect(newError.ValidatedStatusCode("", nil)).To(Equal(http.StatusForbidden)) + Expect(newError.ValidatedStatusCode(nil)).To(Equal(http.StatusForbidden)) Expect(newError.LoggerAction()).To(Equal(failureResponse.LoggerAction())) errorResponse, typeCast := newError.ErrorResponse().(brokerapi.ErrorResponse) @@ -78,7 +80,7 @@ var _ = Describe("FailureResponse", func() { newError := failureResponse.AppendErrorMessage("and some more details") Expect(newError.Error()).To(Equal("my error message and some more details")) - Expect(newError.ValidatedStatusCode("", nil)).To(Equal(http.StatusForbidden)) + Expect(newError.ValidatedStatusCode(nil)).To(Equal(http.StatusForbidden)) Expect(newError.LoggerAction()).To(Equal(failureResponse.LoggerAction())) Expect(newError.ErrorResponse()).To(Equal(failureResponse.ErrorResponse())) }) @@ -87,25 +89,25 @@ var _ = Describe("FailureResponse", func() { Describe("ValidatedStatusCode", func() { It("returns the status code that was passed in", func() { failureResponse := asFailureResponse(brokerapi.NewFailureResponse(errors.New("my error message"), http.StatusForbidden, "log-key")) - Expect(failureResponse.ValidatedStatusCode("", nil)).To(Equal(http.StatusForbidden)) + Expect(failureResponse.ValidatedStatusCode(nil)).To(Equal(http.StatusForbidden)) }) It("when error key is provided it returns the status code that was passed in", func() { failureResponse := brokerapi.NewFailureResponseBuilder(errors.New("my error message"), http.StatusForbidden, "log-key").WithErrorKey("error key").Build() - Expect(failureResponse.ValidatedStatusCode("", nil)).To(Equal(http.StatusForbidden)) + Expect(failureResponse.ValidatedStatusCode(nil)).To(Equal(http.StatusForbidden)) }) Context("when the status code is invalid", func() { It("returns 500", func() { failureResponse := asFailureResponse(brokerapi.NewFailureResponse(errors.New("my error message"), 600, "log-key")) - Expect(failureResponse.ValidatedStatusCode("", nil)).To(Equal(http.StatusInternalServerError)) + Expect(failureResponse.ValidatedStatusCode(nil)).To(Equal(http.StatusInternalServerError)) }) It("logs that the status has been changed", func() { log := gbytes.NewBuffer() - logger := slog.New(slog.NewJSONHandler(log, nil)) + logger := blog.New(context.TODO(), slog.New(slog.NewJSONHandler(log, nil)), "prefix") failureResponse := asFailureResponse(brokerapi.NewFailureResponse(errors.New("my error message"), 600, "log-key")) - failureResponse.ValidatedStatusCode("", logger) + failureResponse.ValidatedStatusCode(logger) Expect(log).To(gbytes.Say("Invalid failure http response code: 600, expected 4xx or 5xx, returning internal server error: 500.")) }) }) diff --git a/handlers/api_handler.go b/handlers/api_handler.go index 192eaa4e..fc39f30b 100644 --- a/handlers/api_handler.go +++ b/handlers/api_handler.go @@ -8,17 +8,13 @@ import ( "net/http" "github.com/pivotal-cf/brokerapi/v10/domain" - "github.com/pivotal-cf/brokerapi/v10/internal/logutil" ) const ( invalidServiceDetailsErrorKey = "invalid-service-details" - instanceIDLogKey = "instance-id" serviceIdMissingKey = "service-id-missing" planIdMissingKey = "plan-id-missing" unknownErrorKey = "unknown-error" - - bindingIDLogKey = "binding-id" ) var ( @@ -48,7 +44,7 @@ func (h APIHandler) respond(w http.ResponseWriter, status int, requestIdentity s encoder.SetEscapeHTML(false) err := encoder.Encode(response) if err != nil { - h.logger.Error("encoding response", logutil.Error(err), slog.Int("status", status), slog.Any("response", response)) + h.logger.Error("encoding response", slog.Any("error", err), slog.Int("status", status), slog.Any("response", response)) } } diff --git a/handlers/bind.go b/handlers/bind.go index 9d83c290..6d0a5162 100644 --- a/handlers/bind.go +++ b/handlers/bind.go @@ -3,30 +3,26 @@ package handlers import ( "encoding/json" "fmt" - "log/slog" "net/http" + "github.com/pivotal-cf/brokerapi/v10/internal/blog" + "github.com/go-chi/chi/v5" "github.com/pivotal-cf/brokerapi/v10/domain" "github.com/pivotal-cf/brokerapi/v10/domain/apiresponses" - "github.com/pivotal-cf/brokerapi/v10/internal/logutil" "github.com/pivotal-cf/brokerapi/v10/middlewares" - "github.com/pivotal-cf/brokerapi/v10/utils" ) const ( bindLogKey = "bind" - invalidBindDetailsErrorKey = "bind.invalid-bind-details" + invalidBindDetailsErrorKey = "invalid-bind-details" ) func (h APIHandler) Bind(w http.ResponseWriter, req *http.Request) { instanceID := chi.URLParam(req, "instance_id") bindingID := chi.URLParam(req, "binding_id") - logger := h.logger.With(append( - []any{slog.String(instanceIDLogKey, instanceID), slog.String(bindingIDLogKey, bindingID)}, - utils.ContextAttr(req.Context(), middlewares.CorrelationIDKey, middlewares.RequestIdentityKey)..., - )...) + logger := blog.New(req.Context(), h.logger, bindLogKey, blog.InstanceID(instanceID), blog.BindingID(bindingID)) version := getAPIVersion(req) asyncAllowed := false @@ -38,7 +34,7 @@ func (h APIHandler) Bind(w http.ResponseWriter, req *http.Request) { var details domain.BindDetails if err := json.NewDecoder(req.Body).Decode(&details); err != nil { - logger.Error(invalidBindDetailsErrorKey, logutil.Error(err)) + logger.Error(invalidBindDetailsErrorKey, err) h.respond(w, http.StatusUnprocessableEntity, requestId, apiresponses.ErrorResponse{ Description: err.Error(), }) @@ -46,7 +42,7 @@ func (h APIHandler) Bind(w http.ResponseWriter, req *http.Request) { } if details.ServiceID == "" { - logger.Error(logutil.Join(bindLogKey, serviceIdMissingKey), logutil.Error(serviceIdError)) + logger.Error(serviceIdMissingKey, serviceIdError) h.respond(w, http.StatusBadRequest, requestId, apiresponses.ErrorResponse{ Description: serviceIdError.Error(), }) @@ -54,7 +50,7 @@ func (h APIHandler) Bind(w http.ResponseWriter, req *http.Request) { } if details.PlanID == "" { - logger.Error(logutil.Join(bindLogKey, planIdMissingKey), logutil.Error(planIdError)) + logger.Error(planIdMissingKey, planIdError) h.respond(w, http.StatusBadRequest, requestId, apiresponses.ErrorResponse{ Description: planIdError.Error(), }) @@ -65,7 +61,7 @@ func (h APIHandler) Bind(w http.ResponseWriter, req *http.Request) { if err != nil { switch err := err.(type) { case *apiresponses.FailureResponse: - statusCode := err.ValidatedStatusCode(bindLogKey, logger) + statusCode := err.ValidatedStatusCode(logger) errorResponse := err.ErrorResponse() if err == apiresponses.ErrInstanceDoesNotExist { // work around ErrInstanceDoesNotExist having different pre-refactor behaviour to other actions @@ -74,10 +70,10 @@ func (h APIHandler) Bind(w http.ResponseWriter, req *http.Request) { } statusCode = http.StatusNotFound } - logger.Error(logutil.Join(bindLogKey, err.LoggerAction()), logutil.Error(err)) + logger.Error(err.LoggerAction(), err) h.respond(w, statusCode, requestId, errorResponse) default: - logger.Error(logutil.Join(bindLogKey, unknownErrorKey), logutil.Error(err)) + logger.Error(unknownErrorKey, err) h.respond(w, http.StatusInternalServerError, requestId, apiresponses.ErrorResponse{ Description: err.Error(), }) @@ -109,7 +105,7 @@ func (h APIHandler) Bind(w http.ResponseWriter, req *http.Request) { for _, vol := range binding.VolumeMounts { experimentalConfig, err := json.Marshal(vol.Device.MountConfig) if err != nil { - logger.Error(logutil.Join(bindLogKey, unknownErrorKey), logutil.Error(err)) + logger.Error(unknownErrorKey, err) h.respond(w, http.StatusInternalServerError, requestId, apiresponses.ErrorResponse{Description: err.Error()}) return } diff --git a/handlers/catalog.go b/handlers/catalog.go index bfe6d5aa..8788d0a0 100644 --- a/handlers/catalog.go +++ b/handlers/catalog.go @@ -4,24 +4,24 @@ import ( "fmt" "net/http" + "github.com/pivotal-cf/brokerapi/v10/internal/blog" + "github.com/pivotal-cf/brokerapi/v10/domain/apiresponses" - "github.com/pivotal-cf/brokerapi/v10/internal/logutil" "github.com/pivotal-cf/brokerapi/v10/middlewares" - "github.com/pivotal-cf/brokerapi/v10/utils" ) const getCatalogLogKey = "getCatalog" func (h *APIHandler) Catalog(w http.ResponseWriter, req *http.Request) { - logger := h.logger.With(utils.ContextAttr(req.Context(), middlewares.CorrelationIDKey, middlewares.RequestIdentityKey)...) + logger := blog.New(req.Context(), h.logger, getCatalogLogKey) requestId := fmt.Sprintf("%v", req.Context().Value(middlewares.RequestIdentityKey)) services, err := h.serviceBroker.Services(req.Context()) if err != nil { switch err := err.(type) { case *apiresponses.FailureResponse: - logger.Error(logutil.Join(getCatalogLogKey, err.LoggerAction()), logutil.Error(err)) - h.respond(w, err.ValidatedStatusCode(getCatalogLogKey, logger), requestId, err.ErrorResponse()) + logger.Error(err.LoggerAction(), err) + h.respond(w, err.ValidatedStatusCode(logger), requestId, err.ErrorResponse()) default: logger.Error(unknownErrorKey, err) h.respond(w, http.StatusInternalServerError, requestId, apiresponses.ErrorResponse{ diff --git a/handlers/deprovision.go b/handlers/deprovision.go index 318bfc44..dabfbfbe 100644 --- a/handlers/deprovision.go +++ b/handlers/deprovision.go @@ -2,15 +2,14 @@ package handlers import ( "fmt" - "log/slog" "net/http" + "github.com/pivotal-cf/brokerapi/v10/internal/blog" + "github.com/go-chi/chi/v5" "github.com/pivotal-cf/brokerapi/v10/domain" "github.com/pivotal-cf/brokerapi/v10/domain/apiresponses" - "github.com/pivotal-cf/brokerapi/v10/internal/logutil" "github.com/pivotal-cf/brokerapi/v10/middlewares" - "github.com/pivotal-cf/brokerapi/v10/utils" ) const deprovisionLogKey = "deprovision" @@ -18,10 +17,7 @@ const deprovisionLogKey = "deprovision" func (h APIHandler) Deprovision(w http.ResponseWriter, req *http.Request) { instanceID := chi.URLParam(req, "instance_id") - logger := h.logger.With(append( - []any{slog.String(instanceIDLogKey, instanceID)}, - utils.ContextAttr(req.Context(), middlewares.CorrelationIDKey, middlewares.RequestIdentityKey)..., - )...) + logger := blog.New(req.Context(), h.logger, deprovisionLogKey, blog.InstanceID(instanceID)) details := domain.DeprovisionDetails{ PlanID: req.FormValue("plan_id"), @@ -35,7 +31,7 @@ func (h APIHandler) Deprovision(w http.ResponseWriter, req *http.Request) { h.respond(w, http.StatusBadRequest, requestId, apiresponses.ErrorResponse{ Description: serviceIdError.Error(), }) - logger.Error(logutil.Join(deprovisionLogKey, serviceIdMissingKey), logutil.Error(serviceIdError)) + logger.Error(serviceIdMissingKey, serviceIdError) return } @@ -43,7 +39,7 @@ func (h APIHandler) Deprovision(w http.ResponseWriter, req *http.Request) { h.respond(w, http.StatusBadRequest, requestId, apiresponses.ErrorResponse{ Description: planIdError.Error(), }) - logger.Error(logutil.Join(deprovisionLogKey, planIdMissingKey), logutil.Error(planIdError)) + logger.Error(planIdMissingKey, planIdError) return } @@ -53,10 +49,10 @@ func (h APIHandler) Deprovision(w http.ResponseWriter, req *http.Request) { if err != nil { switch err := err.(type) { case *apiresponses.FailureResponse: - logger.Error(logutil.Join(deprovisionLogKey, err.LoggerAction()), logutil.Error(err)) - h.respond(w, err.ValidatedStatusCode(deprovisionLogKey, logger), requestId, err.ErrorResponse()) + logger.Error(err.LoggerAction(), err) + h.respond(w, err.ValidatedStatusCode(logger), requestId, err.ErrorResponse()) default: - logger.Error(logutil.Join(deprovisionLogKey, unknownErrorKey), logutil.Error(err)) + logger.Error(unknownErrorKey, err) h.respond(w, http.StatusInternalServerError, requestId, apiresponses.ErrorResponse{ Description: err.Error(), }) diff --git a/handlers/get_binding.go b/handlers/get_binding.go index 8bf61ee6..88a76a5e 100644 --- a/handlers/get_binding.go +++ b/handlers/get_binding.go @@ -3,16 +3,14 @@ package handlers import ( "errors" "fmt" - "log/slog" "net/http" - "github.com/pivotal-cf/brokerapi/v10/internal/logutil" + "github.com/pivotal-cf/brokerapi/v10/internal/blog" "github.com/go-chi/chi/v5" "github.com/pivotal-cf/brokerapi/v10/domain" "github.com/pivotal-cf/brokerapi/v10/domain/apiresponses" "github.com/pivotal-cf/brokerapi/v10/middlewares" - "github.com/pivotal-cf/brokerapi/v10/utils" ) const getBindLogKey = "getBinding" @@ -21,10 +19,7 @@ func (h APIHandler) GetBinding(w http.ResponseWriter, req *http.Request) { instanceID := chi.URLParam(req, "instance_id") bindingID := chi.URLParam(req, "binding_id") - logger := h.logger.With(append( - []any{slog.String(instanceIDLogKey, instanceID), slog.String(bindingIDLogKey, bindingID)}, - utils.ContextAttr(req.Context(), middlewares.CorrelationIDKey, middlewares.RequestIdentityKey)..., - )...) + logger := blog.New(req.Context(), h.logger, getBindLogKey, blog.InstanceID(instanceID), blog.BindingID(bindingID)) requestId := fmt.Sprintf("%v", req.Context().Value(middlewares.RequestIdentityKey)) @@ -34,7 +29,7 @@ func (h APIHandler) GetBinding(w http.ResponseWriter, req *http.Request) { h.respond(w, http.StatusPreconditionFailed, requestId, apiresponses.ErrorResponse{ Description: err.Error(), }) - logger.Error(logutil.Join(getBindLogKey, middlewares.ApiVersionInvalidKey), logutil.Error(err)) + logger.Error(middlewares.ApiVersionInvalidKey, err) return } @@ -47,10 +42,10 @@ func (h APIHandler) GetBinding(w http.ResponseWriter, req *http.Request) { if err != nil { switch err := err.(type) { case *apiresponses.FailureResponse: - logger.Error(logutil.Join(getBindLogKey, err.LoggerAction()), logutil.Error(err)) - h.respond(w, err.ValidatedStatusCode(getBindLogKey, logger), requestId, err.ErrorResponse()) + logger.Error(err.LoggerAction(), err) + h.respond(w, err.ValidatedStatusCode(logger), requestId, err.ErrorResponse()) default: - logger.Error(logutil.Join(getBindLogKey, unknownErrorKey), logutil.Error(err)) + logger.Error(unknownErrorKey, err) h.respond(w, http.StatusInternalServerError, requestId, apiresponses.ErrorResponse{ Description: err.Error(), }) diff --git a/handlers/get_instance.go b/handlers/get_instance.go index 052fd1a7..2ad1bf69 100644 --- a/handlers/get_instance.go +++ b/handlers/get_instance.go @@ -3,16 +3,14 @@ package handlers import ( "errors" "fmt" - "log/slog" "net/http" - "github.com/pivotal-cf/brokerapi/v10/internal/logutil" + "github.com/pivotal-cf/brokerapi/v10/internal/blog" "github.com/go-chi/chi/v5" "github.com/pivotal-cf/brokerapi/v10/domain" "github.com/pivotal-cf/brokerapi/v10/domain/apiresponses" "github.com/pivotal-cf/brokerapi/v10/middlewares" - "github.com/pivotal-cf/brokerapi/v10/utils" ) const getInstanceLogKey = "getInstance" @@ -20,10 +18,7 @@ const getInstanceLogKey = "getInstance" func (h APIHandler) GetInstance(w http.ResponseWriter, req *http.Request) { instanceID := chi.URLParam(req, "instance_id") - logger := h.logger.With(append( - []any{slog.String(instanceIDLogKey, instanceID)}, - utils.ContextAttr(req.Context(), middlewares.CorrelationIDKey, middlewares.RequestIdentityKey)..., - )...) + logger := blog.New(req.Context(), h.logger, getInstanceLogKey, blog.InstanceID(instanceID)) requestId := fmt.Sprintf("%v", req.Context().Value(middlewares.RequestIdentityKey)) @@ -33,7 +28,7 @@ func (h APIHandler) GetInstance(w http.ResponseWriter, req *http.Request) { h.respond(w, http.StatusPreconditionFailed, requestId, apiresponses.ErrorResponse{ Description: err.Error(), }) - logger.Error(logutil.Join(getInstanceLogKey, middlewares.ApiVersionInvalidKey), logutil.Error(err)) + logger.Error(middlewares.ApiVersionInvalidKey, err) return } @@ -46,10 +41,10 @@ func (h APIHandler) GetInstance(w http.ResponseWriter, req *http.Request) { if err != nil { switch err := err.(type) { case *apiresponses.FailureResponse: - logger.Error(logutil.Join(getInstanceLogKey, err.LoggerAction()), logutil.Error(err)) - h.respond(w, err.ValidatedStatusCode(getInstanceLogKey, logger), requestId, err.ErrorResponse()) + logger.Error(err.LoggerAction(), err) + h.respond(w, err.ValidatedStatusCode(logger), requestId, err.ErrorResponse()) default: - logger.Error(logutil.Join(getInstanceLogKey, unknownErrorKey), logutil.Error(err)) + logger.Error(unknownErrorKey, err) h.respond(w, http.StatusInternalServerError, requestId, apiresponses.ErrorResponse{ Description: err.Error(), }) diff --git a/handlers/last_binding_operation.go b/handlers/last_binding_operation.go index 6e298e44..8c863472 100644 --- a/handlers/last_binding_operation.go +++ b/handlers/last_binding_operation.go @@ -6,13 +6,11 @@ import ( "log/slog" "net/http" - "github.com/pivotal-cf/brokerapi/v10/internal/logutil" - "github.com/go-chi/chi/v5" "github.com/pivotal-cf/brokerapi/v10/domain" "github.com/pivotal-cf/brokerapi/v10/domain/apiresponses" + "github.com/pivotal-cf/brokerapi/v10/internal/blog" "github.com/pivotal-cf/brokerapi/v10/middlewares" - "github.com/pivotal-cf/brokerapi/v10/utils" ) const lastBindingOperationLogKey = "lastBindingOperation" @@ -26,10 +24,7 @@ func (h APIHandler) LastBindingOperation(w http.ResponseWriter, req *http.Reques OperationData: req.FormValue("operation"), } - logger := h.logger.With(append( - []any{slog.String(instanceIDLogKey, instanceID)}, - utils.ContextAttr(req.Context(), middlewares.CorrelationIDKey, middlewares.RequestIdentityKey)..., - )...) + logger := blog.New(req.Context(), h.logger, lastBindingOperationLogKey, blog.InstanceID(instanceID)) requestId := fmt.Sprintf("%v", req.Context().Value(middlewares.RequestIdentityKey)) @@ -39,20 +34,20 @@ func (h APIHandler) LastBindingOperation(w http.ResponseWriter, req *http.Reques h.respond(w, http.StatusPreconditionFailed, requestId, apiresponses.ErrorResponse{ Description: err.Error(), }) - logger.Error(logutil.Join(lastBindingOperationLogKey, middlewares.ApiVersionInvalidKey), logutil.Error(err)) + logger.Error(middlewares.ApiVersionInvalidKey, err) return } - logger.Info(logutil.Join(lastBindingOperationLogKey, "starting-check-for-binding-operation")) + logger.Info("starting-check-for-binding-operation") lastOperation, err := h.serviceBroker.LastBindingOperation(req.Context(), instanceID, bindingID, pollDetails) if err != nil { switch err := err.(type) { case *apiresponses.FailureResponse: - logger.Error(logutil.Join(lastBindingOperationLogKey, err.LoggerAction()), logutil.Error(err)) - h.respond(w, err.ValidatedStatusCode(lastBindingOperationLogKey, logger), requestId, err.ErrorResponse()) + logger.Error(err.LoggerAction(), err) + h.respond(w, err.ValidatedStatusCode(logger), requestId, err.ErrorResponse()) default: - logger.Error(logutil.Join(lastBindingOperationLogKey, unknownErrorKey), logutil.Error(err)) + logger.Error(unknownErrorKey, err) h.respond(w, http.StatusInternalServerError, requestId, apiresponses.ErrorResponse{ Description: err.Error(), }) @@ -60,7 +55,7 @@ func (h APIHandler) LastBindingOperation(w http.ResponseWriter, req *http.Reques return } - logger.Info(logutil.Join(lastBindingOperationLogKey, "done-check-for-binding-operation"), slog.Any("state", lastOperation.State)) + logger.Info("done-check-for-binding-operation", slog.Any("state", lastOperation.State)) lastOperationResponse := apiresponses.LastOperationResponse{ State: lastOperation.State, diff --git a/handlers/last_operation.go b/handlers/last_operation.go index a0c2f75a..17de300b 100644 --- a/handlers/last_operation.go +++ b/handlers/last_operation.go @@ -5,13 +5,11 @@ import ( "log/slog" "net/http" - "github.com/pivotal-cf/brokerapi/v10/internal/logutil" - "github.com/go-chi/chi/v5" "github.com/pivotal-cf/brokerapi/v10/domain" "github.com/pivotal-cf/brokerapi/v10/domain/apiresponses" + "github.com/pivotal-cf/brokerapi/v10/internal/blog" "github.com/pivotal-cf/brokerapi/v10/middlewares" - "github.com/pivotal-cf/brokerapi/v10/utils" ) const lastOperationLogKey = "lastOperation" @@ -24,12 +22,9 @@ func (h APIHandler) LastOperation(w http.ResponseWriter, req *http.Request) { OperationData: req.FormValue("operation"), } - logger := h.logger.With(append( - []any{slog.String(instanceIDLogKey, instanceID)}, - utils.ContextAttr(req.Context(), middlewares.CorrelationIDKey, middlewares.RequestIdentityKey)..., - )...) + logger := blog.New(req.Context(), h.logger, lastOperationLogKey, blog.InstanceID(instanceID)) - logger.Info(logutil.Join(lastOperationLogKey, "starting-check-for-operation")) + logger.Info("starting-check-for-operation") requestId := fmt.Sprintf("%v", req.Context().Value(middlewares.RequestIdentityKey)) @@ -37,10 +32,10 @@ func (h APIHandler) LastOperation(w http.ResponseWriter, req *http.Request) { if err != nil { switch err := err.(type) { case *apiresponses.FailureResponse: - logger.Error(logutil.Join(lastOperationLogKey, err.LoggerAction()), logutil.Error(err)) - h.respond(w, err.ValidatedStatusCode(lastOperationLogKey, logger), requestId, err.ErrorResponse()) + logger.Error(err.LoggerAction(), err) + h.respond(w, err.ValidatedStatusCode(logger), requestId, err.ErrorResponse()) default: - logger.Error(logutil.Join(lastOperationLogKey, unknownErrorKey), logutil.Error(err)) + logger.Error(unknownErrorKey, err) h.respond(w, http.StatusInternalServerError, requestId, apiresponses.ErrorResponse{ Description: err.Error(), }) @@ -48,7 +43,7 @@ func (h APIHandler) LastOperation(w http.ResponseWriter, req *http.Request) { return } - logger.Info(logutil.Join(lastOperationLogKey, "done-check-for-operation"), slog.Any("state", lastOperation.State)) + logger.Info("done-check-for-operation", slog.Any("state", lastOperation.State)) lastOperationResponse := apiresponses.LastOperationResponse{ State: lastOperation.State, diff --git a/handlers/provision.go b/handlers/provision.go index 2d6f31a4..6552a146 100644 --- a/handlers/provision.go +++ b/handlers/provision.go @@ -6,11 +6,10 @@ import ( "log/slog" "net/http" - "github.com/pivotal-cf/brokerapi/v10/internal/logutil" - "github.com/go-chi/chi/v5" "github.com/pivotal-cf/brokerapi/v10/domain" "github.com/pivotal-cf/brokerapi/v10/domain/apiresponses" + "github.com/pivotal-cf/brokerapi/v10/internal/blog" "github.com/pivotal-cf/brokerapi/v10/middlewares" "github.com/pivotal-cf/brokerapi/v10/utils" ) @@ -27,16 +26,13 @@ const ( func (h *APIHandler) Provision(w http.ResponseWriter, req *http.Request) { instanceID := chi.URLParam(req, "instance_id") - logger := h.logger.With(append( - []any{slog.String(instanceIDLogKey, instanceID)}, - utils.ContextAttr(req.Context(), middlewares.CorrelationIDKey, middlewares.RequestIdentityKey)..., - )...) + logger := blog.New(req.Context(), h.logger, provisionLogKey, blog.InstanceID(instanceID)) requestId := fmt.Sprintf("%v", req.Context().Value(middlewares.RequestIdentityKey)) var details domain.ProvisionDetails if err := json.NewDecoder(req.Body).Decode(&details); err != nil { - logger.Error(logutil.Join(provisionLogKey, invalidServiceDetailsErrorKey), logutil.Error(err)) + logger.Error(invalidServiceDetailsErrorKey, err) h.respond(w, http.StatusUnprocessableEntity, requestId, apiresponses.ErrorResponse{ Description: err.Error(), }) @@ -44,7 +40,7 @@ func (h *APIHandler) Provision(w http.ResponseWriter, req *http.Request) { } if details.ServiceID == "" { - logger.Error(logutil.Join(provisionLogKey, serviceIdMissingKey), logutil.Error(serviceIdError)) + logger.Error(serviceIdMissingKey, serviceIdError) h.respond(w, http.StatusBadRequest, requestId, apiresponses.ErrorResponse{ Description: serviceIdError.Error(), }) @@ -52,7 +48,7 @@ func (h *APIHandler) Provision(w http.ResponseWriter, req *http.Request) { } if details.PlanID == "" { - logger.Error(logutil.Join(provisionLogKey, planIdMissingKey), logutil.Error(planIdError)) + logger.Error(planIdMissingKey, planIdError) h.respond(w, http.StatusBadRequest, requestId, apiresponses.ErrorResponse{ Description: planIdError.Error(), }) @@ -69,7 +65,7 @@ func (h *APIHandler) Provision(w http.ResponseWriter, req *http.Request) { } } if !valid { - logger.Error(logutil.Join(provisionLogKey, invalidServiceID), logutil.Error(invalidServiceIDError)) + logger.Error(invalidServiceID, invalidServiceIDError) h.respond(w, http.StatusBadRequest, requestId, apiresponses.ErrorResponse{ Description: invalidServiceIDError.Error(), }) @@ -87,7 +83,7 @@ func (h *APIHandler) Provision(w http.ResponseWriter, req *http.Request) { } } if !valid { - logger.Error(logutil.Join(provisionLogKey, invalidPlanID), logutil.Error(invalidPlanIDError)) + logger.Error(invalidPlanID, invalidPlanIDError) h.respond(w, http.StatusBadRequest, requestId, apiresponses.ErrorResponse{ Description: invalidPlanIDError.Error(), }) @@ -103,10 +99,10 @@ func (h *APIHandler) Provision(w http.ResponseWriter, req *http.Request) { if err != nil { switch err := err.(type) { case *apiresponses.FailureResponse: - logger.Error(logutil.Join(provisionLogKey, err.LoggerAction()), logutil.Error(err)) - h.respond(w, err.ValidatedStatusCode(provisionLogKey, logger), requestId, err.ErrorResponse()) + logger.Error(err.LoggerAction(), err) + h.respond(w, err.ValidatedStatusCode(logger), requestId, err.ErrorResponse()) default: - logger.Error(logutil.Join(provisionLogKey, unknownErrorKey), logutil.Error(err)) + logger.Error(unknownErrorKey, err) h.respond(w, http.StatusInternalServerError, requestId, apiresponses.ErrorResponse{ Description: err.Error(), }) diff --git a/handlers/unbind.go b/handlers/unbind.go index 9944fddc..0ae0bdc2 100644 --- a/handlers/unbind.go +++ b/handlers/unbind.go @@ -2,16 +2,13 @@ package handlers import ( "fmt" - "log/slog" "net/http" - "github.com/pivotal-cf/brokerapi/v10/internal/logutil" - "github.com/go-chi/chi/v5" "github.com/pivotal-cf/brokerapi/v10/domain" "github.com/pivotal-cf/brokerapi/v10/domain/apiresponses" + "github.com/pivotal-cf/brokerapi/v10/internal/blog" "github.com/pivotal-cf/brokerapi/v10/middlewares" - "github.com/pivotal-cf/brokerapi/v10/utils" ) const unbindLogKey = "unbind" @@ -20,10 +17,7 @@ func (h APIHandler) Unbind(w http.ResponseWriter, req *http.Request) { instanceID := chi.URLParam(req, "instance_id") bindingID := chi.URLParam(req, "binding_id") - logger := h.logger.With(append( - []any{slog.String(instanceIDLogKey, instanceID), slog.String(bindingIDLogKey, bindingID)}, - utils.ContextAttr(req.Context(), middlewares.CorrelationIDKey, middlewares.RequestIdentityKey)..., - )...) + logger := blog.New(req.Context(), h.logger, unbindLogKey, blog.InstanceID(instanceID), blog.BindingID(bindingID)) requestId := fmt.Sprintf("%v", req.Context().Value(middlewares.RequestIdentityKey)) @@ -36,7 +30,7 @@ func (h APIHandler) Unbind(w http.ResponseWriter, req *http.Request) { h.respond(w, http.StatusBadRequest, requestId, apiresponses.ErrorResponse{ Description: serviceIdError.Error(), }) - logger.Error(logutil.Join(unbindLogKey, serviceIdMissingKey), logutil.Error(serviceIdError)) + logger.Error(serviceIdMissingKey, serviceIdError) return } @@ -44,7 +38,7 @@ func (h APIHandler) Unbind(w http.ResponseWriter, req *http.Request) { h.respond(w, http.StatusBadRequest, requestId, apiresponses.ErrorResponse{ Description: planIdError.Error(), }) - logger.Error(logutil.Join(unbindLogKey, planIdMissingKey), logutil.Error(planIdError)) + logger.Error(planIdMissingKey, planIdError) return } @@ -53,10 +47,10 @@ func (h APIHandler) Unbind(w http.ResponseWriter, req *http.Request) { if err != nil { switch err := err.(type) { case *apiresponses.FailureResponse: - logger.Error(logutil.Join(unbindLogKey, err.LoggerAction()), logutil.Error(err)) - h.respond(w, err.ValidatedStatusCode(unbindLogKey, logger), requestId, err.ErrorResponse()) + logger.Error(err.LoggerAction(), err) + h.respond(w, err.ValidatedStatusCode(logger), requestId, err.ErrorResponse()) default: - logger.Error(logutil.Join(unbindLogKey, unknownErrorKey), logutil.Error(err)) + logger.Error(unknownErrorKey, err) h.respond(w, http.StatusInternalServerError, requestId, apiresponses.ErrorResponse{ Description: err.Error(), }) diff --git a/handlers/update.go b/handlers/update.go index 02c7a96c..3cb5ede1 100644 --- a/handlers/update.go +++ b/handlers/update.go @@ -3,17 +3,14 @@ package handlers import ( "encoding/json" "fmt" - "log/slog" "net/http" "strconv" - "github.com/pivotal-cf/brokerapi/v10/internal/logutil" - "github.com/go-chi/chi/v5" "github.com/pivotal-cf/brokerapi/v10/domain" "github.com/pivotal-cf/brokerapi/v10/domain/apiresponses" + "github.com/pivotal-cf/brokerapi/v10/internal/blog" "github.com/pivotal-cf/brokerapi/v10/middlewares" - "github.com/pivotal-cf/brokerapi/v10/utils" ) const updateLogKey = "update" @@ -21,16 +18,13 @@ const updateLogKey = "update" func (h APIHandler) Update(w http.ResponseWriter, req *http.Request) { instanceID := chi.URLParam(req, "instance_id") - logger := h.logger.With(append( - []any{slog.String(instanceIDLogKey, instanceID)}, - utils.ContextAttr(req.Context(), middlewares.CorrelationIDKey, middlewares.RequestIdentityKey)..., - )...) + logger := blog.New(req.Context(), h.logger, updateLogKey, blog.InstanceID(instanceID)) requestId := fmt.Sprintf("%v", req.Context().Value(middlewares.RequestIdentityKey)) var details domain.UpdateDetails if err := json.NewDecoder(req.Body).Decode(&details); err != nil { - logger.Error(logutil.Join(updateLogKey, invalidServiceDetailsErrorKey), logutil.Error(err)) + logger.Error(invalidServiceDetailsErrorKey, err) h.respond(w, http.StatusUnprocessableEntity, requestId, apiresponses.ErrorResponse{ Description: err.Error(), }) @@ -38,7 +32,7 @@ func (h APIHandler) Update(w http.ResponseWriter, req *http.Request) { } if details.ServiceID == "" { - logger.Error(logutil.Join(updateLogKey, serviceIdMissingKey), logutil.Error(serviceIdError)) + logger.Error(serviceIdMissingKey, serviceIdError) h.respond(w, http.StatusBadRequest, requestId, apiresponses.ErrorResponse{ Description: serviceIdError.Error(), }) @@ -51,10 +45,10 @@ func (h APIHandler) Update(w http.ResponseWriter, req *http.Request) { if err != nil { switch err := err.(type) { case *apiresponses.FailureResponse: - logger.Error(logutil.Join(updateLogKey, err.LoggerAction()), logutil.Error(err)) - h.respond(w, err.ValidatedStatusCode(updateLogKey, logger), requestId, err.ErrorResponse()) + logger.Error(err.LoggerAction(), err) + h.respond(w, err.ValidatedStatusCode(logger), requestId, err.ErrorResponse()) default: - logger.Error(logutil.Join(updateLogKey, unknownErrorKey), logutil.Error(err)) + logger.Error(unknownErrorKey, err) h.respond(w, http.StatusInternalServerError, requestId, apiresponses.ErrorResponse{ Description: err.Error(), }) diff --git a/internal/blog/blog.go b/internal/blog/blog.go new file mode 100644 index 00000000..24c8ee07 --- /dev/null +++ b/internal/blog/blog.go @@ -0,0 +1,68 @@ +// Package blog is the brokerapi logger +// BrokerAPI was originally written to use the CloudFoundry Lager logger, and it relied on some idiosyncrasies +// of that logger that are not supported by the (subsequently written) standard library log/slog logger. +// This package is a wrapper around log/slog that adds back the idiosyncrasies of lager, so that the behavior +// is exactly the same. +package blog + +import ( + "context" + "log/slog" + "strings" + + "github.com/pivotal-cf/brokerapi/v10/middlewares" +) + +const ( + instanceIDLogKey = "instance-id" + bindingIDLogKey = "binding-id" + errorKey = "error" +) + +type Blog struct { + logger *slog.Logger + prefix string +} + +func New(ctx context.Context, logger *slog.Logger, prefix string, supplementary ...slog.Attr) Blog { + var attr []any + for _, s := range supplementary { + attr = append(attr, s) + } + + for _, key := range []middlewares.ContextKey{middlewares.CorrelationIDKey, middlewares.RequestIdentityKey} { + if value := ctx.Value(key); value != nil { + attr = append(attr, slog.Any(string(key), value)) + } + } + + return Blog{ + logger: logger.With(attr...), + prefix: prefix, + } +} + +func (b Blog) Error(message string, err error) { + b.logger.Error(join(b.prefix, message), slog.Any(errorKey, err)) +} + +func (b Blog) Info(message string, attr ...any) { + b.logger.Info(join(b.prefix, message), attr...) +} + +func (b Blog) With(attr ...any) Blog { + b.logger = b.logger.With(attr...) + return b +} + +func InstanceID(instanceID string) slog.Attr { + return slog.String(instanceIDLogKey, instanceID) +} + +func BindingID(bindingID string) slog.Attr { + return slog.String(bindingIDLogKey, bindingID) +} + +func join(s ...string) string { + return strings.Join(s, ".") +} diff --git a/internal/blog/blog_suite_test.go b/internal/blog/blog_suite_test.go new file mode 100644 index 00000000..efeab197 --- /dev/null +++ b/internal/blog/blog_suite_test.go @@ -0,0 +1,13 @@ +package blog_test + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestBlog(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "BrokerAPI logger Suite") +} diff --git a/internal/blog/blog_test.go b/internal/blog/blog_test.go new file mode 100644 index 00000000..d80fa263 --- /dev/null +++ b/internal/blog/blog_test.go @@ -0,0 +1,55 @@ +package blog_test + +import ( + "context" + "encoding/json" + "log/slog" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "github.com/onsi/gomega/gbytes" + "github.com/pivotal-cf/brokerapi/v10/middlewares" + + "github.com/pivotal-cf/brokerapi/v10/internal/blog" +) + +var _ = Describe("Context data", func() { + When("the context has the values", func() { + It("logs the values", func() { + const ( + correlationID = "fake-correlation-id" + requestID = "fake-request-id" + ) + + ctx := context.TODO() + ctx = context.WithValue(ctx, middlewares.CorrelationIDKey, correlationID) + ctx = context.WithValue(ctx, middlewares.RequestIdentityKey, requestID) + + buffer := gbytes.NewBuffer() + logger := slog.New(slog.NewJSONHandler(buffer, nil)) + + blog.New(ctx, logger, "prefix").Info("hello") + + var receiver map[string]any + Expect(json.Unmarshal(buffer.Contents(), &receiver)).To(Succeed()) + + Expect(receiver).To(HaveKeyWithValue(string(middlewares.CorrelationIDKey), correlationID)) + Expect(receiver).To(HaveKeyWithValue(string(middlewares.RequestIdentityKey), requestID)) + }) + }) + + When("the context does not have the values", func() { + It("does not log them", func() { + buffer := gbytes.NewBuffer() + logger := slog.New(slog.NewJSONHandler(buffer, nil)) + + blog.New(context.TODO(), logger, "prefix").Info("hello") + + var receiver map[string]any + Expect(json.Unmarshal(buffer.Contents(), &receiver)).To(Succeed()) + + Expect(receiver).NotTo(HaveKey(string(middlewares.CorrelationIDKey))) + Expect(receiver).NotTo(HaveKey(string(middlewares.RequestIdentityKey))) + }) + }) +}) diff --git a/internal/logutil/error.go b/internal/logutil/error.go deleted file mode 100644 index 3ef0f962..00000000 --- a/internal/logutil/error.go +++ /dev/null @@ -1,7 +0,0 @@ -package logutil - -import "log/slog" - -func Error(err error) slog.Attr { - return slog.Any("error", err.Error()) -} diff --git a/internal/logutil/join.go b/internal/logutil/join.go deleted file mode 100644 index 950588ac..00000000 --- a/internal/logutil/join.go +++ /dev/null @@ -1,7 +0,0 @@ -package logutil - -import "strings" - -func Join(s ...string) string { - return strings.Join(s, ".") -} diff --git a/middlewares/api_version_header.go b/middlewares/api_version_header.go index a7960898..4032e123 100644 --- a/middlewares/api_version_header.go +++ b/middlewares/api_version_header.go @@ -21,8 +21,6 @@ import ( "fmt" "log/slog" "net/http" - - "github.com/pivotal-cf/brokerapi/v10/internal/logutil" ) const ( @@ -32,7 +30,7 @@ const ( ) type APIVersionMiddleware struct { - Logger *slog.Logger + Logger interface{ Error(string, ...any) } } type ErrorResponse struct { @@ -43,7 +41,7 @@ func (m APIVersionMiddleware) ValidateAPIVersionHdr(next http.Handler) http.Hand return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { err := checkBrokerAPIVersionHdr(req) if err != nil { - m.Logger.Error(logutil.Join(apiVersionLogKey, ApiVersionInvalidKey), logutil.Error(err)) + m.Logger.Error(fmt.Sprintf("%s.%s", apiVersionLogKey, ApiVersionInvalidKey), slog.Any("error", err)) w.Header().Set("Content-type", "application/json") setBrokerRequestIdentityHeader(req, w) @@ -54,12 +52,7 @@ func (m APIVersionMiddleware) ValidateAPIVersionHdr(next http.Handler) http.Hand Description: err.Error(), } if err := json.NewEncoder(w).Encode(errorResp); err != nil { - m.Logger.Error( - logutil.Join(apiVersionLogKey, "encoding response"), - logutil.Error(err), - slog.Int("status", statusResponse), - slog.Any("response", errorResp), - ) + m.Logger.Error(fmt.Sprintf("%s.%s", apiVersionLogKey, "encoding response"), slog.Any("error", err), slog.Int("status", statusResponse), slog.Any("response", errorResp)) } return diff --git a/utils/context.go b/utils/context.go index 397a3117..9b76a396 100644 --- a/utils/context.go +++ b/utils/context.go @@ -2,10 +2,8 @@ package utils import ( "context" - "log/slog" "github.com/pivotal-cf/brokerapi/v10/domain" - "github.com/pivotal-cf/brokerapi/v10/middlewares" ) type contextKey string @@ -42,13 +40,3 @@ func RetrieveServicePlanFromContext(ctx context.Context) *domain.ServicePlan { } return nil } - -func ContextAttr(context context.Context, dataKeys ...middlewares.ContextKey) (result []any) { - for _, key := range dataKeys { - if value := context.Value(key); value != nil { - result = append(result, slog.Any(string(key), value)) - } - } - - return -} diff --git a/utils/context_test.go b/utils/context_test.go index 958540da..3270771a 100644 --- a/utils/context_test.go +++ b/utils/context_test.go @@ -2,12 +2,10 @@ package utils_test import ( "context" - "log/slog" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/pivotal-cf/brokerapi/v10/domain" - "github.com/pivotal-cf/brokerapi/v10/middlewares" "github.com/pivotal-cf/brokerapi/v10/utils" ) @@ -75,42 +73,4 @@ var _ = Describe("Context", func() { }) }) }) - - Describe("Log data for context", func() { - const testKey middlewares.ContextKey = "test-key" - - Context("the provided key is present in the context", func() { - It("returns data containing the key", func() { - expectedValue := "123" - ctx = context.WithValue(ctx, testKey, expectedValue) - - data := utils.ContextAttr(ctx, testKey) - Expect(data).To(ConsistOf(slog.Attr{ - Key: string(testKey), - Value: slog.StringValue(expectedValue), - })) - }) - - Context("the key value is a struct", func() { - It("returns data containing the key", func() { - type testType struct{} - expectedValue := testType{} - ctx = context.WithValue(ctx, testKey, expectedValue) - - data := utils.ContextAttr(ctx, testKey) - Expect(data).To(ConsistOf(slog.Attr{ - Key: string(testKey), - Value: slog.AnyValue(expectedValue), - })) - }) - }) - }) - - Context("the provided key is not in the context", func() { - It("returns data without the key", func() { - data := utils.ContextAttr(ctx, testKey) - Expect(data).To(BeEmpty()) - }) - }) - }) })