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

Commit 14a4ca3

Browse files
committed
Introduce tests for logger, fail validation if path is not w
ritable
1 parent 585202f commit 14a4ca3

File tree

4 files changed

+295
-67
lines changed

4 files changed

+295
-67
lines changed

src/chains/ethereum/options/src/index.ts

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,12 @@ import { ForkConfig, ForkOptions } from "./fork-options";
77
import {
88
Base,
99
Defaults,
10-
Definitions,
1110
ExternalConfig,
1211
InternalConfig,
1312
Legacy,
1413
LegacyOptions,
1514
OptionName,
1615
OptionRawType,
17-
Options,
1816
OptionsConfig
1917
} from "@ganache/options";
2018
import { UnionToIntersection } from "./helper-types";
@@ -45,11 +43,9 @@ export type EthereumLegacyProviderOptions = Partial<
4543
MakeLegacyOptions<ForkConfig>
4644
>;
4745

48-
export type EthereumProviderOptions = Partial<
49-
{
50-
[K in keyof EthereumConfig]: ExternalConfig<EthereumConfig[K]>;
51-
}
52-
>;
46+
export type EthereumProviderOptions = Partial<{
47+
[K in keyof EthereumConfig]: ExternalConfig<EthereumConfig[K]>;
48+
}>;
5349

5450
export type EthereumInternalOptions = {
5551
[K in keyof EthereumConfig]: InternalConfig<EthereumConfig[K]>;

src/chains/ethereum/options/src/logging-options.ts

Lines changed: 78 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import { normalize } from "./helpers";
22
import { Definitions } from "@ganache/options";
3-
import { promises } from "fs";
3+
import { promises, openSync, closeSync } from "fs";
44
const open = promises.open;
55
import { format } from "util";
66

7-
export type Logger = {
8-
log(message?: any, ...optionalParams: any[]): void;
7+
export type LogFunc = (message?: any, ...optionalParams: any[]) => void;
8+
9+
type Logger = {
10+
log: LogFunc;
911
};
1012

1113
export type LoggingConfig = {
@@ -112,7 +114,19 @@ export const LoggingOptions: Definitions<LoggingConfig> = {
112114
cliType: "boolean"
113115
},
114116
file: {
115-
normalize,
117+
normalize: rawInput => {
118+
// this will throw if the file is not writable
119+
try {
120+
const fh = openSync(rawInput, "a");
121+
closeSync(fh);
122+
} catch (err) {
123+
throw new Error(
124+
`Failed to write logs to ${rawInput}. Please check if the file path is valid and if the process has write permissions to the directory.`
125+
);
126+
}
127+
128+
return rawInput;
129+
},
116130
cliDescription: "The path of a file to which logs will be appended.",
117131
cliType: "string"
118132
},
@@ -123,41 +137,69 @@ export const LoggingOptions: Definitions<LoggingConfig> = {
123137
disableInCLI: true,
124138
// disable the default logger if `quiet` is `true`
125139
default: config => {
126-
let logger: (message?: any, ...optionalParams: any[]) => void;
127-
const consoleLogger = config.quiet ? () => {} : console.log;
128-
129-
if (config.file == null) {
130-
logger = consoleLogger;
131-
} else {
132-
const diskLogFormatter = (message: any) => {
133-
const linePrefix = `${new Date().toISOString()} `;
134-
return message.toString().replace(/^/gm, linePrefix);
135-
};
136-
137-
const formatter = (message: any, additionalParams: any[]) => {
138-
const formattedMessage = format(message, ...additionalParams);
139-
// we are logging to a file, but we still need to log to console
140-
consoleLogger(formattedMessage);
141-
return diskLogFormatter(formattedMessage) + "\n";
142-
};
143-
144-
const whenHandle = open(config.file, "a");
145-
let writing: Promise<void>;
146-
147-
logger = (message: any, ...additionalParams: any[]) => {
148-
whenHandle.then(async handle => {
149-
if (writing) {
150-
await writing;
151-
}
152-
writing = handle.appendFile(formatter(message, additionalParams));
153-
});
154-
};
155-
}
156-
140+
const { log } = createLogger(config);
157141
return {
158-
log: logger
142+
log
159143
};
160144
},
161145
legacyName: "logger"
162146
}
163147
};
148+
149+
type CreateLoggerConfig = {
150+
quiet?: boolean;
151+
file?: string;
152+
};
153+
154+
/**
155+
* Create a logger function based on the provided config.
156+
*
157+
* @param config specifying the configuration for the logger
158+
* @returns an object containing a `log` function and optional `getWaitHandle`
159+
* function returning a `Promise<void>` that resolves when any asyncronous
160+
* activies are completed.
161+
*/
162+
export function createLogger(config: { quiet?: boolean; file?: string }): {
163+
log: LogFunc;
164+
getWaitHandle?: () => Promise<void>;
165+
} {
166+
const logToConsole = config.quiet
167+
? async () => {}
168+
: async (message: any, ...optionalParams: any[]) =>
169+
console.log(message, ...optionalParams);
170+
171+
if ("file" in config) {
172+
const diskLogFormatter = (message: any) => {
173+
const linePrefix = `${new Date().toISOString()} `;
174+
return message.toString().replace(/^/gm, linePrefix);
175+
};
176+
177+
// we never close this handle, which is only ever problematic if we create a
178+
// _lot_ of handles. This can't happen, except (potentially) in tests,
179+
// because we only ever create one logger per Ganache instance.
180+
const whenHandle = open(config.file, "a");
181+
182+
let writing = Promise.resolve<void>(null);
183+
184+
const log = (message: any, ...optionalParams: any[]) => {
185+
const formattedMessage = format(message, ...optionalParams);
186+
// we are logging to a file, but we still need to log to console
187+
logToConsole(formattedMessage);
188+
189+
const currentWriting = writing;
190+
writing = whenHandle.then(async handle => {
191+
await currentWriting;
192+
193+
return handle.appendFile(diskLogFormatter(formattedMessage) + "\n");
194+
});
195+
};
196+
return {
197+
log,
198+
getWaitHandle: () => writing
199+
};
200+
} else {
201+
return {
202+
log: logToConsole
203+
};
204+
}
205+
}

src/chains/ethereum/options/tests/index.test.ts

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,30 +3,6 @@ import { EthereumDefaults, EthereumOptionsConfig } from "../src";
33
import sinon from "sinon";
44

55
describe("EthereumOptionsConfig", () => {
6-
describe("options", () => {
7-
let spy: any;
8-
beforeEach(() => {
9-
spy = sinon.spy(console, "log");
10-
});
11-
afterEach(() => {
12-
spy.restore();
13-
});
14-
it("logs via console.log by default", () => {
15-
const message = "message";
16-
const options = EthereumOptionsConfig.normalize({});
17-
options.logging.logger.log(message);
18-
assert.strictEqual(spy.withArgs(message).callCount, 1);
19-
});
20-
21-
it("disables the logger when the quiet flag is used", () => {
22-
const message = "message";
23-
const options = EthereumOptionsConfig.normalize({
24-
logging: { quiet: true }
25-
});
26-
options.logging.logger.log(message);
27-
assert.strictEqual(spy.withArgs(message).callCount, 0);
28-
});
29-
});
306
describe(".normalize", () => {
317
it("returns an options object with all default namespaces", () => {
328
const options = EthereumOptionsConfig.normalize({});

0 commit comments

Comments
 (0)