Skip to content
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

[Event Hubs] add tracing support when sending events #5207

Merged
merged 9 commits into from
Oct 1, 2019

Conversation

chradek
Copy link
Contributor

@chradek chradek commented Sep 20, 2019

Relates to #5180

This PR is a WIP that adds support for tracing send requests. I need some clarification on setting the send span statuses, specifically when an error is encountered.

Also waiting on #5182 #5233 #5283 to be merged as there are some API changes made there that will impact this PR.

Copy link
Member

@xirzec xirzec left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really like this PR, just a few different thoughts

* @param traceParent Serialized span context data as a `traceparent` header value.
* @returns The `SpanContext` generated from the `traceparent` value.
*/
export function extractSpanContextFromTraceParent(traceParent: string): SpanContext {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I think spanContextFromTraceParentHeader might be a slightly better name

@@ -0,0 +1,26 @@
import { SpanContext } from '../interfaces/span_context';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not to dump more work on you, but I feel like this function would be a nice/easy thing to add a couple unit tests for ;)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I plan to! This PR is still in draft so not what I would consider ship-able yet!

* @param spanContext Contains context for a specific span.
* @returns The `spanContext` represented as a `traceparent` value.
*/
export function getTraceParent(spanContext: SpanContext): string {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getTraceParentHeader?

*/
export function getTraceParent(spanContext: SpanContext): string {
if (!spanContext.traceId || !spanContext.spanId) {
throw new Error(`Missing required fields "traceId" or "spanId" from spanContext.`);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know it's tedious, but can you break this into two separate warnings? That way if this fails in production someone can look at the logs and know which field was missing

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Related to another comment, what do you think about returning null if these checks fail, and logging the errors instead of throwing? That fits better with the overall philosophy that turning on tracing shouldn't break user's applications.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Love it

@@ -0,0 +1,21 @@
import { SpanContext } from "../interfaces/span_context";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we combine these two utility functions into one ts file? I'm not sure how the other libs organize themselves

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

otherwise it feels like these functions should be the default export...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I'd prefer to combine them. I became wary of default exports as I went back and forth between commonjs and es2015 imports, but I think our packages handle those differences today without friction...

It's also a personal habit of mine to give exports their own file (rollup flattens them in the build artifact so no performance penalty) but I can adjust if others find that annoying!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not totally opposed to the practice, it just felt like overkill, but maybe I go too much the other way (put everything in one module and break that out later)


export const TRACEPARENT_PROPERTY = "Diagnostic_Id";

export function instrumentEventData(eventData: EventData, span: Span): boolean {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm curious, what is the success boolean used for?

}

try {
const traceParent = getTraceParent(span.context());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we should consider a way of parsing that doesn't throw, like returning null or some other sentinel value, since exceptions-as-control-flow is a little more expensive.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, and think null or undefined would be good choices. I think instead of throwing an error, the getTraceParent method could log instead (I don't think logging has been added to core-tracing yet but it should eventually).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Anders would say to use undefined

const messageSpan = createMessageSpan(options.parentSpan);
// Create a shallow copy of eventData and eventData.properties in case we add the diagnostic id to the properties.
eventData = {...eventData, properties: {...eventData.properties}};
isInstrumented = instrumentEventData(eventData, messageSpan);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we didn't want instrumentEventData to mutate the object, we could just treat its input as readonly and do the object copy trick inside it and return the new version, right?

It just feels odd to have a method that mutates a passed-in argument if the only user of that method doesn't want that behavior

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I originally had that but then came across a situation where I also needed to know if the event was instrumented by the method. I didn't want to make the return type return the boolean and the event data, but maybe I can tackle the isInstrumented check another way.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I remember now. I wanted to know if an event being added to a batch via tryAdd was instrumented by instrumentEventData, or if it was already instrumented before being added. I can be smarter about handling this in tryAdd and have instrumentEventData go back to returning a new copy of EventData.

sdk/eventhub/event-hubs/src/sender.ts Outdated Show resolved Hide resolved
sdk/eventhub/event-hubs/src/sender.ts Show resolved Hide resolved
@chradek
Copy link
Contributor Author

chradek commented Sep 27, 2019

I think I've addressed all the feedback. Waiting on #5283 so I can use the TestTracer to write tests for the event-hubs changes.

@chradek chradek marked this pull request as ready for review September 27, 2019 23:47
Copy link
Member

@xirzec xirzec left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking real good! Happy to get this landed soon, sprinkling a few more comments for now.

}


const VERSION = "00";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can this be at the top of the file?

}

const flags = spanContext.traceFlags || TraceFlags.UNSAMPLED;
const traceFlags = (flags < 10) ? `0${flags.toString(16)}` : flags.toString(16);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe encode the flags at hex in the previous step to save some .toString(16) in this line?

sdk/core/core-tracing/package.json Show resolved Hide resolved

const traceParent = getTraceParentHeader(span.context());
if (traceParent) {
eventData.properties![TRACEPARENT_PROPERTY] = traceParent;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

feels like good minor feedback to the TS team that we've implicitly initialized this above... 😉

sdk/eventhub/event-hubs/src/eventHubClient.ts Show resolved Hide resolved
@chradek chradek force-pushed the event-hubs-tracing branch from f3cf6a8 to 789dab6 Compare October 1, 2019 04:15
@chradek
Copy link
Contributor Author

chradek commented Oct 1, 2019

/azp run js - eventhubs-client - tests

@azure-pipelines
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

Copy link
Member

@xirzec xirzec left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good! 👍

I really like that you used chai for your test assertions, I think I'd like to see us standardize around that in other packages as well. ☕️

Left a few last comments for the road. ☄

* The set of options to configure the behavior of `tryAdd`.
* - `parentSpan` : The `Span` or `SpanContext` to use as the `parent` of the span created while calling this operation.
*/
export interface TryAddOptions {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this same interface is defined in messageSpan.ts

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does not appear to be so.

* The set of options to manually propagate `Span` context for distributed tracing.
* - `parentSpan` : The `Span` or `SpanContext` for the operation to use as a `parent` when creating its own span.
*/
export interface ParentSpanOptions {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this also looks familiar... maybe there are some common interfaces that need to be pulled out?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, maybe this can be pulled into core-tracing. I think core-http has something similar on RequestOptions, but this library doesn't depend on that so I duplicated it here.


tracer.getSpanGraph(rootSpan.context().traceId).should.eql(expectedGraph);
tracer.getActiveSpans().length.should.equal(0, "All spans should have had end called.");
});
});
}).timeout(60000);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kinda curious why the timeout is so big. I feel like we have a few tests that need to be sped up through mocking (though don't worry about this for this PR)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this is because currently all the tests are live tests. I'll be taking a look at mocking in the near future :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants