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

feat: expose logs of detached instances via ganache instances logs command #4254

Draft
wants to merge 37 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 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
f4e7ad1
implement 'ganache instances logs <name>' with support for --since, -…
jeffsmale90 Feb 22, 2023
978aab1
Fix issue where --follow fails with unhelpful error message, if the l…
jeffsmale90 Feb 23, 2023
5b9a55b
Add logs-stream.fixture.log and improve related test, add tests for g…
jeffsmale90 Mar 10, 2023
89ec342
Improve help text for 'ganache instances logs'
jeffsmale90 Mar 10, 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
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 @@ -105,10 +106,6 @@ type BlockchainTypedEvents = {
stop: undefined;
};

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

export type BlockchainOptions = {
db?: string | object;
db_path?: string;
Expand Down
8 changes: 7 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,7 @@ import {
MessageEvent,
VmConsoleLogEvent
} from "./provider-events";

import { closeSync } from "fs";
declare type RequestMethods = KnownKeys<EthereumApi>;

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

// only need to do this if it's an `AsyncronousLogger`
if ("getCompletionHandle" in this.#options.logging.logger) {
await this.#options.logging.logger.getCompletionHandle();
closeSync(this.#options.logging.file);
}

this.#executor.end();
this.emit("disconnect");
};
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/typings/src/logging-options";
import { Logger } from "@ganache/utils";

describe("api", () => {
describe("eth", () => {
Expand Down
30 changes: 30 additions & 0 deletions src/chains/ethereum/ethereum/tests/provider.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ 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;

describe("provider", () => {
describe("options", () => {
Expand Down Expand Up @@ -644,6 +646,34 @@ describe("provider", () => {
});
});

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

try {
const descriptor = (await provider).getOptions().logging.file;
assert.strictEqual(
typeof descriptor,
"number",
`File descriptor has unexepected value ${typeof descriptor}`
);

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"
);
} finally {
await unlink(filePath);
}
});

// 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
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
62 changes: 47 additions & 15 deletions src/chains/ethereum/options/src/logging-options.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
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, createLogger } from "@ganache/utils";

export type LoggingConfig = {
options: {
Expand Down Expand Up @@ -74,6 +72,15 @@ export type LoggingConfig = {
type: boolean;
hasDefault: true;
};

/**
* If you set this option, Ganache will write logs to a file located at the
* specified path.
*/
readonly file: {
type: number;
rawType: PathLike;
};
};
};

Expand All @@ -92,23 +99,48 @@ export const LoggingOptions: Definitions<LoggingConfig> = {
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) {
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.`
);
}
return descriptor;
},
cliDescription:
"If set, Ganache will write logs to a file located at the specified path.",
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"
}
};
10 changes: 5 additions & 5 deletions src/chains/ethereum/options/src/miner-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ const toNumberOrString = (str: string) => {
return parseInt(str);
}
};

const normalizeQuantity = value => Quantity.from(value);
export const MinerOptions: Definitions<MinerConfig> = {
blockTime: {
normalize: rawInput => {
Expand All @@ -246,7 +246,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 +256,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 +274,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
25 changes: 0 additions & 25 deletions src/chains/ethereum/options/tests/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,7 @@
import assert from "assert";
import { EthereumDefaults, EthereumOptionsConfig } from "../src";
import sinon from "sinon";

describe("EthereumOptionsConfig", () => {
describe("options", () => {
let spy: any;
beforeEach(() => {
spy = sinon.spy(console, "log");
});
afterEach(() => {
spy.restore();
});
it("logs via console.log by default", () => {
const message = "message";
const options = EthereumOptionsConfig.normalize({});
options.logging.logger.log(message);
assert.strictEqual(spy.withArgs(message).callCount, 1);
});

it("disables the logger when the quiet flag is used", () => {
const message = "message";
const options = EthereumOptionsConfig.normalize({
logging: { quiet: true }
});
options.logging.logger.log(message);
assert.strictEqual(spy.withArgs(message).callCount, 0);
});
});
describe(".normalize", () => {
it("returns an options object with all default namespaces", () => {
const options = EthereumOptionsConfig.normalize({});
Expand Down
Loading