Skip to content
This repository has been archived by the owner on Feb 26, 2024. It is now read-only.

Commit

Permalink
feat: allow logs to be written to a file by providing the `--logging.…
Browse files Browse the repository at this point in the history
…file` argument (#4195)

Co-authored-by: cds-amal <[email protected]>
Co-authored-by: David Murdoch <[email protected]>
  • Loading branch information
3 people committed Jun 27, 2023
1 parent 20a5f09 commit 47bec2e
Show file tree
Hide file tree
Showing 27 changed files with 1,234 additions and 102 deletions.
9 changes: 5 additions & 4 deletions scripts/link-ts-references.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,12 +93,12 @@ function updateConfig(config: PackageInfo) {
// add package.json deps to tsconfig references:
references.forEach(name => {
const referenceConfig = getConfigByName(name);
if(!referenceConfig) throw new Error(`missing config ${name}`);
if (!referenceConfig) throw new Error(`missing config ${name}`);

// projects that are referenced by other projects must have the `composite: true` in their tsconfig compileOptions
if (
(!referenceConfig.tsConfig.compilerOptions ||
!referenceConfig.tsConfig.compilerOptions.composite)
!referenceConfig.tsConfig.compilerOptions ||
!referenceConfig.tsConfig.compilerOptions.composite
) {
if (!referenceConfig.tsConfig.compilerOptions)
referenceConfig.tsConfig.compilerOptions = {};
Expand Down Expand Up @@ -137,7 +137,8 @@ function saveConfigs(configs: PackageInfo[]) {
configs.forEach(({ modified, path, tsConfig }) => {
if (modified) {
const tsConfigFile = join(path, "tsconfig.json");
writeFileSync(tsConfigFile, JSON5.stringify(tsConfig, null, 2));
const body = JSON5.stringify(tsConfig, null, 2) + "\n";
writeFileSync(tsConfigFile, body);
}
});
}
Expand Down
7 changes: 2 additions & 5 deletions src/chains/ethereum/ethereum/src/blockchain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ import {
BUFFER_32_ZERO,
BUFFER_256_ZERO,
KNOWN_CHAINIDS,
keccak
keccak,
Logger
} from "@ganache/utils";
import AccountManager from "./data-managers/account-manager";
import BlockManager from "./data-managers/block-manager";
Expand Down Expand Up @@ -106,10 +107,6 @@ type BlockchainTypedEvents = {
stop: undefined;
};

interface Logger {
log(message?: any, ...optionalParams: any[]): void;
}

export type BlockchainOptions = {
db?: string | object;
db_path?: string;
Expand Down
3 changes: 2 additions & 1 deletion src/chains/ethereum/ethereum/src/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ import {
MessageEvent,
VmConsoleLogEvent
} from "./provider-events";

declare type RequestMethods = KnownKeys<EthereumApi>;

function parseCoinbase(
Expand Down Expand Up @@ -432,6 +431,8 @@ export class EthereumProvider
this.#executor.stop();
await this.#blockchain.stop();

await this.#options.logging.logger.close();

this.#executor.end();
this.emit("disconnect");
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,8 @@ describe("api", () => {
` > \`${hash}\` has not\n` +
" > yet been mined." +
" See https://trfl.io/v7-instamine for additional information."
)
),
`Actual: ${logger.loggedStuff}`
);
});

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import getProvider from "../../helpers/getProvider";
import assert from "assert";
import { Logger } from "@ganache/ethereum-options";
import { Logger } from "@ganache/utils";

describe("api", () => {
describe("eth", () => {
Expand Down
37 changes: 37 additions & 0 deletions src/chains/ethereum/ethereum/tests/provider.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@ import EthereumApi from "../src/api";
import getProvider from "./helpers/getProvider";
import compile from "./helpers/compile";
import Web3 from "web3";
import { promises, closeSync } from "fs";
const { stat, unlink } = promises;
import { INITCODE_TOO_LARGE } from "@ganache/ethereum-utils";
import tmp from "tmp-promise";
import { resolve } from "path";

describe("provider", () => {
describe("options", () => {
Expand Down Expand Up @@ -767,6 +771,39 @@ describe("provider", () => {
});
});

it("closes the logging fileDescriptor", async () => {
await tmp.withDir(
async ({ path }) => {
const filePath = resolve(path, "closes-logging-descriptor.log");
const provider = await getProvider({ logging: { file: filePath } });

const descriptor = provider.getOptions().logging.file;
assert.strictEqual(
typeof descriptor,
"number",
`File descriptor has unexpected type`
);

assert(
(await stat(filePath)).isFile(),
`log file: ${filePath} was not created`
);

await provider.disconnect();

assert.throws(
() => closeSync(descriptor),
"File descriptor is still valid after disconnect() called"
);
},
{
// `unsafeCleanup` instructs tmp-promise to recursively remove the
// created temporary directory, even when it's not empty.
unsafeCleanup: true
}
);
});

// todo: Reinstate this test when https://github.com/trufflesuite/ganache/issues/3499 is fixed
describe.skip("without asyncRequestProcessing", () => {
// we only test this with asyncRequestProcessing: false, because it's impossible to force requests
Expand Down
27 changes: 27 additions & 0 deletions src/chains/ethereum/options/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src/chains/ethereum/options/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
"cross-env": "7.0.3",
"mocha": "9.1.3",
"sinon": "12.0.1",
"tmp-promise": "3.0.2",
"ts-node": "10.9.1",
"typescript": "4.7.4"
}
Expand Down
10 changes: 3 additions & 7 deletions src/chains/ethereum/options/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,12 @@ import { ForkConfig, ForkOptions } from "./fork-options";
import {
Base,
Defaults,
Definitions,
ExternalConfig,
InternalConfig,
Legacy,
LegacyOptions,
OptionName,
OptionRawType,
Options,
OptionsConfig
} from "@ganache/options";
import { UnionToIntersection } from "./helper-types";
Expand Down Expand Up @@ -45,11 +43,9 @@ export type EthereumLegacyProviderOptions = Partial<
MakeLegacyOptions<ForkConfig>
>;

export type EthereumProviderOptions = Partial<
{
[K in keyof EthereumConfig]: ExternalConfig<EthereumConfig[K]>;
}
>;
export type EthereumProviderOptions = Partial<{
[K in keyof EthereumConfig]: ExternalConfig<EthereumConfig[K]>;
}>;

export type EthereumInternalOptions = {
[K in keyof EthereumConfig]: InternalConfig<EthereumConfig[K]>;
Expand Down
76 changes: 57 additions & 19 deletions src/chains/ethereum/options/src/logging-options.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { normalize } from "./helpers";
import { Definitions } from "@ganache/options";

export type Logger = {
log(message?: any, ...optionalParams: any[]): void;
};
import { openSync, PathLike } from "fs";
import { Logger, InternalLogger, createLogger } from "@ganache/utils";
import { EOL } from "os";

export type LoggingConfig = {
options: {
Expand Down Expand Up @@ -38,7 +37,8 @@ export type LoggingConfig = {
* ```
*/
readonly logger: {
type: Logger;
rawType: Logger;
type: InternalLogger;
hasDefault: true;
legacy: {
/**
Expand All @@ -65,15 +65,28 @@ export type LoggingConfig = {
};

/**
* Set to `true` to disable logging. This option overrides
* logging.logger and option.verbose.
* Set to `true` to disable writing logs to stdout (or logging.logger if specified).
* This option does not impact writing logs to a file (with logging.file).
*
* @defaultValue false
*/
readonly quiet: {
type: boolean;
hasDefault: true;
};

/**
* The file to append logs to.
*
* Can be a filename, or an instance of URL.
* note: the URL scheme must be `file`, e.g., `file://path/to/file.log`.
*
* By default no log file is created.
*/
readonly file: {
type: number;
rawType: PathLike;
};
};
};

Expand All @@ -87,28 +100,53 @@ export const LoggingOptions: Definitions<LoggingConfig> = {
},
quiet: {
normalize,
cliDescription: "Set to `true` to disable logging.",
cliDescription: "Set to `true` to disable writing logs to `logger.log` (`stdout` by default).",
default: () => false,
cliAliases: ["q", "quiet"],
cliType: "boolean"
},
logger: {
normalize,
cliDescription:
"An object, like `console`, that implements a `log` function.",
disableInCLI: true,
// disable the default logger if `quiet` is `true`
default: config => ({
log: config.quiet ? () => {} : console.log
}),
legacyName: "logger"
},
verbose: {
normalize,
cliDescription: "Set to `true` to log detailed RPC requests.",
default: () => false,
legacyName: "verbose",
cliAliases: ["v", "verbose"],
cliType: "boolean"
},
file: {
normalize: (raw: PathLike): number => {
let descriptor: number;

try {
descriptor = openSync(raw, "a");
} catch (err) {
const details = (err as Error).message;
throw new Error(
`Failed to open log file ${raw}. Please check if the file path is valid and if the process has write permissions to the directory.${EOL}${details}`
);
}
return descriptor;
},
cliDescription: "The file to append logs to.",
cliType: "string"
},
logger: {
normalize: (logger: Logger, config: Readonly<{ file: number }>) => {
return createLogger({
file: config.file,
baseLogger: logger
});
},
cliDescription:
"An object, like `console`, that implements a `log` function.",
disableInCLI: true,
default: config => {
const baseLogger = config.quiet ? { log: () => {} } : console;
return createLogger({
file: config.file,
baseLogger
});
},
legacyName: "logger"
}
};
15 changes: 11 additions & 4 deletions src/chains/ethereum/options/src/miner-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,13 @@ const toNumberOrString = (str: string) => {
}
};

// The `normalize` property expects a function with a signature of
// `normalize(value, config)`, but `Quantity.from(value, nullable)` doesn't
// match, so we wrap the `from` method in a function that matches the signature.
// We only instantiate the wrapper function once to avoid unnecessary function
// allocations.
const normalizeQuantity = value => Quantity.from(value);

export const MinerOptions: Definitions<MinerConfig> = {
blockTime: {
normalize: rawInput => {
Expand All @@ -246,7 +253,7 @@ export const MinerOptions: Definitions<MinerConfig> = {
cliType: "string"
},
defaultGasPrice: {
normalize: Quantity.from,
normalize: normalizeQuantity,
cliDescription:
"Sets the default gas price in WEI for transactions if not otherwise specified.",
default: () => Quantity.from(2_000_000_000),
Expand All @@ -256,7 +263,7 @@ export const MinerOptions: Definitions<MinerConfig> = {
cliCoerce: toBigIntOrString
},
blockGasLimit: {
normalize: Quantity.from,
normalize: normalizeQuantity,
cliDescription: "Sets the block gas limit in WEI.",
default: () => Quantity.from(30_000_000),
legacyName: "gasLimit",
Expand All @@ -274,15 +281,15 @@ export const MinerOptions: Definitions<MinerConfig> = {
cliCoerce: estimateOrToBigIntOrString
},
difficulty: {
normalize: Quantity.from,
normalize: normalizeQuantity,
cliDescription:
"Sets the block difficulty. Value is always 0 after the merge hardfork.",
default: () => Quantity.One,
cliType: "string",
cliCoerce: toBigIntOrString
},
callGasLimit: {
normalize: Quantity.from,
normalize: normalizeQuantity,
cliDescription:
"Sets the transaction gas limit in WEI for `eth_call` and `eth_estimateGas` calls.",
default: () => Quantity.from(50_000_000),
Expand Down
Loading

0 comments on commit 47bec2e

Please sign in to comment.