-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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
[Service Bus] Tracing for send API #11651
Changes from 19 commits
a8c1d5d
a73fd31
e46e805
4af1738
c7d3a14
b5f77a8
cd71e42
d631ee5
8375ed8
8038315
6fa23f7
a8b34fc
d04a38e
9742d97
f262e45
ba4fef7
e46deb4
fea2177
5c5795f
6b2409a
7de9b6f
d1af28e
060842e
3197fbc
84e48aa
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 |
---|---|---|
|
@@ -262,6 +262,12 @@ export abstract class LinkEntity<LinkT extends Receiver | AwaitableSender | Requ | |
this.checkIfConnectionReady(); | ||
|
||
logger.verbose(`${this._logPrefix} Creating with options %O`, options); | ||
if (this.link) { | ||
// For the cases such as connection is disconnected, onAmqpClose and onSessionClose events, | ||
// we don't clear the link for sender. To compensate for the above, | ||
// we are calling close on the link to cleanup before creating the new one. | ||
await this.link.close(); | ||
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. Why 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.
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. Anyways, reverted this in favour of the proposal for cleanup with detached where onDetached would be called before the |
||
} | ||
this._link = await this.createRheaLink(options); | ||
checkAborted(); | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
// Copyright (c) Microsoft Corporation. | ||
// Licensed under the MIT license. | ||
|
||
import { extractSpanContextFromTraceParentHeader, getTraceParentHeader } from "@azure/core-tracing"; | ||
import { Span, SpanContext } from "@opentelemetry/api"; | ||
import { ServiceBusMessage } from "../serviceBusMessage"; | ||
|
||
/** | ||
* @ignore | ||
*/ | ||
export const TRACEPARENT_PROPERTY = "Diagnostic-Id"; | ||
|
||
/** | ||
* Populates the `ServiceBusMessage` with `SpanContext` info to support trace propagation. | ||
* Creates and returns a copy of the passed in `ServiceBusMessage` unless the `ServiceBusMessage` | ||
* has already been instrumented. | ||
* @param message The `ServiceBusMessage` to instrument. | ||
* @param span The `Span` containing the context to propagate tracing information. | ||
* @ignore | ||
* @internal | ||
*/ | ||
export function instrumentServiceBusMessage( | ||
message: ServiceBusMessage, | ||
span: Span | ||
): ServiceBusMessage { | ||
if (message.properties && message.properties[TRACEPARENT_PROPERTY]) { | ||
return message; | ||
} | ||
|
||
// create a copy so the original isn't modified | ||
message = { ...message, properties: { ...message.properties } }; | ||
|
||
const traceParent = getTraceParentHeader(span.context()); | ||
if (traceParent) { | ||
message.properties![TRACEPARENT_PROPERTY] = traceParent; | ||
} | ||
|
||
return message; | ||
} | ||
|
||
/** | ||
* Extracts the `SpanContext` from an `ServiceBusMessage` if the context exists. | ||
* @param message An individual `ServiceBusMessage` object. | ||
* @internal | ||
* @ignore | ||
*/ | ||
export function extractSpanContextFromServiceBusMessage( | ||
message: ServiceBusMessage | ||
): SpanContext | undefined { | ||
if (!message.properties || !message.properties[TRACEPARENT_PROPERTY]) { | ||
return; | ||
} | ||
|
||
const diagnosticId = message.properties[TRACEPARENT_PROPERTY] as string; | ||
return extractSpanContextFromTraceParentHeader(diagnosticId); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
// Copyright (c) Microsoft Corporation. | ||
// Licensed under the MIT license. | ||
|
||
import { ConnectionConfig } from "@azure/core-amqp"; | ||
import { getTracer } from "@azure/core-tracing"; | ||
import { Span, SpanContext, SpanKind } from "@opentelemetry/api"; | ||
|
||
/** | ||
* @internal | ||
* @ignore | ||
*/ | ||
export function createMessageSpan( | ||
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. Feedback not for this PR, but a note for both Event Hubs & Service Bus for which we can log a generic issue on improvements and make changes for both packages at once in the future:
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. Yes to the first two!
parentSpan is not a required parameter but feels primary, I believe that was the reason Chris made the config optional in event-hubs. I can make it the first param in both service-bus and event-hubs and make it required in a later PR. 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. Logged #11687 |
||
parentSpan?: Span | SpanContext | null, | ||
config?: Pick<ConnectionConfig, "entityPath" | "host"> | ||
): Span { | ||
const tracer = getTracer(); | ||
const span = tracer.startSpan("Azure.ServiceBus.message", { | ||
kind: SpanKind.PRODUCER, | ||
parent: parentSpan | ||
}); | ||
span.setAttribute("az.namespace", "Microsoft.ServiceBus"); | ||
if (config) { | ||
span.setAttribute("message_bus.destination", config.entityPath); | ||
span.setAttribute("peer.address", config.host); | ||
} | ||
return span; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,8 +3,9 @@ | |
|
||
// TODO: this code is a straight-copy from EventHubs. Need to merge. | ||
|
||
import { Span, SpanContext } from "@opentelemetry/api"; | ||
import { Link, Span, SpanContext, SpanKind } from "@opentelemetry/api"; | ||
import { OperationOptions } from "@azure/core-http"; | ||
import { getTracer, OperationTracingOptions } from "@azure/core-tracing"; | ||
|
||
/** | ||
* NOTE: This type is intended to mirror the relevant fields and structure from @azure/core-http OperationOptions | ||
|
@@ -18,7 +19,41 @@ export type OperationOptionsBase = Pick<OperationOptions, "abortSignal" | "traci | |
* @ignore | ||
*/ | ||
export function getParentSpan( | ||
options: Pick<OperationOptionsBase, "tracingOptions"> | ||
options?: OperationTracingOptions | ||
): Span | SpanContext | null | undefined { | ||
return options.tracingOptions?.spanOptions?.parent; | ||
return options?.spanOptions?.parent; | ||
} | ||
|
||
export function createSendSpan( | ||
parentSpan?: Span | SpanContext | null, | ||
spanContextsToLink: SpanContext[] = [], | ||
entityPath?: string, | ||
host?: string | ||
): Span { | ||
const links: Link[] = spanContextsToLink.map((context) => { | ||
return { | ||
context | ||
}; | ||
}); | ||
const tracer = getTracer(); | ||
const span = tracer.startSpan("Azure.ServiceBus.send", { | ||
kind: SpanKind.CLIENT, | ||
parent: parentSpan, | ||
links | ||
}); | ||
|
||
span.setAttribute("az.namespace", "Microsoft.ServiceBus"); | ||
span.setAttribute("message_bus.destination", entityPath); | ||
span.setAttribute("peer.address", host); | ||
|
||
return span; | ||
} | ||
/** | ||
* The set of options to manually propagate `Span` context for distributed tracing. | ||
*/ | ||
export interface TryAddOptions { | ||
/** | ||
* The `Span` or `SpanContext` to use as the `parent` of any spans created while calling operations that make a request to the service. | ||
*/ | ||
parentSpan?: Span | SpanContext | null; | ||
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 noticed in Event Hubs that 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. There are other places where we allow the span to be "null" which is a valid value and it signifies the rootSpan. Moreover, I needed to allow this "null" since I'm calling getParentSpan() for the tryAdd over array of messages which may return a null value. In event-hubs, this tracing for "array of messages" case is duplicated. 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'll log an issue for fixing it in event-hubs. 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. Logged #11687 |
||
} |
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.
Take the case of a disconnected event firing and user making no more send requests. In that case, the loop to keep renewing the token on the sender link does not get cleared and we will have a rogue loop on our hands
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 realize that we can argue that in the normal case of user stopping to make any send requests and no disconnected event, our loop will still keep trying to renew the lock. In that case, at some point the service will close the link because it has been idle for too long. That results in an "amqp close" event in which case we should clean up the timers and assets from our end. In this PR, we are removing those as well
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.
Upon discussion, reverted this in favour of the proposal for cleanup with detached where onDetached would be called before the refreshConnection() is invoked.
Triggered live tests https://dev.azure.com/azure-sdk/internal/_build/results?buildId=567200&view=results