Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Log Support in Attribute Processor (1/2) #1783

Merged
merged 2 commits into from
Sep 30, 2020
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
12 changes: 12 additions & 0 deletions internal/data/testdata/log.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,18 @@ func generateLogOtlpOneEmptyOneNilResourceLogs() []*otlplogs.ResourceLogs {
}
}

func GenerateLogDataOneEmptyOneNilInstrumentationLibrary() pdata.Logs {
return pdata.LogsFromInternalRep(internal.LogsFromOtlp(generateLogOtlpOneEmptyOneNilInstrumentationLibrary()))

}

func generateLogOtlpOneEmptyOneNilInstrumentationLibrary() []*otlplogs.ResourceLogs {
return []*otlplogs.ResourceLogs{
{},
{nil, []*otlplogs.InstrumentationLibraryLogs{nil}},
}
}

func GenerateLogDataNoLogRecords() pdata.Logs {
ld := GenerateLogDataOneEmptyResourceLogs()
rs0 := ld.ResourceLogs().At(0)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,50 +4,53 @@
// 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
// 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 filterspan
package filterconfig

import (
"errors"

"go.opentelemetry.io/collector/internal/processor/filterset"
)

// MatchConfig has two optional MatchProperties one to define what is processed
// by the processor, captured under the 'include' and the second, exclude, to
// define what is excluded from the processor.
type MatchConfig struct {
// Include specifies the set of span properties that must be present in order
// Include specifies the set of span/log properties that must be present in order
// for this processor to apply to it.
// Note: If `exclude` is specified, the span is compared against those
// Note: If `exclude` is specified, the span/log is compared against those
// properties after the `include` properties.
// This is an optional field. If neither `include` and `exclude` are set, all spans
// This is an optional field. If neither `include` and `exclude` are set, all span/logs
// are processed. If `include` is set and `exclude` isn't set, then all
// spans matching the properties in this structure are processed.
// span/logs matching the properties in this structure are processed.
Include *MatchProperties `mapstructure:"include"`

// Exclude specifies when this processor will not be applied to the Spans
// Exclude specifies when this processor will not be applied to the span/logs
// which match the specified properties.
// Note: The `exclude` properties are checked after the `include` properties,
// if they exist, are checked.
// If `include` isn't specified, the `exclude` properties are checked against
// all spans.
// This is an optional field. If neither `include` and `exclude` are set, all spans
// all span/logs.
// This is an optional field. If neither `include` and `exclude` are set, all span/logs
// are processed. If `exclude` is set and `include` isn't set, then all
// spans that do no match the properties in this structure are processed.
// span/logs that do no match the properties in this structure are processed.
Exclude *MatchProperties `mapstructure:"exclude"`
}

// MatchProperties specifies the set of properties in a span to match against
// and if the span should be included or excluded from the processor.
// At least one of services, span names or attributes must be specified. It is
// supported to have all specified, but this requires all of the properties to
// match for the inclusion/exclusion to occur.
// MatchProperties specifies the set of properties in a span/log to match
// against and if the span/log should be included or excluded from the
// processor. At least one of services (spans only), span/log names or
// attributes must be specified. It is supported to have all specified, but
// this requires all of the properties to match for the inclusion/exclusion to
// occur.
// The following are examples of invalid configurations:
// attributes/bad1:
// # This is invalid because include is specified with neither services or
Expand All @@ -68,7 +71,10 @@ type MatchProperties struct {
// Config configures the matching patterns used when matching span properties.
filterset.Config `mapstructure:",squash"`

// Note: one of Services, SpanNames or Attributes must be specified with a
// Note: For spans, one of Services, SpanNames or Attributes must be specified with a
// non-empty value for a valid configuration.

// For logs, one of LogNames or Attributes must be specified with a
// non-empty value for a valid configuration.

// Services specify the list of of items to match service name against.
Expand All @@ -81,13 +87,41 @@ type MatchProperties struct {
// This is an optional field.
SpanNames []string `mapstructure:"span_names"`

// LogNames is a list of strings that the LogRecord's name field must match
// against.
LogNames []string `mapstructure:"log_names"`

// Attributes specifies the list of attributes to match against.
// All of these attributes must match exactly for a match to occur.
// Only match_type=strict is allowed if "attributes" are specified.
// This is an optional field.
Attributes []Attribute `mapstructure:"attributes"`
}

func (mp *MatchProperties) ValidateForSpans() error {
if len(mp.LogNames) > 0 {
return errors.New("log_names should not be specified for trace spans")
}

if len(mp.Services) == 0 && len(mp.SpanNames) == 0 && len(mp.Attributes) == 0 {
return errors.New(`at least one of "services", "span_names" or "attributes" field must be specified`)
}

return nil
}

func (mp *MatchProperties) ValidateForLogs() error {
if len(mp.SpanNames) > 0 || len(mp.Services) > 0 {
return errors.New("neither services nor span_names should be specified for log records")
}

if len(mp.LogNames) == 0 && len(mp.Attributes) == 0 {
return errors.New(`at least one of "log_names" or "attributes" field must be specified`)
}

return nil
}

// MatchTypeFieldName is the mapstructure field name for MatchProperties.Attributes field.
const AttributesFieldName = "attributes"

Expand Down
15 changes: 15 additions & 0 deletions internal/processor/filterconfig/config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// 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 filterconfig
167 changes: 167 additions & 0 deletions internal/processor/filterlog/filterlog.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
// 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 filterlog

import (
"errors"
"fmt"

"go.opentelemetry.io/collector/consumer/pdata"
"go.opentelemetry.io/collector/internal/processor/filterconfig"
"go.opentelemetry.io/collector/internal/processor/filterhelper"
"go.opentelemetry.io/collector/internal/processor/filterset"
)

// TODO: Modify Matcher to invoke both the include and exclude properties so
// calling processors will always have the same logic.
// Matcher is an interface that allows matching a log record against a
// configuration of a match.
type Matcher interface {
MatchLogRecord(lr pdata.LogRecord) bool
}

// propertiesMatcher allows matching a log record against various log record properties.
type propertiesMatcher struct {
// log names to compare to.
nameFilters filterset.FilterSet

// The attribute values are stored in the internal format.
Attributes attributesMatcher
}

type attributesMatcher []attributeMatcher

// attributeMatcher is a attribute key/value pair to match to.
type attributeMatcher struct {
Key string
// If nil only check for key existence.
AttributeValue *pdata.AttributeValue
}

// NewMatcher creates a LogRecord Matcher that matches based on the given MatchProperties.
func NewMatcher(mp *filterconfig.MatchProperties) (Matcher, error) {
if mp == nil {
return nil, nil
}

if err := mp.ValidateForLogs(); err != nil {
return nil, err
}

var err error

var am attributesMatcher
if len(mp.Attributes) > 0 {
am, err = newAttributesMatcher(mp)
if err != nil {
return nil, err
}
}

var nameFS filterset.FilterSet = nil
if len(mp.LogNames) > 0 {
nameFS, err = filterset.CreateFilterSet(mp.LogNames, &mp.Config)
if err != nil {
return nil, fmt.Errorf("error creating log record name filters: %v", err)
}
}

return &propertiesMatcher{
nameFilters: nameFS,
Attributes: am,
}, nil
}

func newAttributesMatcher(mp *filterconfig.MatchProperties) (attributesMatcher, error) {
// attribute matching is only supported with strict matching
if mp.Config.MatchType != filterset.Strict {
return nil, fmt.Errorf(
"%s=%s is not supported for %q",
filterset.MatchTypeFieldName, filterset.Regexp, filterconfig.AttributesFieldName,
)
}

// Convert attribute values from mp representation to in-memory representation.
var rawAttributes []attributeMatcher
for _, attribute := range mp.Attributes {

if attribute.Key == "" {
return nil, errors.New("error creating processor. Can't have empty key in the list of attributes")
}

entry := attributeMatcher{
Key: attribute.Key,
}
if attribute.Value != nil {
val, err := filterhelper.NewAttributeValueRaw(attribute.Value)
if err != nil {
return nil, err
}
entry.AttributeValue = &val
}

rawAttributes = append(rawAttributes, entry)
}
return rawAttributes, nil
}

// MatchLogRecord matches a log record to a set of properties.
// There are 3 sets of properties to match against.
// The log record names are matched, if specified.
// The attributes are then checked, if specified.
// At least one of log record names or attributes must be specified. It is
// supported to have more than one of these specified, and all specified must
// evaluate to true for a match to occur.
func (mp *propertiesMatcher) MatchLogRecord(lr pdata.LogRecord) bool {
if mp.nameFilters != nil && !mp.nameFilters.Matches(lr.Name()) {
return false
}

return mp.Attributes.match(lr)
}

// match attributes specification against a log record.
func (ma attributesMatcher) match(lr pdata.LogRecord) bool {
// If there are no attributes to match against, the log matches.
if len(ma) == 0 {
return true
}

attrs := lr.Attributes()
// At this point, it is expected of the log record to have attributes
// because of len(ma) != 0. This means for log records with no attributes,
// it does not match.
if attrs.Len() == 0 {
return false
}

// Check that all expected properties are set.
for _, property := range ma {
attr, exist := attrs.Get(property.Key)
if !exist {
return false
}

// This is for the case of checking that the key existed.
if property.AttributeValue == nil {
continue
}

if !attr.Equal(*property.AttributeValue) {
return false
}
}
return true
}
Loading