Skip to content

Commit

Permalink
feat(express): allow rewriting span names (open-telemetry#463)
Browse files Browse the repository at this point in the history
  • Loading branch information
seemk authored Jan 25, 2022
1 parent 4de977e commit 7510757
Show file tree
Hide file tree
Showing 9 changed files with 398 additions and 172 deletions.
6 changes: 6 additions & 0 deletions plugins/node/opentelemetry-instrumentation-express/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ Express instrumentation has few options available to choose from. You can set th
| ------- | ---- | ------- | ----------- |
| `ignoreLayers` | `IgnoreMatcher[]` | `[/^\/_internal\//]` | Ignore layers that by match. |
| `ignoreLayersType`| `ExpressLayerType[]` | `['request_handler']` | Ignore layers of specified type. |
| `spanNameHook` | `SpanNameHook` | `() => 'my-span-name'` | Can be used to customize span names by returning a new name from the hook. |

`ignoreLayers` accepts an array of elements of types:

Expand All @@ -75,6 +76,11 @@ Express instrumentation has few options available to choose from. You can set th
- `middleware`,
- `request_handler` is the name for anything that's not a router or a middleware.

`spanNameHook` is invoked with 2 arguments:

- `info: ExpressRequestInfo` containing the incoming Express.js request, the current route handler creating a span and `ExpressLayerType` - the type of the handling layer or undefined when renaming the root HTTP instrumentation span.
- `defaultName: string` - original name proposed by the instrumentation.

## Useful links

- For more information on OpenTelemetry, visit: <https://opentelemetry.io/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"types": "build/src/index.d.ts",
"repository": "open-telemetry/opentelemetry-js-contrib",
"scripts": {
"test": "nyc ts-mocha -p tsconfig.json 'test/**/*.ts'",
"test": "nyc ts-mocha -p tsconfig.json 'test/**/*.test.ts'",
"codecov": "nyc report --reporter=json && codecov -f coverage/*.json -p ../../",
"tdd": "yarn test -- --watch-extensions ts --watch",
"clean": "rimraf build/*",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,14 @@ import {
RPCType,
} from '@opentelemetry/core';
import { trace, context, diag, SpanAttributes } from '@opentelemetry/api';
import * as express from 'express';
import type * as express from 'express';
import {
ExpressLayer,
ExpressRouter,
PatchedRequest,
_LAYERS_STORE_PROPERTY,
ExpressInstrumentationConfig,
ExpressRequestInfo,
} from './types';
import { ExpressLayerType } from './enums/ExpressLayerType';
import { AttributeNames } from './enums/AttributeNames';
Expand Down Expand Up @@ -58,6 +59,14 @@ export class ExpressInstrumentation extends InstrumentationBase<
);
}

override setConfig(config: ExpressInstrumentationConfig = {}) {
this._config = Object.assign({}, config);
}

override getConfig(): ExpressInstrumentationConfig {
return this._config as ExpressInstrumentationConfig;
}

init() {
return [
new InstrumentationNodeModuleDefinition<typeof express>(
Expand Down Expand Up @@ -202,9 +211,14 @@ export class ExpressInstrumentation extends InstrumentationBase<
ExpressLayerType.REQUEST_HANDLER &&
rpcMetadata?.type === RPCType.HTTP
) {
rpcMetadata.span.updateName(
const name = instrumentation._getSpanName(
{
request: req,
route,
},
`${req.method} ${route.length > 0 ? route : '/'}`
);
rpcMetadata.span.updateName(name);
}

// verify against the config if the layer should be ignored
Expand All @@ -218,7 +232,15 @@ export class ExpressInstrumentation extends InstrumentationBase<
return original.apply(this, arguments);
}

const span = instrumentation.tracer.startSpan(metadata.name, {
const spanName = instrumentation._getSpanName(
{
request: req,
layerType: type,
route,
},
metadata.name
);
const span = instrumentation.tracer.startSpan(spanName, {
attributes: Object.assign(attributes, metadata.attributes),
});
const startTime = hrTime();
Expand Down Expand Up @@ -277,4 +299,22 @@ export class ExpressInstrumentation extends InstrumentationBase<
};
});
}

_getSpanName(info: ExpressRequestInfo, defaultName: string) {
const hook = this.getConfig().spanNameHook;

if (!(hook instanceof Function)) {
return defaultName;
}

try {
return hook(info, defaultName) ?? defaultName;
} catch (err) {
diag.error(
'express instrumentation: error calling span name rewrite hook',
err
);
return defaultName;
}
}
}
19 changes: 19 additions & 0 deletions plugins/node/opentelemetry-instrumentation-express/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,24 @@ export type LayerMetadata = {

export type IgnoreMatcher = string | RegExp | ((name: string) => boolean);

export type ExpressRequestInfo = {
request: Request;
route: string;
/**
* If layerType is undefined, SpanNameHook is being invoked to rename the original root HTTP span.
*/
layerType?: ExpressLayerType;
};

export type SpanNameHook = (
info: ExpressRequestInfo,
/**
* If no decision is taken based on RequestInfo, the default name
* supplied by the instrumentation can be used instead.
*/
defaultName: string
) => string;

/**
* Options available for the Express Instrumentation (see [documentation](https://github.com/open-telemetry/opentelemetry-js/tree/main/packages/opentelemetry-Instrumentation-express#express-Instrumentation-options))
*/
Expand All @@ -78,4 +96,5 @@ export interface ExpressInstrumentationConfig extends InstrumentationConfig {
ignoreLayers?: IgnoreMatcher[];
/** Ignore specific layers based on their type */
ignoreLayersType?: ExpressLayerType[];
spanNameHook?: SpanNameHook;
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { RPCType, setRPCMetadata } from '@opentelemetry/core';
import { ExpressLayerType } from '../src/enums/ExpressLayerType';
import { AttributeNames } from '../src/enums/AttributeNames';
import { ExpressInstrumentation, ExpressInstrumentationConfig } from '../src';
import { createServer, httpRequest } from './utils';

const instrumentation = new ExpressInstrumentation({
ignoreLayersType: [ExpressLayerType.MIDDLEWARE],
Expand All @@ -36,26 +37,6 @@ instrumentation.disable();

import * as express from 'express';
import * as http from 'http';
import { AddressInfo } from 'net';

const httpRequest = {
get: (options: http.ClientRequestArgs | string) => {
return new Promise((resolve, reject) => {
return http.get(options, resp => {
let data = '';
resp.on('data', chunk => {
data += chunk;
});
resp.on('end', () => {
resolve(data);
});
resp.on('error', err => {
reject(err);
});
});
});
},
};

describe('ExpressInstrumentation', () => {
const provider = new NodeTracerProvider();
Expand Down Expand Up @@ -84,9 +65,9 @@ describe('ExpressInstrumentation', () => {

beforeEach(async () => {
app = express();
server = http.createServer(app);
await new Promise<void>(resolve => server.listen(0, resolve));
port = (server.address() as AddressInfo).port;
const httpServer = await createServer(app);
server = httpServer.server;
port = httpServer.port;
});

afterEach(() => {
Expand Down
Loading

0 comments on commit 7510757

Please sign in to comment.