Skip to content

Commit

Permalink
[core] - Extract OpenTelemetry to a separate package (#19734)
Browse files Browse the repository at this point in the history
## What

- Rewrite core-tracing using an abstraction of an instrumenter
- Remove @opentelemetry packages from core-tracing
- Introduce a stateful client that can be used to interact with the underlying instrumenter
- Introduce @azure/opentelemetry-instrumentation-azure-sdk package providing an OTel instrumenter
  - Integrate this package with OpenTelemetry's instrumentation APIs to provide a plug-in that lights up OTel based tracing
- Add a `chaiAzureTrace` chai plugin to allow for quick and simple validation of tracing logic for packages

## Why

### Core-tracing

This is essentially a rewrite of core-tracing to abstract away from @opentelemetry/api for multiple reasons:
1. Decreased bundle size
2. Making OTel tracing truly opt-in. If you don't use OTel, there's no reason for us to call OTel APIs. While the APIs is now stable, it has been problematic for us in the past.
3. No enums, no TS > 3.9 features to downlevel 
3. Finally, @opentelemetry/api is very flexible and able to support many scenarios at the cost of what is a very wide API surface area. We can avoid much of this complexity.

### new instrumentation package

With the changes to core-tracing, we now need a way to tie our abstraction to a concrete OTel implementation. A new package, `@azure/opentelemetry-instrumentation-azure-sdk` provides this hook using well-known `registerInstrumentations` API to light-up OTel based tracing when a user opts-in.

### New chai plugin

This is our attempt at reducing the boilerplate around tracing, providing what is essentially a custom assertion wrapped in a chai plugin. While this may change significantly, I feel comfortable merging this in and making follow-up changes on main.
  • Loading branch information
maorleger committed Jan 13, 2022
1 parent 1ef3d4d commit 9d78d16
Show file tree
Hide file tree
Showing 61 changed files with 3,483 additions and 1,456 deletions.
523 changes: 521 additions & 2 deletions common/config/rush/pnpm-lock.yaml

Large diffs are not rendered by default.

7 changes: 6 additions & 1 deletion rush.json
Original file line number Diff line number Diff line change
Expand Up @@ -606,6 +606,11 @@
"projectFolder": "sdk/core/logger",
"versionPolicyName": "core"
},
{
"packageName": "@azure/opentelemetry-instrumentation-azure-sdk",
"projectFolder": "sdk/instrumentation/opentelemetry-instrumentation-azure-sdk",
"versionPolicyName": "client"
},
{
"packageName": "@azure/schema-registry",
"projectFolder": "sdk/schemaregistry/schema-registry",
Expand Down Expand Up @@ -1302,4 +1307,4 @@
"versionPolicyName": "management"
}
]
}
}
15 changes: 7 additions & 8 deletions sdk/core/core-auth/review/core-auth.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,6 @@ export class AzureSASCredential implements SASCredential {
update(newSignature: string): void;
}

// @public
export interface Context {
deleteValue(key: symbol): Context;
getValue(key: symbol): unknown;
setValue(key: symbol, value: unknown): Context;
}

// @public
export interface GetTokenOptions {
abortSignal?: AbortSignalLike;
Expand All @@ -49,7 +42,7 @@ export interface GetTokenOptions {
};
tenantId?: string;
tracingOptions?: {
tracingContext?: Context;
tracingContext?: TracingContext;
};
}

Expand Down Expand Up @@ -83,6 +76,12 @@ export interface TokenCredential {
getToken(scopes: string | string[], options?: GetTokenOptions): Promise<AccessToken | null>;
}

// @public
export interface TracingContext {
deleteValue(key: symbol): TracingContext;
getValue(key: symbol): unknown;
setValue(key: symbol, value: unknown): TracingContext;
}

// (No @packageDocumentation comment for this package)

Expand Down
2 changes: 1 addition & 1 deletion sdk/core/core-auth/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,4 @@ export {
isTokenCredential,
} from "./tokenCredential";

export { Context } from "./tracing";
export { TracingContext } from "./tracing";
6 changes: 3 additions & 3 deletions sdk/core/core-auth/src/tokenCredential.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// Licensed under the MIT license.

import { AbortSignalLike } from "@azure/abort-controller";
import { Context } from "./tracing";
import { TracingContext } from "./tracing";

/**
* Represents a credential capable of providing an authentication token.
Expand Down Expand Up @@ -43,9 +43,9 @@ export interface GetTokenOptions {
*/
tracingOptions?: {
/**
* OpenTelemetry context
* Tracing Context for the current request.
*/
tracingContext?: Context;
tracingContext?: TracingContext;
};

/**
Expand Down
6 changes: 3 additions & 3 deletions sdk/core/core-auth/src/tracing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
/**
* An interface structurally compatible with OpenTelemetry.
*/
export interface Context {
export interface TracingContext {
/**
* Get a value from the context.
*
Expand All @@ -21,12 +21,12 @@ export interface Context {
* @param key - context key for which to set the value
* @param value - value to set for the given key
*/
setValue(key: symbol, value: unknown): Context;
setValue(key: symbol, value: unknown): TracingContext;
/**
* Return a new context which inherits from this context but does
* not contain a value for the given key.
*
* @param key - context key for which to clear a value
*/
deleteValue(key: symbol): Context;
deleteValue(key: symbol): TracingContext;
}
7 changes: 5 additions & 2 deletions sdk/core/core-tracing/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@

### Breaking Changes

- SpanOptions has been removed from OperationTracingOptions as it is internal and should not be exposed by client libraries.
- Customizing a newly created Span is only supported via passing `SpanOptions` to `createSpanFunction`
- @azure/core-tracing has been rewritten in order to provide cleaner abstractions for client libraries as well as remove @opentelemetry/api as a direct dependency.
- @opentelemetry/api is no longer a direct dependency of @azure/core-tracing providing for smaller bundle sizes and lower incidence of version conflicts
- `createSpanFunction` has been removed and replaced with a stateful `TracingClient` which can be created using the `createTracingClient` function.
- `TracingClient` introduces a new API for creating tracing spans. Use `TracingClient#withSpan` to wrap an invocation in a span, ensuring the span is ended and exceptions are captured.
- `TracingClient` also provides the lower-level APIs necessary to start a span without making it active, create request headers, serialize `traceparent` header, and wrapping a callback with an active context.

### Bugs Fixed

Expand Down
6 changes: 1 addition & 5 deletions sdk/core/core-tracing/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@
"sdk-type": "client",
"main": "dist/index.js",
"module": "dist-esm/src/index.js",
"browser": {
"./dist-esm/src/utils/global.js": "./dist-esm/src/utils/global.browser.js"
},
"browser": {},
"react-native": {
"./dist/index.js": "./dist-esm/src/index.js"
},
Expand Down Expand Up @@ -59,15 +57,13 @@
"homepage": "https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/core/core-tracing/README.md",
"sideEffects": false,
"dependencies": {
"@opentelemetry/api": "^1.0.1",
"tslib": "^2.2.0"
},
"devDependencies": {
"@azure/core-auth": "^1.3.0",
"@azure/dev-tool": "^1.0.0",
"@azure/eslint-plugin-azure-sdk": "^3.0.0",
"@microsoft/api-extractor": "^7.18.11",
"@opentelemetry/tracing": "^0.22.0",
"@types/chai": "^4.1.6",
"@types/mocha": "^7.0.2",
"@types/node": "^12.0.0",
Expand Down
201 changes: 62 additions & 139 deletions sdk/core/core-tracing/review/core-tracing.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,183 +5,106 @@
```ts

// @public
export interface Context {
deleteValue(key: symbol): Context;
getValue(key: symbol): unknown;
setValue(key: symbol, value: unknown): Context;
}

// @public
const context_2: ContextAPI;
export { context_2 as context }

// @public
export interface ContextAPI {
active(): Context;
}
export function createTracingClient(options: TracingClientOptions): TracingClient;

// @public
export function createSpanFunction(args: CreateSpanFunctionArgs): <T extends {
tracingOptions?: OperationTracingOptions | undefined;
}>(operationName: string, operationOptions?: T | undefined, startSpanOptions?: SpanOptions | undefined) => {
span: Span;
updatedOptions: T;
};

// @public
export interface CreateSpanFunctionArgs {
namespace: string;
packagePrefix: string;
export interface Instrumenter {
createRequestHeaders(tracingContext?: TracingContext): Record<string, string>;
parseTraceparentHeader(traceparentHeader: string): TracingContext | undefined;
startSpan(name: string, spanOptions: InstrumenterSpanOptions): {
span: TracingSpan;
tracingContext: TracingContext;
};
withContext<CallbackArgs extends unknown[], Callback extends (...args: CallbackArgs) => ReturnType<Callback>>(context: TracingContext, callback: Callback, ...callbackArgs: CallbackArgs): ReturnType<Callback>;
}

// @public
export type Exception = ExceptionWithCode | ExceptionWithMessage | ExceptionWithName | string;

// @public
export interface ExceptionWithCode {
code: string | number;
message?: string;
name?: string;
stack?: string;
}

// @public
export interface ExceptionWithMessage {
code?: string | number;
message: string;
name?: string;
stack?: string;
}

// @public
export interface ExceptionWithName {
code?: string | number;
message?: string;
name: string;
stack?: string;
}

// @public
export function extractSpanContextFromTraceParentHeader(traceParentHeader: string): SpanContext | undefined;

// @public
export function getSpan(context: Context): Span | undefined;

// @public
export function getSpanContext(context: Context): SpanContext | undefined;

// @public
export function getTraceParentHeader(spanContext: SpanContext): string | undefined;

// @public
export function getTracer(): Tracer;

// @public
export function getTracer(name: string, version?: string): Tracer;

// @public
export type HrTime = [number, number];

// @public
export function isSpanContextValid(context: SpanContext): boolean;

// @public
export interface Link {
attributes?: SpanAttributes;
context: SpanContext;
export interface InstrumenterSpanOptions extends TracingSpanOptions {
packageName: string;
packageVersion?: string;
tracingContext?: TracingContext;
}

// @public
export interface OperationTracingOptions {
tracingContext?: Context;
tracingContext?: TracingContext;
}

// @public
export function setSpan(context: Context, span: Span): Context;
export type SpanStatus = SpanStatusSuccess | SpanStatusError;

// @public
export function setSpanContext(context: Context, spanContext: SpanContext): Context;

// @public
export interface Span {
addEvent(name: string, attributesOrStartTime?: SpanAttributes | TimeInput, startTime?: TimeInput): this;
end(endTime?: TimeInput): void;
isRecording(): boolean;
recordException(exception: Exception, time?: TimeInput): void;
setAttribute(key: string, value: SpanAttributeValue): this;
setAttributes(attributes: SpanAttributes): this;
setStatus(status: SpanStatus): this;
spanContext(): SpanContext;
updateName(name: string): this;
}

// @public
export interface SpanAttributes {
[attributeKey: string]: SpanAttributeValue | undefined;
}

// @public
export type SpanAttributeValue = string | number | boolean | Array<null | undefined | string> | Array<null | undefined | number> | Array<null | undefined | boolean>;
export type SpanStatusError = {
status: "error";
error?: Error | string;
};

// @public
export interface SpanContext {
spanId: string;
traceFlags: number;
traceId: string;
traceState?: TraceState;
}
export type SpanStatusSuccess = {
status: "success";
};

// @public
export enum SpanKind {
CLIENT = 2,
CONSUMER = 4,
INTERNAL = 0,
PRODUCER = 3,
SERVER = 1
export interface TracingClient {
createRequestHeaders(tracingContext?: TracingContext): Record<string, string>;
parseTraceparentHeader(traceparentHeader: string): TracingContext | undefined;
startSpan<Options extends {
tracingOptions?: OperationTracingOptions;
}>(name: string, operationOptions?: Options, spanOptions?: TracingSpanOptions): {
span: TracingSpan;
updatedOptions: Options;
};
withContext<CallbackArgs extends unknown[], Callback extends (...args: CallbackArgs) => ReturnType<Callback>>(context: TracingContext, callback: Callback, ...callbackArgs: CallbackArgs): ReturnType<Callback>;
withSpan<Options extends {
tracingOptions?: OperationTracingOptions;
}, Callback extends (updatedOptions: Options, span: Omit<TracingSpan, "end">) => ReturnType<Callback>>(name: string, operationOptions: Options, callback: Callback, spanOptions?: TracingSpanOptions): Promise<ReturnType<Callback>>;
}

// @public
export interface SpanOptions {
attributes?: SpanAttributes;
kind?: SpanKind;
links?: Link[];
startTime?: TimeInput;
export interface TracingClientOptions {
namespace: string;
packageName: string;
packageVersion?: string;
}

// @public
export interface SpanStatus {
code: SpanStatusCode;
message?: string;
export interface TracingContext {
deleteValue(key: symbol): TracingContext;
getValue(key: symbol): unknown;
setValue(key: symbol, value: unknown): TracingContext;
}

// @public
export enum SpanStatusCode {
ERROR = 2,
OK = 1,
UNSET = 0
export interface TracingSpan {
end(): void;
isRecording(): boolean;
recordException(exception: Error | string): void;
setAttribute(name: string, value: unknown): void;
setStatus(status: SpanStatus): void;
}

// @public
export type TimeInput = HrTime | number | Date;
export type TracingSpanKind = "client" | "server" | "producer" | "consumer" | "internal";

// @public
export const enum TraceFlags {
NONE = 0,
SAMPLED = 1
export interface TracingSpanLink {
attributes?: {
[key: string]: unknown;
};
tracingContext: TracingContext;
}

// @public
export interface Tracer {
startSpan(name: string, options?: SpanOptions, context?: Context): Span;
export interface TracingSpanOptions {
spanAttributes?: {
[key: string]: unknown;
};
spanKind?: TracingSpanKind;
spanLinks?: TracingSpanLink[];
}

// @public
export interface TraceState {
get(key: string): string | undefined;
serialize(): string;
set(key: string, value: string): TraceState;
unset(key: string): TraceState;
}
export function useInstrumenter(instrumenter: Instrumenter): void;

// (No @packageDocumentation comment for this package)

Expand Down
Loading

0 comments on commit 9d78d16

Please sign in to comment.