Skip to content

Commit

Permalink
Merge pull request #3822 from alphagov/pp-11683-healthcontroller-upda…
Browse files Browse the repository at this point in the history
…te-to-use-axios

PP-11683 Update healthcheck controller to use Axios
  • Loading branch information
iqbalgds authored Apr 16, 2024
2 parents f2ce872 + c915499 commit 9459043
Show file tree
Hide file tree
Showing 8 changed files with 272 additions and 37 deletions.
34 changes: 25 additions & 9 deletions app/controllers/healthcheck.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@
const _ = require('lodash')
const logger = require('../utils/logger')(__filename)
const { getLoggingFields } = require('../utils/logging-fields-helper')
const baseClient = require('../services/clients/base.client/base.client')
const { Client } = require('@govuk-pay/pay-js-commons/lib/utils/axios-base-client/axios-base-client')
const { configureClient } = require('../services/clients/base/config')
const SERVICE_NAME = 'frontend'
const client = new Client(SERVICE_NAME)

const healthyPingResponse = { ping: { healthy: true } }

Expand All @@ -11,21 +14,34 @@ const respond = (res, statusCode, data) => {
res.end(JSON.stringify(data))
}

module.exports.healthcheck = (req, res) => {
module.exports.healthcheck = async (req, res) => {
if (process.env.FORWARD_PROXY_URL) {
baseClient.get(`${process.env.FORWARD_PROXY_URL}/nginx_status`, {}, (err, response) => {
const statusCode = _.get(response, 'statusCode')
if (err || statusCode !== 200) {
const url = `${process.env.FORWARD_PROXY_URL}/nginx_status`

configureClient(client, url)

let response

try {
response = await client.get(url, 'Healthcheck')

if (response.status !== 200) {
logger.error('Healthchecking forward proxy returned error', {
...getLoggingFields(req),
error: err,
status_code: statusCode
status_code: response.status
})
respond(res, 502, _.merge(healthyPingResponse, { proxy: { healthy: false } }))
} else {
respond(res, 200, _.merge(healthyPingResponse, { proxy: { healthy: true } }))
respond(res, 200, healthyPingResponse)
}
})
} catch (err) {
logger.error('Healthchecking forward proxy returned error', {
...getLoggingFields(req),
error: err,
status_code: response.status
})
respond(res, 502, _.merge(healthyPingResponse, { proxy: { healthy: false } }))
}
} else {
respond(res, 200, healthyPingResponse)
}
Expand Down
40 changes: 40 additions & 0 deletions app/services/clients/base/config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
'use strict'

const requestLogger = require('./request-logger')
const { getRequestCorrelationIDField } = require('./request-context')
const { CORRELATION_HEADER } = require('../../../../config/correlation-header')

function transformRequestAddHeaders () {
const correlationId = getRequestCorrelationIDField()
const headers = {}
if (correlationId) {
headers[CORRELATION_HEADER] = correlationId
}
return headers
}

function onRequestStart (context) {
requestLogger.logRequestStart(context)
}

function onSuccessResponse (context) {
requestLogger.logRequestEnd(context)
}

function onFailureResponse (context) {
requestLogger.logRequestEnd(context)
requestLogger.logRequestFailure(context)
}

function configureClient (client, baseUrl) {
client.configure(baseUrl, {
transformRequestAddHeaders,
onRequestStart,
onSuccessResponse,
onFailureResponse
})
}

module.exports = {
configureClient
}
90 changes: 90 additions & 0 deletions app/services/clients/base/config.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
'use strict'

const nock = require('nock')
const sinon = require('sinon')
const proxyquire = require('proxyquire')
const { expect } = require('chai')
const { Client } = require('@govuk-pay/pay-js-commons/lib/utils/axios-base-client/axios-base-client')

const baseUrl = 'http://localhost:8000'
const app = 'an-app'

const logInfoSpy = sinon.spy()

function getConfigWithMocks (correlationId) {
const config = proxyquire('./config.js', {
'./request-context': {
getRequestCorrelationIDField: () => correlationId
},
'./request-logger': proxyquire('./request-logger', {
'../../../utils/logger': () => ({
info: logInfoSpy
})
})
})
return config
}

describe('Client config', () => {
beforeEach(() => {
logInfoSpy.resetHistory()
})

describe('Headers', () => {
it('should add correlation ID as header when correlation ID exists on request context', async () => {
const client = new Client(app)
const config = getConfigWithMocks('abc123')

config.configureClient(client, baseUrl)

nock(baseUrl)
.get('/')
.reply(200)

const response = await client.get('/', 'foo')

expect(response.status).to.equal(200)
expect(response.request.headers).to.have.property('x-request-id', 'abc123')
})

it('should not add correlation ID as header when correlation ID does not exist on request context', async () => {
const client = new Client(app)
const config = getConfigWithMocks()
config.configureClient(client, baseUrl)

nock(baseUrl)
.get('/')
.reply(200)

const response = await client.get('/', 'foo')
expect(response.status).to.equal(200)
expect(response.request.headers).to.not.have.key('x-request-id')
})
})

describe('Logging', () => {
it('should log request start', async () => {
const client = new Client(app)
const config = getConfigWithMocks('abc123')
config.configureClient(client, baseUrl)

nock(baseUrl)
.get('/')
.reply(200)

const response = await client.get('/', 'do something', {
additionalLoggingFields: { foo: 'bar' }
})

expect(response.status).to.equal(200)

sinon.assert.calledWith(logInfoSpy, 'Calling an-app to do something', {
service: app,
method: 'get',
url: '/',
description: 'do something',
foo: 'bar'
})
})
})
})
38 changes: 38 additions & 0 deletions app/services/clients/base/request-context.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
'use strict'

const { CORRELATION_ID } = require('@govuk-pay/pay-js-commons').logging.keys

const { AsyncLocalStorage } = require('async_hooks')
const { CORRELATION_HEADER } = require('../../../../config/correlation-header')

const asyncLocalStorage = new AsyncLocalStorage()

function requestContextMiddleware (req, res, next) {
asyncLocalStorage.run({}, () => {
asyncLocalStorage.getStore()[CORRELATION_ID] = req.headers[CORRELATION_HEADER]
next()
})
}

function addField (key, value) {
if (asyncLocalStorage.getStore()) {
asyncLocalStorage.getStore()[key] = value
}
}

function getRequestCorrelationIDField () {
if (asyncLocalStorage.getStore()) {
return asyncLocalStorage.getStore()[CORRELATION_ID]
}
}

function getLoggingFields () {
return asyncLocalStorage.getStore()
}

module.exports = {
requestContextMiddleware,
addField,
getRequestCorrelationIDField,
getLoggingFields
}
53 changes: 53 additions & 0 deletions app/services/clients/base/request-logger.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
const logger = require('../../../utils/logger')(__filename)

module.exports = {
logRequestStart: context => {
logger.info(`Calling ${context.service} to ${context.description}`, {
service: context.service,
method: context.method,
url: context.url,
description: context.description,
...context.additionalLoggingFields
})
},

logRequestEnd: (context) => {
const responseTime = (context.startTime && new Date() - context.startTime) || context.responseTime
logger.info(`${context.method} to ${context.url} ended - elapsed time: ${responseTime} ms`, {
service: context.service,
method: context.method,
url: context.url,
description: context.description,
response_time: responseTime,
status: context.status,
...context.additionalLoggingFields
})
},

logRequestFailure: (context) => {
let message = `Calling ${context.service} to ${context.description} failed`
if (context.retry) {
message = message + ' - request will be retried'
}

logger.info(message, {
service: context.service,
method: context.method,
url: context.url,
description: context.description,
status: context.status,
...context.additionalLoggingFields
})
},

logRequestError: (context, error) => {
logger.error(`Calling ${context.service} to ${context.description} threw exception`, {
service: context.service,
method: context.method,
url: context.url,
description: context.description,
error: error,
...context.additionalLoggingFields
})
}
}
Loading

0 comments on commit 9459043

Please sign in to comment.