Skip to content

Commit

Permalink
infer route from pathname
Browse files Browse the repository at this point in the history
Adds an option to infer route from the pathname. Any path segment that
matches a permissive UUID will be replaced by `{paramN}`, as an example:

`/entity/bba1b7c5-3ce1-42cd-984a-9d95988b08e7`
becomes
`/entity/{param1}`
  • Loading branch information
nordfjord committed Jul 1, 2024
1 parent 9928188 commit c1746cb
Show file tree
Hide file tree
Showing 6 changed files with 47 additions and 5 deletions.
6 changes: 3 additions & 3 deletions src/browser-attributes-span-processor.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { Span } from '@opentelemetry/api';
import { SpanProcessor } from '@opentelemetry/sdk-trace-base';
import { pathnameToRoute } from './pathname-to-route';

/**
* A {@link SpanProcessor} that adds browser specific attributes to each span
* that might change over the course of a session.
* Static attributes (e.g. User Agent) are added to the Resource.
*/
export class BrowserAttributesSpanProcessor implements SpanProcessor {
constructor() {}
constructor(public inferRoute = false) {}

onStart(span: Span) {
const { href, pathname, search, hash, hostname } = window.location;
Expand All @@ -17,10 +18,9 @@ export class BrowserAttributesSpanProcessor implements SpanProcessor {
'browser.height': window.innerHeight,
'page.hash': hash,
'page.url': href,
'page.route': pathname,
'page.route': this.inferRoute ? pathnameToRoute(pathname) : pathname,
'page.hostname': hostname,
'page.search': search,

'url.path': pathname,
});
}
Expand Down
19 changes: 19 additions & 0 deletions src/pathname-to-route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/** a permissive regex for UUIDs, any sequence of hex characters in the uuid format will do */
const uuidRe = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/i;

/**
* takes a URL pathname, replaces uuid segments with paramN
*/
export function pathnameToRoute(pathname: string): string {
const segments = pathname.split('/');

let count = 0;
const routeSegments = segments.map((segment) => {
if (uuidRe.test(segment)) {
return `{param${++count}}`;
}
return segment;
});

return routeSegments.join('/');
}
4 changes: 3 additions & 1 deletion src/span-processor-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,9 @@ export const configureSpanProcessors = (options?: HoneycombOptions) => {
honeycombSpanProcessor.addProcessor(new BaggageSpanProcessor());

// we always want to add the browser attrs span processor
honeycombSpanProcessor.addProcessor(new BrowserAttributesSpanProcessor());
honeycombSpanProcessor.addProcessor(
new BrowserAttributesSpanProcessor(options?.inferRoute ?? false),
);

// if there is a user provided span processor, add it to the composite span processor
if (options?.spanProcessor) {
Expand Down
3 changes: 3 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,9 @@ export interface HoneycombOptions extends Partial<WebSDKConfiguration> {

/** Config options for web vitals instrumentation. Enabled by default. */
webVitalsInstrumentationConfig?: WebVitalsInstrumentationConfig;

/** When true, infers the route of the URL pathname by replacing UUID segments with "paramN" */
inferRoute?: boolean;
}

/* Configure which fields to include in the `entry_page` resource attributes. By default,
Expand Down
2 changes: 1 addition & 1 deletion test/browser-attributes-span-processor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { ROOT_CONTEXT, SpanKind, TraceFlags } from '@opentelemetry/api';
import { BasicTracerProvider, Span } from '@opentelemetry/sdk-trace-base';

describe('BrowserAttributesSpanProcessor', () => {
const browserAttrsSpanProcessor = new BrowserAttributesSpanProcessor();
const browserAttrsSpanProcessor = new BrowserAttributesSpanProcessor(false);

let span: Span;

Expand Down
18 changes: 18 additions & 0 deletions test/pathname-to-route.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { pathnameToRoute } from '../src/pathname-to-route';

describe('pathnameToRoute', () => {
it('infers the route from the pathname', () => {
const cases = [
['/entity/BBA1B7C5-3CE1-42CD-984A-9D95988B08E7', '/entity/{param1}'],
['/entity/abcde', '/entity/abcde'],
['/entity/bba1b7c5-3ce1-42cd-984a-9d95988b08e7', '/entity/{param1}'],
[
'/entity/bba1b7c5-3ce1-42cd-984a-9d95988b08e7/sub/BBA1B7C5-3CE1-42CD-984A-9D95988B08E8',
'/entity/{param1}/sub/{param2}',
],
];
for (const [pathname, route] of cases) {
expect(pathnameToRoute(pathname)).toEqual(route);
}
});
});

0 comments on commit c1746cb

Please sign in to comment.