Skip to content

Commit

Permalink
Merge pull request #85 from DEFRA/feat/ability-to-disable-health-checks
Browse files Browse the repository at this point in the history
health check updates
  • Loading branch information
defra-dom authored Oct 13, 2024
2 parents 4ce2288 + 3f6a333 commit f53c185
Show file tree
Hide file tree
Showing 24 changed files with 218 additions and 101 deletions.
8 changes: 5 additions & 3 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
PORT=3000
LOG_LEVEL=debug
ALL_SCHEMA_ON=true
ENVIRONMENT= # Set to name of environment

# Mock server
PORT_MOCK=3100
Expand Down Expand Up @@ -42,9 +43,10 @@ RP_INTERNAL_APIM_ACCESS_TOKEN_URL=http://127.0.0.1:3100/apim/
RP_INTERNAL_APIM_URL=http://127.0.0.1:3100/v1/

# Health checks
HEALTH_CHECK_ENABLED=true # Enable health checks
RP_INTERNAL_HEALTH_CHECK_ORGANISATION_ID=some-org-id # Organisation ID for health check
ENTRA_HEALTH_CHECK_USER_OBJECT_ID=some-user-object-id # User object ID for health check

HEALTH_CHECK_AUTHENTICATE_DATABASE_THROTTLE_TIME_MS= # time in ms
HEALTH_CHECK_RURAL_PAYMENTS_APIM_THROTTLE_TIME_MS= # time in ms
HEALTH_CHECK_ENTRA_THROTTLE_TIME_MS= # time in ms
HEALTH_CHECK_AUTHENTICATE_DATABASE_THROTTLE_TIME_MS=300000 # Only check authenticate database every 5 miutes
HEALTH_CHECK_RURAL_PAYMENTS_APIM_THROTTLE_TIME_MS=300000 # Only check rural payments and apim every 5 miutes
HEALTH_CHECK_ENTRA_THROTTLE_TIME_MS=300000 # Only check entra every 5 miutes
12 changes: 10 additions & 2 deletions app/data-sources/rural-payments/RuralPayments.js
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,11 @@ export class RuralPayments extends RESTDataSource {
this.logger.http('#datasource - Rural payments - response', {
code: RURALPAYMENTS_API_REQUEST_001,
requestTimeMs,
request: { ...request, path },
request: {
method: request.method.toUpperCase(),
headers: request.headers,
path
},
response: { statusCode: request.response?.status }
})
this.logger.debug('#datasource - Rural payments - response detail', {
Expand All @@ -242,7 +246,11 @@ export class RuralPayments extends RESTDataSource {
this.logger.http('#datasource - apim - response', {
code: APIM_APIM_REQUEST_001,
requestTimeMs,
request: { ...request, path },
request: {
method: request.method.toUpperCase(),
headers: request.headers,
path
},
response: { status: result.response?.status }
})
this.logger.debug('#datasource - apim - response detail', {
Expand Down
10 changes: 5 additions & 5 deletions app/data-sources/rural-payments/RuralPaymentsBusiness.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { NotFound } from '../../errors/graphql.js'
import { RURALPAYMENTS_API_NOT_FOUND_001 } from '../../logger/codes.js'
import { sampleResponse } from '../../logger/utils.js'
import { RuralPayments } from './RuralPayments.js'

export class RuralPaymentsBusiness extends RuralPayments {
Expand Down Expand Up @@ -39,9 +38,10 @@ export class RuralPaymentsBusiness extends RuralPayments {
throw new NotFound('Rural payments organisation not found')
}

this.logger.silly('Organisation by SBI', { response: { body: organisationResponse } })

const response = organisationResponse?._data?.pop() || {}

this.logger.silly('Organisation by SBI', { response: sampleResponse(response) })
return response?.id ? this.getOrganisationById(response.id) : null
}

Expand All @@ -51,7 +51,7 @@ export class RuralPaymentsBusiness extends RuralPayments {
const response = await this.get(
`authorisation/organisation/${organisationId}`
)
this.logger.silly('Organisation customers by organisation ID', { response: sampleResponse(response) })
this.logger.silly('Organisation customers by organisation ID', { response: { body: response } })
return response._data
}

Expand Down Expand Up @@ -89,7 +89,7 @@ export class RuralPaymentsBusiness extends RuralPayments {
`SitiAgriApi/cph/organisation/${organisationId}/cph-numbers`
)

this.logger.silly('Organisation CPH collection by organisation ID', { response: sampleResponse(response) })
this.logger.silly('Organisation CPH collection by organisation ID', { response: { body: response } })
return response.data
}

Expand All @@ -105,7 +105,7 @@ export class RuralPaymentsBusiness extends RuralPayments {
)}`
)

this.logger.silly('Organisation CPH info by organisation ID and CPH number', { response: sampleResponse(response) })
this.logger.silly('Organisation CPH info by organisation ID and CPH number', { response: { body: response } })

return response.data
}
Expand Down
12 changes: 6 additions & 6 deletions app/data-sources/rural-payments/RuralPaymentsCustomer.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { NotFound } from '../../errors/graphql.js'
import { RURALPAYMENTS_API_NOT_FOUND_001 } from '../../logger/codes.js'
import { sampleResponse } from '../../logger/utils.js'
import { RuralPayments } from './RuralPayments.js'

export class RuralPaymentsCustomer extends RuralPayments {
Expand All @@ -20,11 +19,12 @@ export class RuralPaymentsCustomer extends RuralPayments {
'Content-Type': 'application/json'
}
})
this.logger.silly('Customer by CRN response', { response: { body: customerResponse } })

const response = customerResponse._data.pop() || {}
this.logger.silly('Customer by CRN response', { response: sampleResponse(response) })

if (!response?.id) {
this.logger.warn('#datasource - Rural payments - Customer not found for CRN', { crn, code: RURALPAYMENTS_API_NOT_FOUND_001 })
this.logger.warn('#datasource - Rural payments - Customer not found for CRN', { crn, code: RURALPAYMENTS_API_NOT_FOUND_001, response: { body: customerResponse } })
throw new NotFound('Rural payments customer not found')
}

Expand All @@ -37,11 +37,11 @@ export class RuralPaymentsCustomer extends RuralPayments {
const response = await this.get(`person/${personId}/summary`)

if (!response?._data?.id) {
this.logger.warn('#datasource - Rural payments - Customer not found for Person ID', { personId, code: RURALPAYMENTS_API_NOT_FOUND_001 })
this.logger.warn('#datasource - Rural payments - Customer not found for Person ID', { personId, code: RURALPAYMENTS_API_NOT_FOUND_001, response: { body: response } })
throw new NotFound('Rural payments customer not found')
}

this.logger.silly('Person by person ID response', { response: sampleResponse(response) })
this.logger.silly('Person by person ID response', { response: { body: response } })
return response._data
}

Expand All @@ -54,7 +54,7 @@ export class RuralPaymentsCustomer extends RuralPayments {
`organisation/person/${personId}/summary?search=&page-size=${process.env.VERSION_1_PAGE_SIZE || 100}`
)

this.logger.silly('Person businesses by person ID response', { response: sampleResponse(personBusinessSummaries) })
this.logger.silly('Person businesses by person ID response', { response: { body: personBusinessSummaries } })
return personBusinessSummaries._data
}

Expand Down
3 changes: 1 addition & 2 deletions app/graphql/resolvers/business/business.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { NotFound } from '../../../errors/graphql.js'
import { RURALPAYMENTS_API_NOT_FOUND_001 } from '../../../logger/codes.js'
import { logger } from '../../../logger/logger.js'
import { sampleResponse } from '../../../logger/utils.js'
import { transformOrganisationCSApplicationToBusinessApplications } from '../../../transformers/rural-payments/applications-cs.js'
import { transformOrganisationCPH } from '../../../transformers/rural-payments/business-cph.js'
import {
Expand Down Expand Up @@ -32,7 +31,7 @@ export const Business = {
)
logger.silly('Got business customers', {
organisationId,
customers: sampleResponse(customers)
response: { body: customers }
})
return transformOrganisationCustomers(customers)
},
Expand Down
5 changes: 2 additions & 3 deletions app/graphql/resolvers/customer/customer.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { logger } from '../../../logger/logger.js'
import { sampleResponse } from '../../../logger/utils.js'
import { transformAuthenticateQuestionsAnswers } from '../../../transformers/authenticate/question-answers.js'
import {
ruralPaymentsPortalCustomerTransformer,
Expand Down Expand Up @@ -35,7 +34,7 @@ export const Customer = {
sbi
)

logger.silly('Got customer business', { crn, personId, summary: sampleResponse(summary) })
logger.silly('Got customer business', { crn, personId, response: { body: summary } })
return transformPersonSummaryToCustomerAuthorisedFilteredBusiness(
{ personId, crn, sbi },
summary
Expand All @@ -49,7 +48,7 @@ export const Customer = {
personId
)

logger.silly('Got customer businesses', { crn, personId, summary: sampleResponse(summary) })
logger.silly('Got customer businesses', { crn, personId, response: { body: summary } })
return transformPersonSummaryToCustomerAuthorisedBusinesses(
{ personId, crn },
summary
Expand Down
1 change: 1 addition & 0 deletions app/logger/codes.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export const DAL_APPLICATION_REQUEST_001 = 'DAL_APPLICATION_REQUEST_001'
export const DAL_APPLICATION_RESPONSE_001 = 'DAL_APPLICATION_RESPONSE_001'
export const DAL_UNHANDLED_ERROR_001 = 'DAL_UNHANDLED_ERROR_001'
export const DAL_RESOLVERS_BUSINESS_001 = 'DAL_RESOLVERS_BUSINESS_001'
export const DAL_HEALTH_CHECK_001 = 'DAL_HEALTH_CHECK_001'

export const RURALPAYMENTS_API_REQUEST_001 = 'RURALPAYMENTS_API_REQUEST_001'
export const RURALPAYMENTS_API_NOT_FOUND_001 = 'RURALPAYMENTS_API_NOT_FOUND_001'
Expand Down
19 changes: 11 additions & 8 deletions app/logger/logger.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,34 +4,37 @@
* error: // Used for errors that prevent the application from operating correctly
* warn: // Used for errors that do not prevent the application from operating correctly, but may cause issues
* info: // Application access logs
* http: // Third party response logs without body
* verbose: // Third party access logs
* debug: // Third party response logs
* debug: // Third party response logs with body
* silly: // Detailed function logs
*/

import { createLogger, format, transports } from 'winston'
import { jsonStringify } from './utils.js'
import { stackTraceFormatter, redactSensitiveData } from './winstonFormatters.js'
import { redactSensitiveData, sampleResponseBodyData } from './winstonFormatters.js'

const transportTypes = []
// If AppInsights is enabled, means we are running in Azure, format logs for AppInsights
if (process.env.APPINSIGHTS_CONNECTIONSTRING) {
transportTypes.push(
new transports.Console({
format: format.combine(redactSensitiveData, stackTraceFormatter, format.json())
format: format.combine(
sampleResponseBodyData(),
redactSensitiveData(),
format.json()
)
})
)
} else {
// if AppInsights is not enabled, send logs to console
transportTypes.push(new transports.Console({
format: format.combine(
sampleResponseBodyData(),
redactSensitiveData(),
format.align(),
format.colorize(),
redactSensitiveData,
stackTraceFormatter,
format.printf(info => {
return `${info.level}: ${info.message} ${jsonStringify(info)}`
})
format.printf(info => `${info.level}: ${info.message}${Object.keys(info).length > 2 ? `\n${jsonStringify(info)}` : ''}`)
)
}))
}
Expand Down
3 changes: 2 additions & 1 deletion app/logger/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@ export const sampleResponse = response => {
if (Array.isArray(response?._data)) {
return {
...response,
_data: sampleArray(response._data)
_data: sampleArray(response._data),
__sampled: true
}
}

Expand Down
48 changes: 19 additions & 29 deletions app/logger/winstonFormatters.js
Original file line number Diff line number Diff line change
@@ -1,39 +1,29 @@
import fastRedact from 'fast-redact'
import { format } from 'winston'
import { sampleResponse } from './utils.js'

const maxStackTrace = 50

Error.stackTraceFormatterLimit = Error.stackTraceFormatterLimit < maxStackTrace ? maxStackTrace : Error.stackTraceFormatterLimit
const serialize = info => {
const symbols = Object.getOwnPropertySymbols(info).reduce((symbols, symbol) => {
symbols[symbol] = info[symbol]
return symbols
}, {})
const infoCloned = structuredClone(info)
return {
...infoCloned,
...symbols
}
}

const redact = fastRedact({
paths: ['request.headers.authorization'],
serialize: false
paths: ['*.*.authorization', '*.*.Authorization'],
serialize
})

export const stackTraceFormatter = format.printf((info) => {
if (!info?.stack && !info?.error?.stack) {
try {
throw new Error()
} catch (e) {
info.stack = e.stack
.split('\n')
.filter(
line =>
!line.includes('node_modules') &&
!line.includes('logger.js') &&
!line.includes('winstonFormatters.js') &&
!line.includes('node:')
)
info.stack.shift()
if (!info?.stack?.length) {
delete info.stack
}
}
}
export const redactSensitiveData = format(redact)

export const sampleResponseBodyData = format(info => {
if (info?.response?.body) {
info.response.body = sampleResponse(info.response.body)
}
return info
})

export const redactSensitiveData = format.printf(info => {
return redact(JSON.parse(JSON.stringify(info)))
})
Loading

0 comments on commit f53c185

Please sign in to comment.