11import { normalize } from "./helpers" ;
22import { Definitions } from "@ganache/options" ;
3- import { promises } from "fs" ;
3+ import { promises , openSync , closeSync } from "fs" ;
44const open = promises . open ;
55import { 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
1113export 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+ }
0 commit comments