-
Notifications
You must be signed in to change notification settings - Fork 0
feat: Convert OTel Spans to Sentry Spans #3
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cool stuff!
702a04f
to
ec0dac2
Compare
635c6fe
to
198ba86
Compare
3f751b4
to
33ce01b
Compare
Update: rebased onto changes from #2. Also addressed some suggestions from previous PR in this PR. cc @rhcarvalho |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
first pass
// UnixNanoToTime converts UNIX Epoch time in nanoseconds | ||
// to a Time struct | ||
func UnixNanoToTime(u pdata.TimestampUnixNano) time.Time { | ||
return time.Unix(0, int64(u)).UTC() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Trivia: this is converting a uint64
(see TimestampUnixNano) into an int64
without bounds check. It's going to be a few hundreds of years until this matters in practice.
I'd keep the implementation like that; we may document the undefined behavior for dates too far into the future.
ea38d34
to
9f1ebbf
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
second pass of eyes
var dString strings.Builder | ||
|
||
// If http.method exists, this is an http request span. | ||
if httpMethod, ok := attrs.Get(conventions.AttributeHTTPMethod); ok { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we assume that only one of these conventions is present in the attribute map?
Is it possible to have both e.g. HTTPMethod and FaaS?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So this does operate under the assumption that only one of the conventions are present. In the possible case that multiple convention attributes are available, I select conventions based on what I think is most likely (so http is prioritized over FaaS).
OTel used to expose a component
attribute which would tell you exactly what type of span it was, but it was removed in favour of this approach of setting attributes (not sure I agree with it). open-telemetry/opentelemetry-specification#447
I think it's fine to keep with this approach, we are giving our best guess to categorize spans, and it seems that OTel implementations keep this in mind.
cc @bruno-garcia?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TIL: component
was dropped.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@AbhiPrasad reasoning make sense to me
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So this does operate under the assumption that only one of the conventions are present. In the possible case that multiple convention attributes are available, I select conventions based on what I think is most likely (so http is prioritized over FaaS).
@AbhiPrasad can we please document the above ^^
switch attr.Type() { | ||
case pdata.AttributeValueSTRING: | ||
tags[key] = attr.StringVal() | ||
case pdata.AttributeValueBOOL: | ||
tags[key] = strconv.FormatBool(attr.BoolVal()) | ||
case pdata.AttributeValueDOUBLE: | ||
tags[key] = strconv.FormatFloat(attr.DoubleVal(), 'g', -1, 64) | ||
case pdata.AttributeValueINT: | ||
tags[key] = strconv.FormatInt(attr.IntVal(), 10) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shall we add a default
case here to catch other types?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are only these types, not sure what the default case should entail. https://pkg.go.dev/github.com/open-telemetry/opentelemetry-proto/gen/go/common/v1?tab=doc#AttributeKeyValue_ValueType
Maybe we should just log something out?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Logging something would be nice.
Assume things can change, new types can come in later.
return "", name | ||
} | ||
|
||
func generateTagsFromAttributes(attrs pdata.AttributeMap) Tags { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Should we always include a tag to mark that this span was imported via OTel exporter?
Or does this metadata go somewhere else?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can include the tag as part of every transaction, but I think that's something we can think about when we do a final review.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We'd like to add to envelope headers a SDK
value which is name/version
and possible the extended packages
and/or integrations
.
For now we can do that on the event.sdk
bits which should be moved into the envelope header later
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👏 👏
var dString strings.Builder | ||
|
||
// If http.method exists, this is an http request span. | ||
if httpMethod, ok := attrs.Get(conventions.AttributeHTTPMethod); ok { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@AbhiPrasad reasoning make sense to me
switch attr.Type() { | ||
case pdata.AttributeValueSTRING: | ||
tags[key] = attr.StringVal() | ||
case pdata.AttributeValueBOOL: | ||
tags[key] = strconv.FormatBool(attr.BoolVal()) | ||
case pdata.AttributeValueDOUBLE: | ||
tags[key] = strconv.FormatFloat(attr.DoubleVal(), 'g', -1, 64) | ||
case pdata.AttributeValueINT: | ||
tags[key] = strconv.FormatInt(attr.IntVal(), 10) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Logging something would be nice.
Assume things can change, new types can come in later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's merge this and improve later. A few suggestions, nothing blocking.
) | ||
|
||
// Tags describes a Sentry Tag. | ||
type Tags map[string]string |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd inline uses of this type. It has no attached methods and no special semantic meaning.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will change in future PRs when we are working with Tags from different sources (Resource
vs InstrumentationLibrary
vs the Span
itself)
type Tags map[string]string | ||
|
||
// SentrySpan describes a Span following the Sentry format. | ||
type SentrySpan struct { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When this moves into the Go SDK, it will most likely be called Span
-- "Sentry" is already part of the context here.
sentryexporter.Span
vs stutter:
sentryexporter.SentrySpan
A further suggestion is to hide what is not part of the intended public API of the exporter into an internal package:
exporter/sentryexporter/internal/sentryexporter/exporter.go
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will explore this after #5 is merged in
testSpan := pdata.NewSpan() | ||
testSpan.InitEmpty() | ||
|
||
parentSpanID := []byte{0, 0, 0, 0, 0, 0, 0, 0} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Normally this would be more bytes, no?
make([]byte, 16)
make([]byte, 32)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When []byte{0, 0, 0, 0, 0, 0, 0, 0}
gets encoded it becomes a 16 length string. So it's size of 8 bytes, but encoded becomes a string length 16.
traceID := []byte{1, 2, 3, 4, 5, 6, 7, 8, 8, 7, 6, 5, 4, 3, 2, 1} | ||
spanID := []byte{1, 2, 3, 4, 5, 6, 7, 8} | ||
parentSpanID := []byte{8, 7, 6, 5, 4, 3, 2, 1} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is fine, but letting you know that there's an alternative:
s := []byte("123434")
It is often more ergonomic to initialize this kind of byte slice from literal strings, e.g.
Note: surely byte(1) != '1'
, but the actual byte values don't really matter in tests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am just using the conventions from the otel-collector tests - https://github.com/open-telemetry/opentelemetry-collector/blob/master/consumer/pdata/generated_trace_test.go#L417
testSpan := pdata.NewSpan() | ||
testSpan.InitEmpty() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see that some collectors from https://github.com/open-telemetry/opentelemetry-collector construct Spans from
tracepb "github.com/census-instrumentation/opencensus-proto/gen-go/trace/v1"
Those seem to be much easier to work with to create adhoc values.
Is the pdata
part of the new format of exporters? I remember @AbhiPrasad said something about a change in how exporters are written.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes pdata
is part of the new exporter format. IMO it's much harder to work with API, see: https://pkg.go.dev/github.com/open-telemetry/opentelemetry-collector/consumer?tab=doc#TraceConsumer
* Add DataDog exporter back from old fork commit 99129fb96e29e9c1a92da00b7e3f8efcae8a31e8 Author: Pablo Baeyens <[email protected]> Date: Thu Sep 3 18:10:28 2020 +0200 Handle namespace at initialization time commit babca25927926a60c0c416294af3aadf784d41b9 Author: Pablo Baeyens <[email protected]> Date: Thu Sep 3 17:23:53 2020 +0200 Initialize on a separate function This way the variables can be checked without worrying about the env commit 24d0cb4cc566fa5313a8650c904a27bea68bf555 Author: Pablo Baeyens <[email protected]> Date: Thu Sep 3 14:30:35 2020 +0200 Check environment variables for unified service tagging commit 6695f8297ab8b1fcae71b05acb027c4a0992e3a0 Author: Pablo Baeyens <[email protected]> Date: Wed Sep 2 14:57:37 2020 +0200 Add support for sending metrics through the API - Use datadog.Metric type for simplicity - Get host if unset commit c366603 Author: Pablo Baeyens <[email protected]> Date: Wed Sep 2 09:56:24 2020 +0200 Disable Queue and Retry settings (#72) These are handled by the statsd package. OpenTelemetry docs are confusing and the default configuration (disabled) is different from the one returned by "GetDefault..." functions commit a660b56 Author: Pablo Baeyens <[email protected]> Date: Tue Sep 1 15:26:14 2020 +0200 Add support for summary and distribution metric types (#65) * Add support for summary metric type * Add support for distribution metrics * Refactor metrics construction - Drop name in Metrics (now they act as Metric values) - Refactor constructor so that errors happen at compile-time * Report Summary total sum and count values Snapshot values are not filled in by OpenTelemetry * Report p00 and p100 as `.min` and `.max` This is more similar to what we do for our own non-additive type * Keep hostname if it has not been overridden commit c95adc4 Author: Pablo Baeyens <[email protected]> Date: Thu Aug 27 13:00:02 2020 +0200 Update dependencies and `make gofmt` The collector was updated to 0.9.0 upstream commit 20afb0e Author: Pablo Baeyens <[email protected]> Date: Wed Aug 26 18:25:49 2020 +0200 Refactor configuration (#45) * Refactor configuration * Implement telemetry and tags configuration handling * Update example configuration and README file Co-authored-by: Kylian Serrania <[email protected]> commit fdc98b5 Author: Pablo Baeyens <[email protected]> Date: Fri Aug 21 11:09:08 2020 +0200 Initial DogStatsD implementation (#15) Initial metrics exporter through DogStatsD with support for all metric types but summary and distribution commit e848a60 Author: Pablo Baeyens <[email protected]> Date: Fri Aug 21 10:42:45 2020 +0200 Bump collector version commit 58be9a4 Author: Pablo Baeyens <[email protected]> Date: Thu Aug 6 10:04:32 2020 +0200 Address linter commit 695430c Author: Pablo Baeyens <[email protected]> Date: Tue Aug 4 13:28:01 2020 +0200 Fix field name error MetricsEndpoint was renamed to MetricsURL commit 168b319 Author: Pablo Baeyens <[email protected]> Date: Mon Aug 3 11:05:01 2020 +0200 Create initial outline for Datadog exporter (#1) * Add support for basic configuration options * Documents configuration options * go mod tidy * Address feedback from upstream PR we did not merge (#1) * Backport changes from upstream PR Remove `err` from MapMetrics * Remove usage of pdatautil * Fix tests * Use TCPAddr * Review which functions should be private * Remove DogStatsD mode (#2) * Remove DogStatsD mode * go mod tidy * Remove mentions to DogStatSD * Improve test coverage (#3) * Improve test coverage Added unit tests for - API key censoring - Hostname - Metrics exporter Renamed test and implementation files for consistency * Add one additional test * Remove client validation (#6) The zorkian API does not validate the API key unless you also have an application key, even though the endpoint works without it. I am removing this validation until this gets fixed on the zorkian library * Keep only configuration and factory methods Following the contribution guidelines we need to make a first PR with this * Use latest version of collector * Remove `report_percentiles` option It is not being used as of now until the OTLP metrics format stabilizes and we have a Summary type metric again * Correct configuration The API key is now a required setting * Remove test not relevant for this PR * Remove unnecessary imports after removing test * Address review comment * Apply suggestions from code review Co-authored-by: Tigran Najaryan <[email protected]> * Separate documentation into two examples One example with the minimal configuration, for sending to `datadoghq.com` and a second one for sending to `datadoghq.eu` Co-authored-by: Tigran Najaryan <[email protected]>
This PR adds functionality to convert an OTel span to a Sentry span.
A
SentrySpan
can be defined by the following interfaceThe
trace_id
,span_id
andparent_span_id
are grabbed from the OTel span. In addition, theparent_span_id
is used to figure out if the span is a root span (which is important when we get to creating transactions).The OTel span start time and end time are converted to
Time
type from thetime
package and stored in theSentrySpan
OTel Span Attributes == Sentry tags, so we use a helper func to generate them
generateTagsFromAttributes()
The span op and description are generated based on OTel's semantic conventions using the
generateSpanDescriptors
helper func - https://github.com/open-telemetry/opentelemetry-specification/tree/master/specification/trace/semantic_conventionsWe use the helper func
generateStatusFromSpanStatus
to parse the status code of the OTel span to attach a span status to the sentry span.