|
22 | 22 | )
|
23 | 23 |
|
24 | 24 | type fieldsCtxMarker struct{}
|
| 25 | +type fieldsCtxValue struct { |
| 26 | + fields Fields |
| 27 | +} |
25 | 28 |
|
26 | 29 | var (
|
27 | 30 | // fieldsCtxMarkerKey is the Context value marker that is used by logging middleware to read and write logging fields into context.
|
@@ -161,30 +164,45 @@ NextAddField:
|
161 | 164 | // If there are no fields in the context, it returns an empty Fields value.
|
162 | 165 | // Extracted fields are useful to construct your own logger that has fields from gRPC interceptors.
|
163 | 166 | func ExtractFields(ctx context.Context) Fields {
|
164 |
| - t, ok := ctx.Value(fieldsCtxMarkerKey).(Fields) |
| 167 | + t, ok := ctx.Value(fieldsCtxMarkerKey).(*fieldsCtxValue) |
165 | 168 | if !ok {
|
166 | 169 | return nil
|
167 | 170 | }
|
168 |
| - n := make(Fields, len(t)) |
169 |
| - copy(n, t) |
| 171 | + n := make(Fields, len(t.fields)) |
| 172 | + copy(n, t.fields) |
170 | 173 | return n
|
171 | 174 | }
|
172 | 175 |
|
173 |
| -// InjectFields allows adding fields to any existing Fields that will be used by the logging interceptor or can be |
| 176 | +// InjectFields returns a new context with merged fields that will be used by the logging interceptor or can be |
174 | 177 | // extracted further in ExtractFields.
|
175 | 178 | // For explicitness, in case of duplicates, the newest field occurrence wins. This allows nested components to update
|
176 | 179 | // popular fields like grpc.component (e.g. server invoking gRPC client).
|
177 | 180 | //
|
178 | 181 | // Don't overuse mutation of fields to avoid surprises.
|
179 | 182 | func InjectFields(ctx context.Context, f Fields) context.Context {
|
180 |
| - return context.WithValue(ctx, fieldsCtxMarkerKey, f.WithUnique(ExtractFields(ctx))) |
| 183 | + return context.WithValue(ctx, fieldsCtxMarkerKey, &fieldsCtxValue{fields: f.WithUnique(ExtractFields(ctx))}) |
181 | 184 | }
|
182 | 185 |
|
183 | 186 | // InjectLogField is like InjectFields, just for one field.
|
184 | 187 | func InjectLogField(ctx context.Context, key string, val any) context.Context {
|
185 | 188 | return InjectFields(ctx, Fields{key, val})
|
186 | 189 | }
|
187 | 190 |
|
| 191 | +// AddFields updates the fields already in the context that will be used by the logging interceptor or can be |
| 192 | +// extracted further in ExtractFields. For explicitness, in case of duplicates, the newest field occurrence wins. |
| 193 | +// |
| 194 | +// InjectFields should be used instead of AddFields where possible, as it does not require mutating the values |
| 195 | +// already in the context. |
| 196 | +// |
| 197 | +// This function is not safe to call concurrently. |
| 198 | +func AddFields(ctx context.Context, f Fields) { |
| 199 | + t, ok := ctx.Value(fieldsCtxMarkerKey).(*fieldsCtxValue) |
| 200 | + if !ok { |
| 201 | + return |
| 202 | + } |
| 203 | + t.fields = f.AppendUnique(t.fields) |
| 204 | +} |
| 205 | + |
188 | 206 | // Logger requires Log method, similar to experimental slog, allowing logging interceptor to be interoperable. Official
|
189 | 207 | // adapters for popular loggers are in `provider/` directory (separate modules). It's totally ok to copy simple function
|
190 | 208 | // implementation over.
|
|
0 commit comments