Skip to content
Open
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
4 changes: 4 additions & 0 deletions .chloggen/add-sensitive-data-logging-policy.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
change_type: enhancement
component: docs
note: Add security policy for logging sensitive telemetry data.
issues: [14431]
4 changes: 4 additions & 0 deletions .chloggen/fix_mdatagen_enum_test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
change_type: bug_fix
component: cmd/mdatagen
note: Fix invalid generated tests for conditionally_required enum attributes in metrics.
issues: [14196, 14230]
10 changes: 10 additions & 0 deletions cmd/mdatagen/internal/samplescraper/metadata.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -194,3 +194,13 @@ metrics:
aggregation_temporality: cumulative
semantic_convention:
ref: https://github.com/open-telemetry/semantic-conventions/blob/v1.38.0/docs/system/system-metrics.md#metric-systemcputime
system.cpu.utilization:
enabled: true
stability:
level: beta
description: Histogram metric for testing histogram generation.
unit: "1"
histogram:
value_type: double
bucket_boundaries: [0, 0.25, 0.5, 0.75, 1]
attributes: [string_attr]
30 changes: 29 additions & 1 deletion cmd/mdatagen/internal/templates/metrics.go.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -100,9 +100,13 @@ func (maof metricAttributeOptionFunc) apply(dp pmetric.NumberDataPoint) {
}

{{ range getMetricConditionalAttributes .Attributes }}
func With{{ .Render }}MetricAttribute({{ .RenderUnexported }}AttributeValue {{ (attributeInfo .).Type.Primitive }}) MetricAttributeOption {
func With{{ .Render }}MetricAttribute({{ .RenderUnexported }}AttributeValue {{ if (attributeInfo .).Enum }}Attribute{{ .Render }}{{ else }}{{ (attributeInfo .).Type.Primitive }}{{ end }}) MetricAttributeOption {
return metricAttributeOptionFunc(func(dp pmetric.NumberDataPoint) {
{{- if (attributeInfo .).Enum }}
dp.Attributes().PutStr("{{ (attributeInfo .).Name }}", {{ .RenderUnexported }}AttributeValue.String())
{{- else }}
{{- template "putAttribute" . }}
{{- end }}
})
}
{{ end }}
Expand Down Expand Up @@ -195,6 +199,29 @@ func (m *metric{{ $name.Render }}) recordDataPoint(start pcommon.Timestamp, ts p
dp := m.data.{{ $metric.Data.Type }}().DataPoints().AppendEmpty()
dp.SetStartTimestamp(start)
dp.SetTimestamp(ts)
{{- if eq $metric.Data.Type "Histogram" }}
{{- range $metric.Attributes }}
{{- if not (attributeInfo .).IsConditional -}}
{{- template "putAttribute" . -}}
{{- end }}
{{- end }}

{{- if $metric.HasConditionalAttributes $.Attributes }}
for _, op := range options {
// Note: Histogram data points don't support conditional attributes the same way as NumberDataPoint
_ = op
}
{{- end }}
// Record the value in the histogram
dp.SetCount(1)
dp.SetSum(val)
{{- if $metric.Data.Boundaries }}
if dp.ExplicitBounds().Len() == 0 {
dp.ExplicitBounds().FromRaw([]float64{ {{- range $idx, $boundary := $metric.Data.Boundaries }}{{if $idx}}, {{end}}{{ $boundary }}{{- end -}} })
dp.BucketCounts().FromRaw(make([]uint64, {{ len $metric.Data.Boundaries }} + 1))
}
{{- end }}
{{- else }}
dp.Set{{ $metric.Data.MetricValueType }}Value(val)
{{- range $metric.Attributes }}
{{- if not (attributeInfo .).IsConditional -}}
Expand All @@ -207,6 +234,7 @@ func (m *metric{{ $name.Render }}) recordDataPoint(start pcommon.Timestamp, ts p
op.apply(dp)
}
{{- end }}
{{- end }}
}
{{- end }}

Expand Down
5 changes: 5 additions & 0 deletions cmd/mdatagen/internal/templates/metrics_test.go.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -331,12 +331,17 @@ func TestMetricsBuilder(t *testing.T) {
dp := ms.At(i).{{ $metric.Data.Type }}().DataPoints().At(0)
assert.Equal(t, start, dp.StartTimestamp())
assert.Equal(t, ts, dp.Timestamp())
{{- if ne $metric.Data.Type "Histogram" }}
assert.Equal(t, pmetric.NumberDataPointValueType{{ $metric.Data.MetricValueType }}, dp.ValueType())
{{- if eq $metric.Data.MetricValueType.BasicType "float64" }}
assert.InDelta(t, {{ $metric.Data.MetricValueType.BasicType }}(1), dp.{{ $metric.Data.MetricValueType }}Value(), 0.01)
{{- else }}
assert.Equal(t, {{ $metric.Data.MetricValueType.BasicType }}(1), dp.{{ $metric.Data.MetricValueType }}Value())
{{- end }}
{{- else }}
assert.Equal(t, uint64(1), dp.Count())
assert.InDelta(t, float64(1), dp.Sum(), 0.01)
{{- end }}

{{- range $i, $attr := $metric.Attributes }}
attrVal, ok {{ if eq $i 0 }}:{{ end }}= dp.Attributes().Get("{{ (attributeInfo $attr).Name }}")
Expand Down
39 changes: 39 additions & 0 deletions docs/security/logging.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Logging Policy for Sensitive Data

To ensure the security and privacy of data processed by the OpenTelemetry Collector, the following rules regarding logging must be observed.

## Rules

1. **No Sensitive Data at Info/Warn/Error Levels**
- Telemetry data (metric names, label values, attribute values, resource attributes) must NOT be logged at `Info`, `Warn`, or `Error` levels.
- PII (Personally Identifiable Information), credentials, and exact payload contents are strictly prohibited in high-level logs.

2. **Error Messages**
- Returned errors must be generic and actionable without embedding specific metric data.
- Do NOT wrap errors with formatted strings containing raw telemetry values (e.g., `fmt.Errorf("failed processing metric %s: %w", metricName, err)` is prohibited).
- Use structural logging fields at `Debug` level if context is needed.

3. **Debug Level Exception**
- Detailed diagnostic context, including specific metric names or attribute values, MAY be logged at `Debug` level only.
- This allows operators to opt-in to verbose logging for troubleshooting without polluting default logs with potentially sensitive data.

## Implementation Guidelines

**Incorrect:**
```go
// BAD: Leaks sensitive metric name in error
if err := process(metric); err != nil {
return fmt.Errorf("failed to process metric %s: %w", metric.Name(), err)
}
```

**Correct:**
```go
// GOOD: Logs details at debug, returns generic error
if err := process(metric); err != nil {
logger.Debug("failed to process metric",
zap.String("metric_name", metric.Name()),
zap.Error(err))
return fmt.Errorf("failed to process metric: %w", err)
}
```
2 changes: 1 addition & 1 deletion internal/cmd/pdatagen/internal/pdata/one_of_field.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
)

const oneOfAccessorTemplate = `// {{ .typeFuncName }} returns the type of the {{ .lowerOriginFieldName }} for this {{ .structName }}.
// Calling this function on zero-initialized {{ .structName }} will cause a panic.
// Calling this function on zero-initialized {{ .structName }} is invalid and will cause a panic.
func (ms {{ .structName }}) {{ .typeFuncName }}() {{ .typeName }} {
switch ms.{{ .origAccessor }}.{{ .originFieldName }}.(type) {
{{- range .values }}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const oneOfMessageAccessorsTemplate = `// {{ .fieldName }} returns the {{ .lower
// Calling this function when {{ .originOneOfTypeFuncName }}() != {{ .typeName }} returns an invalid
// zero-initialized instance of {{ .returnType }}. Note that using such {{ .returnType }} instance can cause panic.
//
// Calling this function on zero-initialized {{ .structName }} will cause a panic.
// Calling this function on zero-initialized {{ .structName }} is invalid and will cause a panic.
func (ms {{ .structName }}) {{ .fieldName }}() {{ .returnType }} {
v, ok := ms.orig.Get{{ .originOneOfFieldName }}().(*internal.{{ .originStructType }})
if !ok {
Expand All @@ -28,7 +28,7 @@ func (ms {{ .structName }}) {{ .fieldName }}() {{ .returnType }} {
//
// After this, {{ .originOneOfTypeFuncName }}() function will return {{ .typeName }}".
//
// Calling this function on zero-initialized {{ .structName }} will cause a panic.
// Calling this function on zero-initialized {{ .structName }} is invalid and will cause a panic.
func (ms {{ .structName }}) SetEmpty{{ .fieldName }}() {{ .returnType }} {
ms.state.AssertMutable()
var ov *internal.{{ .originStructType }}
Expand Down
24 changes: 12 additions & 12 deletions pdata/pcommon/value.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ func (v Value) getState() *internal.State {
}

// FromRaw sets the value from the given raw value.
// Calling this function on zero-initialized Value will cause a panic.
// Calling this function on zero-initialized Value is invalid and will cause a panic.
func (v Value) FromRaw(iv any) error {
switch tv := iv.(type) {
case nil:
Expand Down Expand Up @@ -194,7 +194,7 @@ func (v Value) FromRaw(iv any) error {
}

// Type returns the type of the value for this Value.
// Calling this function on zero-initialized Value will cause a panic.
// Calling this function on zero-initialized Value is invalid and will cause a panic.
func (v Value) Type() ValueType {
switch v.getOrig().Value.(type) {
case *internal.AnyValue_StringValue:
Expand Down Expand Up @@ -277,7 +277,7 @@ func (v Value) Bytes() ByteSlice {
// it also changes the type to be ValueTypeStr.
// The shorter name is used instead of SetString to avoid implementing
// fmt.Stringer interface by the corresponding getter method.
// Calling this function on zero-initialized Value will cause a panic.
// Calling this function on zero-initialized Value is invalid and will cause a panic.
func (v Value) SetStr(sv string) {
v.getState().AssertMutable()
// Delete everything but the AnyValue object itself.
Expand All @@ -289,7 +289,7 @@ func (v Value) SetStr(sv string) {

// SetInt replaces the int64 value associated with this Value,
// it also changes the type to be ValueTypeInt.
// Calling this function on zero-initialized Value will cause a panic.
// Calling this function on zero-initialized Value is invalid and will cause a panic.
func (v Value) SetInt(iv int64) {
v.getState().AssertMutable()
// Delete everything but the AnyValue object itself.
Expand All @@ -301,7 +301,7 @@ func (v Value) SetInt(iv int64) {

// SetDouble replaces the float64 value associated with this Value,
// it also changes the type to be ValueTypeDouble.
// Calling this function on zero-initialized Value will cause a panic.
// Calling this function on zero-initialized Value is invalid and will cause a panic.
func (v Value) SetDouble(dv float64) {
v.getState().AssertMutable()
// Delete everything but the AnyValue object itself.
Expand All @@ -313,7 +313,7 @@ func (v Value) SetDouble(dv float64) {

// SetBool replaces the bool value associated with this Value,
// it also changes the type to be ValueTypeBool.
// Calling this function on zero-initialized Value will cause a panic.
// Calling this function on zero-initialized Value is invalid and will cause a panic.
func (v Value) SetBool(bv bool) {
v.getState().AssertMutable()
// Delete everything but the AnyValue object itself.
Expand All @@ -324,7 +324,7 @@ func (v Value) SetBool(bv bool) {
}

// SetEmptyBytes sets value to an empty byte slice and returns it.
// Calling this function on zero-initialized Value will cause a panic.
// Calling this function on zero-initialized Value is invalid and will cause a panic.
func (v Value) SetEmptyBytes() ByteSlice {
v.getState().AssertMutable()
// Delete everything but the AnyValue object itself.
Expand All @@ -335,7 +335,7 @@ func (v Value) SetEmptyBytes() ByteSlice {
}

// SetEmptyMap sets value to an empty map and returns it.
// Calling this function on zero-initialized Value will cause a panic.
// Calling this function on zero-initialized Value is invalid and will cause a panic.
func (v Value) SetEmptyMap() Map {
v.getState().AssertMutable()
// Delete everything but the AnyValue object itself.
Expand All @@ -347,7 +347,7 @@ func (v Value) SetEmptyMap() Map {
}

// SetEmptySlice sets value to an empty slice and returns it.
// Calling this function on zero-initialized Value will cause a panic.
// Calling this function on zero-initialized Value is invalid and will cause a panic.
func (v Value) SetEmptySlice() Slice {
v.getState().AssertMutable()
// Delete everything but the AnyValue object itself.
Expand All @@ -360,7 +360,7 @@ func (v Value) SetEmptySlice() Slice {

// MoveTo moves the Value from current overriding the destination and
// resetting the current instance to empty value.
// Calling this function on zero-initialized Value will cause a panic.
// Calling this function on zero-initialized Value is invalid and will cause a panic.
func (v Value) MoveTo(dest Value) {
v.getState().AssertMutable()
dest.getState().AssertMutable()
Expand All @@ -373,7 +373,7 @@ func (v Value) MoveTo(dest Value) {
}

// CopyTo copies the Value instance overriding the destination.
// Calling this function on zero-initialized Value will cause a panic.
// Calling this function on zero-initialized Value is invalid and will cause a panic.
func (v Value) CopyTo(dest Value) {
dest.getState().AssertMutable()
internal.CopyAnyValue(dest.getOrig(), v.getOrig())
Expand All @@ -382,7 +382,7 @@ func (v Value) CopyTo(dest Value) {
// AsString converts an OTLP Value object of any type to its equivalent string
// representation. This differs from Str which only returns a non-empty value
// if the ValueType is ValueTypeStr.
// Calling this function on zero-initialized Value will cause a panic.
// Calling this function on zero-initialized Value is invalid and will cause a panic.
func (v Value) AsString() string {
switch v.Type() {
case ValueTypeEmpty:
Expand Down
2 changes: 1 addition & 1 deletion pdata/pmetric/generated_exemplar.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 11 additions & 11 deletions pdata/pmetric/generated_metric.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion pdata/pmetric/generated_numberdatapoint.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading