Skip to content

Commit

Permalink
[processor/transform] Add SpanID and TraceID to the grammar (#10487)
Browse files Browse the repository at this point in the history
* Add SpanID and TraceID to the grammar

* Updated NewGetter

* Updated readme

* Update NewFunctionCall to handle SpanID and TraceID

* Update Changelog

* Update processor/transformprocessor/internal/common/functions.go

Co-authored-by: Pablo Baeyens <[email protected]>

* Updated error message

* Fix lint issue

* Add byte slice type to grammar

* update tests

* Add TraceID function

* Add SpanID function

* Updated changelog

* Updated readme

* Add error check

* Fixing build checks

* Fix lint issues

Co-authored-by: Pablo Baeyens <[email protected]>
Co-authored-by: Daniel Jaglowski <[email protected]>
  • Loading branch information
3 people authored Jun 9, 2022
1 parent eac0998 commit 5e799df
Show file tree
Hide file tree
Showing 15 changed files with 377 additions and 27 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

### 💡 Enhancements 💡

- `transformprocessor`: Add byte slice literal to the grammar. Add new SpanID and TraceID functions that take a byte slice and return a Span/Trace ID. (#10487)
- `elasticsearchreceiver`: Add integration test for elasticsearch receiver (#10165)

### 🧰 Bug fixes 🧰
Expand Down
6 changes: 5 additions & 1 deletion processor/transformprocessor/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,15 @@ in the OTLP protobuf definition. e.g., `status.code`, `attributes["http.method"]
- Metric data types are `None`, `Gauge`, `Sum`, `Histogram`, `ExponentialHistogram`, and `Summary`
- `aggregation_temporality` is converted to and from the [protobuf's numeric definition](https://github.com/open-telemetry/opentelemetry-proto/blob/main/opentelemetry/proto/metrics/v1/metrics.proto#L291). Interact with this field using 0, 1, or 2.
- Until the grammar can handle booleans, `is_monotic` is handled via strings the strings `"true"` and `"false"`.
- Literals: Strings, ints, and floats can be referenced as literal values
- Literals: Strings, ints, and floats can be referenced as literal values. Byte slices can be references as a literal value via a hex string prefaced with `0x`, such as `0x0001`.
- Function invocations: Functions can be invoked with arguments matching the function's expected arguments
- Where clause: Telemetry to modify can be filtered by appending `where a <op> b`, with `a` and `b` being any of the above.

Supported functions:
- `SpanID(bytes)` - `bytes` is a byte slice of exactly 8 bytes. The function returns a SpanID from `bytes`. e.g., `SpanID(0x0000000000000000)`

- `TraceID(bytes)` - `bytes` is a byte slice of exactly 16 bytes. The function returns a TraceID from `bytes`. e.g., `TraceID(0x00000000000000000000000000000000)`

- `set(target, value)` - `target` is a path expression to a telemetry field to set `value` into. `value` is any value type.
e.g., `set(attributes["http.path"], "/foo")`, `set(name, attributes["http.route"])`. If `value` resolves to `nil`, e.g.
it references an unset map value, there will be no action.
Expand Down
3 changes: 3 additions & 0 deletions processor/transformprocessor/internal/common/expression.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ func NewGetter(val Value, functions map[string]interface{}, pathParser PathExpre
if i := val.Int; i != nil {
return &literal{value: *i}, nil
}
if b := val.Bytes; b != nil {
return &literal{value: ([]byte)(*b)}, nil
}

if val.Path != nil {
return pathParser(val.Path)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,13 @@ func Test_newGetter(t *testing.T) {
},
want: int64(12),
},
{
name: "bytes literal",
val: Value{
Bytes: (*Bytes)(&[]byte{1, 2, 3, 4, 5, 6, 7, 8}),
},
want: []byte{1, 2, 3, 4, 5, 6, 7, 8},
},
{
name: "path expression",
val: Value{
Expand Down
33 changes: 33 additions & 0 deletions processor/transformprocessor/internal/common/func_span_id.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package common // import "github.com/open-telemetry/opentelemetry-collector-contrib/processor/transformprocessor/internal/common"

import (
"errors"

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

func spanID(bytes []byte) (ExprFunc, error) {
if len(bytes) != 8 {
return nil, errors.New("span ids must be 8 bytes")
}
var idArr [8]byte
copy(idArr[:8], bytes)
id := pcommon.NewSpanID(idArr)
return func(ctx TransformContext) interface{} {
return id
}, nil
}
71 changes: 71 additions & 0 deletions processor/transformprocessor/internal/common/func_span_id_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package common

import (
"testing"

"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/pcommon"

"github.com/open-telemetry/opentelemetry-collector-contrib/processor/transformprocessor/internal/common/testhelper"
)

func Test_spanID(t *testing.T) {
tests := []struct {
name string
bytes []byte
want pcommon.SpanID
}{
{
name: "create span id",
bytes: []byte{1, 2, 3, 4, 5, 6, 7, 8},
want: pcommon.NewSpanID([8]byte{1, 2, 3, 4, 5, 6, 7, 8}),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {

ctx := testhelper.TestTransformContext{}

exprFunc, _ := spanID(tt.bytes)
actual := exprFunc(ctx)

assert.Equal(t, tt.want, actual)
})
}
}

func Test_spanID_validation(t *testing.T) {
tests := []struct {
name string
bytes []byte
}{
{
name: "byte slice less than 8",
bytes: []byte{1, 2, 3, 4, 5, 6, 7},
},
{
name: "byte slice longer than 8",
bytes: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := traceID(tt.bytes)
assert.Error(t, err, "span ids must be 8 bytes")
})
}
}
33 changes: 33 additions & 0 deletions processor/transformprocessor/internal/common/func_trace_id.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package common // import "github.com/open-telemetry/opentelemetry-collector-contrib/processor/transformprocessor/internal/common"

import (
"errors"

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

func traceID(bytes []byte) (ExprFunc, error) {
if len(bytes) != 16 {
return nil, errors.New("traces ids must be 16 bytes")
}
var idArr [16]byte
copy(idArr[:16], bytes)
id := pcommon.NewTraceID(idArr)
return func(ctx TransformContext) interface{} {
return id
}, nil
}
71 changes: 71 additions & 0 deletions processor/transformprocessor/internal/common/func_trace_id_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package common

import (
"testing"

"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/pcommon"

"github.com/open-telemetry/opentelemetry-collector-contrib/processor/transformprocessor/internal/common/testhelper"
)

func Test_traceID(t *testing.T) {
tests := []struct {
name string
bytes []byte
want pcommon.TraceID
}{
{
name: "create trace id",
bytes: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16},
want: pcommon.NewTraceID([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) {

ctx := testhelper.TestTransformContext{}

exprFunc, _ := traceID(tt.bytes)
actual := exprFunc(ctx)

assert.Equal(t, tt.want, actual)
})
}
}

func Test_traceID_validation(t *testing.T) {
tests := []struct {
name string
bytes []byte
}{
{
name: "byte slice less than 16",
bytes: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15},
},
{
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},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := traceID(tt.bytes)
assert.Error(t, err, "traces ids must be 16 bytes")
})
}
}
7 changes: 7 additions & 0 deletions processor/transformprocessor/internal/common/functions.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import (
)

var registry = map[string]interface{}{
"TraceID": traceID,
"SpanID": spanID,
"keep_keys": keepKeys,
"set": set,
"truncate_all": truncateAll,
Expand Down Expand Up @@ -109,6 +111,11 @@ func buildSliceArg(inv Invocation, argType reflect.Type, startingIndex int, args
arg = append(arg, *inv.Arguments[j].Int)
}
*args = append(*args, reflect.ValueOf(arg))
case reflect.Uint8:
if inv.Arguments[startingIndex].Bytes == nil {
return fmt.Errorf("invalid argument for slice parameter at position %v, must be a byte slice literal", startingIndex)
}
*args = append(*args, reflect.ValueOf(([]byte)(*inv.Arguments[startingIndex].Bytes)))
default:
return fmt.Errorf("unsupported slice type for function %v", inv.Function)
}
Expand Down
36 changes: 36 additions & 0 deletions processor/transformprocessor/internal/common/functions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ func Test_NewFunctionCall_invalid(t *testing.T) {
functions["testing_getter"] = functionWithGetter
functions["testing_multiple_args"] = functionWithMultipleArgs
functions["testing_string"] = functionWithString
functions["testing_byte_slice"] = functionWithByteSlice

tests := []struct {
name string
Expand Down Expand Up @@ -97,6 +98,23 @@ func Test_NewFunctionCall_invalid(t *testing.T) {
},
},
},
{
name: "not matching arg type when byte slice",
inv: Invocation{
Function: "testing_byte_slice",
Arguments: []Value{
{
String: testhelper.Strp("test"),
},
{
String: testhelper.Strp("test"),
},
{
String: testhelper.Strp("test"),
},
},
},
},
{
name: "function call returns error",
inv: Invocation{
Expand All @@ -117,6 +135,7 @@ func Test_NewFunctionCall(t *testing.T) {
functions["testing_string_slice"] = functionWithStringSlice
functions["testing_float_slice"] = functionWithFloatSlice
functions["testing_int_slice"] = functionWithIntSlice
functions["testing_byte_slice"] = functionWithByteSlice
functions["testing_setter"] = functionWithSetter
functions["testing_getsetter"] = functionWithGetSetter
functions["testing_getter"] = functionWithGetter
Expand Down Expand Up @@ -276,6 +295,17 @@ func Test_NewFunctionCall(t *testing.T) {
},
},
},
{
name: "bytes arg",
inv: Invocation{
Function: "testing_byte_slice",
Arguments: []Value{
{
Bytes: (*Bytes)(&[]byte{1, 2, 3, 4, 5, 6, 7, 8}),
},
},
},
},
{
name: "multiple args",
inv: Invocation{
Expand Down Expand Up @@ -335,6 +365,12 @@ func functionWithIntSlice(_ []int64) (ExprFunc, error) {
}, nil
}

func functionWithByteSlice(_ []byte) (ExprFunc, error) {
return func(ctx TransformContext) interface{} {
return "anything"
}, nil
}

func functionWithSetter(_ Setter) (ExprFunc, error) {
return func(ctx TransformContext) interface{} {
return "anything"
Expand Down
Loading

0 comments on commit 5e799df

Please sign in to comment.