Skip to content

Commit

Permalink
feat(http-plugin): add options to disable new spans if no parent #931
Browse files Browse the repository at this point in the history
  • Loading branch information
vmarchaud committed Apr 15, 2020
1 parent 8e56923 commit 344d9c0
Show file tree
Hide file tree
Showing 4 changed files with 132 additions and 7 deletions.
2 changes: 2 additions & 0 deletions packages/opentelemetry-plugin-http/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ Http plugin has few options available to choose from. You can set the following:
| [`ignoreIncomingPaths`](https://github.com/open-telemetry/opentelemetry-js/blob/master/packages/opentelemetry-plugin-http/src/types.ts#L28) | `IgnoreMatcher[]` | Http plugin will not trace all incoming requests that match paths |
| [`ignoreOutgoingUrls`](https://github.com/open-telemetry/opentelemetry-js/blob/master/packages/opentelemetry-plugin-http/src/types.ts#L28) | `IgnoreMatcher[]` | Http plugin will not trace all outgoing requests that match urls |
| [`serverName`](https://github.com/open-telemetry/opentelemetry-js/blob/master/packages/opentelemetry-plugin-http/src/types.ts#L28) | `string` | The primary server name of the matched virtual host. |
| `requireParentforOutgoingSpans` | Boolean | Require that is a parent span to create new span for outgoing requests. |
| `requireParentforIncomingSpans` | Boolean | Require that is a parent span to create new span for incoming requests. |

## Useful links
- For more information on OpenTelemetry, visit: <https://opentelemetry.io/>
Expand Down
54 changes: 47 additions & 7 deletions packages/opentelemetry-plugin-http/src/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,15 @@ import {
SpanKind,
SpanOptions,
Status,
SpanContext,
TraceFlags,
NoopSpan,
} from '@opentelemetry/api';
import { BasePlugin } from '@opentelemetry/core';
import {
BasePlugin,
NoRecordingSpan,
getExtractedSpanContext,
} from '@opentelemetry/core';
import {
ClientRequest,
IncomingMessage,
Expand Down Expand Up @@ -57,6 +64,12 @@ export class HttpPlugin extends BasePlugin<Http> {
/** keep track on spans not ended */
private readonly _spanNotEnded: WeakSet<Span>;

private readonly _emptySpanContext: SpanContext = {
traceId: '',
spanId: '',
traceFlags: TraceFlags.NONE,
};

constructor(readonly moduleName: string, readonly version: string) {
super(`@opentelemetry/plugin-${moduleName}`, VERSION);
// For now component is equal to moduleName but it can change in the future.
Expand Down Expand Up @@ -295,10 +308,23 @@ export class HttpPlugin extends BasePlugin<Http> {
};

return context.with(propagation.extract(headers), () => {
const span = plugin._startHttpSpan(
`${method} ${pathname}`,
spanOptions
);
let span: Span = new NoopSpan();
const hasParent = plugin._tracer.getCurrentSpan() !== undefined;
/*
* If a parent is required but not present, we use a `NoRecordingSpan` to still
* propagate context without recording it.
*/
if (
plugin._config.requireParentforIncomingSpans === true &&
hasParent === false
) {
const spanContext =
getExtractedSpanContext(context.active()) ??
plugin._emptySpanContext;
span = new NoRecordingSpan(spanContext);
} else {
span = plugin._startHttpSpan(`${method} ${pathname}`, spanOptions);
}

return plugin._tracer.withSpan(span, () => {
context.bind(request);
Expand Down Expand Up @@ -396,8 +422,22 @@ export class HttpPlugin extends BasePlugin<Http> {
const spanOptions: SpanOptions = {
kind: SpanKind.CLIENT,
};

const span = plugin._startHttpSpan(operationName, spanOptions);
const hasParent = plugin._tracer.getCurrentSpan() !== undefined;
let span: Span = new NoopSpan();
/*
* If a parent is required but not present, we use a `NoRecordingSpan` to still
* propagate context without recording it.
*/
if (
plugin._config.requireParentforOutgoingSpans === true &&
hasParent === false
) {
const spanContext =
getExtractedSpanContext(context.active()) ?? plugin._emptySpanContext;
span = new NoRecordingSpan(spanContext);
} else {
span = plugin._startHttpSpan(operationName, spanOptions);
}

return plugin._tracer.withSpan(span, () => {
if (!optionsParsed.headers) optionsParsed.headers = {};
Expand Down
4 changes: 4 additions & 0 deletions packages/opentelemetry-plugin-http/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ export interface HttpPluginConfig extends PluginConfig {
applyCustomAttributesOnSpan?: HttpCustomAttributeFunction;
/** The primary server name of the matched virtual host. */
serverName?: string;
/** Require parent to create span for outgoing requests */
requireParentforOutgoingSpans?: boolean;
/** Require parent to create span for incoming requests */
requireParentforIncomingSpans?: boolean;
}

export interface Err extends Error {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -696,5 +696,84 @@ describe('HttpPlugin', () => {
req.end();
});
});

describe('with require parent span', () => {
beforeEach(() => {
memoryExporter.reset();
plugin.enable(http, provider, provider.logger, {});
server = http.createServer((request, response) => {
response.end('Test Server Response');
});
server.listen(serverPort);
});

afterEach(() => {
server.close();
plugin.disable();
});

it(`should not trace without parent with options enabled (both client & server)`, async () => {
plugin.disable();
const config: HttpPluginConfig = {
requireParentforIncomingSpans: true,
requireParentforOutgoingSpans: true,
};
plugin.enable(http, provider, provider.logger, config);
const testPath = `/test/test`;
await httpRequest.get(
`${protocol}://${hostname}:${serverPort}${testPath}`
);
const spans = memoryExporter.getFinishedSpans();
assert.strictEqual(spans.length, 0);
});

it(`should not trace without parent with options enabled (client only)`, async () => {
plugin.disable();
const config: HttpPluginConfig = {
requireParentforOutgoingSpans: true,
};
plugin.enable(http, provider, provider.logger, config);
const testPath = `/test/test`;
const result = await httpRequest.get(
`${protocol}://${hostname}:${serverPort}${testPath}`
);
assert(
result.reqHeaders[DummyPropagation.TRACE_CONTEXT_KEY] !== undefined
);
assert(
result.reqHeaders[DummyPropagation.SPAN_CONTEXT_KEY] !== undefined
);
const spans = memoryExporter.getFinishedSpans();
assert.strictEqual(spans.length, 1);
assert.strictEqual(
spans.every(span => span.kind === SpanKind.SERVER),
true
);
});

it(`should not trace without parent with options enabled (server only)`, async () => {
plugin.disable();
const config: HttpPluginConfig = {
requireParentforIncomingSpans: true,
};
plugin.enable(http, provider, provider.logger, config);
const testPath = `/test/test`;
const result = await httpRequest.get(
`${protocol}://${hostname}:${serverPort}${testPath}`
);
assert(
result.reqHeaders[DummyPropagation.TRACE_CONTEXT_KEY] !== undefined
);
assert(
result.reqHeaders[DummyPropagation.SPAN_CONTEXT_KEY] !== undefined
);
const spans = memoryExporter.getFinishedSpans();
assert.strictEqual(spans.length, 1);
assert.strictEqual(
spans.every(span => span.kind === SpanKind.CLIENT),
true
);
});
});
});
});

0 comments on commit 344d9c0

Please sign in to comment.