From 0fce77ca1f3a70ff637fe2f2fd87840dedd740a5 Mon Sep 17 00:00:00 2001 From: Gus Narea Date: Fri, 29 Jan 2021 15:56:30 +0000 Subject: [PATCH] feat: Implement cloud logging and error reporting Part of https://github.com/relaycorp/cloud-gateway/issues/10 --- package-lock.json | 9 ++++ package.json | 1 + src/utilities/logging.spec.ts | 78 +++++++++++++++++++++++++++++++++++ src/utilities/logging.ts | 13 ++++++ 4 files changed, 101 insertions(+) create mode 100644 src/utilities/logging.spec.ts create mode 100644 src/utilities/logging.ts diff --git a/package-lock.json b/package-lock.json index ee0d53328..d0eb57771 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1992,6 +1992,15 @@ "aws-sdk": "^2.831.0" } }, + "@relaycorp/pino-cloud": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@relaycorp/pino-cloud/-/pino-cloud-1.0.0.tgz", + "integrity": "sha512-/3ielTghmn6lN8dUE62NyPABA5AHcY2F0UmATvuDgm7V0rSOIfS1cMwRqjK/6UuhiwUWbF8H9QvjN8/2fu/iJQ==", + "requires": { + "@types/pino": "^6.3.5", + "pino": "^6.11.0" + } + }, "@relaycorp/relaynet-core": { "version": "1.42.3", "resolved": "https://registry.npmjs.org/@relaycorp/relaynet-core/-/relaynet-core-1.42.3.tgz", diff --git a/package.json b/package.json index 4b82ceaf6..f92275811 100644 --- a/package.json +++ b/package.json @@ -70,6 +70,7 @@ "@relaycorp/cogrpc": "^1.3.1", "@relaycorp/keystore-vault": "^1.2.1", "@relaycorp/object-storage": "^1.3.2", + "@relaycorp/pino-cloud": "^1.0.0", "@relaycorp/relaynet-core": "^1.42.3", "@relaycorp/relaynet-pohttp": "^1.6.0", "@typegoose/typegoose": "^7.4.8", diff --git a/src/utilities/logging.spec.ts b/src/utilities/logging.spec.ts new file mode 100644 index 000000000..afe4154d3 --- /dev/null +++ b/src/utilities/logging.spec.ts @@ -0,0 +1,78 @@ +import { getPinoOptions } from '@relaycorp/pino-cloud'; +import { EnvVarError } from 'env-var'; +import pino from 'pino'; + +import { configureMockEnvVars, getMockInstance } from '../services/_test_utils'; +import { makeLogger } from './logging'; + +const REQUIRED_ENV_VARS = { + GATEWAY_VERSION: '1.0.1', +}; +const mockEnvVars = configureMockEnvVars(REQUIRED_ENV_VARS); + +const COMPONENT = 'the-component'; + +jest.mock('@relaycorp/pino-cloud', () => ({ + getPinoOptions: jest.fn().mockReturnValue({}), +})); + +describe('makeLogger', () => { + test('Log level should be info if LOG_LEVEL env var is absent', () => { + mockEnvVars(REQUIRED_ENV_VARS); + + const logger = makeLogger(COMPONENT); + + expect(logger).toHaveProperty('level', 'info'); + }); + + test('Log level in LOG_LEVEL env var should be honoured if present', () => { + const loglevel = 'debug'; + mockEnvVars({ ...REQUIRED_ENV_VARS, LOG_LEVEL: loglevel }); + + const logger = makeLogger(COMPONENT); + + expect(logger).toHaveProperty('level', loglevel); + }); + + test('Log level in LOG_LEVEL env var should be lower-cased if present', () => { + const loglevel = 'DEBUG'; + mockEnvVars({ ...REQUIRED_ENV_VARS, LOG_LEVEL: loglevel }); + + const logger = makeLogger(COMPONENT); + + expect(logger).toHaveProperty('level', loglevel.toLowerCase()); + }); + + test('GATEWAY_VERSION env var should be required', () => { + mockEnvVars({ ...REQUIRED_ENV_VARS, GATEWAY_VERSION: undefined }); + + expect(() => makeLogger(COMPONENT)).toThrowWithMessage(EnvVarError, /GATEWAY_VERSION/); + }); + + test('Cloud logging options should be used', () => { + const messageKey = 'foo'; + getMockInstance(getPinoOptions).mockReturnValue({ messageKey }); + const logger = makeLogger(COMPONENT); + + expect(logger[pino.symbols.messageKeySym as any]).toEqual(messageKey); + expect(getPinoOptions).toBeCalledWith(undefined, { + name: COMPONENT, + version: REQUIRED_ENV_VARS.GATEWAY_VERSION, + }); + }); + + test('LOG_TARGET env var should be honoured if present', () => { + const loggingTarget = 'the-logging-target'; + mockEnvVars({ ...REQUIRED_ENV_VARS, LOG_TARGET: loggingTarget }); + + makeLogger(COMPONENT); + + expect(getPinoOptions).toBeCalledWith(loggingTarget, expect.anything()); + }); + + test('Logging target should be unset if LOG_TARGET env var is absent', () => { + makeLogger(COMPONENT); + + expect(getPinoOptions).toBeCalledWith(undefined, expect.anything()); + }); +}); diff --git a/src/utilities/logging.ts b/src/utilities/logging.ts new file mode 100644 index 000000000..89c464182 --- /dev/null +++ b/src/utilities/logging.ts @@ -0,0 +1,13 @@ +import { getPinoOptions, LoggingTarget } from '@relaycorp/pino-cloud'; +import { get as getEnvVar } from 'env-var'; +import pino, { Level, Logger } from 'pino'; + +export function makeLogger(component: string): Logger { + const logTarget = getEnvVar('LOG_TARGET').asString(); + const gatewayVersion = getEnvVar('GATEWAY_VERSION').required().asString(); + const appContext = { name: component, version: gatewayVersion }; + const cloudPinoOptions = getPinoOptions(logTarget as LoggingTarget, appContext); + + const logLevel = getEnvVar('LOG_LEVEL').default('info').asString().toLowerCase() as Level; + return pino({ ...cloudPinoOptions, level: logLevel }); +}