Skip to content

Commit

Permalink
logging: add AddFields
Browse files Browse the repository at this point in the history
  • Loading branch information
kindermoumoute committed Dec 21, 2024
1 parent 66bb7d8 commit f34053b
Show file tree
Hide file tree
Showing 2 changed files with 31 additions and 5 deletions.
25 changes: 20 additions & 5 deletions interceptors/logging/logging.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ var (
)

type fieldsCtxMarker struct{}
type fieldsCtxValue struct {
fields Fields
}

var (
// fieldsCtxMarkerKey is the Context value marker that is used by logging middleware to read and write logging fields into context.
Expand Down Expand Up @@ -161,30 +164,42 @@ NextAddField:
// If there are no fields in the context, it returns an empty Fields value.
// Extracted fields are useful to construct your own logger that has fields from gRPC interceptors.
func ExtractFields(ctx context.Context) Fields {
t, ok := ctx.Value(fieldsCtxMarkerKey).(Fields)
t, ok := ctx.Value(fieldsCtxMarkerKey).(*fieldsCtxValue)
if !ok {
return nil
}
n := make(Fields, len(t))
copy(n, t)
n := make(Fields, len(t.fields))
copy(n, t.fields)
return n
}

// InjectFields allows adding fields to any existing Fields that will be used by the logging interceptor or can be
// InjectFields returns a new context with merged fields that will be used by the logging interceptor or can be
// extracted further in ExtractFields.
// For explicitness, in case of duplicates, the newest field occurrence wins. This allows nested components to update
// popular fields like grpc.component (e.g. server invoking gRPC client).
//
// Don't overuse mutation of fields to avoid surprises.
func InjectFields(ctx context.Context, f Fields) context.Context {
return context.WithValue(ctx, fieldsCtxMarkerKey, f.WithUnique(ExtractFields(ctx)))
return context.WithValue(ctx, fieldsCtxMarkerKey, &fieldsCtxValue{fields: f.WithUnique(ExtractFields(ctx))})
}

// InjectLogField is like InjectFields, just for one field.
func InjectLogField(ctx context.Context, key string, val any) context.Context {
return InjectFields(ctx, Fields{key, val})
}

// AddFields updates the fields already in the context that will be used by the logging interceptor or can be
// extracted further in ExtractFields. For explicitness, in case of duplicates, the newest field occurrence wins.
//
// This function is not safe to call concurrently.
func AddFields(ctx context.Context, f Fields) {
t, ok := ctx.Value(fieldsCtxMarkerKey).(*fieldsCtxValue)
if !ok {
return
}
t.fields = f.AppendUnique(t.fields)
}

// Logger requires Log method, similar to experimental slog, allowing logging interceptor to be interoperable. Official
// adapters for popular loggers are in `provider/` directory (separate modules). It's totally ok to copy simple function
// implementation over.
Expand Down
11 changes: 11 additions & 0 deletions interceptors/logging/logging_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,14 @@ func TestFieldsDelete(t *testing.T) {
f.Delete("c")
require.Equal(t, Fields{}, f)
}

func TestAddFields(t *testing.T) {
c := InjectFields(context.Background(), Fields{"a", "2", "c", "3"})
f := ExtractFields(c)
require.Equal(t, Fields{"a", "2", "c", "3"}, f)
AddFields(c, Fields{"a", "1", "b", "2"})

// First context should have updated values.
f = ExtractFields(c)
require.Equal(t, Fields{"a", "1", "b", "2", "c", "3"}, f)
}

0 comments on commit f34053b

Please sign in to comment.