Skip to content
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
5 changes: 5 additions & 0 deletions .chloggen/fix_gcp_snake_case.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
change_type: bug_fix
component: extension/googlecloudlogentry_encoding
note: Fix incorrect snake_case conversion for keys containing numbers (e.g., "k8s" becoming "k8_s") in Google Cloud log entries.
issues: [46571]
subtext: ""
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
"strings"

gojson "github.com/goccy/go-json"
"github.com/iancoleman/strcase"
"go.opentelemetry.io/collector/pdata/pcommon"
conventions "go.opentelemetry.io/otel/semconv/v1.38.0"

Expand Down Expand Up @@ -295,7 +294,7 @@ func handlePolicyViolationInfo(info *policyViolationInfo, attr pcommon.Map) {
if len(info.OrgPolicyViolationInfo.ResourceTags) > 0 {
tags := attr.PutEmptyMap(gcpAuditPolicyViolationResourceTags)
for name, value := range info.OrgPolicyViolationInfo.ResourceTags {
shared.PutStr(strcase.ToSnakeWithIgnore(name, "."), value, tags)
shared.PutStr(shared.ToSnakeCase(name, "."), value, tags)
}
}

Expand Down Expand Up @@ -365,7 +364,7 @@ func handleRequestMetadata(metadata *requestMetadata, attr pcommon.Map) error {
if len(metadata.DestinationAttributes.Labels) > 0 {
m := attr.PutEmptyMap(gcpAuditDestinationLabels)
for l, v := range metadata.DestinationAttributes.Labels {
shared.PutStr(strcase.ToSnakeWithIgnore(l, "."), v, m)
shared.PutStr(shared.ToSnakeCase(l, "."), v, m)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package shared // import "github.com/open-telemetry/opentelemetry-collector-contrib/extension/encoding/googlecloudlogentryencodingextension/internal/shared"

import (
"regexp"
"strings"

"github.com/iancoleman/strcase"
)

// strcase incorrectly treats digit-to-letter transitions as word boundaries,
// inserting underscores (e.g. "k8s" -> "k8_s").
// This regex reverses that.
// See: https://github.com/open-telemetry/opentelemetry-collector-contrib/issues/46571
var digitToLetter = regexp.MustCompile(`(\d)_([a-z])`)

// ToSnakeCase converts a string to snake_case while preserving characters in
// the ignore set, and fixes strcase's incorrect splitting at digit-to-letter
// boundaries like "k8s" into "k8_s".
func ToSnakeCase(s, ignore string) string {
result := strcase.ToSnakeWithIgnore(s, ignore)
// Fast path: if there are no digits, skip the regex entirely
if !strings.ContainsAny(result, "0123456789") {
return result
}
return digitToLetter.ReplaceAllString(result, "${1}${2}")
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import (
"time"

gojson "github.com/goccy/go-json"
"github.com/iancoleman/strcase"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/plog"
conventions "go.opentelemetry.io/otel/semconv/v1.38.0"
Expand Down Expand Up @@ -593,7 +592,7 @@ func handleLogEntryFields(resourceAttributes pcommon.Map, scopeLogs plog.ScopeLo
if log.Resource != nil {
resourceAttributes.PutStr(gcpResourceTypeField, log.Resource.Type)
for k, v := range log.Resource.Labels {
shared.PutStr(strcase.ToSnakeWithIgnore(fmt.Sprintf("gcp.label.%s", k), "."), v, resourceAttributes)
shared.PutStr(shared.ToSnakeCase(fmt.Sprintf("gcp.label.%s", k), "."), v, resourceAttributes)
}
}

Expand Down Expand Up @@ -624,7 +623,7 @@ func handleLogEntryFields(resourceAttributes pcommon.Map, scopeLogs plog.ScopeLo
}

for k, v := range log.Labels {
logRecord.Attributes().PutStr(strcase.ToSnakeWithIgnore(fmt.Sprintf("gcp.label.%v", k), "."), v)
logRecord.Attributes().PutStr(shared.ToSnakeCase(fmt.Sprintf("gcp.label.%v", k), "."), v)
}

handleOperationField(logRecord.Attributes(), log.Operation)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"testing"

gojson "github.com/goccy/go-json"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/plog"
Expand All @@ -17,6 +18,7 @@ import (
"github.com/open-telemetry/opentelemetry-collector-contrib/extension/encoding/googlecloudlogentryencodingextension/internal/constants"
"github.com/open-telemetry/opentelemetry-collector-contrib/extension/encoding/googlecloudlogentryencodingextension/internal/passthroughnlb"
"github.com/open-telemetry/opentelemetry-collector-contrib/extension/encoding/googlecloudlogentryencodingextension/internal/proxynlb"
"github.com/open-telemetry/opentelemetry-collector-contrib/extension/encoding/googlecloudlogentryencodingextension/internal/shared"
"github.com/open-telemetry/opentelemetry-collector-contrib/extension/encoding/googlecloudlogentryencodingextension/internal/vpcflowlog"
"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/golden"
"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatatest/plogtest"
Expand Down Expand Up @@ -595,3 +597,43 @@ func TestHandleProtoPayload(t *testing.T) {
})
}
}

func TestToSnakeCase(t *testing.T) {
tests := []struct {
name string
input string
ignore string
expected string
}{
{
name: "k8s label key preserved",
input: "labels.authorization.k8s.io/decision",
ignore: ".",
expected: "labels.authorization.k8s.io/decision",
},
{
name: "already snake_case unchanged",
input: "already_snake",
ignore: ".",
expected: "already_snake",
},
{
name: "simple camelCase converted",
input: "simpleLabel",
ignore: ".",
expected: "simple_label",
},
{
name: "h2c in lowercase preserved",
input: "proxy.h2c.enabled",
ignore: ".",
expected: "proxy.h2c.enabled",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := shared.ToSnakeCase(tt.input, tt.ignore)
assert.Equal(t, tt.expected, result)
})
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,10 @@ resourceLogs:
- key: user_agent.original
value:
stringValue: gcp-controller-manager/v0.0.0 (linux/amd64) kubernetes/$Format/leader-election
- key: gcp.label.authorization.k8_s.io/reason
- key: gcp.label.authorization.k8s.io/reason
value:
stringValue: 'RBAC: allowed by ClusterRoleBinding "system:gcp-controller-manager" of ClusterRole "system:gcp-controller-manager" to User "system:gcp-controller-manager"'
- key: gcp.label.authorization.k8_s.io/decision
- key: gcp.label.authorization.k8s.io/decision
value:
stringValue: allow
- key: gcp.operation.id
Expand Down
Loading