Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Race-safe LoggingParams #28

Merged
merged 1 commit into from
Feb 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions httpclient/logging_round_tripper.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,22 @@ func (rt *LoggingRoundTripper) getLogger(ctx context.Context) log.FieldLogger {
}

// RoundTrip adds logging capabilities to the HTTP transport.
// It logs HTTP requests and responses, tracking execution time and success/failure status.
//
// Logging behavior:
// - If logging is disabled (no logger), the request is simply forwarded.
// - If the request completes successfully and is not slow, it may be ignored based on logging mode.
// - Slow requests, failed requests, and configured conditions determine log level (Info, Warn, Error).
//
// Additional logged information:
// - HTTP method and URL.
// - Response status code and elapsed time.
// - Client type if available.
// - X-Request-ID from request headers.
// - Request type extracted from the context.
// - The request duration in milliseconds is accumulated in LoggingParams, allowing tracking of the total time
// spent on external requests when the handler execution is finalized. Note that this value is accurate only for
// sequential requests.
func (rt *LoggingRoundTripper) RoundTrip(r *http.Request) (*http.Response, error) {
ctx := r.Context()
logger := rt.getLogger(ctx)
Expand Down
4 changes: 3 additions & 1 deletion httpserver/middleware/logging.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,9 @@ func (h *loggingHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
duration := time.Since(startTime)
if duration >= h.opts.SlowRequestThreshold {
lp.AddTimeSlotDurationInMs("writing_response_ms", wrw.ElapsedTime())
lp.fields = append(lp.fields, log.Field{Key: "time_slots", Type: logf.FieldTypeObject, Any: lp.timeSlots})
lp.fields = append(
lp.fields, log.Field{Key: "time_slots", Type: logf.FieldTypeObject, Any: lp.getTimeSlots()},
)
}
logger.Info(
fmt.Sprintf("response completed in %.3fs", duration.Seconds()),
Expand Down
14 changes: 12 additions & 2 deletions httpserver/middleware/logging_params.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Released under MIT license.
package middleware

import (
"sync"
"time"

"github.com/ssgreg/logf"
Expand All @@ -27,8 +28,9 @@ func (lm loggableIntMap) EncodeLogfObject(e logf.FieldEncoder) error {
// LoggingParams stores parameters for the Logging middleware
// that may be modified dynamically by the other underlying middlewares/handlers.
type LoggingParams struct {
fields []log.Field
timeSlots loggableIntMap
fields []log.Field
timeSlots loggableIntMap
timeSlotsMu sync.RWMutex
}

// ExtendFields extends list of fields that will be logged by the Logging middleware.
Expand All @@ -47,8 +49,16 @@ func (lp *LoggingParams) AddTimeSlotDurationInMs(name string, dur time.Duration)
}

func (lp *LoggingParams) setIntMapFieldValue(fieldName string, value int64) {
lp.timeSlotsMu.Lock()
defer lp.timeSlotsMu.Unlock()
if lp.timeSlots == nil {
lp.timeSlots = make(loggableIntMap, 1)
}
lp.timeSlots[fieldName] += value
}

func (lp *LoggingParams) getTimeSlots() loggableIntMap {
lp.timeSlotsMu.RLock()
defer lp.timeSlotsMu.RUnlock()
return lp.timeSlots
}
4 changes: 2 additions & 2 deletions httpserver/middleware/logging_params_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ func TestLoggingParams_SetTimeSlotDurationMs(t *testing.T) {

lp.AddTimeSlotInt("slot1", 100)
lp.AddTimeSlotInt("slot2", 200)
lp.fields = append(lp.fields, log.Field{Key: "time_slots", Type: logf.FieldTypeObject, Any: lp.timeSlots})
lp.fields = append(lp.fields, log.Field{Key: "time_slots", Type: logf.FieldTypeObject, Any: lp.getTimeSlots()})

expected := LoggingParams{
fields: []log.Field{
Expand All @@ -44,7 +44,7 @@ func TestLoggingParams_SetTimeSlotDuration(t *testing.T) {

lp.AddTimeSlotDurationInMs("slot1", 1*time.Second)
lp.AddTimeSlotDurationInMs("slot2", 2*time.Second)
lp.fields = append(lp.fields, log.Field{Key: "time_slots", Type: logf.FieldTypeObject, Any: lp.timeSlots})
lp.fields = append(lp.fields, log.Field{Key: "time_slots", Type: logf.FieldTypeObject, Any: lp.getTimeSlots()})

expected := LoggingParams{
fields: []log.Field{
Expand Down