Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/main'
Browse files Browse the repository at this point in the history
  • Loading branch information
sakulali committed Sep 20, 2023
2 parents 7d602b4 + 6f15174 commit 7a5decd
Show file tree
Hide file tree
Showing 20 changed files with 1,708 additions and 828 deletions.
27 changes: 27 additions & 0 deletions .chloggen/26304-tailsampling-processor-checkapi.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: breaking

# The name of the component, or a single word describing the area of concern, (e.g. filelogreceiver)
component: tailsamplingprocessor

# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
note: Unexport `SamplingProcessorMetricViews` to comply with checkapi

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

# (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:

# 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: [api]
27 changes: 27 additions & 0 deletions .chloggen/filelogreceiver_log_globing_io_errors.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. filelogreceiver)
component: filelogreceiver

# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
note: Log the globbing IO errors

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

# (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:

# 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: []
27 changes: 27 additions & 0 deletions .chloggen/mongodbreceiver-checkapi-26304.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: breaking

# The name of the component, or a single word describing the area of concern, (e.g. filelogreceiver)
component: mongodbreceiver

# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
note: Do not export the function `NewClient` and pass checkapi.

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

# (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:

# 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: [api]
30 changes: 30 additions & 0 deletions .chloggen/ottl-named-arguments.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# 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. filelogreceiver)
component: pkg/ottl

# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
note: Allow named arguments in function invocations

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

# (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: |
Arguments can now be specified by a snake-cased version of their name in the function's
`Arguments` struct. Named arguments can be specified in any order, but must be specified
after arguments without a name.
# 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: []
29 changes: 29 additions & 0 deletions .chloggen/ottl-optional-parameters.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# 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. filelogreceiver)
component: pkg/ottl

# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
note: Add support for optional parameters

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

# (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: |
The new `ottl.Optional` type can now be used in a function's `Arguments` struct
to indicate that a parameter is optional.
# 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: [api]
2 changes: 0 additions & 2 deletions cmd/checkapi/allowlist.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,13 @@ processor/groupbyattrsprocessor
processor/groupbytraceprocessor
processor/probabilisticsamplerprocessor
processor/servicegraphprocessor
processor/tailsamplingprocessor
receiver/carbonreceiver
receiver/collectdreceiver
receiver/dockerstatsreceiver
receiver/jaegerreceiver
receiver/journaldreceiver
receiver/kafkareceiver
receiver/mongodbatlasreceiver
receiver/mongodbreceiver
receiver/podmanreceiver
receiver/pulsarreceiver
receiver/windowseventlogreceiver
12 changes: 12 additions & 0 deletions pkg/ottl/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,18 @@ For slice parameters, the following types are supported:
- `uint8`. Byte slice literals are parsed as byte slices by the OTTL.
- `Getter`

To make a parameter optional, use the `Optional` type, which takes a type argument for the underlying
parameter type. For example, an optional string parameter would be specified as `Optional[string]`.
All optional parameters must be specified after all required parameters.

#### Arguments in invocations

Function arguments must be passed in the order defined in the `Arguments` struct for the function unless they are named, in which case the arguments can come in any order. All named arguments must come after all arguments without
names. Argument names are snake-cased versions of the argument's field name in the function's `Arguments` struct.

When passing optional arguments, all optional arguments preceding a given optional argument must be specified if
the arguments are not named. Passing a named argument allows skipping the preceding optional arguments.

### Values

Values are passed as function parameters or are used in a Boolean Expression. Values can take the form of:
Expand Down
140 changes: 124 additions & 16 deletions pkg/ottl/functions.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (
"fmt"
"reflect"
"strings"

"github.com/iancoleman/strcase"
)

type PathExpressionParser[K any] func(*Path) (GetSetter[K], error)
Expand All @@ -21,17 +23,20 @@ func (p *Parser[K]) newFunctionCall(ed editor) (Expr[K], error) {
if !ok {
return Expr[K]{}, fmt.Errorf("undefined function %q", ed.Function)
}
args := f.CreateDefaultArguments()
defaultArgs := f.CreateDefaultArguments()
var args Arguments

// A nil value indicates the function takes no arguments.
if args != nil {
if defaultArgs != nil {
// Pointer values are necessary to fulfill the Go reflection
// settability requirements. Non-pointer values are not
// modifiable through reflection.
if reflect.TypeOf(args).Kind() != reflect.Pointer {
if reflect.TypeOf(defaultArgs).Kind() != reflect.Pointer {
return Expr[K]{}, fmt.Errorf("factory for %q must return a pointer to an Arguments value in its CreateDefaultArguments method", ed.Function)
}

args = reflect.New(reflect.ValueOf(defaultArgs).Elem().Type()).Interface()

err := p.buildArgs(ed, reflect.ValueOf(args).Elem())
if err != nil {
return Expr[K]{}, fmt.Errorf("error while parsing arguments for call to %q: %w", ed.Function, err)
Expand All @@ -47,24 +52,70 @@ func (p *Parser[K]) newFunctionCall(ed editor) (Expr[K], error) {
}

func (p *Parser[K]) buildArgs(ed editor, argsVal reflect.Value) error {
if len(ed.Arguments) != argsVal.NumField() {
return fmt.Errorf("incorrect number of arguments. Expected: %d Received: %d", argsVal.NumField(), len(ed.Arguments))
requiredArgs := 0
seenNamed := false

for i := 0; i < len(ed.Arguments); i++ {
if !seenNamed && ed.Arguments[i].Name != "" {
seenNamed = true
} else if seenNamed && ed.Arguments[i].Name == "" {
return errors.New("unnamed argument used after named argument")
}
}

for i := 0; i < argsVal.NumField(); i++ {
field := argsVal.Field(i)
fieldType := field.Type()
argVal := ed.Arguments[i]
if !strings.HasPrefix(argsVal.Field(i).Type().Name(), "Optional") {
requiredArgs++
}
}

if len(ed.Arguments) < requiredArgs || len(ed.Arguments) > argsVal.NumField() {
return fmt.Errorf("incorrect number of arguments. Expected: %d Received: %d", argsVal.NumField(), len(ed.Arguments))
}

for i, edArg := range ed.Arguments {
var field reflect.Value
var fieldType reflect.Type
var isOptional bool
var arg argument

if edArg.Name == "" {
field = argsVal.Field(i)
fieldType = field.Type()
isOptional = strings.HasPrefix(fieldType.Name(), "Optional")
arg = ed.Arguments[i]
} else {
field = argsVal.FieldByName(strcase.ToCamel(edArg.Name))
if !field.IsValid() {
return fmt.Errorf("no such parameter: %s", edArg.Name)
}
fieldType = field.Type()
isOptional = strings.HasPrefix(fieldType.Name(), "Optional")
arg = edArg
}

var val any
var manager optionalManager
var err error
var ok bool
if isOptional {
manager, ok = field.Interface().(optionalManager)

if !ok {
return errors.New("optional type is not manageable by the OTTL parser. This is an error in the OTTL")
}

fieldType = manager.get().Type()
}

switch {
case strings.HasPrefix(fieldType.Name(), "FunctionGetter"):
var name string
switch {
case argVal.Enum != nil:
name = string(*argVal.Enum)
case argVal.FunctionName != nil:
name = *argVal.FunctionName
case arg.Value.Enum != nil:
name = string(*arg.Value.Enum)
case arg.Value.FunctionName != nil:
name = *arg.Value.FunctionName
default:
return fmt.Errorf("invalid function name given")
}
Expand All @@ -74,14 +125,18 @@ func (p *Parser[K]) buildArgs(ed editor, argsVal reflect.Value) error {
}
val = StandardFunctionGetter[K]{fCtx: FunctionContext{Set: p.telemetrySettings}, fact: f}
case fieldType.Kind() == reflect.Slice:
val, err = p.buildSliceArg(argVal, fieldType)
val, err = p.buildSliceArg(arg.Value, fieldType)
default:
val, err = p.buildArg(argVal, fieldType)
val, err = p.buildArg(arg.Value, fieldType)
}
if err != nil {
return fmt.Errorf("invalid argument at position %v: %w", i, err)
}
field.Set(reflect.ValueOf(val))
if isOptional {
field.Set(manager.set(val))
} else {
field.Set(reflect.ValueOf(val))
}
}

return nil
Expand Down Expand Up @@ -280,7 +335,7 @@ func (p *Parser[K]) buildArg(argVal value, argType reflect.Type) (any, error) {
}
return bool(*argVal.Bool), nil
default:
return nil, errors.New("unsupported argument type")
return nil, fmt.Errorf("unsupported argument type: %s", name)
}
}

Expand Down Expand Up @@ -310,3 +365,56 @@ func buildSlice[T any](argVal value, argType reflect.Type, buildArg buildArgFunc

return vals, nil
}

// optionalManager provides a way for the parser to handle Optional[T] structs
// without needing to know the concrete type of T, which is inaccessible through
// the reflect package.
// Would likely be resolved by https://github.com/golang/go/issues/54393.
type optionalManager interface {
// set takes a non-reflection value and returns a reflect.Value of
// an Optional[T] struct with this value set.
set(val any) reflect.Value

// get returns a reflect.Value value of the value contained within
// an Optional[T]. This allows obtaining a reflect.Type for T.
get() reflect.Value
}

type Optional[T any] struct {
val T
hasValue bool
}

// This is called only by reflection.
// nolint:unused
func (o Optional[T]) set(val any) reflect.Value {
return reflect.ValueOf(Optional[T]{
val: val.(T),
hasValue: true,
})
}

func (o Optional[T]) IsEmpty() bool {
return !o.hasValue
}

func (o Optional[T]) Get() T {
return o.val
}

func (o Optional[T]) get() reflect.Value {
// `(reflect.Value).Call` will create a reflect.Value containing a zero-valued T.
// Trying to create a reflect.Value for T by calling reflect.TypeOf or
// reflect.ValueOf on an empty T value creates an invalid reflect.Value object,
// the `Call` method appears to do extra processing to capture the type.
return reflect.ValueOf(o).MethodByName("Get").Call(nil)[0]
}

// Allows creating an Optional with a value already populated for use in testing
// OTTL functions.
func NewTestingOptional[T any](val T) Optional[T] {
return Optional[T]{
val: val,
hasValue: true,
}
}
Loading

0 comments on commit 7a5decd

Please sign in to comment.