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

feat: allow logs to be written to a file by providing the --logging.file argument #4195

Merged
merged 66 commits into from
Jun 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
c7f86d3
Basic implementation of logging to file
jeffsmale90 Feb 22, 2023
54b85c5
Simplify logging
jeffsmale90 Feb 22, 2023
b0ca2e9
Simplify regex for inserting timestamps
jeffsmale90 Feb 3, 2023
a397b6f
Remove --timestamps argument
jeffsmale90 Feb 22, 2023
0f13a36
Use a persistent filehandle instead of calling 'appendFile' each tile…
jeffsmale90 Feb 20, 2023
b206a42
Introduce tests for logger, fail validation if path is not w
jeffsmale90 Feb 21, 2023
d1ec4fe
Update tests to use per-test log files
jeffsmale90 Feb 23, 2023
feb57bd
Resolve invalid path
jeffsmale90 Feb 23, 2023
0b8673f
In windows, use reserved path 'c:\NUL' for invalid path
jeffsmale90 Feb 23, 2023
6d73be8
Revert changes and just await the rejection
jeffsmale90 Feb 23, 2023
8b4ae04
Let's do use the NUL workaround for windows
jeffsmale90 Feb 23, 2023
4b6159e
Attempt to get tests to passing state
jeffsmale90 Feb 23, 2023
e2db28f
Use current working directory as invalid file path
jeffsmale90 Feb 23, 2023
d2db1a4
Tidy up logging
jeffsmale90 Feb 23, 2023
4e64fc7
Create file when validating logging.file path is writable.
jeffsmale90 Feb 23, 2023
8423a65
Move logger into cli project, allow caller to close underlying fileha…
jeffsmale90 Feb 27, 2023
fcf39ed
Revert unrelated changes, fix flaky initialisation of logger
jeffsmale90 Feb 27, 2023
22ec214
Fix error when no logging config exists
jeffsmale90 Feb 27, 2023
37e1616
Switch to use 'fs.openSync()' instead of 'fs.writeSync()' because win…
jeffsmale90 Feb 27, 2023
44a7107
Work in progress. Move logger into @ganache/utils, resolve filehandle…
jeffsmale90 Mar 3, 2023
b02821d
Improve tests, implementation, support for filecoin
jeffsmale90 Mar 5, 2023
4e7ddd6
Finish tidying up implementation. Broke the console.log tests though …
jeffsmale90 Mar 6, 2023
f9aac47
Support setting both file and logger, refactor tangentially related test
jeffsmale90 Mar 7, 2023
6cc6fb2
tidy up implementation, fix tests, don't accept filehandle as logging…
jeffsmale90 Mar 8, 2023
303330d
Update package-lock.json - adds reference to @ganache/utils
jeffsmale90 Mar 8, 2023
7edf108
Upgrade @ganache/utils
jeffsmale90 Mar 8, 2023
ca8f581
Update reference to Logger type in tests
jeffsmale90 Mar 9, 2023
04a0674
Modify 'EPERM' test to support Windows (who does some freaky stuff wh…
jeffsmale90 Mar 9, 2023
8352f82
Pin @types/sinon
jeffsmale90 Mar 9, 2023
5a556d6
Add test to ensure that resolved file descriptor is append only
jeffsmale90 Mar 9, 2023
1ff9eb9
Fix failing tests
jeffsmale90 Mar 9, 2023
d08ff26
Merge remote-tracking branch 'origin/feat/logging' into feat/logging
jeffsmale90 Mar 9, 2023
b577089
Add test to ensure that provider.disconnect() closes the file descriptor
jeffsmale90 Mar 9, 2023
cc87e1e
Fix @jeffsmale90's broken regex :)
jeffsmale90 Mar 12, 2023
0d445cb
Fix @jeffsmale90's broken regex :)
jeffsmale90 Mar 12, 2023
a235c7a
Fix @jeffsmale90's broken regex :)
jeffsmale90 Mar 12, 2023
838b9dc
Comments and refactor from review
jeffsmale90 Mar 12, 2023
ff67775
Oops - messed up comments
jeffsmale90 Mar 15, 2023
2af6a26
Use WriteStream instead of appending to an open filehandle directly. …
jeffsmale90 Mar 27, 2023
faad325
Fix up my mistakes
jeffsmale90 Mar 28, 2023
33849ce
Close file handle correctly in test
jeffsmale90 Mar 28, 2023
67d4db3
Refactor logging options tests, correctly remove fixture files
jeffsmale90 Mar 29, 2023
685b6f9
Close test log files correctly in logging-options tests
jeffsmale90 Mar 29, 2023
acc4bef
Remove unnecessary import
jeffsmale90 Mar 29, 2023
12f3bd1
Don't await `getProvider()` twice
jeffsmale90 Apr 13, 2023
247454c
change 'writing' to `'write' because it makes sense this way
jeffsmale90 Apr 17, 2023
daa040a
Refinement as per PR
jeffsmale90 Apr 17, 2023
295ab1a
New line at end of tsconfig files
jeffsmale90 Apr 17, 2023
eb8d2b8
Assert that logs are appended to an existing file. Refactor logging-o…
jeffsmale90 Apr 17, 2023
970fb76
Use tmp-promise to resolve temporary files for use in tests
jeffsmale90 Apr 17, 2023
ec7d5a3
Merge branch 'develop' into feat/logging
jeffsmale90 Apr 17, 2023
d47acdd
Fix baseLogger
jeffsmale90 Apr 17, 2023
39a7adb
Support CRLF and LF line endings in multiline log messages
jeffsmale90 Apr 17, 2023
180b9e8
Use tmp-promise in 'closes the logging fileDescriptor' test
jeffsmale90 Apr 17, 2023
0a80a3e
Await tests that user tmp.with(File|Dir), remove thrown Error from te…
jeffsmale90 Apr 17, 2023
0fb659e
TIL: tmp.dir() only cleans up if the dir is empty. Some formatting fi…
jeffsmale90 Apr 17, 2023
0d7cb88
Use unsafeCleanup to force tmp to remove the temp directory, even whe…
jeffsmale90 Apr 17, 2023
4a76b48
Don't explicitly unlink filepath, rely on tmp-promise to clean itself…
jeffsmale90 Jun 19, 2023
c1d9565
Update src/chains/ethereum/options/src/logging-options.ts
jeffsmale90 Jun 20, 2023
7671bea
Move dep to devdep, fix EOL discrepancy in link-ts-references
jeffsmale90 Jun 20, 2023
084b583
Merge branch 'develop' into feat/logging
jeffsmale90 Jun 21, 2023
b52db3e
Move tmp-promise to devDependencies
jeffsmale90 Jun 22, 2023
6a072f7
Reorder deps, and fix package-locks
jeffsmale90 Jun 22, 2023
6d8cce1
Merge branch 'develop' into feat/logging
davidmurdoch Jun 26, 2023
2213061
Revert those silly package-lock files
jeffsmale90 Jun 26, 2023
50a614c
Doing the tmp-promise shuffle
jeffsmale90 Jun 27, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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(
davidmurdoch marked this conversation as resolved.
Show resolved Hide resolved
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,
davidmurdoch marked this conversation as resolved.
Show resolved Hide resolved
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;
cds-amal marked this conversation as resolved.
Show resolved Hide resolved
};
};
};

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");
cds-amal marked this conversation as resolved.
Show resolved Hide resolved
} catch (err) {
const details = (err as Error).message;
throw new Error(
davidmurdoch marked this conversation as resolved.
Show resolved Hide resolved
`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);
cds-amal marked this conversation as resolved.
Show resolved Hide resolved

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