Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
6d515e4
Accept 32 char hex trace IDs
andrewvc Oct 29, 2025
a4b7477
Cleanup comment
andrewvc Oct 29, 2025
4583350
Add one last test
andrewvc Oct 29, 2025
48ba336
Add changelog
andrewvc Oct 30, 2025
53e89c0
Fix linter errors
andrewvc Nov 2, 2025
e159793
Fix chlog lint issues
andrewvc Nov 3, 2025
b0dc4ad
Merge remote-tracking branch 'origin/main' into accept-str-traceids
andrewvc Nov 3, 2025
ce60a5e
Merge remote-tracking branch 'origin/main' into accept-str-traceids
andrewvc Nov 24, 2025
ed09e82
Testing improvements from PR review
andrewvc Nov 24, 2025
9e053ab
Expand string support to profiles and spans
andrewvc Nov 24, 2025
d3c5002
Update chlog
andrewvc Nov 24, 2025
80fc2c8
Merge branch 'main' into accept-str-traceids
andrewvc Nov 24, 2025
529659e
Merge remote-tracking branch 'origin/main' into accept-str-traceids
andrewvc Dec 1, 2025
310a9d1
Add missing license headers
andrewvc Dec 1, 2025
62b10c9
Merge branch 'accept-str-traceids' of github.com:andrewvc/opentelemet…
andrewvc Dec 1, 2025
e2f928b
Update goporto links
andrewvc Dec 1, 2025
61feb42
Fix broken test, fix linting
andrewvc Dec 1, 2025
9782cc4
Fix var shadowing for linter
andrewvc Dec 1, 2025
69943f0
Incorporate PR feedback
andrewvc Dec 2, 2025
8fb8ed5
Extend function name testing to individual tests
andrewvc Dec 2, 2025
1f4c65a
Merge remote-tracking branch 'origin/main' into accept-str-traceids
andrewvc Dec 2, 2025
070bc34
Add comment to clarify test fn
andrewvc Dec 2, 2025
52ee399
Update pkg/ottl/ottlfuncs/id_helpers.go
andrewvc Dec 3, 2025
4c0252c
Merge branch 'main' into accept-str-traceids
andrewvc Dec 3, 2025
1926df3
Simplify error logic per PR feedback
andrewvc Dec 4, 2025
9a29168
Merge remote-tracking branch 'andrewvc/accept-str-traceids' into acce…
andrewvc Dec 4, 2025
1761636
Merge remote-tracking branch 'origin/main' into accept-str-traceids
andrewvc Dec 5, 2025
7c19b76
Add benchmarks for trace ID performance
andrewvc Dec 5, 2025
d2f9aff
Add support for literal getters
andrewvc Dec 5, 2025
4e624ac
Fix failing tests
andrewvc Dec 5, 2025
ca64679
Benchmark get and set
andrewvc Dec 6, 2025
19f85f3
Add dynamic hex get and set
andrewvc Dec 6, 2025
92912ca
Specialize hex decoding to reduce allocs for hex string decode
andrewvc Dec 8, 2025
0bf8aeb
Error early on init, ensure tests distinguish between init and dynami…
andrewvc Dec 10, 2025
cc18f7f
Remove redundant test
andrewvc Dec 10, 2025
f815ca6
Merge remote-tracking branch 'origin/main' into accept-str-traceids
andrewvc Dec 10, 2025
95072dd
Fix e2e tests
andrewvc Dec 11, 2025
4815a29
Merge remote-tracking branch 'origin/main' into accept-str-traceids
andrewvc Dec 11, 2025
4f96694
Remove unneeded makeLiteralIDGetter and helper
andrewvc Dec 11, 2025
a234318
Switched to table driven benchmark
andrewvc Dec 12, 2025
a496347
Fix import ordering
andrewvc Dec 12, 2025
9ba8c4e
Merge branch 'main' into accept-str-traceids
andrewvc Dec 12, 2025
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
27 changes: 27 additions & 0 deletions .chloggen/accept-str-traceids.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Use this changelog template to create an entry for release notes.

# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
change_type: enhancement

# The name of the component, or a single word describing the area of concern, (e.g. receiver/filelog)
component: pkg/ottl

# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
note: Accept string trace/span/profile IDs for `TraceID()`, `SpanID()`, and `ProfileID()` in OTTL.

# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists.
issues: [43429]

# (Optional) One or more lines of additional information to render under the primary note.
# These lines will be padded with 2 spaces and then inserted directly into the document.
# Use pipe (|) for multiline entries.
subtext: This change allows for a more straightforward use of string values to set trace, span, and profile IDs in OTTL.

# If your change doesn't affect end users or the exported elements of any package,
# you should instead start your pull request title with [chore] or use the "Skip Changelog" label.
# Optional: The change log or logs in which this entry should be included.
# e.g. '[user]' or '[user, api]'
# Include 'user' if the change is relevant to end users.
# Include 'api' if there is a change to a library API.
# Default: '[user]'
change_logs: [user]
12 changes: 12 additions & 0 deletions pkg/ottl/e2e/e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1110,6 +1110,12 @@ func Test_e2e_converters(t *testing.T) {
tCtx.GetLogRecord().SetSpanID(pcommon.NewSpanIDEmpty())
},
},
{
statement: `set(span_id, SpanID("0102030405060708"))`,
want: func(tCtx *ottllog.TransformContext) {
tCtx.GetLogRecord().SetSpanID(pcommon.SpanID([8]byte{1, 2, 3, 4, 5, 6, 7, 8}))
},
},
{
statement: `set(attributes["test"], "pass") where String(ProfileID(0x00000000000000000000000000000001)) == "[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1]"`,
want: func(tCtx *ottllog.TransformContext) {
Expand Down Expand Up @@ -1167,6 +1173,12 @@ func Test_e2e_converters(t *testing.T) {
tCtx.GetLogRecord().SetTraceID(pcommon.NewTraceIDEmpty())
},
},
{
statement: `set(trace_id, TraceID("0102030405060708090a0b0c0d0e0f10"))`,
want: func(tCtx *ottllog.TransformContext) {
tCtx.GetLogRecord().SetTraceID(pcommon.TraceID([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}))
},
},
{
statement: `set(time, TruncateTime(time, Duration("1s")))`,
want: func(tCtx *ottllog.TransformContext) {
Expand Down
6 changes: 6 additions & 0 deletions pkg/ottl/e2e/profiles/e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1055,6 +1055,12 @@ func Test_e2e_converters(t *testing.T) {
tCtx.GetProfile().SetProfileID(pprofile.ProfileID{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0})
},
},
{
statement: `set(profile_id, ProfileID("0102030405060708090a0b0c0d0e0f10"))`,
want: func(_ *testing.T, tCtx ottlprofile.TransformContext) {
tCtx.GetProfile().SetProfileID(pprofile.ProfileID{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16})
},
},
{
statement: `set(attributes["test"], Split(attributes["flags"], "|"))`,
want: func(_ *testing.T, tCtx ottlprofile.TransformContext) {
Expand Down
25 changes: 16 additions & 9 deletions pkg/ottl/ottlfuncs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1897,15 +1897,17 @@ Examples:

### ProfileID

`ProfileID(bytes)`
`ProfileID(bytes|string)`

The `ProfileID` Converter returns a `pprofile.ProfileID` struct from the given byte slice.
The `ProfileID` Converter returns a `pprofile.ProfileID` struct from the given byte slice OR hex string.

`bytes` is a byte slice of exactly 16 bytes.
`bytes` byte slice of exactly 16 bytes.
`string` is a string of exactly 32 hex characters solely composed of valid hexadecimal chars.

Examples:

- `ProfileID(0x00112233445566778899aabbccddeeff)`
- `ProfileID("a389023abaa839283293ed323892389d")`

### RemoveXML

Expand Down Expand Up @@ -2163,15 +2165,17 @@ Examples:

### SpanID

`SpanID(bytes)`
`SpanID(bytes|string)`

The `SpanID` Converter returns a `pdata.SpanID` struct from the given byte slice.
The `SpanID` Converter returns a `pdata.SpanID` struct from the given byte slice OR hex string.

`bytes` is a byte slice of exactly 8 bytes.
`bytes` byte slice of exactly 8 bytes.
`string` is a string of exactly 16 hex characters solely composed of valid hexadecimal chars.

Examples:

- `SpanID(0x0000000000000000)`
- `SpanID("0102030405060708")`

### Split

Expand Down Expand Up @@ -2448,15 +2452,18 @@ Examples:

### TraceID

`TraceID(bytes)`
`TraceID(bytes|string)`

The `TraceID` Converter returns a `pdata.TraceID` struct from the given byte slice.
The `TraceID` Converter returns a `pdata.TraceID` struct from the given byte slice OR hex string.

`bytes` is a byte slice of exactly 16 bytes.
`bytes` byte slice of exactly 16 bytes.
`string` is a string of exactly 16 bytes solely composed of valid hexadecimal chars.

Examples:

- `TraceID(0x00000000000000000000000000000000)`
- `TraceID("a389023abaa839283293ed323892389d")`


### TruncateTime

Expand Down
28 changes: 15 additions & 13 deletions pkg/ottl/ottlfuncs/func_profile_id.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,22 @@
package ottlfuncs // import "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/ottlfuncs"

import (
"context"
"encoding/hex"
"errors"
"fmt"

"go.opentelemetry.io/collector/pdata/pprofile"

"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl"
)

const profileIDFuncName = "ProfileID"

type ProfileIDArguments[K any] struct {
Bytes []byte
Target ottl.ByteSliceLikeGetter[K]
Comment thread
edmocosta marked this conversation as resolved.
}

func NewProfileIDFactory[K any]() ottl.Factory[K] {
return ottl.NewFactory("ProfileID", &ProfileIDArguments[K]{}, createProfileIDFunction[K])
return ottl.NewFactory(profileIDFuncName, &ProfileIDArguments[K]{}, createProfileIDFunction[K])
}

func createProfileIDFunction[K any](_ ottl.FunctionContext, oArgs ottl.Arguments) (ottl.ExprFunc[K], error) {
Expand All @@ -28,16 +29,17 @@ func createProfileIDFunction[K any](_ ottl.FunctionContext, oArgs ottl.Arguments
return nil, errors.New("ProfileIDFactory args must be of type *ProfileIDArguments[K]")
}

return profileID[K](args.Bytes)
return profileID[K](args.Target)
}

func profileID[K any](target ottl.ByteSliceLikeGetter[K]) (ottl.ExprFunc[K], error) {
return newIDExprFunc(profileIDFuncName, target, decodeHexToProfileID)
}

func profileID[K any](bytes []byte) (ottl.ExprFunc[K], error) {
id := pprofile.ProfileID{}
if len(bytes) != len(id) {
return nil, fmt.Errorf("profile ids must be %d bytes", len(id))
func decodeHexToProfileID(b []byte) (pprofile.ProfileID, error) {
var id pprofile.ProfileID
if _, err := hex.Decode(id[:], b); err != nil {
return pprofile.ProfileID{}, err
}
copy(id[:], bytes)
return func(context.Context, K) (any, error) {
return id, nil
}, nil
return id, nil
}
69 changes: 29 additions & 40 deletions pkg/ottl/ottlfuncs/func_profile_id_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,61 +6,50 @@ package ottlfuncs
import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/pdata/pprofile"
)

func Test_profileID(t *testing.T) {
tests := []struct {
name string
bytes []byte
want pprofile.ProfileID
}{
runIDSuccessTests(t, profileID[any], []idSuccessTestCase{
{
name: "create profile id",
bytes: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16},
name: "create profile id from 16 bytes",
value: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16},
want: pprofile.ProfileID([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
exprFunc, err := profileID[any](tt.bytes)
require.NoError(t, err)
result, err := exprFunc(nil, nil)
require.NoError(t, err)
assert.Equal(t, tt.want, result)
})
}
{
name: "create profile id from 32 hex chars",
value: []byte("0102030405060708090a0b0c0d0e0f10"),
want: pprofile.ProfileID([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}),
},
})
}

func Test_profileID_validation(t *testing.T) {
tests := []struct {
name string
bytes []byte
err string
}{
runIDErrorTests(t, profileID[any], profileIDFuncName, []idErrorTestCase{
{
name: "nil profile id",
bytes: nil,
err: "profile ids must be 16 bytes",
value: nil,
err: errIDInvalidLength,
},
{
name: "byte slice less than 16 (15)",
value: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15},
err: errIDInvalidLength,
},
{
name: "byte slice longer than 16 (17)",
value: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17},
err: errIDInvalidLength,
},
{
name: "byte slice less than 16",
bytes: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15},
err: "profile ids must be 16 bytes",
name: "byte slice longer than 32 (33)",
value: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33},
err: errIDInvalidLength,
},
{
name: "byte slice longer than 16",
bytes: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17},
err: "profile ids must be 16 bytes",
name: "invalid hex string",
value: []byte("ZZ02030405060708090a0b0c0d0e0f10"),
err: errIDHexDecode,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := profileID[any](tt.bytes)
require.Error(t, err)
assert.ErrorContains(t, err, tt.err)
})
}
})
}
28 changes: 15 additions & 13 deletions pkg/ottl/ottlfuncs/func_span_id.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,22 @@
package ottlfuncs // import "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/ottlfuncs"

import (
"context"
"encoding/hex"
"errors"

"go.opentelemetry.io/collector/pdata/pcommon"

"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl"
)

const spanIDFuncName = "SpanID"

type SpanIDArguments[K any] struct {
Bytes []byte
Target ottl.ByteSliceLikeGetter[K]
}

func NewSpanIDFactory[K any]() ottl.Factory[K] {
return ottl.NewFactory("SpanID", &SpanIDArguments[K]{}, createSpanIDFunction[K])
return ottl.NewFactory(spanIDFuncName, &SpanIDArguments[K]{}, createSpanIDFunction[K])
}

func createSpanIDFunction[K any](_ ottl.FunctionContext, oArgs ottl.Arguments) (ottl.ExprFunc[K], error) {
Expand All @@ -27,17 +29,17 @@ func createSpanIDFunction[K any](_ ottl.FunctionContext, oArgs ottl.Arguments) (
return nil, errors.New("SpanIDFactory args must be of type *SpanIDArguments[K]")
}

return spanID[K](args.Bytes)
return spanID[K](args.Target)
}

func spanID[K any](target ottl.ByteSliceLikeGetter[K]) (ottl.ExprFunc[K], error) {
return newIDExprFunc(spanIDFuncName, target, decodeHexToSpanID)
}

func spanID[K any](bytes []byte) (ottl.ExprFunc[K], error) {
if len(bytes) != 8 {
return nil, errors.New("span ids must be 8 bytes")
func decodeHexToSpanID(b []byte) (pcommon.SpanID, error) {
var id pcommon.SpanID
if _, err := hex.Decode(id[:], b); err != nil {
return pcommon.SpanID{}, err
}
var idArr [8]byte
copy(idArr[:8], bytes)
id := pcommon.SpanID(idArr)
return func(context.Context, K) (any, error) {
return id, nil
}, nil
return id, nil
}
Loading