-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
fix(opentelemetry): Do not stomp span error status #11169
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
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 |
---|---|---|
@@ -1,28 +1,13 @@ | ||
import { SpanStatusCode } from '@opentelemetry/api'; | ||
import { SemanticAttributes } from '@opentelemetry/semantic-conventions'; | ||
import { SPAN_STATUS_ERROR, SPAN_STATUS_OK } from '@sentry/core'; | ||
import { SPAN_STATUS_ERROR, SPAN_STATUS_OK, getSpanStatusFromHttpCode } from '@sentry/core'; | ||
import type { SpanStatus } from '@sentry/types'; | ||
|
||
import type { AbstractSpan } from '../types'; | ||
import { spanHasAttributes, spanHasStatus } from './spanTypes'; | ||
|
||
// canonicalCodesHTTPMap maps some HTTP codes to Sentry's span statuses. See possible mapping in https://develop.sentry.dev/sdk/event-payloads/span/ | ||
const canonicalCodesHTTPMap: Record<string, SpanStatus['message']> = { | ||
'400': 'failed_precondition', | ||
'401': 'unauthenticated', | ||
'403': 'permission_denied', | ||
'404': 'not_found', | ||
'409': 'aborted', | ||
'429': 'resource_exhausted', | ||
'499': 'cancelled', | ||
'500': 'internal_error', | ||
'501': 'unimplemented', | ||
'503': 'unavailable', | ||
'504': 'deadline_exceeded', | ||
} as const; | ||
|
||
// canonicalCodesGrpcMap maps some GRPC codes to Sentry's span statuses. See description in grpc documentation. | ||
const canonicalCodesGrpcMap: Record<string, SpanStatus['message']> = { | ||
const canonicalGrpcErrorCodesMap: Record<string, SpanStatus['message']> = { | ||
'1': 'cancelled', | ||
'2': 'unknown_error', | ||
'3': 'invalid_argument', | ||
|
@@ -48,28 +33,40 @@ export function mapStatus(span: AbstractSpan): SpanStatus { | |
const attributes = spanHasAttributes(span) ? span.attributes : {}; | ||
const status = spanHasStatus(span) ? span.status : undefined; | ||
|
||
const httpCode = attributes[SemanticAttributes.HTTP_STATUS_CODE]; | ||
const grpcCode = attributes[SemanticAttributes.RPC_GRPC_STATUS_CODE]; | ||
|
||
const code = typeof httpCode === 'string' ? httpCode : typeof httpCode === 'number' ? httpCode.toString() : undefined; | ||
if (code) { | ||
const sentryStatus = canonicalCodesHTTPMap[code]; | ||
if (sentryStatus) { | ||
return { code: SPAN_STATUS_ERROR, message: sentryStatus }; | ||
if (status) { | ||
// Since span status OK is not set by default, we give it priority: https://opentelemetry.io/docs/concepts/signals/traces/#span-status | ||
if (status.code === SpanStatusCode.OK) { | ||
return { code: SPAN_STATUS_OK }; | ||
// If the span is already marked as erroneous we return that exact status | ||
} else if (status.code === SpanStatusCode.ERROR) { | ||
return { code: SPAN_STATUS_ERROR, message: status.message }; | ||
} | ||
} | ||
|
||
if (typeof grpcCode === 'string') { | ||
const sentryStatus = canonicalCodesGrpcMap[grpcCode]; | ||
if (sentryStatus) { | ||
return { code: SPAN_STATUS_ERROR, message: sentryStatus }; | ||
} | ||
// If the span status is UNSET, we try to infer it from HTTP or GRPC status codes. | ||
|
||
const httpCodeAttribute = attributes[SemanticAttributes.HTTP_STATUS_CODE]; | ||
const grpcCodeAttribute = attributes[SemanticAttributes.RPC_GRPC_STATUS_CODE]; | ||
|
||
const numberHttpCode = | ||
typeof httpCodeAttribute === 'number' | ||
? httpCodeAttribute | ||
: typeof httpCodeAttribute === 'string' | ||
? parseInt(httpCodeAttribute) | ||
: undefined; | ||
|
||
if (numberHttpCode) { | ||
return getSpanStatusFromHttpCode(numberHttpCode); | ||
} | ||
|
||
const statusCode = status && status.code; | ||
if (statusCode === SpanStatusCode.OK || statusCode === SpanStatusCode.UNSET) { | ||
return { code: SPAN_STATUS_OK }; | ||
if (typeof grpcCodeAttribute === 'string') { | ||
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. m: can 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. I opened an issue to track this, as I didn't touch that particular logic: #11195 It may very well be that this is a leftover from our first initial OTEL implementation draft. |
||
return { code: SPAN_STATUS_ERROR, message: canonicalGrpcErrorCodesMap[grpcCodeAttribute] || 'unknown_error' }; | ||
} | ||
|
||
return { code: SPAN_STATUS_ERROR, message: 'unknown_error' }; | ||
// We default to setting the spans status to ok. | ||
if (status && status.code === SpanStatusCode.UNSET) { | ||
return { code: SPAN_STATUS_OK }; | ||
} else { | ||
return { code: SPAN_STATUS_ERROR, message: 'unknown_error' }; | ||
} | ||
} |
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.
l (feel free to ignore): Any reason for not adding this link directly to the JSDoc header?
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 a bit cautious with links in js doc bc they might become stale. If it's just in our codebase it's not that bad but as a user it's frustrating if links are dead.
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.
Ok fair point, in this case I think it's totally fine. In other cases I think it's still worth to take that risk but admittedly, I didn't think too much about it until now 😅