diff --git a/processor/spanprocessor/README.md b/processor/spanprocessor/README.md index 594faae85ee2..c651fd722384 100644 --- a/processor/spanprocessor/README.md +++ b/processor/spanprocessor/README.md @@ -123,6 +123,20 @@ span/set_status: description: "some error description" ``` +### Set kind for span + +The following setting is required: + +- `kind`: Represents span kind. One of the following values "Internal", "Client", "Server", "Producer", "Consumer", "Unspecified". + +Example: + +```yaml +# https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/api.md#spankind +span/set_kind: + spankind: + kind: Client +``` Refer to [config.yaml](./testdata/config.yaml) for detailed examples on using the processor. diff --git a/processor/spanprocessor/config.go b/processor/spanprocessor/config.go index 06948d5caa3d..17682d0c9392 100644 --- a/processor/spanprocessor/config.go +++ b/processor/spanprocessor/config.go @@ -38,6 +38,8 @@ type Config struct { // SetStatus specifies status which should be set for this span. SetStatus *Status `mapstructure:"status"` + + SetKind *Kind `mapstructure:"spankind"` } // Name specifies the attributes to use to re-name a span. @@ -92,6 +94,11 @@ type Status struct { Description string `mapstructure:"description"` } +type Kind struct { + // https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/api.md#spankind + Kind string `mapstructure:"kind"` +} + var _ config.Processor = (*Config)(nil) // Validate checks if the processor configuration is valid diff --git a/processor/spanprocessor/factory.go b/processor/spanprocessor/factory.go index ce22b5fb61f5..41e0877edace 100644 --- a/processor/spanprocessor/factory.go +++ b/processor/spanprocessor/factory.go @@ -36,6 +36,13 @@ const ( statusCodeUnset = "Unset" statusCodeError = "Error" statusCodeOk = "Ok" + + spanKindInternal = "Internal" + spanKindClient = "Client" + spanKindServer = "Server" + spanKindProducer = "Producer" + spanKindConsumer = "Consumer" + spanKindUnspecified = "Unspecified" ) var processorCapabilities = consumer.Capabilities{MutatesData: true} @@ -46,8 +53,9 @@ var processorCapabilities = consumer.Capabilities{MutatesData: true} // // 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:\" or \"setStatus\" must be specified") + errMissingRequiredField = errors.New("error creating \"span\" processor: either \"from_attributes\" or \"to_attributes\" must be specified in \"name:\" or \"setStatus\" must be specified or \"spankind\" must be specified") errIncorrectStatusCode = errors.New("error creating \"span\" processor: \"status\" must have specified \"code\" as \"Ok\" or \"Error\" or \"Unset\"") + errIncorrectKind = errors.New("error creating \"span\" processor: \"kind\" must have specified \"Internal\", \"Client\", \"Server\", \"Producer\", \"Consumer\" or \"Unspecified\"") errIncorrectStatusDescription = errors.New("error creating \"span\" processor: \"description\" can be specified only for \"code\" \"Error\"") ) @@ -77,7 +85,7 @@ func createTracesProcessor( oCfg := cfg.(*Config) if len(oCfg.Rename.FromAttributes) == 0 && (oCfg.Rename.ToAttributes == nil || len(oCfg.Rename.ToAttributes.Rules) == 0) && - oCfg.SetStatus == nil { + oCfg.SetStatus == nil && oCfg.SetKind == nil { return nil, errMissingRequiredField } @@ -90,6 +98,11 @@ func createTracesProcessor( } } + if oCfg.SetKind != nil { + if oCfg.SetKind.Kind != spanKindInternal && oCfg.SetKind.Kind != spanKindClient && oCfg.SetKind.Kind != spanKindServer && oCfg.SetKind.Kind != spanKindProducer && oCfg.SetKind.Kind != spanKindConsumer && oCfg.SetKind.Kind != spanKindUnspecified { + return nil, errIncorrectKind + } + } sp, err := newSpanProcessor(*oCfg) if err != nil { return nil, err diff --git a/processor/spanprocessor/span.go b/processor/spanprocessor/span.go index 16da5f7271e4..5c0b5e063796 100644 --- a/processor/spanprocessor/span.go +++ b/processor/spanprocessor/span.go @@ -99,6 +99,7 @@ func (sp *spanProcessor) processTraces(_ context.Context, td ptrace.Traces) (ptr sp.processFromAttributes(s) sp.processToAttributes(s) sp.processUpdateStatus(s) + sp.processUpdateKind(s) } } } @@ -239,3 +240,23 @@ func (sp *spanProcessor) processUpdateStatus(span ptrace.Span) { } } } + +func (sp *spanProcessor) processUpdateKind(span ptrace.Span) { + cfg := sp.config.SetKind + if cfg != nil { + switch cfg.Kind { + case spanKindInternal: + span.SetKind(ptrace.SpanKindInternal) + case spanKindClient: + span.SetKind(ptrace.SpanKindClient) + case spanKindServer: + span.SetKind(ptrace.SpanKindServer) + case spanKindConsumer: + span.SetKind(ptrace.SpanKindConsumer) + case spanKindProducer: + span.SetKind(ptrace.SpanKindProducer) + default: + span.SetKind(ptrace.SpanKindUnspecified) + } + } +} diff --git a/processor/spanprocessor/span_test.go b/processor/spanprocessor/span_test.go index d3e1413e37f4..a413a55ec2aa 100644 --- a/processor/spanprocessor/span_test.go +++ b/processor/spanprocessor/span_test.go @@ -605,6 +605,15 @@ func generateTraceDataSetStatus(code ptrace.StatusCode, description string, attr return td } +func generateTraceDataSetKind(kind ptrace.SpanKind, attrs map[string]interface{}) ptrace.Traces { + td := ptrace.NewTraces() + rs := td.ResourceSpans().AppendEmpty() + span := rs.ScopeSpans().AppendEmpty().Spans().AppendEmpty() + span.SetKind(kind) + pcommon.NewMapFromRaw(attrs).Sort().CopyTo(span.Attributes()) + return td +} + func TestSpanProcessor_setStatusCode(t *testing.T) { factory := NewFactory() cfg := factory.CreateDefaultConfig() @@ -624,6 +633,24 @@ func TestSpanProcessor_setStatusCode(t *testing.T) { assert.EqualValues(t, generateTraceDataSetStatus(ptrace.StatusCodeError, "Set custom error message", nil), td) } +func TestSpanProcessor_setKind(t *testing.T) { + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + oCfg := cfg.(*Config) + oCfg.SetKind = &Kind{ + Kind: spanKindServer, + } + tp, err := factory.CreateTracesProcessor(context.Background(), componenttest.NewNopProcessorCreateSettings(), oCfg, consumertest.NewNop()) + require.Nil(t, err) + require.NotNil(t, tp) + + td := generateTraceDataSetKind(ptrace.SpanKindClient, nil) + + assert.NoError(t, tp.ConsumeTraces(context.Background(), td)) + + assert.EqualValues(t, generateTraceDataSetKind(ptrace.SpanKindServer, nil), td) +} + func TestSpanProcessor_setStatusCodeConditionally(t *testing.T) { factory := NewFactory() cfg := factory.CreateDefaultConfig() diff --git a/unreleased/span_kind_processor.yaml b/unreleased/span_kind_processor.yaml new file mode 100755 index 000000000000..2d7c4fb4863a --- /dev/null +++ b/unreleased/span_kind_processor.yaml @@ -0,0 +1,16 @@ +# 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: spanprocessor + +# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`). +note: Allow changing of SpanKind + +# One or more tracking issues related to the change +issues: [6883] + +# (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: