From d9f1689f174a462c23b90f62d240d31ca65226b9 Mon Sep 17 00:00:00 2001 From: Vijayasingam Thanasekaran Date: Wed, 19 Jun 2024 17:59:08 +0100 Subject: [PATCH] feat(logs): Added an option to control log level --- CHANGELOG.md | 8 +++ README.md | 1 + package.json | 2 +- src/__tests__/internal/logger.test.ts | 82 +++++++++++++++++++++++++++ src/index.ts | 2 +- src/internal/api/client.ts | 3 + src/internal/api/index.ts | 1 + src/internal/api/log-level.ts | 6 ++ src/internal/base/logger.ts | 27 ++++++++- src/internal/types/config.ts | 3 +- src/paddle.ts | 3 +- 11 files changed, 131 insertions(+), 7 deletions(-) create mode 100644 src/__tests__/internal/logger.test.ts create mode 100644 src/internal/api/log-level.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index bf0b397..3e7633c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,14 @@ When we make [non-breaking changes](https://developer.paddle.com/api-reference/a This means when upgrading minor versions of the SDK, you may notice type errors. You can safely ignore these or fix by adding additional type guards. +## 1.4.0 - 2024-06-20 + +### Added + +- Added a new option to change the logging level of the SDK. You can now set the logging level to `verbose`, `warn`, `error` or `none`. The default logging level is `verbose`. + +--- + ## 1.3.0 - 2024-04-18 ### Changed diff --git a/README.md b/README.md index 7e3e0c9..fe4a493 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,7 @@ You can also pass an environment to work with the sandbox: ```typescript const paddle = new Paddle('API_KEY', { environment: Environment.production, // or Environment.sandbox for accessing sandbox API + logLevel: 'verbose' // or 'error' for less verbose logging }) ``` diff --git a/package.json b/package.json index 832601e..95544e0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@paddle/paddle-node-sdk", - "version": "1.3.0", + "version": "1.4.0", "description": "A Node.js SDK that you can use to integrate Paddle Billing with applications written in server-side JavaScript.", "main": "./dist/cjs/index.js", "module": "./dist/esm/index.js", diff --git a/src/__tests__/internal/logger.test.ts b/src/__tests__/internal/logger.test.ts new file mode 100644 index 0000000..8218fb0 --- /dev/null +++ b/src/__tests__/internal/logger.test.ts @@ -0,0 +1,82 @@ +import { LogLevel } from '../../internal'; +import { Logger } from '../../internal/base/logger'; +import { type Response } from 'node-fetch'; + +describe('logger', () => { + afterEach(() => { + jest.restoreAllMocks(); + }); + test.each([ + [LogLevel.verbose, LogLevel.verbose, true], + [LogLevel.verbose, LogLevel.warn, true], + [LogLevel.verbose, LogLevel.error, true], + [LogLevel.verbose, LogLevel.none, false], + [LogLevel.warn, LogLevel.verbose, false], + [LogLevel.warn, LogLevel.warn, true], + [LogLevel.warn, LogLevel.error, true], + [LogLevel.warn, LogLevel.none, false], + [LogLevel.error, LogLevel.verbose, false], + [LogLevel.error, LogLevel.warn, false], + [LogLevel.error, LogLevel.error, true], + [LogLevel.error, LogLevel.none, false], + [LogLevel.none, LogLevel.verbose, false], + [LogLevel.none, LogLevel.warn, false], + [LogLevel.none, LogLevel.error, false], + [LogLevel.none, LogLevel.none, false], + ])('shouldLog(%s) with logLevel %s should return %s', (logLevel, level, expected) => { + Logger.logLevel = logLevel; + // @ts-expect-error - testing private method + expect(Logger.shouldLog(level)).toBe(expected); + }); + test('log', () => { + Logger.logLevel = LogLevel.verbose; + const consoleSpy = jest.spyOn(console, 'log').mockImplementation(); + Logger.log('test'); + expect(consoleSpy).toHaveBeenCalledWith('[Paddle] [LOG]', 'test'); + }); + test('warn', () => { + Logger.logLevel = LogLevel.warn; + const consoleSpy = jest.spyOn(console, 'warn').mockImplementation(); + Logger.warn('test'); + expect(consoleSpy).toHaveBeenCalledWith('[Paddle] [WARN]', 'test'); + }); + test('error', () => { + Logger.logLevel = LogLevel.error; + const consoleSpy = jest.spyOn(console, 'error').mockImplementation(); + Logger.error('test'); + expect(consoleSpy).toHaveBeenCalledWith('[Paddle] [ERROR]', 'test'); + }); + test('logRequest', () => { + Logger.logLevel = LogLevel.verbose; + const consoleSpy = jest.spyOn(console, 'log').mockImplementation(); + Logger.logRequest('GET', 'https://example.com', { 'X-Transaction-ID': '123' }); + expect(consoleSpy).toHaveBeenCalledWith( + '[Paddle] [LOG]', + '[Request]', + 'GET', + 'https://example.com', + 'Transaction ID:', + '123', + ); + }); + test('logResponse', () => { + Logger.logLevel = LogLevel.verbose; + const consoleSpy = jest.spyOn(console, 'log').mockImplementation(); + const response = { + status: 200, + headers: new Map([['Request-Id', '456']]), + }; + Logger.logResponse('GET', 'https://example.com', { 'X-Transaction-ID': '123' }, response as unknown as Response); + expect(consoleSpy).toHaveBeenCalledWith( + '[Paddle] [LOG]', + '[Response]', + 'GET', + 'https://example.com', + '200', + 'Transaction ID:', + '123', + 'Request ID:', + '456', + ); + }); +}); diff --git a/src/index.ts b/src/index.ts index 0c018c2..9db4626 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,7 +4,7 @@ * Changes may be overwritten as part of auto-generation. */ -export { Environment, ApiError, type PaddleOptions } from './internal'; +export { Environment, LogLevel, ApiError, type PaddleOptions } from './internal'; export { SDK_VERSION } from './version'; export { Paddle } from './paddle'; diff --git a/src/internal/api/client.ts b/src/internal/api/client.ts index b41474d..3883291 100644 --- a/src/internal/api/client.ts +++ b/src/internal/api/client.ts @@ -8,6 +8,7 @@ import { randomUUID } from 'node:crypto'; import { Logger } from '../base/logger'; import { convertToSnakeCase } from './case-helpers'; import { type ErrorResponse } from '../types/response'; +import { LogLevel } from './log-level'; export class Client { private readonly baseUrl: string; @@ -17,6 +18,8 @@ export class Client { private readonly options: PaddleOptions, ) { this.baseUrl = this.getBaseUrl(this.options.environment); + // TODO - Change the default to `error` in next major version + Logger.logLevel = this.options.logLevel ?? LogLevel.verbose; } private getBaseUrl(environment?: Environment): string { diff --git a/src/internal/api/index.ts b/src/internal/api/index.ts index 10f4e9c..11154b6 100644 --- a/src/internal/api/index.ts +++ b/src/internal/api/index.ts @@ -1,2 +1,3 @@ export { Environment } from './environment'; export { convertToSnakeCase } from './case-helpers'; +export { LogLevel } from './log-level'; diff --git a/src/internal/api/log-level.ts b/src/internal/api/log-level.ts new file mode 100644 index 0000000..2b6b7b2 --- /dev/null +++ b/src/internal/api/log-level.ts @@ -0,0 +1,6 @@ +export enum LogLevel { + verbose = 'verbose', + warn = 'warn', + error = 'error', + none = 'none', +} diff --git a/src/internal/base/logger.ts b/src/internal/base/logger.ts index 8c3f2d8..688a34b 100644 --- a/src/internal/base/logger.ts +++ b/src/internal/base/logger.ts @@ -1,18 +1,39 @@ import { type Response } from 'node-fetch'; +import { LogLevel } from '../api'; type LogInputProps = Array; export class Logger { + static logLevel: LogLevel; + + private static shouldLog(level: LogLevel) { + switch (Logger.logLevel) { + case LogLevel.verbose: + return level !== LogLevel.none; + case LogLevel.warn: + return level === LogLevel.warn || level === LogLevel.error; + case LogLevel.error: + return level === LogLevel.error; + default: + return false; + } + } static log(...args: LogInputProps) { - console.log('[Paddle] [LOG]', ...args); + if (Logger.shouldLog(LogLevel.verbose)) { + console.log('[Paddle] [LOG]', ...args); + } } static warn(...args: LogInputProps) { - console.warn('[Paddle] [WARN]', ...args); + if (Logger.shouldLog(LogLevel.warn)) { + console.warn('[Paddle] [WARN]', ...args); + } } static error(...args: LogInputProps) { - console.error('[Paddle] [ERROR]', ...args); + if (Logger.shouldLog(LogLevel.error)) { + console.error('[Paddle] [ERROR]', ...args); + } } static logRequest(method: string, url: string | undefined, headers: Record) { diff --git a/src/internal/types/config.ts b/src/internal/types/config.ts index 2284c84..98a8ff5 100644 --- a/src/internal/types/config.ts +++ b/src/internal/types/config.ts @@ -1,5 +1,6 @@ -import { type Environment } from '../api'; +import { type Environment, LogLevel } from '../api'; export interface PaddleOptions { environment?: Environment; + logLevel?: LogLevel; } diff --git a/src/paddle.ts b/src/paddle.ts index 70cab01..5d71ce6 100644 --- a/src/paddle.ts +++ b/src/paddle.ts @@ -15,7 +15,7 @@ import { SubscriptionsResource, TransactionsResource, } from './resources'; -import { Environment, type PaddleOptions } from './internal'; +import { Environment, LogLevel, type PaddleOptions } from './internal'; import { EventsResource } from './resources/events'; import { Webhooks } from './notifications'; @@ -23,6 +23,7 @@ export class Paddle { private readonly client: Client; private readonly defaultPaddleOptions: Partial = { environment: Environment.production, + logLevel: LogLevel.verbose, // TODO - Change the default to `error` in next major version }; public products: ProductsResource;