import { BlockedIpError , GithubClientError , RateLimitingError } from "./errors" ; import Logger from "bunyan" ; import url from "url" ; import statsd from "../../config/statsd" ; import { metricError } from "../../config/metric-names" ; import { AxiosResponse } from "axios" ; /**
 * Extract the path name from a URL.
 */ const extractPath = ( someUrl = "" ) : string => url . parse ( someUrl ) . pathname || "" ; const RESPONSE_TIME_HISTOGRAM_BUCKETS = "100_1000_2000_3000_5000_10000_30000_60000" ; const ONE_HOUR_IN_SECONDS = 60 * 60 ; param { import ( "axios" ) . AxiosRequestConfig } config returns { import ( "axios" ) . AxiosRequestConfig } export const setRequestStartTime = ( config ) => { config . requestStartTime = new Date ( ) ; return config ; } ; const sendResponseMetrics = ( metricName , response ? , status ? ) => { status = `${ status || response ?. status }` ; const requestDurationMs = Number ( Date . now ( ) - ( response ?. config ?. requestStartTime || 0 ) ) ; const tags = { client : "axios" , method : response ?. config ?. method ?. toUpperCase ( ) , path : extractPath ( response ?. config ?. originalUrl ) , status : status } ; statsd . histogram ( metricName , requestDurationMs , tags ) ; tags [ "gsd_histogram" ] = RESPONSE_TIME_HISTOGRAM_BUCKETS ; statsd . histogram ( metricName , requestDurationMs , tags ) ; return response ; } ; export const instrumentRequest = ( metricName ) => ( response ) => { if ( ! response ) { return ; } return sendResponseMetrics ( metricName , response ) ; } ; param { import ( "axios" ) . AxiosError } error param metricName returns { Promise < Error > } export const instrumentFailedRequest = ( metricName ) => ( error ) => { if ( error instanceof RateLimitingError ) { sendResponseMetrics ( metricName , error . cause ?. response , "rateLimiting" ) ; } else if ( error instanceof BlockedIpError ) { sendResponseMetrics ( metricName , error . cause ?. response , "blockedIp" ) ; statsd . increment ( metricError . blockedByGitHubAllowlist ) ; } else if ( error instanceof GithubClientError ) { sendResponseMetrics ( metricName , error . cause ?. response ) ; } else { sendResponseMetrics ( metricName , error . response ) ; } return Promise . reject ( error ) ; } ; export const handleFailedRequest = ( logger ) => ( error ) => { const response = error . response as AxiosResponse ; if ( response ) { const status = response ?. status ; const errorMessage = `Error executing Axios Request ` + error . message ; const rateLimitResetHeaderValue = response . headers ?. [ "x-ratelimit-reset" ] ; const rateLimitRemainingHeaderValue = response . headers ?. [ "x-ratelimit-remaining" ] ; if ( status === 403 && rateLimitRemainingHeaderValue == "0" ) { logger . warn ( { err : error } , "Rate limiting error" ) ; const rateLimitReset = parseInt ( rateLimitResetHeaderValue ) || Date . now ( ) / 1000 + ONE_HOUR_IN_SECONDS ; return Promise . reject ( new RateLimitingError ( rateLimitReset , 0 , error , status ) ) ; } if ( status === 403 && response . data ?. message ?. includes ( "has an IP allow list enabled" ) ) { logger . warn ( { err : error } , "Blocked by GitHub allowlist" ) ; return Promise . reject ( new BlockedIpError ( error , status ) ) ; } const isWarning = status && ( status >= 300 && status < 500 && status !== 400 ) ; isWarning ? logger . warn ( errorMessage ) : logger . error ( { err : error } , errorMessage ) ; return Promise . reject ( new GithubClientError ( errorMessage , error , status ) ) ; } return Promise . reject ( error ) ; } ;