Skip to content

Commit

Permalink
feat: resolve useragent source and add as source label to metrics (#7883
Browse files Browse the repository at this point in the history
)
  • Loading branch information
daveleek authored Aug 15, 2024
1 parent 183a9fc commit cf83043
Show file tree
Hide file tree
Showing 6 changed files with 79 additions and 3 deletions.
1 change: 1 addition & 0 deletions src/lib/metric-events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ type MetricEvent =
type RequestOriginEventPayload = {
type: 'UI' | 'API';
method: Request['method'];
source?: string;
};

type MetricEventPayloads = {
Expand Down
6 changes: 3 additions & 3 deletions src/lib/metrics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -350,7 +350,7 @@ export default class MetricsMonitor {
const requestOriginCounter = createCounter({
name: 'request_origin_counter',
help: 'Number of authenticated requests, including origin information.',
labelNames: ['type', 'method'],
labelNames: ['type', 'method', 'source'],
});

const resourceLimit = createGauge({
Expand Down Expand Up @@ -715,9 +715,9 @@ export default class MetricsMonitor {
events.onMetricEvent(
eventBus,
events.REQUEST_ORIGIN,
({ type, method }) => {
({ type, method, source }) => {
if (flagResolver.isEnabled('originMiddleware')) {
requestOriginCounter.increment({ type, method });
requestOriginCounter.increment({ type, method, source });
}
},
);
Expand Down
29 changes: 29 additions & 0 deletions src/lib/middleware/integration-headers.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { determineIntegrationSource } from './integration-headers';

test('resolves known user agents to source labels', () => {
expect(determineIntegrationSource('axios/0.27.2')).toBe('Axios');
expect(determineIntegrationSource('axios/1.4.0')).toBe('Axios');
expect(determineIntegrationSource('curl/8.6.0')).toBe('Curl');
expect(determineIntegrationSource('node-fetch/1.0.0')).toBe('Node');
expect(determineIntegrationSource('node')).toBe('Node');
expect(determineIntegrationSource('python-requests/2.31.0')).toBe('Python');
expect(determineIntegrationSource('Terraform-Provider-Unleash/1.1.1')).toBe(
'TerraformUnleash',
);
expect(determineIntegrationSource('Jira-Cloud-Unleash')).toBe(
'JiraCloudUnleash',
);
expect(determineIntegrationSource('OpenAPI-Generator/1.0.0/go')).toBe(
'OpenAPIGO',
);
expect(
determineIntegrationSource('Apache-HttpClient/4.5.13 (Java/11.0.22)'),
).toBe('Java');
expect(determineIntegrationSource('Go-http-client/1.1')).toBe('Go');
expect(
determineIntegrationSource(
'rest-client/2.0.2 (linux-gnu x86_64) ruby/2.1.7p400',
),
).toBe('RestClientRuby');
expect(determineIntegrationSource('No-http-client')).toBe('Other');
});
34 changes: 34 additions & 0 deletions src/lib/middleware/integration-headers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import type { Request } from 'express';

const ORIGIN = 'origin';
const httpMatcher = /^https?:\/\//;
const userAgentMatches = [
{ label: 'Axios', matcher: /^axios/ },
{ label: 'Curl', matcher: /^curl/ },
{ label: 'Go', matcher: /^Go-http-client/ },
{ label: 'Python', matcher: /^python-requests/ },
{ label: 'Node', matcher: /^node/ },
{ label: 'Java', matcher: /^Apache-HttpClient.*Java/ },
{ label: 'JiraCloudUnleash', matcher: /^Jira-Cloud-Unleash/ },
{ label: 'TerraformUnleash', matcher: /^Terraform-Provider-Unleash/ },
{ label: 'OpenAPIGO', matcher: /^OpenAPI-Generator\/.*\/go/ },
{ label: 'RestClientRuby', matcher: /^rest-client\/.*ruby/ },
];

export const getFilteredOrigin = (request: Request): string | undefined => {
const origin = request.headers[ORIGIN];
if (origin && httpMatcher.test(origin)) {
return origin;
}

return undefined;
};

export const determineIntegrationSource = (
userAgent: string,
): string | undefined => {
return (
userAgentMatches.find((candidate) => candidate.matcher.test(userAgent))
?.label ?? 'Other'
);
};
2 changes: 2 additions & 0 deletions src/lib/middleware/origin-middleware.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ describe('originMiddleware', () => {
expect(eventBus.emit).toHaveBeenCalledWith(REQUEST_ORIGIN, {
type: 'API',
method: req.method,
source: 'Other',
});
});

Expand All @@ -83,6 +84,7 @@ describe('originMiddleware', () => {
expect(loggerMock.info).toHaveBeenCalledWith('API request', {
method: req.method,
userAgent: TEST_USER_AGENT,
origin: undefined,
});
});
});
10 changes: 10 additions & 0 deletions src/lib/middleware/origin-middleware.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import type { Request, Response, NextFunction } from 'express';
import type { IUnleashConfig } from '../types';
import { REQUEST_ORIGIN, emitMetricEvent } from '../metric-events';
import {
determineIntegrationSource,
getFilteredOrigin,
} from './integration-headers';

export const originMiddleware = ({
getLogger,
Expand All @@ -23,13 +27,19 @@ export const originMiddleware = ({
method: req.method,
});
} else {
const userAgent = req.headers['user-agent'];
const uaLabel = userAgent
? determineIntegrationSource(userAgent)
: 'Other';
logger.info('API request', {
method: req.method,
userAgent: req.headers['user-agent'],
origin: getFilteredOrigin(req),
});
emitMetricEvent(eventBus, REQUEST_ORIGIN, {
type: 'API',
method: req.method,
source: uaLabel,
});
}

Expand Down

0 comments on commit cf83043

Please sign in to comment.