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

Add setStatus operation for spanprocessor #5886

Merged
21 changes: 19 additions & 2 deletions processor/spanprocessor/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@

Supported pipeline types: traces

The span processor modifies the span name based on its attributes or extract span attributes from the span name. Please refer to
[config.go](./config.go) for the config spec.
The span processor modifies the span name based on its attributes or extract span attributes from the span name. It also allows
to change span status. Please refer to [config.go](./config.go) for the config spec.

It optionally supports the ability to [include/exclude spans](../README.md#includeexclude-spans).

The following actions are supported:

- `name`: Modify the name of attributes within a span
- `status`: Modify the status of the span

### Name a span

Expand Down Expand Up @@ -96,5 +97,21 @@ span/to_attributes:
- ^\/api\/v1\/document\/(?P<documentId>.*)\/update$
```

### Set status for span

Example:

```yaml
# Set status allows to set specific status for a given span. Possible values are
# Ok, Error and Unset as per
# https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/api.md#set-status
# additionaly description allows to set human-readable message
TomRoSystems marked this conversation as resolved.
Show resolved Hide resolved
span/set_status:
status:
code: Error
description: "some error description"
```


Refer to [config.yaml](./testdata/config.yaml) for detailed
examples on using the processor.
13 changes: 13 additions & 0 deletions processor/spanprocessor/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ type Config struct {
// Note: The field name is `Rename` to avoid collision with the Name() method
// from config.NamedEntity
Rename Name `mapstructure:"name"`

// SetStatus specifies status which should be set for this span. Please check:
// https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/api.md#set-status
mx-psi marked this conversation as resolved.
Show resolved Hide resolved
SetStatus *Status `mapstructure:"status"`
}

// Name specifies the attributes to use to re-name a span.
Expand Down Expand Up @@ -80,6 +84,15 @@ type ToAttributes struct {
BreakAfterMatch bool `mapstructure:"break_after_match"`
}

type Status struct {
// Code is one of three values "Ok" or "Err" or "Unset". Please check:
mx-psi marked this conversation as resolved.
Show resolved Hide resolved
mx-psi marked this conversation as resolved.
Show resolved Hide resolved
// https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/api.md#set-status
Code string `mapstructure:"code"`

// Optional description
TomRoSystems marked this conversation as resolved.
Show resolved Hide resolved
Description string `mapstructure:"description"`
}

var _ config.Processor = (*Config)(nil)

// Validate checks if the processor configuration is valid
Expand Down
25 changes: 25 additions & 0 deletions processor/spanprocessor/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,31 @@ func TestLoadConfig(t *testing.T) {
},
},
})

// Set name
p4 := cfg.Processors[config.NewComponentIDWithName("span", "set_status_err")]
assert.Equal(t, p4, &Config{
ProcessorSettings: config.NewProcessorSettings(config.NewComponentIDWithName("span", "set_status_err")),
SetStatus: &Status{
Code: "Err",
Description: "some additional error description",
},
})

p5 := cfg.Processors[config.NewComponentIDWithName("span", "set_status_ok")]
assert.Equal(t, p5, &Config{
ProcessorSettings: config.NewProcessorSettings(config.NewComponentIDWithName("span", "set_status_ok")),
MatchConfig: filterconfig.MatchConfig{
Include: &filterconfig.MatchProperties{
Attributes: []filterconfig.Attribute{
{Key: "http.status_code", Value: 400},
},
},
},
SetStatus: &Status{
Code: "Ok",
},
})
}

func createMatchConfig(matchType filterset.MatchType) *filterset.Config {
Expand Down
11 changes: 9 additions & 2 deletions processor/spanprocessor/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ var processorCapabilities = consumer.Capabilities{MutatesData: true}
// is not specified.
// TODO https://github.com/open-telemetry/opentelemetry-collector/issues/215
// Move this to the error package that allows for span name and field to be specified.
var errMissingRequiredField = errors.New("error creating \"span\" processor: either \"from_attributes\" or \"to_attributes\" must be specified in \"name:\"")
var errMissingRequiredField = errors.New("error creating \"span\" processor: either \"from_attributes\" or \"to_attributes\" must be specified in \"name:\" or \"setStatus\" must be specified")
var errIncorrectStatusCode = errors.New("error creating \"span\" processor: \"setStatus\" must have specified \"code\" as \"Ok\" or \"Err\" or \"Unset\"")
mx-psi marked this conversation as resolved.
Show resolved Hide resolved

// NewFactory returns a new factory for the Span processor.
func NewFactory() component.ProcessorFactory {
Expand All @@ -62,10 +63,16 @@ func createTracesProcessor(
// processor to be valid. If not set and not enforced, the processor would do no work.
oCfg := cfg.(*Config)
if len(oCfg.Rename.FromAttributes) == 0 &&
(oCfg.Rename.ToAttributes == nil || len(oCfg.Rename.ToAttributes.Rules) == 0) {
(oCfg.Rename.ToAttributes == nil || len(oCfg.Rename.ToAttributes.Rules) == 0) &&
oCfg.SetStatus == nil {
return nil, errMissingRequiredField
}

if oCfg.SetStatus != nil &&
(oCfg.SetStatus.Code != "Ok" && oCfg.SetStatus.Code != "Err" && oCfg.SetStatus.Code != "Unset") {
mx-psi marked this conversation as resolved.
Show resolved Hide resolved
return nil, errIncorrectStatusCode
}

mx-psi marked this conversation as resolved.
Show resolved Hide resolved
sp, err := newSpanProcessor(*oCfg)
if err != nil {
return nil, err
Expand Down
17 changes: 17 additions & 0 deletions processor/spanprocessor/span.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ func (sp *spanProcessor) processTraces(_ context.Context, td pdata.Traces) (pdat
}
sp.processFromAttributes(s)
sp.processToAttributes(s)
sp.processUpdateStatus(s)
}
}
}
Expand Down Expand Up @@ -220,3 +221,19 @@ func (sp *spanProcessor) processToAttributes(span pdata.Span) {
}
}
}

func (sp *spanProcessor) processUpdateStatus(span pdata.Span) {
mx-psi marked this conversation as resolved.
Show resolved Hide resolved
cfg := sp.config.SetStatus
if cfg != nil {
if cfg.Code == "Ok" {
span.Status().SetCode(pdata.StatusCodeOk)
} else if cfg.Code == "Err" {
span.Status().SetCode(pdata.StatusCodeError)
} else if cfg.Code == "Unset" {
span.Status().SetCode(pdata.StatusCodeUnset)
}
if len(cfg.Description) > 0 {
span.Status().SetMessage(cfg.Description)
}
}
}
81 changes: 81 additions & 0 deletions processor/spanprocessor/span_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -593,3 +593,84 @@ func TestSpanProcessor_skipSpan(t *testing.T) {
runIndividualTestCase(t, tc, tp)
}
}

func generateTraceDatSetStatus(code pdata.StatusCode, description string, attrs map[string]pdata.AttributeValue) pdata.Traces {
td := pdata.NewTraces()
rs := td.ResourceSpans().AppendEmpty()
span := rs.InstrumentationLibrarySpans().AppendEmpty().Spans().AppendEmpty()
span.Status().SetCode(code)
span.Status().SetMessage(description)
span.Attributes().InitFromMap(attrs).Sort()
return td
}

func TestSpanProcessor_setStatusCode(t *testing.T) {
factory := NewFactory()
cfg := factory.CreateDefaultConfig()
oCfg := cfg.(*Config)
oCfg.SetStatus = &Status{
Code: "Err",
Description: "Set custom error message",
}
tp, err := factory.CreateTracesProcessor(context.Background(), componenttest.NewNopProcessorCreateSettings(), oCfg, consumertest.NewNop())
require.Nil(t, err)
require.NotNil(t, tp)

td := generateTraceDatSetStatus(pdata.StatusCodeUnset, "foobar", nil)
td.InternalRep()

assert.NoError(t, tp.ConsumeTraces(context.Background(), td))

assert.EqualValues(t, generateTraceDatSetStatus(pdata.StatusCodeError, "Set custom error message", nil), td)
}

func TestSpanProcessor_setStatusCodeConditionally(t *testing.T) {
factory := NewFactory()
cfg := factory.CreateDefaultConfig()
oCfg := cfg.(*Config)
oCfg.SetStatus = &Status{
Code: "Err",
Description: "custom error message",
}
// This test numer two include rule for applying rule only for status code 400
oCfg.Include = &filterconfig.MatchProperties{
Attributes: []filterconfig.Attribute{
{Key: "http.status_code", Value: 400},
},
}
tp, err := factory.CreateTracesProcessor(context.Background(), componenttest.NewNopProcessorCreateSettings(), oCfg, consumertest.NewNop())
require.Nil(t, err)
require.NotNil(t, tp)

testCases := []struct {
inputAttributes map[string]pdata.AttributeValue
inputStatusCode pdata.StatusCode
outputStatusCode pdata.StatusCode
outputStatusDescription string
}{
{
// without attribiutes - should not apply rule and leave status code as it is
inputStatusCode: pdata.StatusCodeOk,
outputStatusCode: pdata.StatusCodeOk,
},
{
inputAttributes: map[string]pdata.AttributeValue{
"http.status_code": pdata.NewAttributeValueInt(400),
},
inputStatusCode: pdata.StatusCodeOk,
outputStatusCode: pdata.StatusCodeError,
outputStatusDescription: "custom error message",
},
}

for _, tc := range testCases {
t.Run("set-status-test", func(t *testing.T) {
td := generateTraceDatSetStatus(tc.inputStatusCode, "", tc.inputAttributes)
td.InternalRep()

assert.NoError(t, tp.ConsumeTraces(context.Background(), td))

assert.EqualValues(t, generateTraceDatSetStatus(tc.outputStatusCode, tc.outputStatusDescription, tc.inputAttributes), td)
})
}
}
17 changes: 17 additions & 0 deletions processor/spanprocessor/testdata/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,23 @@ processors:
rules:
- "(?P<operation_website>.*?)$"

# This example changes status of a span to error and sets description.
# Possible values for code are: "Ok" and "Err" with an additional descrption.
span/set_status_err:
status:
code: "Err"
description: "some additional error description"

# However you may want to set status conditionally. Example below sets
# status to success only when attribute http.status_code is equal to 400
span/set_status_ok:
include:
attributes:
- Key: http.status_code
Value: 400
status:
code: "Ok"

exporters:
nop:

Expand Down