-
Notifications
You must be signed in to change notification settings - Fork 43
ElasticAPM receiver: handle transactions and spans #294
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
Changes from all commits
ae86647
b01eeeb
4febee7
951be39
ee037cc
c00ab6a
c72f39d
2aa77ce
65ec5bb
357dccd
37a18bc
b772539
e555d12
f193261
d3c85aa
cb7b2be
b0da9e1
893afeb
abad4fe
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| module github.com/gregkalapos/opentelemetry-collector-components | ||
|
|
||
| go 1.22.3 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| // Licensed to Elasticsearch B.V. under one or more contributor | ||
| // license agreements. See the NOTICE file distributed with | ||
| // this work for additional information regarding copyright | ||
| // ownership. Elasticsearch B.V. licenses this file to you 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 attr // import "github.com/elastic/opentelemetry-collector-components/receiver/elasticapmreceiver/internal" | ||
|
|
||
| // These constants hold attribute names that are defined by the Elastic APM data model and do not match | ||
| // any SemConv attribute. These fields are not used by the UI, and store information related to a specific span type | ||
| const ( | ||
| SpanDBLink = "span.db.link" | ||
| SpanDBRowsAffected = "span.db.rows_affected" | ||
| SpanDBUserName = "span.db.user_name" | ||
| HTTPRequestBody = "http.request.body" | ||
| HTTPRequestID = "http.request.id" | ||
| HTTPRequestReferrer = "http.request.referrer" | ||
| HTTPResponseDecodedBodySize = "http.response.decoded_body_size" | ||
| HTTPResponseEncodedBodySize = "http.response.encoded_body_size" | ||
| HTTPResponseTransferSize = "http.response.transfer_size" | ||
| SpanMessageBody = "span.message.body" | ||
| ) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,86 @@ | ||
| // Licensed to Elasticsearch B.V. under one or more contributor | ||
| // license agreements. See the NOTICE file distributed with | ||
| // this work for additional information regarding copyright | ||
| // ownership. Elasticsearch B.V. licenses this file to you 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. | ||
|
|
||
| // This file contains mappings where we move intakeV2 fields into Attributes and Resource attributes on OTel events | ||
| // These fields are not covered by SemConv and are specific to Elastic | ||
|
|
||
| // TODO: attribute names should be pulled in from https://github.com/elastic/opentelemetry-lib/blob/main/enrichments/trace/internal/elastic/attributes.go | ||
| // `opentelemetry-lib` already has a PR to do so, after the next release of that repo, we can update this file to use those constants | ||
|
|
||
| package mappers // import "github.com/elastic/opentelemetry-collector-components/receiver/elasticapmreceiver/internal/mappers" | ||
|
|
||
| import ( | ||
| "strings" | ||
|
|
||
| "github.com/elastic/apm-data/model/modelpb" | ||
| "go.opentelemetry.io/collector/pdata/pcommon" | ||
| ) | ||
|
|
||
| // Sets fields that are NOT part of OTel for transactions. These fields are derived by the Enrichment lib in case of OTLP input | ||
| func SetDerivedFieldsForTransaction(event *modelpb.APMEvent, attributes pcommon.Map) { | ||
|
|
||
| attributes.PutStr("processor.event", "transaction") | ||
| attributes.PutStr("transaction.id", event.Transaction.Id) | ||
| attributes.PutStr("transaction.name", event.Transaction.Name) | ||
| attributes.PutBool("transaction.sampled", event.Transaction.Sampled) | ||
| // from whatever reason Transaction.Root is always false. That seems to be a derived field already - I don't see that fields directly on IntakeV2 - there is only ParentId | ||
| attributes.PutBool("transaction.root", event.ParentId == "") | ||
| attributes.PutStr("transaction.type", event.Transaction.Type) | ||
| attributes.PutStr("transaction.result", event.Transaction.Result) | ||
| attributes.PutInt("transaction.duration.us", int64(event.Event.Duration)) | ||
| } | ||
|
|
||
| // Sets fields that are NOT part of OTel for spans. These fields are derived by the Enrichment lib in case of OTLP input | ||
| func SetDerivedFieldsForSpan(event *modelpb.APMEvent, attributes pcommon.Map) { | ||
|
|
||
| attributes.PutStr("processor.event", "span") | ||
| attributes.PutInt("span.duration.us", int64(event.Event.Duration)) | ||
| attributes.PutStr("span.id", event.Span.Id) | ||
| attributes.PutStr("span.name", event.Span.Name) | ||
| attributes.PutStr("span.type", event.Span.Type) | ||
| attributes.PutStr("span.subtype", event.Span.Subtype) | ||
| attributes.PutStr("span.action", event.Span.Action) | ||
|
|
||
| if event.Span.Sync != nil { | ||
| attributes.PutBool("span.sync", *event.Span.Sync) | ||
| } | ||
|
|
||
| if event.Span.DestinationService != nil { | ||
| attributes.PutStr("service.target.name", event.Span.DestinationService.Name) | ||
| attributes.PutStr("service.target.type", event.Span.DestinationService.Type) | ||
| attributes.PutStr("span.destination.service.resource", event.Span.DestinationService.Resource) | ||
| } | ||
| } | ||
|
|
||
| // Sets resource fields that are NOT part of OTel. These fields are derived by the Enrichment lib in case of OTLP input | ||
| func SetDerivedResourceAttributes(event *modelpb.APMEvent, attributes pcommon.Map) { | ||
| attributes.PutStr("agent.name", event.Agent.Name) | ||
| attributes.PutStr("agent.version", event.Agent.Version) | ||
| } | ||
|
|
||
| // Shared across spans and transactions | ||
| func SetDerivedFieldsCommon(event *modelpb.APMEvent, attributes pcommon.Map) { | ||
| attributes.PutInt("timestamp.us", int64(event.Timestamp)) | ||
|
|
||
| if strings.EqualFold(event.Event.Outcome, "success") { | ||
| attributes.PutStr("event.outcome", "success") | ||
| } else if strings.EqualFold(event.Event.Outcome, "failure") { | ||
| attributes.PutStr("event.outcome", "failure") | ||
| } else { | ||
| attributes.PutStr("event.outcome", "unknown") | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| // Licensed to Elasticsearch B.V. under one or more contributor | ||
| // license agreements. See the NOTICE file distributed with | ||
| // this work for additional information regarding copyright | ||
| // ownership. Elasticsearch B.V. licenses this file to you 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. | ||
|
|
||
| // This file contains mappings where we move intakeV2 fields into Attributes and Resource attributes on OTel events | ||
| // These fields are not covered by SemConv and are specific to Elastic | ||
|
|
||
| package mappers // import "github.com/elastic/opentelemetry-collector-components/receiver/elasticapmreceiver/internal/mappers" | ||
|
|
||
| import ( | ||
| "github.com/elastic/apm-data/model/modelpb" | ||
| attr "github.com/elastic/opentelemetry-collector-components/receiver/elasticapmreceiver/internal" | ||
| "go.opentelemetry.io/collector/pdata/pcommon" | ||
| ) | ||
|
|
||
| // Sets fields on spans that are not defined by OTel. | ||
| // Unlike fields from IntakeV2ToDerivedFields.go, these fields are not used by the UI | ||
| // and store information about a specific span type | ||
| func SetElasticSpecificFieldsForSpan(event *modelpb.APMEvent, attributesMap pcommon.Map) { | ||
| if event.Span.Db != nil { | ||
| attributesMap.PutStr(attr.SpanDBLink, event.Span.Db.Link) | ||
| // SemConv db.response.returned_rows is similar, but not the same | ||
| attributesMap.PutInt(attr.SpanDBRowsAffected, int64(*event.Span.Db.RowsAffected)) | ||
| attributesMap.PutStr(attr.SpanDBUserName, event.Span.Db.UserName) | ||
| } | ||
|
|
||
| if event.Http.Request != nil { | ||
| attributesMap.PutStr(attr.HTTPRequestBody, event.Http.Request.Body.GetStringValue()) | ||
| attributesMap.PutStr(attr.HTTPRequestID, event.Http.Request.Id) | ||
| attributesMap.PutStr(attr.HTTPRequestReferrer, event.Http.Request.Referrer) | ||
| } | ||
|
|
||
| if event.Http.Response != nil { | ||
| // SemConv http.response.body.size may match one of these. | ||
| attributesMap.PutInt(attr.HTTPResponseDecodedBodySize, int64(*event.Http.Response.DecodedBodySize)) | ||
| attributesMap.PutInt(attr.HTTPResponseEncodedBodySize, int64(*event.Http.Response.EncodedBodySize)) | ||
| attributesMap.PutInt(attr.HTTPResponseTransferSize, int64(*event.Http.Response.TransferSize)) | ||
| } | ||
|
|
||
| if event.Span.Message != nil { | ||
| attributesMap.PutStr(attr.SpanMessageBody, event.Span.Message.Body) | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,97 @@ | ||
| // Licensed to Elasticsearch B.V. under one or more contributor | ||
| // license agreements. See the NOTICE file distributed with | ||
| // this work for additional information regarding copyright | ||
| // ownership. Elasticsearch B.V. licenses this file to you 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. | ||
|
|
||
| // This file contains all the mapping from IntakeV2 fields to OTel Semantic Convention | ||
|
|
||
| package mappers // import "github.com/elastic/opentelemetry-collector-components/receiver/elasticapmreceiver/internal/mappers" | ||
|
|
||
| import ( | ||
| "github.com/elastic/apm-data/model/modelpb" | ||
| "go.opentelemetry.io/collector/pdata/pcommon" | ||
| semconv "go.opentelemetry.io/collector/semconv/v1.27.0" | ||
| ) | ||
|
|
||
| // Translates resource attributes from the Elastic APM model to SemConv resource attributes | ||
| func TranslateToOtelResourceAttributes(event *modelpb.APMEvent, attributes pcommon.Map) { | ||
| attributes.PutStr(semconv.AttributeServiceName, event.Service.Name) | ||
| attributes.PutStr(semconv.AttributeServiceVersion, event.Service.Version) | ||
| if event.Service.Language != nil { | ||
| attributes.PutStr(semconv.AttributeTelemetrySDKLanguage, event.Service.Language.Name) | ||
| } | ||
| attributes.PutStr(semconv.AttributeTelemetrySDKName, "ElasticAPM") | ||
| if event.Service.Environment != "" { | ||
| attributes.PutStr(semconv.AttributeDeploymentEnvironmentName, event.Service.Environment) | ||
| } | ||
| } | ||
|
|
||
| // Translates transaction attributes from the Elastic APM model to SemConv attributes | ||
| func TranslateIntakeV2TransactionToOTelAttributes(event *modelpb.APMEvent, attributes pcommon.Map) { | ||
| setHttpAttributes(event, attributes) | ||
|
|
||
| if event.Span.Message != nil { | ||
| attributes.PutStr(semconv.AttributeMessagingDestinationName, event.Transaction.Message.QueueName) | ||
| attributes.PutStr(semconv.AttributeMessagingRabbitmqDestinationRoutingKey, event.Transaction.Message.RoutingKey) | ||
|
|
||
| // This may need to be unified, see AttributeMessagingSystem for spans | ||
| attributes.PutStr(semconv.AttributeMessagingSystem, event.Service.Framework.Name) | ||
| } | ||
| } | ||
|
|
||
| // Translates span attributes from the Elastic APM model to SemConv attributes | ||
| func TranslateIntakeV2SpanToOTelAttributes(event *modelpb.APMEvent, attributes pcommon.Map) { | ||
| if event.Http != nil { | ||
|
|
||
| setHttpAttributes(event, attributes) | ||
|
|
||
| if event.Url != nil && event.Url.Full != "" { | ||
| attributes.PutStr(semconv.AttributeURLFull, event.Url.Full) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure if any of this inner fields can be nil, if so we might need to add a guard function for all the setters.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is actually a good point - we need to guard these. For now I just added another check. This was already setting empty strings. In the spec the required fields are listed: https://github.com/elastic/apm-server/tree/main/docs/spec/v2 If any of those are missing, apm server rejects those documents. But This whole question makes me think... we should probably discuss how to handle invalid documents - like when a required field is missing. In any case - this specific one is handled. Updated: opened https://github.com/elastic/opentelemetry-dev/issues/553?issue=elastic%7Copentelemetry-dev%7C752 |
||
| } | ||
| } | ||
| if event.Span.Db != nil { | ||
| attributes.PutStr(semconv.AttributeDBSystem, event.Span.Db.Type) | ||
| attributes.PutStr(semconv.AttributeDBNamespace, event.Span.Db.Instance) | ||
| attributes.PutStr(semconv.AttributeDBQueryText, event.Span.Db.Statement) | ||
| } | ||
| if event.Span.Message != nil { | ||
| // Elastic APM span.subtype does not 100% overlap with https://opentelemetry.io/docs/specs/semconv/attributes-registry/messaging/#messaging-system | ||
| // E.g. azureservicebus in Elastic APM vs servicebus in SemConv | ||
| attributes.PutStr(semconv.AttributeMessagingSystem, event.Span.Subtype) | ||
| // No 100% overlap either | ||
| attributes.PutStr(semconv.AttributeMessagingOperationName, event.Span.Action) | ||
|
|
||
| if event.Span.Message.QueueName != "" { | ||
| attributes.PutStr(semconv.AttributeMessagingDestinationName, event.Span.Message.QueueName) | ||
| } | ||
| if event.Span.Message.RoutingKey != "" { | ||
| attributes.PutStr(semconv.AttributeMessagingRabbitmqDestinationRoutingKey, event.Span.Message.RoutingKey) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| func setHttpAttributes(event *modelpb.APMEvent, attributes pcommon.Map) { | ||
| if event.Http != nil { | ||
| if event.Http.Request != nil { | ||
| attributes.PutStr(semconv.AttributeHTTPRequestMethod, event.Http.Request.Method) | ||
| if event.Url != nil && event.Url.Full != "" { | ||
| attributes.PutStr(semconv.AttributeURLFull, event.Url.Full) | ||
| } | ||
| } | ||
| if event.Http.Response != nil { | ||
| attributes.PutInt(semconv.AttributeHTTPResponseStatusCode, int64(event.Http.Response.StatusCode)) | ||
| } | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.