Skip to content

Commit eb1a938

Browse files
karakayasemiatoulme
authored andcommitted
[processor/redaction] apply redaction to log.body (open-telemetry#37239) (open-telemetry#37369)
## Description This update applies redaction rules to the `log.body` field by supporting multiple data types, including string, slices, maps, and nested maps. It applies key-based configurations consistently across all map structures. - **Key-Based Configurations**: Applies redaction rules at all nesting levels. - **New Debug Attributes**: - `redaction.body.redacted.keys` / `redaction.body.redacted.count` - `redaction.body.masked.keys` / `redaction.body.masked.count` - `redaction.body.allowed.keys` / `redaction.body.allowed.count` - `redaction.body.ignored.count` #### Link to tracking issue Fixes open-telemetry#37239 ## Testing & Refactoring - **Extended Tests**: Log body redaction added to all existing cases + new tests for detailed body structure validation. - **Code Cleanup**: Introduced helper methods for redaction, masking, allowing, and ignoring logic. - **Improved Readability**: Renamed some const variables for clarity and consistency. --------- Co-authored-by: Antoine Toulme <[email protected]>
1 parent 121ea50 commit eb1a938

File tree

3 files changed

+424
-116
lines changed

3 files changed

+424
-116
lines changed

.chloggen/redact-log-body.yaml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Use this changelog template to create an entry for release notes.
2+
3+
# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
4+
change_type: enhancement
5+
6+
# The name of the component, or a single word describing the area of concern, (e.g. filelogreceiver)
7+
component: processor/redaction
8+
9+
# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
10+
note: "Apply redaction to log.body"
11+
12+
# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists.
13+
issues: [37239]
14+
15+
# (Optional) One or more lines of additional information to render under the primary note.
16+
# These lines will be padded with 2 spaces and then inserted directly into the document.
17+
# Use pipe (|) for multiline entries.
18+
subtext:
19+
20+
# If your change doesn't affect end users or the exported elements of any package,
21+
# you should instead start your pull request title with [chore] or use the "Skip Changelog" label.
22+
# Optional: The change log or logs in which this entry should be included.
23+
# e.g. '[user]' or '[user, api]'
24+
# Include 'user' if the change is relevant to end users.
25+
# Include 'api' if there is a change to a library API.
26+
# Default: '[user]'
27+
change_logs: []

processor/redactionprocessor/processor.go

Lines changed: 184 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,103 @@ func (s *redaction) processResourceLog(ctx context.Context, rl plog.ResourceLogs
144144
for k := 0; k < ils.LogRecords().Len(); k++ {
145145
log := ils.LogRecords().At(k)
146146
s.processAttrs(ctx, log.Attributes())
147+
s.processLogBody(ctx, log.Body(), log.Attributes())
148+
}
149+
}
150+
}
151+
152+
func (s *redaction) processLogBody(ctx context.Context, body pcommon.Value, attributes pcommon.Map) {
153+
var redactedKeys, maskedKeys, allowedKeys, ignoredKeys []string
154+
155+
switch body.Type() {
156+
case pcommon.ValueTypeMap:
157+
var redactedBodyKeys []string
158+
body.Map().Range(func(k string, v pcommon.Value) bool {
159+
if s.shouldIgnoreKey(k) {
160+
ignoredKeys = append(ignoredKeys, k)
161+
return true
162+
}
163+
if s.shouldRedactKey(k) {
164+
redactedBodyKeys = append(redactedBodyKeys, k)
165+
return true
166+
}
167+
if s.shouldMaskKey(k) {
168+
maskedKeys = append(maskedKeys, k)
169+
v.SetStr(s.maskValue(v.Str(), regexp.MustCompile(".*")))
170+
return true
171+
}
172+
s.redactLogBodyRecursive(ctx, k, v, &redactedKeys, &maskedKeys, &allowedKeys, &ignoredKeys)
173+
return true
174+
})
175+
for _, k := range redactedBodyKeys {
176+
body.Map().Remove(k)
177+
redactedKeys = append(redactedKeys, k)
178+
}
179+
case pcommon.ValueTypeSlice:
180+
for i := 0; i < body.Slice().Len(); i++ {
181+
s.redactLogBodyRecursive(ctx, fmt.Sprintf("[%d]", i), body.Slice().At(i), &redactedKeys, &maskedKeys, &allowedKeys, &ignoredKeys)
182+
}
183+
default:
184+
strVal := body.AsString()
185+
if s.shouldAllowValue(strVal) {
186+
allowedKeys = append(allowedKeys, "body")
187+
return
188+
}
189+
processedValue := s.processStringValue(strVal)
190+
if strVal != processedValue {
191+
maskedKeys = append(maskedKeys, "body")
192+
body.SetStr(processedValue)
193+
}
194+
}
195+
196+
s.addMetaAttrs(redactedKeys, attributes, redactionBodyRedactedKeys, redactionBodyRedactedCount)
197+
s.addMetaAttrs(maskedKeys, attributes, redactionBodyMaskedKeys, redactionBodyMaskedCount)
198+
s.addMetaAttrs(allowedKeys, attributes, redactionBodyAllowedKeys, redactionBodyAllowedCount)
199+
s.addMetaAttrs(ignoredKeys, attributes, "", redactionBodyIgnoredCount)
200+
}
201+
202+
func (s *redaction) redactLogBodyRecursive(ctx context.Context, key string, value pcommon.Value, redactedKeys, maskedKeys, allowedKeys, ignoredKeys *[]string) {
203+
switch value.Type() {
204+
case pcommon.ValueTypeMap:
205+
var redactedCurrentValueKeys []string
206+
value.Map().Range(func(k string, v pcommon.Value) bool {
207+
keyWithPath := fmt.Sprintf("%s.%s", key, k)
208+
if s.shouldIgnoreKey(k) {
209+
*ignoredKeys = append(*ignoredKeys, keyWithPath)
210+
return true
211+
}
212+
if s.shouldRedactKey(k) {
213+
redactedCurrentValueKeys = append(redactedCurrentValueKeys, k)
214+
return true
215+
}
216+
if s.shouldMaskKey(k) {
217+
*maskedKeys = append(*maskedKeys, keyWithPath)
218+
v.SetStr(s.maskValue(v.Str(), regexp.MustCompile(".*")))
219+
return true
220+
}
221+
s.redactLogBodyRecursive(ctx, keyWithPath, v, redactedKeys, maskedKeys, allowedKeys, ignoredKeys)
222+
return true
223+
})
224+
for _, k := range redactedCurrentValueKeys {
225+
value.Map().Remove(k)
226+
keyWithPath := fmt.Sprintf("%s.%s", key, k)
227+
*redactedKeys = append(*redactedKeys, keyWithPath)
228+
}
229+
case pcommon.ValueTypeSlice:
230+
for i := 0; i < value.Slice().Len(); i++ {
231+
keyWithPath := fmt.Sprintf("%s.[%d]", key, i)
232+
s.redactLogBodyRecursive(ctx, keyWithPath, value.Slice().At(i), redactedKeys, maskedKeys, allowedKeys, ignoredKeys)
233+
}
234+
default:
235+
strVal := value.AsString()
236+
if s.shouldAllowValue(strVal) {
237+
*allowedKeys = append(*allowedKeys, key)
238+
return
239+
}
240+
processedValue := s.processStringValue(strVal)
241+
if strVal != processedValue {
242+
*maskedKeys = append(*maskedKeys, key)
243+
value.SetStr(processedValue)
147244
}
148245
}
149246
}
@@ -192,10 +289,7 @@ func (s *redaction) processResourceMetric(ctx context.Context, rm pmetric.Resour
192289
// processAttrs redacts the attributes of a resource span or a span
193290
func (s *redaction) processAttrs(_ context.Context, attributes pcommon.Map) {
194291
// TODO: Use the context for recording metrics
195-
var toDelete []string
196-
var toBlock []string
197-
var allowed []string
198-
var ignoring []string
292+
var redactedKeys, maskedKeys, allowedKeys, ignoredKeys []string
199293

200294
// Identify attributes to redact and mask in the following sequence
201295
// 1. Make a list of attribute keys to redact
@@ -207,66 +301,41 @@ func (s *redaction) processAttrs(_ context.Context, attributes pcommon.Map) {
207301
// - Don't mask any values if the whole attribute is slated for deletion
208302
AttributeLoop:
209303
for k, value := range attributes.All() {
210-
// don't delete or redact the attribute if it should be ignored
211-
if _, ignored := s.ignoreList[k]; ignored {
212-
ignoring = append(ignoring, k)
213-
// Skip to the next attribute
304+
if s.shouldIgnoreKey(k) {
305+
ignoredKeys = append(ignoredKeys, k)
214306
continue AttributeLoop
215307
}
216-
217-
// Make a list of attribute keys to redact
218-
if !s.config.AllowAllKeys {
219-
if _, allowed := s.allowList[k]; !allowed {
220-
toDelete = append(toDelete, k)
221-
// Skip to the next attribute
222-
continue AttributeLoop
223-
}
308+
if s.shouldRedactKey(k) {
309+
redactedKeys = append(redactedKeys, k)
310+
continue AttributeLoop
224311
}
225-
226312
strVal := value.Str()
227-
// Allow any values matching the allowed list regex
228-
for _, compiledRE := range s.allowRegexList {
229-
if match := compiledRE.MatchString(strVal); match {
230-
allowed = append(allowed, k)
231-
continue AttributeLoop
232-
}
313+
if s.shouldAllowValue(strVal) {
314+
allowedKeys = append(allowedKeys, k)
315+
continue AttributeLoop
233316
}
234-
235-
// Mask any blocked keys for the other attributes
236-
for _, compiledRE := range s.blockKeyRegexList {
237-
if match := compiledRE.MatchString(k); match {
238-
toBlock = append(toBlock, k)
239-
maskedValue := s.maskValue(strVal, regexp.MustCompile(".*"))
240-
value.SetStr(maskedValue)
241-
continue AttributeLoop
242-
}
317+
if s.shouldMaskKey(k) {
318+
maskedKeys = append(maskedKeys, k)
319+
maskedValue := s.maskValue(strVal, regexp.MustCompile(".*"))
320+
value.SetStr(maskedValue)
321+
continue AttributeLoop
243322
}
244-
245-
// Mask any blocked values for the other attributes
246-
var matched bool
247-
for _, compiledRE := range s.blockRegexList {
248-
if compiledRE.MatchString(strVal) {
249-
if !matched {
250-
matched = true
251-
toBlock = append(toBlock, k)
252-
}
253-
254-
maskedValue := s.maskValue(strVal, compiledRE)
255-
value.SetStr(maskedValue)
256-
strVal = maskedValue
257-
}
323+
processedString := s.processStringValue(strVal)
324+
if processedString != strVal {
325+
maskedKeys = append(maskedKeys, k)
326+
value.SetStr(processedString)
258327
}
259328
}
260329

261330
// Delete the attributes on the redaction list
262-
for _, k := range toDelete {
331+
for _, k := range redactedKeys {
263332
attributes.Remove(k)
264333
}
265334
// Add diagnostic information to the span
266-
s.addMetaAttrs(toDelete, attributes, redactedKeys, redactedKeyCount)
267-
s.addMetaAttrs(toBlock, attributes, maskedValues, maskedValueCount)
268-
s.addMetaAttrs(allowed, attributes, allowedValues, allowedValueCount)
269-
s.addMetaAttrs(ignoring, attributes, "", ignoredKeyCount)
335+
s.addMetaAttrs(redactedKeys, attributes, redactionRedactedKeys, redactionRedactedCount)
336+
s.addMetaAttrs(maskedKeys, attributes, redactionMaskedKeys, redactionMaskedCount)
337+
s.addMetaAttrs(allowedKeys, attributes, redactionAllowedKeys, redactionAllowedCount)
338+
s.addMetaAttrs(ignoredKeys, attributes, "", redactionIgnoredCount)
270339
}
271340

272341
//nolint:gosec
@@ -314,16 +383,70 @@ func (s *redaction) addMetaAttrs(redactedAttrs []string, attributes pcommon.Map,
314383
}
315384
}
316385

386+
func (s *redaction) processStringValue(strVal string) string {
387+
// Mask any blocked values for the other attributes
388+
for _, compiledRE := range s.blockRegexList {
389+
match := compiledRE.MatchString(strVal)
390+
if match {
391+
strVal = s.maskValue(strVal, compiledRE)
392+
}
393+
}
394+
return strVal
395+
}
396+
397+
func (s *redaction) shouldMaskKey(k string) bool {
398+
// Mask any blocked keys for the other attributes
399+
for _, compiledRE := range s.blockKeyRegexList {
400+
if match := compiledRE.MatchString(k); match {
401+
return true
402+
}
403+
}
404+
return false
405+
}
406+
407+
func (s *redaction) shouldAllowValue(strVal string) bool {
408+
// Allow any values matching the allowed list regex
409+
for _, compiledRE := range s.allowRegexList {
410+
if match := compiledRE.MatchString(strVal); match {
411+
return true
412+
}
413+
}
414+
return false
415+
}
416+
417+
func (s *redaction) shouldIgnoreKey(k string) bool {
418+
if _, ignored := s.ignoreList[k]; ignored {
419+
return true
420+
}
421+
return false
422+
}
423+
424+
func (s *redaction) shouldRedactKey(k string) bool {
425+
if !s.config.AllowAllKeys {
426+
if _, found := s.allowList[k]; !found {
427+
return true
428+
}
429+
}
430+
return false
431+
}
432+
317433
const (
318-
debug = "debug"
319-
info = "info"
320-
redactedKeys = "redaction.redacted.keys"
321-
redactedKeyCount = "redaction.redacted.count"
322-
maskedValues = "redaction.masked.keys"
323-
maskedValueCount = "redaction.masked.count"
324-
allowedValues = "redaction.allowed.keys"
325-
allowedValueCount = "redaction.allowed.count"
326-
ignoredKeyCount = "redaction.ignored.count"
434+
debug = "debug"
435+
info = "info"
436+
redactionRedactedKeys = "redaction.redacted.keys"
437+
redactionRedactedCount = "redaction.redacted.count"
438+
redactionMaskedKeys = "redaction.masked.keys"
439+
redactionMaskedCount = "redaction.masked.count"
440+
redactionAllowedKeys = "redaction.allowed.keys"
441+
redactionAllowedCount = "redaction.allowed.count"
442+
redactionIgnoredCount = "redaction.ignored.count"
443+
redactionBodyRedactedKeys = "redaction.body.redacted.keys"
444+
redactionBodyRedactedCount = "redaction.body.redacted.count"
445+
redactionBodyMaskedKeys = "redaction.body.masked.keys"
446+
redactionBodyMaskedCount = "redaction.body.masked.count"
447+
redactionBodyAllowedKeys = "redaction.body.allowed.keys"
448+
redactionBodyAllowedCount = "redaction.body.allowed.count"
449+
redactionBodyIgnoredCount = "redaction.body.ignored.count"
327450
)
328451

329452
// makeAllowList sets up a lookup table of allowed span attribute keys
@@ -338,7 +461,7 @@ func makeAllowList(c *Config) map[string]string {
338461
// span attributes (e.g. `notes`, `description`), then it will those
339462
// attribute keys in `redaction.masked.keys` and set the
340463
// `redaction.masked.count` to 2
341-
redactionKeys := []string{redactedKeys, redactedKeyCount, maskedValues, maskedValueCount, ignoredKeyCount}
464+
redactionKeys := []string{redactionRedactedKeys, redactionRedactedCount, redactionMaskedKeys, redactionMaskedCount, redactionIgnoredCount}
342465
// allowList consists of the keys explicitly allowed by the configuration
343466
// as well as of the new span attributes that the processor creates to
344467
// summarize its changes

0 commit comments

Comments
 (0)