• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

igoramadas / anyhow / 4894865863

05 May 2023 03:10PM UTC coverage: 89.359% (+57.8%) from 31.554%
4894865863

push

github

Igor Ramadas
Updating tests.

313 of 364 branches covered (85.99%)

Branch coverage included in aggregate %.

5 of 5 new or added lines in 1 file covered. (100.0%)

426 of 463 relevant lines covered (92.01%)

1824.81 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.71
/src/index.ts
1
// Anyhow: index.ts
2

3
import {AnyhowOptions, Logger} from "./types"
4
import {libSetup} from "./setup"
2✔
5
import {cloneDeep, dedupArray, getTimestamp, mergeDeep} from "./utils"
2✔
6
import parser from "./parser"
2✔
7

8
// Chalk (colorized console output). Will be instantiated on setup().
9
let chalk = null
2✔
10

11
// Default options.
12
const defaultOptions: AnyhowOptions = {
2✔
13
    compact: true,
14
    maxDepth: 6,
15
    appName: "Anyhow",
16
    separator: " | ",
17
    levels: ["info", "warn", "error"],
18
    styles: {
19
        debug: ["gray"],
20
        info: ["white"],
21
        warn: ["yellow"],
22
        error: ["red", "bold"]
23
    },
24
    preprocessors: [],
25
    preprocessorOptions: {
26
        maskedFields: [
27
            "authorization",
28
            "password",
29
            "passcode",
30
            "secret",
31
            "token",
32
            "accesstoken",
33
            "access_token",
34
            "refreshtoken",
35
            "refresh_token",
36
            "clientsecret",
37
            "client_secret",
38
            "apikey",
39
            "api_key",
40
            "apisecret",
41
            "api_secret",
42
            "privatekey",
43
            "private_key"
44
        ],
45
        clone: true
46
    }
47
}
48

49
/**
50
 * This is the main class of the Anyhow library.
51
 * @example const logger = require("anyhow")
52
 */
53
class Anyhow {
54
    private static _instance: Anyhow
55
    /** @hidden */
56
    static get Instance() {
57
        return this._instance || (this._instance = new this())
2✔
58
    }
59

60
    /**
61
     * Init with default options.
62
     */
63
    constructor() {
64
        this.options = cloneDeep(defaultOptions)
2✔
65
    }
66

67
    // PROPERTIES
68
    // --------------------------------------------------------------------------
69

70
    /**
71
     * Internal object defining a Logger.
72
     */
73
    private _logger: Logger = null
2✔
74

75
    /**
76
     * Getter for _lib, to be used by external modules.
77
     */
78
    get logger(): Logger {
79
        return this._logger
1✔
80
    }
81

82
    /**
83
     * For compatibility only, now returns the logger's name.
84
     */
85
    get lib(): string {
86
        return this._logger ? this._logger.name : "none"
4✔
87
    }
88

89
    /**
90
     * Helper to check if [[setup]] was already called and logger is ready to log.
91
     */
92
    get isReady(): boolean {
93
        if (this._logger) {
35✔
94
            return true
33✔
95
        }
96

97
        return false
2✔
98
    }
99

100
    /**
101
     * Library options (internal).
102
     */
103
    private _options: AnyhowOptions
104

105
    /**
106
     * Get library options.
107
     */
108
    get options(): AnyhowOptions {
109
        return this._options
55✔
110
    }
111

112
    /**
113
     * Set library options.
114
     */
115
    set options(value: AnyhowOptions) {
116
        const newOptions = value ? mergeDeep(defaultOptions, value) : cloneDeep(defaultOptions)
3✔
117
        this.applyOptions(newOptions)
3✔
118
    }
119

120
    /**
121
     * Function to catch and log uncaught exceptions, set by [[uncaughtExceptions]].
122
     */
123
    private _uncaughtExceptionHandler: Function = null
2✔
124

125
    /**
126
     * Enable or disable the uncaught exception handler.
127
     */
128
    private set uncaughtExceptions(value: boolean) {
129
        if (value) {
51✔
130
            this._uncaughtExceptionHandler = (err) => {
2✔
131
                this.error(this._options.appName, "Uncaught exception", err)
×
132

133
                return
×
134
            }
135
            process.on("uncaughtException" as any, this._uncaughtExceptionHandler as any)
2✔
136
        } else {
137
            if (this._uncaughtExceptionHandler) {
49✔
138
                process.off("uncaughtException", this._uncaughtExceptionHandler as any)
2✔
139
            }
140
            this._uncaughtExceptionHandler = null
49✔
141
        }
142
    }
143

144
    /**
145
     * Function to catch and log unhandled rejections, set by [[unhandledRejections]].
146
     */
147
    private _unhandledRejectionHandler: Function = null
2✔
148

149
    /**
150
     * Enable or disable the unhandled rejection handler.
151
     */
152
    private set unhandledRejections(value: boolean) {
153
        if (value) {
49✔
154
            this._unhandledRejectionHandler = (err) => {
2✔
155
                this.error(this._options.appName, "Unhandled rejection", err)
1✔
156

157
                return
1✔
158
            }
159
            process.on("unhandledRejection" as any, this._unhandledRejectionHandler as any)
2✔
160
        } else {
161
            if (this._unhandledRejectionHandler) {
47✔
162
                process.off("Unhandled rejection", this._unhandledRejectionHandler as any)
1✔
163
            }
164
            this._unhandledRejectionHandler = null
47✔
165
        }
166
    }
167

168
    /**
169
     * Auto-generated list of messages that should not be logged.
170
     */
171
    private ignoreMessages: {[message: string]: number} = {}
2✔
172

173
    // LOGGING METHODS
174
    // --------------------------------------------------------------------------
175

176
    /**
177
     * Default logging method.
178
     * @param level String representing the level: error, warn, info, verbose, debug, silly
179
     * @param args Array of arguments to be logged.
180
     * @returns The generated message that was just logged.
181
     */
182
    log(level: string, args: any | any[]): string {
183
        if (this._options.levels.indexOf(level) < 0) return null
37✔
184

185
        let message = parser.getMessage(args)
32✔
186

187
        // Add timestamp?
188
        if (this.options.timestamp) {
32✔
189
            message = `${getTimestamp()}${this.options.separator}${message}`
13✔
190
        }
191

192
        // If setup was not called yet, defaults to console logging and emit warning.
193
        if (!this.isReady) {
32✔
194
            console.warn("Anyhow: please call Anyhow's setup() on your application startup. Will default to console.log() for now.")
1✔
195
            this.setup("console")
1✔
196
            this.console(level, message)
1✔
197
        } else {
198
            this._logger.log(level, message)
31✔
199
        }
200

201
        return message
32✔
202
    }
203

204
    /**
205
     * Shortcut to [[log]]("debug", args).
206
     */
207
    debug = (...args: any[]): string => {
2✔
208
        if (this._options.levels.indexOf("debug") < 0) return null
7✔
209
        if (args.length < 1) return
2✔
210
        let message = parser.getMessage(args, ["friendlyErrors"])
1✔
211
        return this.log("debug", message)
1✔
212
    }
213

214
    /**
215
     * Shortcut to [[log]]("info", args).
216
     */
217
    info = (...args: any[]): string => {
2✔
218
        if (this._options.levels.indexOf("info") < 0) return null
25✔
219
        if (args.length < 1) return
22✔
220
        let message = parser.getMessage(args, ["friendlyErrors"])
21✔
221
        return this.log("info", message)
21✔
222
    }
223

224
    /**
225
     * Shortcut to [[log]]("warn", args).
226
     */
227
    warn = (...args: any[]): string => {
2✔
228
        if (this._options.levels.indexOf("warn") < 0) return null
7✔
229
        if (args.length < 1) return
4✔
230
        let message = parser.getMessage(args)
3✔
231
        return this.log("warn", message)
3✔
232
    }
233

234
    /**
235
     * Shortcut to [[log]]("error", args).
236
     */
237
    error = (...args: any[]): string => {
2✔
238
        if (this._options.levels.indexOf("error") < 0) return null
7✔
239
        if (args.length < 1) return
4✔
240
        let message = parser.getMessage(args)
3✔
241
        return this.log("error", message)
3✔
242
    }
243

244
    /**
245
     * Shortcut to [[log]]("warn", args), with a deprecation notice.
246
     * Will not log if the noDeprecation flag is set.
247
     */
248
    deprecated = (...args: any[]): string => {
2✔
249
        if (args.length < 1 || process["noDeprecation"]) return
5✔
250
        let message = parser.getMessage(args)
4✔
251
        if (this.ignoreMessages[message]) {
4✔
252
            this.ignoreMessages[message]++
3✔
253
            return ""
3✔
254
        }
255
        this.ignoreMessages[message] = 1
1✔
256
        return this.log("warn", `DEPRECATED! ${message}`)
1✔
257
    }
258

259
    /**
260
     * Shortcut to [[log]]("debug", args) with object inspection instead of plain text.
261
     */
262
    inspect = (...args: any[]): string => {
2✔
263
        if (args.length < 1) return
5✔
264
        let message = parser.getInspection(args)
4✔
265
        return this.log("debug", message)
4✔
266
    }
267

268
    /**
269
     * Log directly to the console. This is the default logger handler
270
     * in case no other compatible libraries are found.
271
     * @param level String representing the level: error, warn, info, debug
272
     * @param args Array of arguments to be logged.
273
     * @returns The generated message that was just logged.
274
     */
275
    console = (level: string, args: any): string => {
2✔
276
        if (this._options.levels.indexOf(level.toLowerCase()) < 0) return null
23✔
277

278
        let message = parser.getMessage(args)
22✔
279

280
        // Add level to the output?
281
        if (this._options.levelOnConsole) {
22✔
282
            message = `${level.toUpperCase()}: ${message}`
1✔
283
        }
284

285
        let styledMessage = message
22✔
286
        let logMethod = console.log
22✔
287

288
        // Check if console supports the passed level. Defaults to "log".
289
        if (console[level] && level != "debug") {
22✔
290
            logMethod = console[level]
20✔
291
        }
292

293
        // Is chalk enabled? Use it to colorize the messages.
294
        if (chalk && this._options.styles) {
22✔
295
            let styles = this._options.styles[level]
20✔
296
            let chalkStyle
297

298
            if (styles) {
20✔
299
                chalkStyle = chalk
17✔
300
                for (let s of styles) {
17✔
301
                    chalkStyle = chalkStyle[s]
22✔
302
                }
303
            } else {
304
                chalkStyle = chalk.white
3✔
305
            }
306

307
            styledMessage = chalkStyle(message)
20✔
308
        }
309

310
        logMethod(styledMessage)
22✔
311

312
        return message
22✔
313
    }
314

315
    // SETUP AND CONFIGURING
316
    // --------------------------------------------------------------------------
317

318
    /**
319
     * Setup will try to load compatible loggers, and fall back to the console
320
     * if nothing was found. Will try using libraries on this order:
321
     * winston, bunyan, pino, gcloud, console.
322
     * @param lib Optional, force a specific library or Logger to be used, defaults to console.
323
     * @param libOptions Additional options to be passed to the underlying logging library.
324
     */
325
    setup = (lib?: "winston" | "bunyan" | "pino" | "gcloud" | "console" | "none" | Logger, libOptions?: any): void => {
2✔
326
        if (!lib) lib = "console"
19✔
327

328
        libSetup(this, lib, libOptions)
19✔
329

330
        if (lib == "console" && this._options.styles) {
17✔
331
            try {
5✔
332
                if (chalk === null) {
5✔
333
                    chalk = require("chalk")
2✔
334
                }
335
            } catch (ex) {
336
                /* istanbul ignore next */
337
                chalk = false
338
            }
339
        }
340
    }
341

342
    /**
343
     * Helper to set partial library options. Only the passed parameters will be updated.
344
     * @param options Options to be updated.
345
     */
346
    setOptions = (options: AnyhowOptions): void => {
2✔
347
        if (!options) return
45!
348

349
        const newOptions = mergeDeep(this._options, options)
45✔
350
        this.applyOptions(newOptions)
45✔
351
    }
352

353
    /**
354
     * Apply a new options object to the library.
355
     * @param newOptions New options object to be applied.
356
     */
357
    private applyOptions = (newOptions: AnyhowOptions): void => {
2✔
358
        if (newOptions.levels?.length > 0) {
48✔
359
            newOptions.levels = dedupArray(newOptions.levels)
47✔
360
        } else {
361
            newOptions.levels = []
1✔
362
        }
363

364
        if (newOptions.preprocessors?.length > 0) {
48✔
365
            newOptions.preprocessors = dedupArray(newOptions.preprocessors)
8✔
366
        } else {
367
            newOptions.preprocessors = []
40✔
368
        }
369

370
        if (newOptions.preprocessorOptions?.maskedFields?.length > 0) {
48!
371
            newOptions.preprocessorOptions.maskedFields = dedupArray(newOptions.preprocessorOptions.maskedFields)
48✔
372
        } else if (newOptions.preprocessorOptions) {
×
373
            newOptions.preprocessorOptions.maskedFields = []
×
374
        }
375

376
        if (newOptions.styles) {
48✔
377
            for (let key in newOptions.styles) {
47✔
378
                if (newOptions.styles[key].length > 0) {
101!
379
                    newOptions.styles[key] = dedupArray(newOptions.styles[key])
101✔
380
                }
381
            }
382
        } else {
383
            newOptions.styles = {}
1✔
384
        }
385

386
        this.uncaughtExceptions = newOptions.uncaughtExceptions
48✔
387
        this.unhandledRejections = newOptions.unhandledRejections
48✔
388

389
        this._options = newOptions
48✔
390
        parser.options = newOptions
48✔
391
    }
392
}
393

394
// Exports...
395
export = Anyhow.Instance
2✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc