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

[tracing] Add support for addEvent #31162

Merged
merged 11 commits into from
Sep 20, 2024
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions sdk/core/core-tracing/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

### Features Added

- Added support for attaching events to a span. [#31162](https://github.com/Azure/azure-sdk-for-js/pull/31162)

### Breaking Changes

### Bugs Fixed
Expand Down
7 changes: 7 additions & 0 deletions sdk/core/core-tracing/review/core-tracing.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@

```ts

// @public
export interface AddEventOptions {
attributes?: Record<string, unknown>;
startTime?: Date;
}

// @public
export function createTracingClient(options: TracingClientOptions): TracingClient;

Expand Down Expand Up @@ -90,6 +96,7 @@ export interface TracingContext {

// @public
export interface TracingSpan {
addEvent?(name: string, options?: AddEventOptions): void;
end(): void;
isRecording(): boolean;
recordException(exception: Error | string): void;
Expand Down
1 change: 1 addition & 0 deletions sdk/core/core-tracing/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the MIT License.

export {
AddEventOptions,
Instrumenter,
InstrumenterSpanOptions,
OperationTracingOptions,
Expand Down
3 changes: 3 additions & 0 deletions sdk/core/core-tracing/src/instrumenter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ export function createDefaultTracingSpan(): TracingSpan {
setStatus: () => {
// noop
},
addEvent: () => {
// noop
},
};
}

Expand Down
19 changes: 19 additions & 0 deletions sdk/core/core-tracing/src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,20 @@ export type SpanStatusError = { status: "error"; error?: Error | string };
*/
export type SpanStatus = SpanStatusSuccess | SpanStatusError;

/**
* Represents options you can pass to {@link TracingSpan.addEvent}.
*/
export interface AddEventOptions {
/**
* A set of attributes to attach to the event.
*/
attributes?: Record<string, unknown>;
/**
* The start time of the event.
*/
startTime?: Date;
}

/**
* Represents an implementation agnostic tracing span.
*/
Expand Down Expand Up @@ -248,6 +262,11 @@ export interface TracingSpan {
* Depending on the span implementation, this may return false if the span is not being sampled.
*/
isRecording(): boolean;

/**
* Adds an event to the span.
*/
addEvent?(name: string, options?: AddEventOptions): void;
}

/** An immutable context bag of tracing values for the current operation. */
Expand Down
1 change: 1 addition & 0 deletions sdk/core/core-tracing/test/instrumenter.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ describe("Instrumenter", () => {
span.setStatus({ status: "success" });
span.setAttribute("foo", "bar");
span.recordException(new Error("test"));
span.addEvent!("I said span not Spren!", { startTime: new Date() });
maorleger marked this conversation as resolved.
Show resolved Hide resolved
span.end();
assert.isFalse(span.isRecording());
});
Expand Down
7 changes: 7 additions & 0 deletions sdk/core/ts-http-runtime/review/ts-http-runtime.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ export interface AddCredentialPipelinePolicyOptions {
credential?: TokenCredential | KeyCredential;
}

// @public
export interface AddEventOptions {
attributes?: Record<string, unknown>;
startTime?: Date;
}

// @public
export interface AdditionalPolicyConfig {
policy: PipelinePolicy;
Expand Down Expand Up @@ -789,6 +795,7 @@ export interface TracingPolicyOptions {

// @public
export interface TracingSpan {
addEvent?(name: string, options?: AddEventOptions): void;
end(): void;
isRecording(): boolean;
recordException(exception: Error | string): void;
Expand Down
1 change: 1 addition & 0 deletions sdk/core/ts-http-runtime/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ export { AbortError } from "./abort-controller/AbortError.js";
export { AccessToken, GetTokenOptions, TokenCredential } from "./auth/tokenCredential.js";
export { KeyCredential, isKeyCredential } from "./auth/keyCredential.js";
export {
AddEventOptions,
Instrumenter,
InstrumenterSpanOptions,
OperationTracingOptions,
Expand Down
3 changes: 3 additions & 0 deletions sdk/core/ts-http-runtime/src/tracing/instrumenter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ export function createDefaultTracingSpan(): TracingSpan {
setStatus: () => {
// noop
},
addEvent: () => {
// noop
},
};
}

Expand Down
19 changes: 19 additions & 0 deletions sdk/core/ts-http-runtime/src/tracing/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,20 @@ export type SpanStatusError = { status: "error"; error?: Error | string };
*/
export type SpanStatus = SpanStatusSuccess | SpanStatusError;

/**
* Represents options you can pass to {@link TracingSpan.addEvent}.
*/
export interface AddEventOptions {
/**
* A set of attributes to attach to the event.
*/
attributes?: Record<string, unknown>;
/**
* The start time of the event.
*/
startTime?: Date;
}

/**
* Represents an implementation agnostic tracing span.
*/
Expand Down Expand Up @@ -248,6 +262,11 @@ export interface TracingSpan {
* Depending on the span implementation, this may return false if the span is not being sampled.
*/
isRecording(): boolean;

/**
* Adds an event to the span.
*/
addEvent?(name: string, options?: AddEventOptions): void;
}

/** An immutable context bag of tracing values for the current operation. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ describe("Instrumenter", () => {
span.setStatus({ status: "success" });
span.setAttribute("foo", "bar");
span.recordException(new Error("test"));
span.addEvent!("I said Span not Spren!", { attributes: { foo: "Bar" } });
maorleger marked this conversation as resolved.
Show resolved Hide resolved
span.end();
assert.isFalse(span.isRecording());
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

### Features Added

- Added support for attaching events to a span. [#31162](https://github.com/Azure/azure-sdk-for-js/pull/31162)

### Breaking Changes

### Bugs Fixed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@
"sideEffects": false,
"prettier": "@azure/eslint-plugin-azure-sdk/prettier.json",
"dependencies": {
"@azure/core-tracing": "^1.0.0",
"@azure/core-tracing": "^1.1.3",
"@azure/logger": "^1.0.0",
"@opentelemetry/api": "^1.9.0",
"@opentelemetry/core": "^1.26.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

import { Span, AttributeValue, SpanStatusCode } from "@opentelemetry/api";
import { SpanStatus, TracingSpan } from "@azure/core-tracing";
import { Span, SpanStatusCode } from "@opentelemetry/api";
import { SpanStatus, TracingSpan, AddEventOptions } from "@azure/core-tracing";
import { isAttributeValue, sanitizeAttributes } from "@opentelemetry/core";

export class OpenTelemetrySpanWrapper implements TracingSpan {
private _span: Span;
Expand All @@ -25,8 +26,8 @@ export class OpenTelemetrySpanWrapper implements TracingSpan {
}

setAttribute(name: string, value: unknown): void {
if (value !== null && value !== undefined) {
this._span.setAttribute(name, value as AttributeValue);
if (value !== null && value !== undefined && isAttributeValue(value)) {
this._span.setAttribute(name, value);
}
}

Expand All @@ -42,6 +43,10 @@ export class OpenTelemetrySpanWrapper implements TracingSpan {
return this._span.isRecording();
}

addEvent(name: string, options: AddEventOptions = {}): void {
this._span.addEvent(name, sanitizeAttributes(options.attributes), options.startTime);
}

/**
* Allows getting the wrapped span as needed.
* @internal
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
// Licensed under the MIT License.

import { InstrumenterSpanOptions, TracingSpanKind, TracingSpanLink } from "@azure/core-tracing";
import { Attributes, AttributeValue, Link, SpanKind, SpanOptions, trace } from "@opentelemetry/api";
import { Link, SpanKind, SpanOptions, trace } from "@opentelemetry/api";
import { sanitizeAttributes } from "@opentelemetry/core";

/**
* Converts our TracingSpanKind to the corresponding OpenTelemetry SpanKind.
Expand Down Expand Up @@ -41,32 +42,13 @@ function toOpenTelemetryLinks(spanLinks: TracingSpanLink[] = []): Link[] {
if (spanContext) {
acc.push({
context: spanContext,
attributes: toOpenTelemetrySpanAttributes(tracingSpanLink.attributes),
attributes: sanitizeAttributes(tracingSpanLink.attributes),
});
}
return acc;
}, [] as Link[]);
}

/**
* Converts core-tracing's span attributes to OpenTelemetry attributes.
*
* @param spanAttributes - The set of attributes to convert.
* @returns An {@link SpanAttributes} to set on a span.
*/
function toOpenTelemetrySpanAttributes(
spanAttributes: { [key: string]: unknown } | undefined,
): Attributes {
const attributes: ReturnType<typeof toOpenTelemetrySpanAttributes> = {};
for (const key in spanAttributes) {
// Any non-nullish value is allowed.
if (spanAttributes[key] !== null && spanAttributes[key] !== undefined) {
attributes[key] = spanAttributes[key] as AttributeValue;
}
}
return attributes;
}

/**
* Converts core-tracing span options to OpenTelemetry options.
*
Expand All @@ -76,7 +58,7 @@ function toOpenTelemetrySpanAttributes(
export function toSpanOptions(spanOptions?: InstrumenterSpanOptions): SpanOptions {
const { spanAttributes, spanLinks, spanKind } = spanOptions || {};

const attributes: Attributes = toOpenTelemetrySpanAttributes(spanAttributes);
const attributes = sanitizeAttributes(spanAttributes);
const kind = toOpenTelemetrySpanKind(spanKind);
const links = toOpenTelemetryLinks(spanLinks);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,36 @@ describe("OpenTelemetrySpanWrapper", () => {
});
});

describe("#addEvent", () => {
it("records events on the span", () => {
span.addEvent("test", {
startTime: new Date(2024, 1, 1),
attributes: { key: "value" },
});

const otSpan = getExportedSpan(span);
assert.lengthOf(otSpan.events, 1);
const event = otSpan.events[0];
assert.equal(event.name, "test");
assert.deepEqual(event.attributes, { key: "value" });
assert.equal(
event.time[0],
new Date(2024, 1, 1).getTime() / 1000 /** Millseconds to seconds */,
);
});

it("drops invalid attributes", () => {
span.addEvent("test", {
attributes: { key: { key1: 5 } }, // objects are not valid per the spec
});

const otSpan = getExportedSpan(span);
assert.lengthOf(otSpan.events, 1);
const event = otSpan.events[0];
assert.deepEqual(event.attributes, {});
});
});

describe("#recordException", () => {
it("sets the error on the wrapped span", () => {
const error = new Error("test");
Expand Down