mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	feat: resolve useragent source and add as source label to metrics (#7883)
This commit is contained in:
		
							parent
							
								
									183a9fc737
								
							
						
					
					
						commit
						cf83043d8a
					
				| @ -30,6 +30,7 @@ type MetricEvent = | ||||
| type RequestOriginEventPayload = { | ||||
|     type: 'UI' | 'API'; | ||||
|     method: Request['method']; | ||||
|     source?: string; | ||||
| }; | ||||
| 
 | ||||
| type MetricEventPayloads = { | ||||
|  | ||||
| @ -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({ | ||||
| @ -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 }); | ||||
|                 } | ||||
|             }, | ||||
|         ); | ||||
|  | ||||
							
								
								
									
										29
									
								
								src/lib/middleware/integration-headers.test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								src/lib/middleware/integration-headers.test.ts
									
									
									
									
									
										Normal file
									
								
							| @ -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
									
								
								src/lib/middleware/integration-headers.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								src/lib/middleware/integration-headers.ts
									
									
									
									
									
										Normal file
									
								
							| @ -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' | ||||
|     ); | ||||
| }; | ||||
| @ -69,6 +69,7 @@ describe('originMiddleware', () => { | ||||
|         expect(eventBus.emit).toHaveBeenCalledWith(REQUEST_ORIGIN, { | ||||
|             type: 'API', | ||||
|             method: req.method, | ||||
|             source: 'Other', | ||||
|         }); | ||||
|     }); | ||||
| 
 | ||||
| @ -83,6 +84,7 @@ describe('originMiddleware', () => { | ||||
|         expect(loggerMock.info).toHaveBeenCalledWith('API request', { | ||||
|             method: req.method, | ||||
|             userAgent: TEST_USER_AGENT, | ||||
|             origin: undefined, | ||||
|         }); | ||||
|     }); | ||||
| }); | ||||
|  | ||||
| @ -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, | ||||
| @ -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, | ||||
|             }); | ||||
|         } | ||||
| 
 | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user