From 9acf4661f18e6ce71bad9239253465d1fd07bef0 Mon Sep 17 00:00:00 2001 From: Yousaf Nabi Date: Tue, 25 Jun 2024 22:51:45 +0100 Subject: [PATCH] feat: support logFile option to makeConsumerPact & verify functions note logging options are set globally and therefore cannot be updated between tests, this is a limitation of the current pact rust core implementation --- native/addon.cc | 1 + native/ffi.cc | 58 ++++++++++++++++++++++++++ package.json | 2 +- src/consumer/index.ts | 10 +++-- src/ffi/index.ts | 33 ++++++++------- src/ffi/types.ts | 12 +++--- src/verifier/nativeVerifier.ts | 2 +- src/verifier/types.ts | 1 + src/verifier/validateOptions.ts | 1 + test/consumer.integration.spec.ts | 3 -- test/matt.consumer.integration.spec.ts | 3 -- 11 files changed, 93 insertions(+), 33 deletions(-) diff --git a/native/addon.cc b/native/addon.cc index 3c52b408..f0ff8107 100644 --- a/native/addon.cc +++ b/native/addon.cc @@ -8,6 +8,7 @@ Napi::Object Init(Napi::Env env, Napi::Object exports) { exports.Set(Napi::String::New(env, "pactffiVersion"), Napi::Function::New(env, PactffiVersion)); exports.Set(Napi::String::New(env, "pactffiInit"), Napi::Function::New(env, PactffiInit)); exports.Set(Napi::String::New(env, "pactffiInitWithLogLevel"), Napi::Function::New(env, PactffiInitWithLogLevel)); + exports.Set(Napi::String::New(env, "pactffiLogToFile"), Napi::Function::New(env, PactffiLogToFile)); // Consumer exports.Set(Napi::String::New(env, "pactffiMockServerMatched"), Napi::Function::New(env, PactffiMockServerMatched)); diff --git a/native/ffi.cc b/native/ffi.cc index ccdb9d20..48e60702 100644 --- a/native/ffi.cc +++ b/native/ffi.cc @@ -49,6 +49,64 @@ Napi::Value PactffiInitWithLogLevel(const Napi::CallbackInfo& info) { return env.Undefined(); } +LevelFilter integerToLevelFilter(Napi::Env &env, uint32_t number) { + LevelFilter logLevel = LevelFilter::LevelFilter_Off; + + switch(number) { + case 0: + logLevel = LevelFilter::LevelFilter_Off; + break; + case 1: + logLevel = LevelFilter::LevelFilter_Error; + break; + case 2: + logLevel = LevelFilter::LevelFilter_Warn; + break; + case 3: + logLevel = LevelFilter::LevelFilter_Info; + break; + case 4: + logLevel = LevelFilter::LevelFilter_Debug; + break; + case 5: + logLevel = LevelFilter::LevelFilter_Trace; + break; + default: + std::string err = "pact-js-core C integration: Unable to parse log level number: "; + err += number; + + throw Napi::Error::New(env, err); + } + + return logLevel; +} + + +Napi::Value PactffiLogToFile(const Napi::CallbackInfo& info) { + // return: int + Napi::Env env = info.Env(); + + if (info.Length() < 2) { + throw Napi::Error::New(env, "PactffiLogToFile(envVar) received < 2 arguments"); + } + + if (!info[0].IsString()) { + throw Napi::Error::New(env, "PactffiLogToFile(envVar) expected a string"); + } + + if (!info[1].IsNumber()) { + throw Napi::Error::New(env, "PactffiLogToFile(envVar) expected a number"); + } + + // Extract log level + std::string fileName = info[0].As().Utf8Value(); + uint32_t levelFilterNumber = info[1].As().Uint32Value(); + LevelFilter levelFilter = integerToLevelFilter(env, levelFilterNumber); + + int res = pactffi_log_to_file(fileName.c_str(), levelFilter); + + return Napi::Number::New(env, res); +} /* Napi::Value Pactffi_log_message(const Napi::CallbackInfo& info) { diff --git a/package.json b/package.json index 950f152e..898614de 100644 --- a/package.json +++ b/package.json @@ -116,7 +116,7 @@ "build": "tsc --project tsconfig.build.json", "prerelease": "npm run snyk-protect", "release": "commit-and-tag-version", - "test": "cross-env LOGLEVEL=debug PACT_DO_NOT_TRACK=true mocha \"{src,test}/**/*.spec.ts\"", + "test": "cross-env LOG_LEVEL=${LOG_LEVEL:-trace} PACT_DO_NOT_TRACK=true mocha \"{src,test}/**/*.spec.ts\"", "snyk-protect": "snyk-protect", "format:base": "prettier --parser typescript", "format:check": "npm run format:base -- --list-different \"{src,test}/**/*.{ts,tsx}\"", diff --git a/src/consumer/index.ts b/src/consumer/index.ts index 52fe4391..ed7b3d46 100644 --- a/src/consumer/index.ts +++ b/src/consumer/index.ts @@ -62,12 +62,13 @@ export const makeConsumerPact = ( consumer: string, provider: string, version: FfiSpecificationVersion = 3, - logLevel = getLogLevel() + logLevel = getLogLevel(), + logFile?: string ): ConsumerPact => { - const ffi = getFfiLib(logLevel); if (logLevel) { setLogLevel(logLevel); } + const ffi = getFfiLib(logLevel, logFile); const pactPtr = ffi.pactffiNewPact(consumer, provider); if (!ffi.pactffiWithSpecification(pactPtr, version)) { @@ -370,12 +371,13 @@ export const makeConsumerMessagePact = ( consumer: string, provider: string, version: FfiSpecificationVersion = 4, - logLevel = getLogLevel() + logLevel = getLogLevel(), + logFile?: string ): ConsumerMessagePact => { - const ffi = getFfiLib(logLevel); if (logLevel) { setLogLevel(logLevel); } + const ffi = getFfiLib(logLevel, logFile); const pactPtr = ffi.pactffiNewPact(consumer, provider); if (!ffi.pactffiWithSpecification(pactPtr, version) || version < 4) { diff --git a/src/ffi/index.ts b/src/ffi/index.ts index ac217c2f..18ee9cdb 100644 --- a/src/ffi/index.ts +++ b/src/ffi/index.ts @@ -2,7 +2,7 @@ import path from 'node:path'; import bindings = require('node-gyp-build'); import logger, { DEFAULT_LOG_LEVEL } from '../logger'; import { LogLevel } from '../logger/types'; -import { Ffi } from './types'; +import { Ffi, FfiLogLevelFilter } from './types'; export const PACT_FFI_VERSION = '0.4.21'; @@ -85,9 +85,8 @@ const renderBinaryErrorMessage = (error: unknown) => { }; let ffi: typeof ffiLib; -let ffiLogLevel: LogLevel; -const initialiseFfi = (logLevel: LogLevel): typeof ffi => { +const initialiseFfi = (): typeof ffi => { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore if (process.stdout._handle) { @@ -95,8 +94,6 @@ const initialiseFfi = (logLevel: LogLevel): typeof ffi => { // @ts-ignore process.stdout._handle.setBlocking(true); } - logger.debug(`Initalising native core at log level '${logLevel}'`); - ffiLogLevel = logLevel; try { bindingPaths.every((bindingPath, i) => { try { @@ -114,7 +111,7 @@ const initialiseFfi = (logLevel: LogLevel): typeof ffi => { return true; } }); - ffiLib.pactffiInitWithLogLevel(logLevel); + // ffiLib.pactffiInitWithLogLevel(logLevel); } catch (error) { renderBinaryErrorMessage(error); throw new Error( @@ -125,18 +122,24 @@ const initialiseFfi = (logLevel: LogLevel): typeof ffi => { }; export const getFfiLib = ( - logLevel: LogLevel = DEFAULT_LOG_LEVEL + logLevel: LogLevel = DEFAULT_LOG_LEVEL, + logFile: string | undefined = undefined ): typeof ffi => { if (!ffi) { - logger.trace('Initiliasing ffi for the first time'); - ffi = initialiseFfi(logLevel); - } else { - logger.trace('Ffi has already been initialised, no need to repeat it'); - if (logLevel !== ffiLogLevel) { - logger.warn( - `The pact native core has already been initialised at log level '${ffiLogLevel}'` + logger.trace('Initialising ffi for the first time'); + ffi = initialiseFfi(); + logger.debug( + `Initialising native core at log level '${logLevel}'`, + logFile + ); + if (logFile) { + logger.debug( + `writing log file at level ${FfiLogLevelFilter[logLevel]} to ${logFile}` ); - logger.warn(`The new requested log level '${logLevel}' will be ignored`); + const res = ffiLib.pactffiLogToFile(logFile, FfiLogLevelFilter[logLevel]); + logger.debug(`result of writing to file '${res}'`); + } else { + ffiLib.pactffiInitWithLogLevel(logLevel); } } return ffi; diff --git a/src/ffi/types.ts b/src/ffi/types.ts index 333177cb..b498ed4d 100644 --- a/src/ffi/types.ts +++ b/src/ffi/types.ts @@ -110,12 +110,12 @@ export enum FfiFunctionResult { } export enum FfiLogLevelFilter { - LOG_LEVEL_OFF = 0, - LOG_LEVEL_ERROR, - LOG_LEVEL_WARN, - LOG_LEVEL_INFO, - LOG_LEVEL_DEBUG, - LOG_LEVEL_TRACE, + off = 0, + error = 1, + warn = 2, + info = 3, + debug = 4, + trace = 5, } export type Ffi = { diff --git a/src/verifier/nativeVerifier.ts b/src/verifier/nativeVerifier.ts index 730b6733..d5d48cfd 100644 --- a/src/verifier/nativeVerifier.ts +++ b/src/verifier/nativeVerifier.ts @@ -14,7 +14,7 @@ export const verify = (opts: VerifierOptions): Promise => { if (opts.logLevel) { setLogLevel(opts.logLevel); } - const ffi = getFfiLib(opts.logLevel); + const ffi = getFfiLib(opts.logLevel, opts.logFile); const handle = ffi.pactffiVerifierNewForApplication( pkg.name.split('/')[1], diff --git a/src/verifier/types.ts b/src/verifier/types.ts index 6e3769a9..b5b290b8 100644 --- a/src/verifier/types.ts +++ b/src/verifier/types.ts @@ -46,6 +46,7 @@ export interface VerifierOptions { consumerVersionSelectors?: ConsumerVersionSelector[]; timeout?: number; logLevel?: LogLevel; + logFile?: string; disableSslVerification?: boolean; buildUrl?: string; customProviderHeaders?: CustomHeaders | string[]; diff --git a/src/verifier/validateOptions.ts b/src/verifier/validateOptions.ts index aa7e1088..8c3e191c 100644 --- a/src/verifier/validateOptions.ts +++ b/src/verifier/validateOptions.ts @@ -247,6 +247,7 @@ export const validationRules: ArgumentValidationRules { - setLogLevel('trace'); - let port: number; let pact: ConsumerPact; const bytes: Buffer = zlib.gzipSync('this is an encoded string'); diff --git a/test/matt.consumer.integration.spec.ts b/test/matt.consumer.integration.spec.ts index fa439829..20773df2 100644 --- a/test/matt.consumer.integration.spec.ts +++ b/test/matt.consumer.integration.spec.ts @@ -9,7 +9,6 @@ import { makeConsumerMessagePact, makeConsumerPact, } from '../src'; -import { setLogLevel } from '../src/logger'; import { FfiSpecificationVersion } from '../src/ffi/types'; chai.use(chaiAsPromised); @@ -44,8 +43,6 @@ const sendMattMessageTCP = ( const skipPluginTests = process.env['SKIP_PLUGIN_TESTS'] === 'true'; (skipPluginTests ? describe.skip : describe)('MATT protocol test', () => { - setLogLevel('trace'); - let provider: ConsumerPact; let tcpProvider: ConsumerMessagePact; let port: number;