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

logging: add AddFields #739

Merged
merged 2 commits into from
Dec 23, 2024
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
28 changes: 23 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,45 @@ 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.
//
// InjectFields should be used instead of AddFields where possible, as it does not require mutating the values
// already in the context.
//
// 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)
}
Loading