Merge pull request #26198 from overleaf/jpa-recurly-metrics
[web] add metrics for recurly API usage GitOrigin-RevId: 89840829f86ce1ff750d57f3445f279f4b151d6f
This commit is contained in:
@@ -22,6 +22,7 @@ const {
|
||||
MissingBillingInfoError,
|
||||
SubtotalLimitExceededError,
|
||||
} = require('./Errors')
|
||||
const RecurlyMetrics = require('./RecurlyMetrics')
|
||||
|
||||
/**
|
||||
* @import { PaymentProviderSubscriptionChangeRequest } from './PaymentProviderEntities'
|
||||
@@ -29,10 +30,28 @@ const {
|
||||
* @import { PaymentMethod } from './types'
|
||||
*/
|
||||
|
||||
class RecurlyClientWithErrorHandling extends recurly.Client {
|
||||
/**
|
||||
* @param {import('recurly/lib/recurly/Http').Response} response
|
||||
* @return {Error | null}
|
||||
* @private
|
||||
*/
|
||||
_errorFromResponse(response) {
|
||||
RecurlyMetrics.recordMetrics(
|
||||
response.status,
|
||||
response.rateLimit,
|
||||
response.rateLimitRemaining,
|
||||
response.rateLimitReset.getTime()
|
||||
)
|
||||
// @ts-ignore
|
||||
return super._errorFromResponse(response)
|
||||
}
|
||||
}
|
||||
|
||||
const recurlySettings = Settings.apis.recurly
|
||||
const recurlyApiKey = recurlySettings ? recurlySettings.apiKey : undefined
|
||||
|
||||
const client = new recurly.Client(recurlyApiKey)
|
||||
const client = new RecurlyClientWithErrorHandling(recurlyApiKey)
|
||||
|
||||
/**
|
||||
* Get account for a given user
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
const Metrics = require('@overleaf/metrics')
|
||||
|
||||
/**
|
||||
* @param {number} status
|
||||
* @param {number} rateLimit
|
||||
* @param {number} rateLimitRemaining
|
||||
* @param {number} rateLimitReset
|
||||
*/
|
||||
function recordMetrics(status, rateLimit, rateLimitRemaining, rateLimitReset) {
|
||||
Metrics.inc('recurly_request', 1, { status })
|
||||
const metrics = { rateLimit, rateLimitRemaining, rateLimitReset }
|
||||
for (const [method, v] of Object.entries(metrics)) {
|
||||
if (Number.isNaN(v)) continue
|
||||
Metrics.gauge('recurly_request_rate_limiting', v, 1, { method })
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Response} response
|
||||
*/
|
||||
function recordMetricsFromResponse(response) {
|
||||
const rateLimit = parseInt(
|
||||
response.headers.get('X-RateLimit-Limit') || '',
|
||||
10
|
||||
)
|
||||
const rateLimitRemaining = parseInt(
|
||||
response.headers.get('X-RateLimit-Remaining') || '',
|
||||
10
|
||||
)
|
||||
const rateLimitReset =
|
||||
parseInt(response.headers.get('X-RateLimit-Reset') || '', 10) * 1000
|
||||
recordMetrics(response.status, rateLimit, rateLimitRemaining, rateLimitReset)
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
recordMetrics,
|
||||
recordMetricsFromResponse,
|
||||
}
|
||||
@@ -9,6 +9,7 @@ const logger = require('@overleaf/logger')
|
||||
const Errors = require('../Errors/Errors')
|
||||
const SubscriptionErrors = require('./Errors')
|
||||
const { callbackify } = require('@overleaf/promise-utils')
|
||||
const RecurlyMetrics = require('./RecurlyMetrics')
|
||||
|
||||
/**
|
||||
* Updates the email address of a Recurly account
|
||||
@@ -417,9 +418,15 @@ const promises = {
|
||||
}
|
||||
|
||||
try {
|
||||
return await fetchStringWithResponse(fetchUrl, fetchOptions)
|
||||
const { body, response } = await fetchStringWithResponse(
|
||||
fetchUrl,
|
||||
fetchOptions
|
||||
)
|
||||
RecurlyMetrics.recordMetricsFromResponse(response)
|
||||
return { body, response }
|
||||
} catch (error) {
|
||||
if (error instanceof RequestFailedError) {
|
||||
RecurlyMetrics.recordMetricsFromResponse(error.response)
|
||||
if (error.response.status === 404 && expect404) {
|
||||
return { response: error.response, body: null }
|
||||
} else if (error.response.status === 422 && expect422) {
|
||||
|
||||
@@ -7,6 +7,9 @@ class MockRecurlyApi extends AbstractMockApi {
|
||||
this.mockSubscriptions = []
|
||||
this.redemptions = {}
|
||||
this.coupons = {}
|
||||
this.rateLimitResetSeconds = Math.ceil(
|
||||
(Date.now() + 24 * 60 * 60 * 1000) / 1000
|
||||
)
|
||||
}
|
||||
|
||||
addMockSubscription(recurlySubscription) {
|
||||
@@ -25,7 +28,21 @@ class MockRecurlyApi extends AbstractMockApi {
|
||||
)
|
||||
}
|
||||
|
||||
getRateLimitHeaders() {
|
||||
return {
|
||||
'X-RateLimit-Limit': 1000,
|
||||
'X-RateLimit-Remaining': 999,
|
||||
'X-RateLimit-Reset': this.rateLimitResetSeconds,
|
||||
}
|
||||
}
|
||||
|
||||
applyRoutes() {
|
||||
this.app.use((req, res, next) => {
|
||||
for (const [name, v] of Object.entries(this.getRateLimitHeaders())) {
|
||||
res.setHeader(name, v)
|
||||
}
|
||||
next()
|
||||
})
|
||||
this.app.get('/subscriptions/:id', (req, res) => {
|
||||
const subscription = this.getMockSubscriptionById(req.params.id)
|
||||
if (!subscription) {
|
||||
|
||||
Reference in New Issue
Block a user