diff --git a/packages/lib-sourcify/README.md b/packages/lib-sourcify/README.md index e143fa845..9e27c8990 100644 --- a/packages/lib-sourcify/README.md +++ b/packages/lib-sourcify/README.md @@ -104,3 +104,49 @@ const match = await verifyCreate2( console.log(match.chainId); // '0'. create2 matches return 0 as chainId console.log(match.status); // 'perfect' ``` + +## Logging + +`lib-sourcify` has a basic logging system. + +You can specify the log level using the `setLibSourcifyLoggerLevel(level)` where: + +- `0` is nothing +- `1` is errors +- `2` is warnings _[default]_ +- `3` is infos +- `4` is debug + +You can override the logger by calling `setLogger(logger: ILibSourcifyLogger)`. This is an example: + +```javascript +const winston = require('winston'); +const logger = winston.createLogger({ + // ... +}); + +setLibSourcifyLogger({ + logLevel: 4, + setLevel(level: number) { + this.logLevel = level; + }, + log(level, msg) { + if (level <= this.logLevel) { + switch (level) { + case 1: + logger.error(msg); + break; + case 2: + logger.warn(msg); + break; + case 3: + logger.info(msg); + break; + case 4: + logger.debug(msg); + break; + } + } + }, +}); +``` diff --git a/packages/lib-sourcify/src/index.ts b/packages/lib-sourcify/src/index.ts index d409f2d6b..7a7c0fa6e 100644 --- a/packages/lib-sourcify/src/index.ts +++ b/packages/lib-sourcify/src/index.ts @@ -1,5 +1,10 @@ +import { setLogger, setLevel, ILogger } from './lib/logger'; + export * from './lib/validation'; export * from './lib/verification'; export * from './lib/CheckedContract'; export * from './lib/types'; export * from './lib/solidityCompiler'; +export const setLibSourcifyLogger = setLogger; +export const setLibSourcifyLoggerLevel = setLevel; +export type ILibSourcifyLogger = ILogger; diff --git a/packages/lib-sourcify/src/lib/CheckedContract.ts b/packages/lib-sourcify/src/lib/CheckedContract.ts index b94b82cab..3556c99ef 100644 --- a/packages/lib-sourcify/src/lib/CheckedContract.ts +++ b/packages/lib-sourcify/src/lib/CheckedContract.ts @@ -17,6 +17,7 @@ import { storeByHash } from './validation'; import { decode as decodeBytecode } from '@ethereum-sourcify/bytecode-utils'; import { ipfsHash } from './hashFunctions/ipfsHash'; import { swarmBzzr0Hash, swarmBzzr1Hash } from './hashFunctions/swarmHash'; +import { logError, logInfo, logWarn } from './logger'; // TODO: find a better place for these constants. Reminder: this sould work also in the browser const IPFS_PREFIX = 'dweb:/ipfs/'; @@ -246,7 +247,11 @@ export class CheckedContract { .map((e: any) => e.formattedMessage); const error = new Error('Compiler error'); - console.error(errorMessages); + logWarn( + `Compiler error in CheckedContract.recompile: \n${errorMessages.join( + '\n\t' + )}` + ); throw error; } @@ -338,14 +343,14 @@ export async function performFetch( hash?: string, fileName?: string ): Promise { - console.log(`Fetching the file ${fileName} from ${url}...`); + logInfo(`Fetching the file ${fileName} from ${url}...`); const res = await fetchWithTimeout(url, { timeout: FETCH_TIMEOUT }).catch( (err) => { if (err.type === 'aborted') - console.log( + logWarn( `Fetching the file ${fileName} from ${url} timed out. Timeout: ${FETCH_TIMEOUT}ms` ); - else console.log(err); + else logError(err); } ); @@ -353,14 +358,14 @@ export async function performFetch( if (res.status === 200) { const content = await res.text(); if (hash && Web3.utils.keccak256(content) !== hash) { - console.log("The calculated and the provided hash don't match."); + logError("The calculated and the provided hash don't match."); return null; } - console.log(`Successfully fetched the file ${fileName}`); + logInfo(`Successfully fetched the file ${fileName}`); return content; } else { - console.log( + logError( `Fetching the file ${fileName} failed with status: ${res?.status}` ); return null; diff --git a/packages/lib-sourcify/src/lib/logger.ts b/packages/lib-sourcify/src/lib/logger.ts new file mode 100644 index 000000000..9e2c36e85 --- /dev/null +++ b/packages/lib-sourcify/src/lib/logger.ts @@ -0,0 +1,58 @@ +export interface ILogger { + logLevel: number; + log: (level: number, message: string) => void; + setLevel: (level: number) => void; +} + +// Default logger behavior +export const DefaultLogger: ILogger = { + logLevel: 2, + setLevel(level: number) { + this.logLevel = level; + }, + log(level, msg) { + if (level <= this.logLevel) { + switch (level) { + case 1: + console.error(msg); + break; + case 2: + console.warn(msg); + break; + case 3: + console.info(msg); + break; + case 4: + console.debug(msg); + break; + } + } + }, +}; + +// Logger variable that will be used throughout the application +let AppLogger: ILogger = DefaultLogger; + +export function setLogger(logger: ILogger) { + AppLogger = logger; +} + +export function setLevel(level: number) { + AppLogger.setLevel(level); +} + +export function logError(message: string) { + AppLogger.log(1, message); +} + +export function logWarn(message: string) { + AppLogger.log(2, message); +} + +export function logInfo(message: string) { + AppLogger.log(3, message); +} + +export function logDebug(message: string) { + AppLogger.log(4, message); +} diff --git a/packages/lib-sourcify/src/lib/solidityCompiler.ts b/packages/lib-sourcify/src/lib/solidityCompiler.ts index ba26901a6..e0eebf50c 100644 --- a/packages/lib-sourcify/src/lib/solidityCompiler.ts +++ b/packages/lib-sourcify/src/lib/solidityCompiler.ts @@ -5,6 +5,7 @@ import { spawnSync } from 'child_process'; import { fetchWithTimeout } from './utils'; import { StatusCodes } from 'http-status-codes'; import { JsonInput, PathBuffer } from './types'; +import { logError, logInfo, logWarn } from './logger'; // eslint-disable-next-line @typescript-eslint/no-var-requires const solc = require('solc'); @@ -47,7 +48,7 @@ export async function useCompiler(version: string, solcJsonInput: JsonInput) { if (solcPlatform) { solcPath = await getSolcExecutable(solcPlatform, version); } - console.time('Compilation time'); + const startCompilation = Date.now(); if (solcPath) { const shellOutputBuffer = spawnSync(solcPath, ['--standard-json'], { input: inputStringified, @@ -68,7 +69,7 @@ export async function useCompiler(version: string, solcJsonInput: JsonInput) { error = new Error(RECOMPILATION_ERR_MSG); } if (error) { - console.error(error); + logWarn(error.message); throw error; } compiled = shellOutputBuffer.stdout.toString(); @@ -79,7 +80,8 @@ export async function useCompiler(version: string, solcJsonInput: JsonInput) { } } - console.timeEnd('Compilation time'); + const endCompilation = Date.now(); + logInfo(`Compilation time : ${endCompilation - startCompilation} ms`); if (!compiled) { throw new Error('Compilation failed. No output from the compiler.'); @@ -92,7 +94,7 @@ export async function useCompiler(version: string, solcJsonInput: JsonInput) { const error = new Error( 'Compiler error:\n ' + JSON.stringify(errorMessages) ); - console.error(error); + logError(error.message); throw error; } return compiledJSON; @@ -150,7 +152,7 @@ export async function getSolcExecutable( } const success = await fetchAndSaveSolc(platform, solcPath, version, fileName); if (success && !validateSolcPath(solcPath)) { - console.log(`Cannot validate solc ${version}.`); + logError(`Cannot validate solc ${version}.`); return null; } return success ? solcPath : null; @@ -168,7 +170,7 @@ function validateSolcPath(solcPath: string): boolean { spawned.stderr.toString() || 'Error running solc, are you on the right platoform? (e.g. x64 vs arm)'; - console.log(error); + logWarn(error); return false; } @@ -215,9 +217,7 @@ async function fetchAndSaveSolc( return true; } else { - console.log( - `Failed fetching solc ${version} from GitHub: ${githubSolcURI}` - ); + logWarn(`Failed fetching solc ${version} from GitHub: ${githubSolcURI}`); } return false; diff --git a/packages/lib-sourcify/src/lib/verification.ts b/packages/lib-sourcify/src/lib/verification.ts index d7cc21e2e..38436ff21 100644 --- a/packages/lib-sourcify/src/lib/verification.ts +++ b/packages/lib-sourcify/src/lib/verification.ts @@ -30,6 +30,7 @@ import { getAddress, getContractAddress } from '@ethersproject/address'; import semverSatisfies from 'semver/functions/satisfies'; import { defaultAbiCoder as abiCoder, ParamType } from '@ethersproject/abi'; import { AbiConstructor } from 'abitype'; +import { logInfo, logWarn } from './logger'; const RPC_TIMEOUT = process.env.RPC_TIMEOUT ? parseInt(process.env.RPC_TIMEOUT) @@ -47,7 +48,7 @@ export async function verifyDeployed( chainId: sourcifyChain.chainId.toString(), status: null, }; - console.log( + logInfo( `Verifying contract ${ checkedContract.name } at address ${address} on chain ${sourcifyChain.chainId.toString()}` @@ -501,7 +502,7 @@ export async function getBytecode( return bytecode; } catch (err) { // Catch to try the next RPC - console.log(err); + logWarn((err as Error).message); } } throw new Error('None of the RPCs responded'); @@ -524,12 +525,12 @@ async function getTx(creatorTxHash: string, sourcifyChain: SourcifyChain) { rejectInMs(RPC_TIMEOUT, rpcURL), ])) as Transaction; if (tx) { - console.log(`Transaction ${creatorTxHash} fetched via ${rpcURL}`); + logInfo(`Transaction ${creatorTxHash} fetched via ${rpcURL}`); return tx; } } catch (err) { // Catch to try the next RPC - console.log(err); + logWarn((err as Error).message); } } throw new Error('None of the RPCs responded'); @@ -682,7 +683,7 @@ function doesContainMetadataHash(bytecode: string) { containsMetadata = !!decodedCBOR.ipfs || !!decodedCBOR['bzzr0'] || !!decodedCBOR['bzzr1']; } catch (e) { - console.log("Can't decode CBOR"); + logInfo("Can't decode CBOR"); containsMetadata = false; } return containsMetadata; diff --git a/src/server/server.ts b/src/server/server.ts index 41a6fd29d..af7194589 100644 --- a/src/server/server.ts +++ b/src/server/server.ts @@ -28,11 +28,39 @@ import { resolveRefs } from "json-refs"; import { initDeprecatedRoutes } from "./deprecated.routes"; import { getAddress, isAddress } from "ethers/lib/utils"; import { logger } from "../common/loggerLoki"; +import { setLibSourcifyLogger } from "@ethereum-sourcify/lib-sourcify"; // eslint-disable-next-line @typescript-eslint/no-var-requires const fileUpload = require("express-fileupload"); const MemoryStore = createMemoryStore(session); +// here we override the standard LibSourcify's Logger with a custom one +setLibSourcifyLogger({ + // No need to set again the logger level because it's set here + logLevel: process.env.NODE_ENV === "production" ? 3 : 4, + setLevel(level: number) { + this.logLevel = level; + }, + log(level, msg) { + if (level <= this.logLevel) { + switch (level) { + case 1: + logger.error(msg); + break; + case 2: + logger.warn(msg); + break; + case 3: + logger.info(msg); + break; + case 4: + logger.debug(msg); + break; + } + } + }, +}); + export class Server { app: express.Application; repository = config.repository.path;