Skip to content

Commit

Permalink
[core-http] Add createSpan helper (Azure#12525)
Browse files Browse the repository at this point in the history
Add helper to create a span using the global tracer. Many of our Track2 SDKs implement their own createSpan, this helper should remove the need to re-implement it for every SDK
  • Loading branch information
joheredi authored Nov 13, 2020
1 parent 266f9f3 commit ae40b6b
Show file tree
Hide file tree
Showing 9 changed files with 341 additions and 1 deletion.
13 changes: 13 additions & 0 deletions sdk/core/core-client/review/core-client.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { Pipeline } from '@azure/core-https';
import { PipelinePolicy } from '@azure/core-https';
import { PipelineRequest } from '@azure/core-https';
import { PipelineResponse } from '@azure/core-https';
import { Span } from '@opentelemetry/api';
import { TokenCredential } from '@azure/core-auth';
import { TransferProgressEvent } from '@azure/core-https';

Expand Down Expand Up @@ -62,6 +63,12 @@ export function createSerializer(modelMappers?: {
[key: string]: any;
}, isXML?: boolean): Serializer;

// @public
export function createSpanFunction({ packagePrefix, namespace }: SpanConfig): <T extends OperationOptions>(operationName: string, operationOptions: T) => {
span: Span;
updatedOptions: T;
};

// @public
export interface DeserializationContentTypes {
json?: string[];
Expand Down Expand Up @@ -330,6 +337,12 @@ export interface SimpleMapperType {
name: "Base64Url" | "Boolean" | "ByteArray" | "Date" | "DateTime" | "DateTimeRfc1123" | "Object" | "Stream" | "String" | "TimeSpan" | "UnixTime" | "Uuid" | "Number" | "any";
}

// @public
export interface SpanConfig {
namespace: string;
packagePrefix: string;
}


// (No @packageDocumentation comment for this package)

Expand Down
59 changes: 59 additions & 0 deletions sdk/core/core-client/src/createSpan.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

import { Span, SpanOptions, SpanKind } from "@opentelemetry/api";
import { getTracer } from "@azure/core-tracing";
import { OperationOptions, SpanConfig } from "./interfaces";

type OperationTracingOptions = OperationOptions["tracingOptions"];

/**
* Creates a function called createSpan to create spans using the global tracer.
* @ignore
* @param spanConfig The name of the operation being performed.
* @param tracingOptions The options for the underlying http request.
*/
export function createSpanFunction({ packagePrefix, namespace }: SpanConfig) {
return function<T extends OperationOptions>(
operationName: string,
operationOptions: T
): { span: Span; updatedOptions: T } {
const tracer = getTracer();
const tracingOptions = operationOptions.tracingOptions || {};
const spanOptions: SpanOptions = {
...tracingOptions.spanOptions,
kind: SpanKind.INTERNAL
};

const span = tracer.startSpan(`${packagePrefix}.${operationName}`, spanOptions);

span.setAttribute("az.namespace", namespace);

let newSpanOptions = tracingOptions.spanOptions || {};
if (span.isRecording()) {
newSpanOptions = {
...tracingOptions.spanOptions,
parent: span.context(),
attributes: {
...spanOptions.attributes,
"az.namespace": namespace
}
};
}

const newTracingOptions: OperationTracingOptions = {
...tracingOptions,
spanOptions: newSpanOptions
};

const newOperationOptions: T = {
...operationOptions,
tracingOptions: newTracingOptions
};

return {
span,
updatedOptions: newOperationOptions
};
};
}
4 changes: 3 additions & 1 deletion sdk/core/core-client/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 { createSerializer, MapperTypeNames } from "./serializer";
export { createSpanFunction } from "./createSpan";
export { ServiceClient, ServiceClientOptions } from "./serviceClient";
export {
OperationSpec,
Expand Down Expand Up @@ -32,7 +33,8 @@ export {
ParameterPath,
OperationResponse,
FullOperationResponse,
PolymorphicDiscriminator
PolymorphicDiscriminator,
SpanConfig
} from "./interfaces";
export {
deserializationPolicy,
Expand Down
14 changes: 14 additions & 0 deletions sdk/core/core-client/src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -467,3 +467,17 @@ export interface UrlParameterValue {
value: string;
skipUrlEncoding: boolean;
}

/**
* Configuration for creating a new Tracing Span
*/
export interface SpanConfig {
/**
* Package name prefix
*/
packagePrefix: string;
/**
* Service namespace
*/
namespace: string;
}
82 changes: 82 additions & 0 deletions sdk/core/core-client/test/createSpan.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

import { assert } from "chai";
import { SpanKind, TraceFlags } from "@opentelemetry/api";
import { setTracer, TestSpan, TestTracer } from "@azure/core-tracing";
import sinon from "sinon";
import { createSpanFunction } from "../src/createSpan";
import { OperationOptions } from "../src/interfaces";

const createSpan = createSpanFunction({ namespace: "Microsoft.Test", packagePrefix: "Azure.Test" });

describe("createSpan", () => {
it("returns a created span with the right metadata", () => {
const tracer = new TestTracer();
const testSpan = new TestSpan(
tracer,
"testing",
{ traceId: "", spanId: "", traceFlags: TraceFlags.NONE },
SpanKind.INTERNAL
);
const setAttributeSpy = sinon.spy(testSpan, "setAttribute");
const startSpanStub = sinon.stub(tracer, "startSpan");
startSpanStub.returns(testSpan);
setTracer(tracer);
const { span } = createSpan("testMethod", {});
assert.strictEqual(span, testSpan, "Should return mocked span");
assert.isTrue(startSpanStub.calledOnce);
const [name, options] = startSpanStub.firstCall.args;
assert.strictEqual(name, "Azure.Test.testMethod");
assert.deepEqual(options, { kind: SpanKind.INTERNAL });
assert.isTrue(setAttributeSpy.calledOnceWithExactly("az.namespace", "Microsoft.Test"));
});

it("returns updated SpanOptions", () => {
const options: OperationOptions = {};
const { span, updatedOptions } = createSpan("testMethod", options);
assert.isEmpty(options, "original options should not be modified");
assert.notStrictEqual(updatedOptions, options, "should return new object");
const expected: OperationOptions = {
tracingOptions: {
spanOptions: {
parent: span.context(),
attributes: {
"az.namespace": "Microsoft.Test"
}
}
}
};
assert.deepEqual(updatedOptions, expected);
});

it("preserves existing attributes", () => {
const options: OperationOptions = {
tracingOptions: {
spanOptions: {
attributes: {
foo: "bar"
}
}
}
};
const { span, updatedOptions } = createSpan("testMethod", options);
assert.notStrictEqual(updatedOptions, options, "should return new object");
const expected: OperationOptions = {
tracingOptions: {
spanOptions: {
parent: span.context(),
attributes: {
"az.namespace": "Microsoft.Test",
foo: "bar"
}
}
}
};
assert.deepEqual(updatedOptions, expected);
});

afterEach(() => {
sinon.restore();
});
});
13 changes: 13 additions & 0 deletions sdk/core/core-http/review/core-http.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { Debugger } from '@azure/logger';
import { GetTokenOptions } from '@azure/core-auth';
import { isTokenCredential } from '@azure/core-auth';
import { OperationTracingOptions } from '@azure/core-tracing';
import { Span } from '@opentelemetry/api';
import { SpanOptions } from '@azure/core-tracing';
import { TokenCredential } from '@azure/core-auth';

Expand Down Expand Up @@ -156,6 +157,12 @@ export const Constants: {
// @public (undocumented)
export function createPipelineFromOptions(pipelineOptions: InternalPipelineOptions, authPolicyFactory?: RequestPolicyFactory): ServiceClientOptions;

// @public
export function createSpanFunction({ packagePrefix, namespace }: SpanConfig): <T extends OperationOptions>(operationName: string, operationOptions: T) => {
span: Span;
updatedOptions: T;
};

// Warning: (ae-forgotten-export) The symbol "FetchHttpClient" needs to be exported by the entry point coreHttp.d.ts
//
// @public (undocumented)
Expand Down Expand Up @@ -790,6 +797,12 @@ export interface SimpleMapperType {
name: "Base64Url" | "Boolean" | "ByteArray" | "Date" | "DateTime" | "DateTimeRfc1123" | "Object" | "Stream" | "String" | "TimeSpan" | "UnixTime" | "Uuid" | "Number" | "any";
}

// @public
export interface SpanConfig {
namespace: string;
packagePrefix: string;
}

// @public
export function stringifyXML(obj: any, opts?: SerializerOptions): string;

Expand Down
1 change: 1 addition & 0 deletions sdk/core/core-http/src/coreHttp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export {
ProxySettings,
ProxyOptions
} from "./serviceClient";
export { createSpanFunction, SpanConfig } from "./createSpan";
export { PipelineOptions, InternalPipelineOptions } from "./pipelineOptions";
export { QueryCollectionFormat } from "./queryCollectionFormat";
export { Constants } from "./util/constants";
Expand Down
73 changes: 73 additions & 0 deletions sdk/core/core-http/src/createSpan.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

import { Span, SpanOptions, SpanKind } from "@opentelemetry/api";
import { getTracer } from "@azure/core-tracing";
import { OperationOptions } from "./coreHttp";

type OperationTracingOptions = OperationOptions["tracingOptions"];

/**
* Configuration for creating a new Tracing Span
*/
export interface SpanConfig {
/**
* Package name prefix
*/
packagePrefix: string;
/**
* Service namespace
*/
namespace: string;
}

/**
* Creates a function called createSpan to create spans using the global tracer.
* @ignore
* @param spanConfig The name of the operation being performed.
* @param tracingOptions The options for the underlying http request.
*/
export function createSpanFunction({ packagePrefix, namespace }: SpanConfig) {
return function<T extends OperationOptions>(
operationName: string,
operationOptions: T
): { span: Span; updatedOptions: T } {
const tracer = getTracer();
const tracingOptions = operationOptions.tracingOptions || {};
const spanOptions: SpanOptions = {
...tracingOptions.spanOptions,
kind: SpanKind.INTERNAL
};

const span = tracer.startSpan(`${packagePrefix}.${operationName}`, spanOptions);

span.setAttribute("az.namespace", namespace);

let newSpanOptions = tracingOptions.spanOptions || {};
if (span.isRecording()) {
newSpanOptions = {
...tracingOptions.spanOptions,
parent: span.context(),
attributes: {
...spanOptions.attributes,
"az.namespace": namespace
}
};
}

const newTracingOptions: OperationTracingOptions = {
...tracingOptions,
spanOptions: newSpanOptions
};

const newOperationOptions: T = {
...operationOptions,
tracingOptions: newTracingOptions
};

return {
span,
updatedOptions: newOperationOptions
};
};
}
Loading

0 comments on commit ae40b6b

Please sign in to comment.