Skip to content

Commit

Permalink
chore(logging): improve logging (#185)
Browse files Browse the repository at this point in the history
* - Improve request logging including tenantId and non-sensitive headers
- Extract Postgrest URL scheme as a config (default http)

* chore(logging): improve logging

* chore(logging): format message request log, add logLevel env
  • Loading branch information
fenos committed Aug 31, 2022
1 parent daf5f4a commit 3cd3c91
Show file tree
Hide file tree
Showing 10 changed files with 117 additions and 10 deletions.
4 changes: 3 additions & 1 deletion src/admin-app.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import fastify, { FastifyInstance, FastifyServerOptions } from 'fastify'
import { default as metrics } from 'fastify-metrics'
import tenantRoutes from './routes/tenant'
import { Registry } from 'prom-client'
import tenantRoutes from './routes/tenant'
import logRequest from './plugins/log-request'

export interface AdminOptions {
register?: Registry
}

const build = (opts: FastifyServerOptions = {}, adminOpts: AdminOptions = {}): FastifyInstance => {
const app = fastify(opts)
app.register(logRequest({ excludeUrls: ['/status'] }))
app.register(tenantRoutes, { prefix: 'tenants' })
app.register(metrics, {
endpoint: '/metrics',
Expand Down
2 changes: 2 additions & 0 deletions src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { authSchema } from './schemas/auth'
import { errorSchema } from './schemas/error'
import logTenantId from './plugins/log-tenant-id'
import tenantId from './plugins/tenant-id'
import logRequest from './plugins/log-request'

interface buildOpts extends FastifyServerOptions {
exposeDocs?: boolean
Expand Down Expand Up @@ -53,6 +54,7 @@ const build = (opts: buildOpts = {}): FastifyInstance => {

app.register(tenantId)
app.register(logTenantId)
app.register(logRequest({ excludeUrls: ['/status'] }))
app.register(bucketRoutes, { prefix: 'bucket' })
app.register(objectRoutes, { prefix: 'object' })

Expand Down
66 changes: 64 additions & 2 deletions src/monitoring/logger.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,34 @@
import pino from 'pino'
import { getConfig } from '../utils/config'

const { logLevel } = getConfig()

export const logger = pino({
transport: buildTransport(),
formatters: {
level(label) {
return { level: label }
level(label, number) {
return { level: number }
},
},
serializers: {
res(reply) {
return {
url: reply.url,
statusCode: reply.statusCode,
}
},
req(request) {
return {
method: request.method,
url: request.url,
headers: whitelistHeaders(request.headers),
hostname: request.hostname,
remoteAddress: request.ip,
remotePort: request.socket.remotePort,
}
},
},
level: logLevel,
timestamp: pino.stdTimeFunctions.isoTime,
})

Expand All @@ -30,3 +51,44 @@ export function buildTransport(): pino.TransportSingleOptions | undefined {
target: './logflare',
}
}

const whitelistHeaders = (headers: Record<string, unknown>) => {
const responseMetadata: Record<string, unknown> = {}
const allowlistedRequestHeaders = [
'accept',
'cf-connecting-ip',
'cf-ipcountry',
'host',
'user-agent',
'x-forwarded-proto',
'referer',
'content-length',
'x-real-ip',
'x-client-info',
'x-forwarded-user-agent',
]
const allowlistedResponseHeaders = [
'cf-cache-status',
'cf-ray',
'content-location',
'content-range',
'content-type',
'content-length',
'date',
'transfer-encoding',
'x-kong-proxy-latency',
'x-kong-upstream-latency',
'sb-gateway-mode',
'sb-gateway-version',
]
Object.keys(headers)
.filter(
(header) =>
allowlistedRequestHeaders.includes(header) || allowlistedResponseHeaders.includes(header)
)
.forEach((header) => {
responseMetadata[header.replace(/-/g, '_')] = headers[header]
})

return responseMetadata
}
25 changes: 25 additions & 0 deletions src/plugins/log-request.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import fastifyPlugin from 'fastify-plugin'

interface RequestLoggerOptions {
excludeUrls?: string[]
}

export default (options: RequestLoggerOptions) =>
fastifyPlugin(async (fastify) => {
fastify.addHook('onResponse', async (req, reply) => {
if (options.excludeUrls?.includes(req.url)) {
return
}

const rMeth = req.method
const rUrl = req.url
const uAgent = req.headers['user-agent']
const rId = req.id
const cIP = req.ip
const statusCode = reply.statusCode

const buildLogMessage = `${rMeth} | ${statusCode} | ${cIP} | ${rId} | ${rUrl} | ${uAgent}`

req.log.info({ req, res: reply, responseTime: reply.getResponseTime() }, buildLogMessage)
})
})
5 changes: 4 additions & 1 deletion src/plugins/log-tenant-id.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ import fastifyPlugin from 'fastify-plugin'

export default fastifyPlugin(async (fastify) => {
fastify.addHook('onRequest', async (request, reply) => {
reply.log = request.log = request.log.child({ tenantId: request.tenantId })
reply.log = request.log = request.log.child({
tenantId: request.tenantId,
project: request.tenantId,
})
})
})
13 changes: 10 additions & 3 deletions src/plugins/postgrest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,15 @@ declare module 'fastify' {
}

async function getPostgrestClient(request: FastifyRequest, jwt: string): Promise<PostgrestClient> {
const { anonKey, isMultitenant, postgrestURL, postgrestURLSuffix, xForwardedHostRegExp } =
getConfig()
const {
anonKey,
isMultitenant,
postgrestURL,
postgrestURLScheme,
postgrestURLSuffix,
xForwardedHostRegExp,
} = getConfig()

let url = postgrestURL
let apiKey = anonKey
if (isMultitenant && xForwardedHostRegExp) {
Expand All @@ -25,7 +32,7 @@ async function getPostgrestClient(request: FastifyRequest, jwt: string): Promise
if (!new RegExp(xForwardedHostRegExp).test(xForwardedHost)) {
throw new Error('X-Forwarded-Host header does not match regular expression')
}
url = `http://${xForwardedHost}${postgrestURLSuffix}`
url = `${postgrestURLScheme}://${xForwardedHost}${postgrestURLSuffix}`
apiKey = await getAnonKey(request.tenantId)
}
const postgrest = new PostgrestClient(url, {
Expand Down
4 changes: 2 additions & 2 deletions src/routes/bucket/createBucket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export default async function routes(fastify: FastifyInstance) {
try {
owner = await getOwner(request.jwt, jwtSecret)
} catch (err: any) {
console.log(err)
request.log.error({ error: err }, 'unable to get owner')
return response.status(400).send(createResponse(err.message, '400', err.message))
}

Expand Down Expand Up @@ -84,7 +84,7 @@ export default async function routes(fastify: FastifyInstance) {
.single()

if (error) {
request.log.error({ error }, 'error bucket')
request.log.error({ error }, 'error creating bucket')
return response.status(400).send(transformPostgrestError(error, status))
}
request.log.info({ results }, 'results')
Expand Down
2 changes: 1 addition & 1 deletion src/routes/object/updateObject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ export default async function routes(fastify: FastifyInstance) {
try {
owner = await getOwner(request.jwt, jwtSecret)
} catch (err: any) {
console.log(err)
request.log.error({ error: err }, 'unable to get owner')
return response.status(400).send(createResponse(err.message, '400', err.message))
}

Expand Down
2 changes: 2 additions & 0 deletions src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const exposeDocs = true

const adminApp: FastifyInstance<Server, IncomingMessage, ServerResponse> = buildAdmin({
logger,
disableRequestLogging: true,
requestIdHeader: adminRequestIdHeader,
})

Expand All @@ -33,6 +34,7 @@ const exposeDocs = true

const app: FastifyInstance<Server, IncomingMessage, ServerResponse> = build({
logger,
disableRequestLogging: true,
exposeDocs,
requestIdHeader,
})
Expand Down
4 changes: 4 additions & 0 deletions src/utils/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@ type StorageConfigType = {
multitenantDatabaseUrl?: string
postgrestURL: string
postgrestURLSuffix?: string
postgrestURLScheme?: string
region: string
requestIdHeader?: string
serviceKey: string
storageBackendType: StorageBackendType
tenantId: string
urlLengthLimit: number
xForwardedHostRegExp?: string
logLevel?: string
logflareEnabled?: boolean
logflareApiKey?: string
logflareSourceToken?: string
Expand Down Expand Up @@ -62,6 +64,7 @@ export function getConfig(): StorageConfigType {
multitenantDatabaseUrl: getOptionalConfigFromEnv('MULTITENANT_DATABASE_URL'),
postgrestURL: getOptionalIfMultitenantConfigFromEnv('POSTGREST_URL') || '',
postgrestURLSuffix: getOptionalConfigFromEnv('POSTGREST_URL_SUFFIX'),
postgrestURLScheme: getOptionalConfigFromEnv('POSTGREST_URL_SCHEME') || 'http',
region: getConfigFromEnv('REGION'),
requestIdHeader: getOptionalConfigFromEnv('REQUEST_ID_HEADER'),
serviceKey: getOptionalIfMultitenantConfigFromEnv('SERVICE_KEY') || '',
Expand All @@ -72,6 +75,7 @@ export function getConfig(): StorageConfigType {
'',
urlLengthLimit: Number(getOptionalConfigFromEnv('URL_LENGTH_LIMIT')) || 7_500,
xForwardedHostRegExp: getOptionalConfigFromEnv('X_FORWARDED_HOST_REGEXP'),
logLevel: getOptionalConfigFromEnv('LOG_LEVEL') || 'trace',
logflareEnabled: getOptionalConfigFromEnv('LOGFLARE_ENABLED') === 'true',
logflareApiKey: getOptionalConfigFromEnv('LOGFLARE_API_KEY'),
logflareSourceToken: getOptionalConfigFromEnv('LOGFLARE_SOURCE_TOKEN'),
Expand Down

0 comments on commit 3cd3c91

Please sign in to comment.