Skip to content

Commit 22815ac

Browse files
XuechunHouedmocostaevan-bradley
authored
[pkg/ottl] A new OTTL function ParseInt (open-telemetry#40786)
Co-authored-by: Edmo Vamerlatti Costa <[email protected]> Co-authored-by: Evan Bradley <[email protected]>
1 parent 4c392a7 commit 22815ac

File tree

6 files changed

+282
-0
lines changed

6 files changed

+282
-0
lines changed
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
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: pkg/ottl
8+
9+
# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
10+
note: Added a new `ParseInt` OTTL Function.
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: [40758]
14+
15+
# If your change doesn't affect end users or the exported elements of any package,
16+
# you should instead start your pull request title with [chore] or use the "Skip Changelog" label.
17+
# Optional: The change log or logs in which this entry should be included.
18+
# e.g. '[user]' or '[user, api]'
19+
# Include 'user' if the change is relevant to end users.
20+
# Include 'api' if there is a change to a library API.
21+
# Default: '[user]'
22+
change_logs: [user]

pkg/ottl/e2e/e2e_test.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -558,6 +558,24 @@ func Test_e2e_converters(t *testing.T) {
558558
tCtx.GetLogRecord().Body().SetStr("<a><b></b><custom>foo</custom></a><c><b></b>bar</c>")
559559
},
560560
},
561+
{
562+
statement: `set(attributes["test"], ParseInt("0xAF", 0))`,
563+
want: func(tCtx ottllog.TransformContext) {
564+
tCtx.GetLogRecord().Attributes().PutInt("test", 175)
565+
},
566+
},
567+
{
568+
statement: `set(attributes["test"], ParseInt("12345", 10))`,
569+
want: func(tCtx ottllog.TransformContext) {
570+
tCtx.GetLogRecord().Attributes().PutInt("test", 12345)
571+
},
572+
},
573+
{
574+
statement: `set(attributes["test"], ParseInt("AF", 16))`,
575+
want: func(tCtx ottllog.TransformContext) {
576+
tCtx.GetLogRecord().Attributes().PutInt("test", 175)
577+
},
578+
},
561579
{
562580
statement: `set(attributes["test"], Double(1.0))`,
563581
want: func(tCtx ottllog.TransformContext) {

pkg/ottl/ottlfuncs/README.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -500,6 +500,7 @@ Available Converters:
500500
- [Nanoseconds](#nanoseconds)
501501
- [Now](#now)
502502
- [ParseCSV](#parsecsv)
503+
- [ParseInt](#parseint)
503504
- [ParseJSON](#parsejson)
504505
- [ParseKeyValue](#parsekeyvalue)
505506
- [ParseSimplifiedXML](#parsesimplifiedxml)
@@ -1539,6 +1540,36 @@ Examples:
15391540

15401541
- `ParseCSV("\"555-555-5556,Joe Smith\",[email protected]", "phone,name,email", mode="ignoreQuotes")`
15411542

1543+
### ParseInt
1544+
1545+
`ParseInt(target, base)`
1546+
1547+
The `ParseInt` Converter interprets a string `target` in the given `base` (0, 2 to 36) and returns its integer representation.
1548+
1549+
`target` is the string to be converted. `target` should be a valid integer represented in string format. For example, "1234" is a valid `target` value, but "notANumber" is not. The `target` may begin with a leading sign: "+" or "-".
1550+
1551+
`base` is an `int64` representing the base of the number in the `target` string. An error occurs if the `base` argument is a negative integer.
1552+
1553+
If the `base` argument is 0, the true base is implied by the string's prefix following the sign (if present): 2 for "0b", 8 for "0" or "0o", 16 for "0x", and 10 otherwise. When the `base` value is 0, underscore characters are permitted as defined by the Go syntax for [integer literals](https://go.dev/ref/spec#Integer_literals).
1554+
1555+
Examples of `ParseInt` behavior when `base` is 0:
1556+
- `ParseInt("0b1111_0000", 0) -> 240`
1557+
- `ParseInt("0b10110", 0) -> 22`
1558+
- `ParseInt("0xFF", 0) -> 255`
1559+
- `ParseInt("-0xFF", 0) -> -255`
1560+
- `ParseInt("-0o123", 0) -> -83`
1561+
1562+
The return type is `int64`.
1563+
1564+
For more information, please refer to the documentation for the Go [strconv.ParseInt](https://pkg.go.dev/strconv#ParseInt) function.
1565+
1566+
Examples:
1567+
1568+
- `ParseInt("12345", 10)`
1569+
- `ParseInt("0xAA", 0)`
1570+
- `ParseInt("AA", 16)`
1571+
- `ParseInt("-20", 8)`
1572+
15421573
### ParseJSON
15431574

15441575
`ParseJSON(target)`
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package ottlfuncs // import "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/ottlfuncs"
5+
6+
import (
7+
"context"
8+
"errors"
9+
"fmt"
10+
"strconv"
11+
12+
"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl"
13+
)
14+
15+
type ParseIntArguments[K any] struct {
16+
Target ottl.StringGetter[K]
17+
Base ottl.IntGetter[K]
18+
}
19+
20+
func NewParseIntFactory[K any]() ottl.Factory[K] {
21+
return ottl.NewFactory("ParseInt", &ParseIntArguments[K]{}, createParseIntFunction[K])
22+
}
23+
24+
func createParseIntFunction[K any](_ ottl.FunctionContext, oArgs ottl.Arguments) (ottl.ExprFunc[K], error) {
25+
args, ok := oArgs.(*ParseIntArguments[K])
26+
27+
if !ok {
28+
return nil, errors.New("ParseIntFactory args must be of type *ParseIntArguments[K]")
29+
}
30+
31+
return parseIntFunc(args.Target, args.Base), nil
32+
}
33+
34+
func parseIntFunc[K any](target ottl.StringGetter[K], base ottl.IntGetter[K]) ottl.ExprFunc[K] {
35+
return func(ctx context.Context, tCtx K) (any, error) {
36+
targetValue, err := target.Get(ctx, tCtx)
37+
if err != nil {
38+
return nil, err
39+
}
40+
if targetValue == "" {
41+
return nil, errors.New("invalid target value for ParseInt function, target cannot be empty")
42+
}
43+
baseValue, err := base.Get(ctx, tCtx)
44+
if err != nil {
45+
return nil, err
46+
}
47+
if baseValue < 0 {
48+
return nil, fmt.Errorf("invalid base value: %d for ParseInt function, base cannot be negative", baseValue)
49+
}
50+
result, err := strconv.ParseInt(targetValue, int(baseValue), 64)
51+
if err != nil {
52+
return nil, err
53+
}
54+
return result, nil
55+
}
56+
}
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package ottlfuncs
5+
6+
import (
7+
"context"
8+
"fmt"
9+
"math"
10+
"testing"
11+
12+
"github.com/stretchr/testify/assert"
13+
14+
"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl"
15+
)
16+
17+
func Test_ParseInt(t *testing.T) {
18+
tests := []struct {
19+
name string
20+
target ottl.StringGetter[any]
21+
base ottl.IntGetter[any]
22+
expected any
23+
err bool
24+
}{
25+
{
26+
name: "string in base 10",
27+
target: &ottl.StandardStringGetter[any]{
28+
Getter: func(_ context.Context, _ any) (any, error) {
29+
return "123456789", nil
30+
},
31+
},
32+
base: &ottl.StandardIntGetter[any]{
33+
Getter: func(context.Context, any) (any, error) {
34+
return int64(10), nil
35+
},
36+
},
37+
expected: int64(123456789),
38+
},
39+
{
40+
name: "string in base 2",
41+
target: &ottl.StandardStringGetter[any]{
42+
Getter: func(_ context.Context, _ any) (any, error) {
43+
return "01010101", nil
44+
},
45+
},
46+
base: &ottl.StandardIntGetter[any]{
47+
Getter: func(context.Context, any) (any, error) {
48+
return int64(2), nil
49+
},
50+
},
51+
expected: int64(85),
52+
},
53+
{
54+
name: "an out of range string",
55+
target: &ottl.StandardStringGetter[any]{
56+
Getter: func(_ context.Context, _ any) (any, error) {
57+
return fmt.Sprintf("%d", uint64(math.MaxUint64)), nil
58+
},
59+
},
60+
base: &ottl.StandardIntGetter[any]{
61+
Getter: func(context.Context, any) (any, error) {
62+
return int64(10), nil
63+
},
64+
},
65+
expected: nil,
66+
err: true,
67+
},
68+
{
69+
name: "string in base 16",
70+
target: &ottl.StandardStringGetter[any]{
71+
Getter: func(_ context.Context, _ any) (any, error) {
72+
return "AF", nil
73+
},
74+
},
75+
base: &ottl.StandardIntGetter[any]{
76+
Getter: func(context.Context, any) (any, error) {
77+
return int64(16), nil
78+
},
79+
},
80+
expected: int64(175),
81+
},
82+
{
83+
name: "string in base 16 with 0x prefix",
84+
target: &ottl.StandardStringGetter[any]{
85+
Getter: func(_ context.Context, _ any) (any, error) {
86+
return "0XAF", nil
87+
},
88+
},
89+
base: &ottl.StandardIntGetter[any]{
90+
Getter: func(context.Context, any) (any, error) {
91+
return int64(0), nil
92+
},
93+
},
94+
expected: int64(175),
95+
},
96+
{
97+
name: "not a number string",
98+
target: &ottl.StandardStringGetter[any]{
99+
Getter: func(_ context.Context, _ any) (any, error) {
100+
return "test", nil
101+
},
102+
},
103+
base: &ottl.StandardIntGetter[any]{
104+
Getter: func(context.Context, any) (any, error) {
105+
return int64(10), nil
106+
},
107+
},
108+
expected: nil,
109+
err: true,
110+
},
111+
{
112+
name: "empty string",
113+
target: &ottl.StandardStringGetter[any]{
114+
Getter: func(_ context.Context, _ any) (any, error) {
115+
return "", nil
116+
},
117+
},
118+
base: &ottl.StandardIntGetter[any]{
119+
Getter: func(context.Context, any) (any, error) {
120+
return int64(10), nil
121+
},
122+
},
123+
expected: nil,
124+
err: true,
125+
},
126+
{
127+
name: "negative base value",
128+
target: &ottl.StandardStringGetter[any]{
129+
Getter: func(_ context.Context, _ any) (any, error) {
130+
return "12345", nil
131+
},
132+
},
133+
base: &ottl.StandardIntGetter[any]{
134+
Getter: func(context.Context, any) (any, error) {
135+
return int64(-10), nil
136+
},
137+
},
138+
expected: nil,
139+
err: true,
140+
},
141+
}
142+
for _, tt := range tests {
143+
t.Run(tt.name, func(t *testing.T) {
144+
exprFunc := parseIntFunc[any](tt.target, tt.base)
145+
result, err := exprFunc(nil, nil)
146+
if tt.err {
147+
assert.Error(t, err)
148+
} else {
149+
assert.NoError(t, err)
150+
}
151+
assert.Equal(t, tt.expected, result)
152+
})
153+
}
154+
}

pkg/ottl/ottlfuncs/functions.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ func converters[K any]() []ottl.Factory[K] {
120120
NewHexFactory[K](),
121121
NewSliceToMapFactory[K](),
122122
NewProfileIDFactory[K](),
123+
NewParseIntFactory[K](),
123124
NewKeysFactory[K](),
124125
}
125126
}

0 commit comments

Comments
 (0)