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

igoramadas / anyhow / 4896311387

05 May 2023 06:12PM UTC coverage: 94.052% (+4.7%) from 89.359%
4896311387

push

github

Igor Ramadas
Version 3.3.0.

324 of 353 branches covered (91.78%)

Branch coverage included in aggregate %.

435 of 454 relevant lines covered (95.81%)

3176.52 hits per line

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

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

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

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

11
/**
12
 * This is the main class of the Anyhow library.
13
 * @example const logger = require("anyhow")
14
 */
15
class Anyhow {
16
    private static _instance: Anyhow
17
    /** @hidden */
18
    static get Instance() {
19
        return this._instance || (this._instance = new this())
3✔
20
    }
21

22
    /**
23
     * Init with default options.
24
     */
25
    constructor() {
26
        this.options = cloneDeep(defaultOptions)
3✔
27
    }
28

29
    // PROPERTIES
30
    // --------------------------------------------------------------------------
31

32
    /**
33
     * Internal object defining a Logger.
34
     */
35
    private _logger: Logger = null
3✔
36

37
    /**
38
     * Getter for _lib, to be used by external modules.
39
     */
40
    get logger(): Logger {
41
        return this._logger
1✔
42
    }
43

44
    /**
45
     * For compatibility only, now returns the logger's name.
46
     */
47
    get lib(): string {
48
        return this._logger ? this._logger.name : "none"
4✔
49
    }
50

51
    /**
52
     * Helper to check if [[setup]] was already called and logger is ready to log.
53
     */
54
    get isReady(): boolean {
55
        if (this._logger) {
38✔
56
            return true
36✔
57
        }
58

59
        return false
2✔
60
    }
61

62
    /**
63
     * Library options (internal).
64
     */
65
    private _options: AnyhowOptions
66

67
    /**
68
     * Get library options.
69
     */
70
    get options(): AnyhowOptions {
71
        return this._options
59✔
72
    }
73

74
    /**
75
     * Set library options.
76
     */
77
    set options(value: AnyhowOptions) {
78
        const newOptions = value ? mergeDeep(defaultOptions, value) : cloneDeep(defaultOptions)
4✔
79
        this.applyOptions(newOptions)
4✔
80
    }
81

82
    /**
83
     * Function to catch and log uncaught exceptions, set by [[uncaughtExceptions]].
84
     */
85
    private _uncaughtExceptionHandler: Function = null
3✔
86

87
    /**
88
     * Enable or disable the uncaught exception handler.
89
     */
90
    private set uncaughtExceptions(value: boolean) {
91
        if (value) {
55✔
92
            this._uncaughtExceptionHandler = (err) => {
3✔
93
                this.error(this._options.appName, "Uncaught exception", err)
1✔
94

95
                return
1✔
96
            }
97
            process.on("uncaughtException" as any, this._uncaughtExceptionHandler as any)
3✔
98
        } else {
99
            if (this._uncaughtExceptionHandler) {
52✔
100
                process.off("uncaughtException", this._uncaughtExceptionHandler as any)
3✔
101
            }
102
            this._uncaughtExceptionHandler = null
52✔
103
        }
104
    }
105

106
    /**
107
     * Function to catch and log unhandled rejections, set by [[unhandledRejections]].
108
     */
109
    private _unhandledRejectionHandler: Function = null
3✔
110

111
    /**
112
     * Enable or disable the unhandled rejection handler.
113
     */
114
    private set unhandledRejections(value: boolean) {
115
        if (value) {
51✔
116
            this._unhandledRejectionHandler = (err) => {
2✔
117
                this.error(this._options.appName, "Unhandled rejection", err)
1✔
118

119
                return
1✔
120
            }
121
            process.on("unhandledRejection" as any, this._unhandledRejectionHandler as any)
2✔
122
        } else {
123
            if (this._unhandledRejectionHandler) {
49✔
124
                process.off("Unhandled rejection", this._unhandledRejectionHandler as any)
1✔
125
            }
126
            this._unhandledRejectionHandler = null
49✔
127
        }
128
    }
129

130
    /**
131
     * Auto-generated list of messages that should not be logged.
132
     */
133
    private ignoreMessages: {[message: string]: number} = {}
3✔
134

135
    // LOGGING METHODS
136
    // --------------------------------------------------------------------------
137

138
    /**
139
     * Default logging method.
140
     * @param level String representing the level: error, warn, info, verbose, debug, silly
141
     * @param args Array of arguments to be logged.
142
     * @returns The generated message that was just logged.
143
     */
144
    log(level: string, args: any | any[]): string {
145
        if (this._options.levels.indexOf(level) < 0) return null
40✔
146

147
        let message = parser.getMessage(args)
35✔
148

149
        // Add timestamp?
150
        if (this.options.timestamp) {
35✔
151
            message = `${getTimestamp()}${this.options.separator}${message}`
14✔
152
        }
153

154
        // If setup was not called yet, defaults to console logging and emit warning.
155
        if (!this.isReady) {
35✔
156
            console.warn("Anyhow: please call Anyhow's setup() on your application startup. Will default to console.log() for now.")
1✔
157
            this.setup("console")
1✔
158
            this.console(level, message)
1✔
159
        } else {
160
            this._logger.log(level, message)
34✔
161
        }
162

163
        return message
35✔
164
    }
165

166
    /**
167
     * Shortcut to [[log]]("debug", args).
168
     */
169
    debug = (...args: any[]): string => {
3✔
170
        if (this._options.levels.indexOf("debug") < 0) return null
7✔
171
        if (args.length < 1) return
2✔
172
        let message = parser.getMessage(args, ["friendlyErrors"])
1✔
173
        return this.log("debug", message)
1✔
174
    }
175

176
    /**
177
     * Shortcut to [[log]]("info", args).
178
     */
179
    info = (...args: any[]): string => {
3✔
180
        if (this._options.levels.indexOf("info") < 0) return null
26✔
181
        if (args.length < 1) return
23✔
182
        let message = parser.getMessage(args, ["friendlyErrors"])
22✔
183
        return this.log("info", message)
22✔
184
    }
185

186
    /**
187
     * Shortcut to [[log]]("warn", args).
188
     */
189
    warn = (...args: any[]): string => {
3✔
190
        if (this._options.levels.indexOf("warn") < 0) return null
7✔
191
        if (args.length < 1) return
4✔
192
        let message = parser.getMessage(args)
3✔
193
        return this.log("warn", message)
3✔
194
    }
195

196
    /**
197
     * Shortcut to [[log]]("error", args).
198
     */
199
    error = (...args: any[]): string => {
3✔
200
        if (this._options.levels.indexOf("error") < 0) return null
10✔
201
        if (args.length < 1) return
7✔
202
        let message = parser.getMessage(args)
6✔
203
        return this.log("error", message)
6✔
204
    }
205

206
    /**
207
     * Shortcut to [[log]]("warn", args), with a deprecation notice.
208
     * Will not log if the noDeprecation flag is set.
209
     */
210
    deprecated = (...args: any[]): string => {
3✔
211
        if (args.length < 1 || process["noDeprecation"]) return
5✔
212
        let message = parser.getMessage(args)
4✔
213
        if (this.ignoreMessages[message]) {
4✔
214
            this.ignoreMessages[message]++
3✔
215
            return ""
3✔
216
        }
217
        this.ignoreMessages[message] = 1
1✔
218
        return this.log("warn", `DEPRECATED! ${message}`)
1✔
219
    }
220

221
    /**
222
     * Shortcut to [[log]]("debug", args) with object inspection instead of plain text.
223
     */
224
    inspect = (...args: any[]): string => {
3✔
225
        if (args.length < 1) return
5✔
226
        let message = parser.getInspection(args)
4✔
227
        return this.log("debug", message)
4✔
228
    }
229

230
    /**
231
     * Log directly to the console. This is the default logger handler
232
     * in case no other compatible libraries are found.
233
     * @param level String representing the level: error, warn, info, debug
234
     * @param args Array of arguments to be logged.
235
     * @returns The generated message that was just logged.
236
     */
237
    console = (level: string, args: any): string => {
3✔
238
        if (this._options.levels.indexOf(level.toLowerCase()) < 0) return null
24✔
239

240
        let message = parser.getMessage(args)
23✔
241

242
        // Add level to the output?
243
        if (this._options.levelOnConsole) {
23✔
244
            message = `${level.toUpperCase()}: ${message}`
1✔
245
        }
246

247
        let styledMessage = message
23✔
248
        let logMethod = console.log
23✔
249

250
        // Check if console supports the passed level. Defaults to "log".
251
        if (console[level] && level != "debug") {
23✔
252
            logMethod = console[level]
21✔
253
        }
254

255
        // Is chalk enabled? Use it to colorize the messages.
256
        if (chalk && this._options.styles) {
23✔
257
            let styles = this._options.styles[level]
20✔
258
            let chalkStyle
259

260
            if (styles) {
20✔
261
                chalkStyle = chalk
17✔
262
                for (let s of styles) {
17✔
263
                    chalkStyle = chalkStyle[s]
22✔
264
                }
265
            } else {
266
                chalkStyle = chalk.white
3✔
267
            }
268

269
            styledMessage = chalkStyle(message)
20✔
270
        }
271

272
        logMethod(styledMessage)
23✔
273

274
        return message
23✔
275
    }
276

277
    // SETUP AND CONFIGURING
278
    // --------------------------------------------------------------------------
279

280
    /**
281
     * Setup will try to load compatible loggers, and fall back to the console
282
     * if nothing was found. Will try using libraries on this order:
283
     * winston, bunyan, pino, gcloud, console.
284
     * @param lib Optional, force a specific library or Logger to be used, defaults to console.
285
     * @param libOptions Additional options to be passed to the underlying logging library.
286
     */
287
    setup = (lib?: "winston" | "bunyan" | "pino" | "gcloud" | "console" | "none" | Logger, libOptions?: any): void => {
3✔
288
        if (!lib) lib = "console"
20✔
289

290
        libSetup(this, lib, libOptions)
20✔
291

292
        if (lib == "console" && this._options.styles) {
18✔
293
            try {
6✔
294
                if (chalk === null) {
6✔
295
                    chalk = require("chalk")
3✔
296
                }
297
            } catch (ex) {
298
                /* istanbul ignore next */
299
                chalk = false
300
            }
301
        }
302
    }
303

304
    /**
305
     * Helper to set partial library options. Only the passed parameters will be updated.
306
     * @param options Options to be updated.
307
     */
308
    setOptions = (options: AnyhowOptions): void => {
3✔
309
        if (!options) return
46!
310

311
        const newOptions = mergeDeep(this._options, options)
46✔
312
        this.applyOptions(newOptions)
46✔
313
    }
314

315
    /**
316
     * Apply a new options object to the library.
317
     * @param newOptions New options object to be applied.
318
     */
319
    private applyOptions = (newOptions: AnyhowOptions): void => {
3✔
320
        if (newOptions.levels?.length > 0) {
50✔
321
            newOptions.levels = dedupArray(newOptions.levels)
49✔
322
        } else {
323
            newOptions.levels = []
1✔
324
        }
325

326
        if (newOptions.preprocessors?.length > 0) {
50✔
327
            newOptions.preprocessors = dedupArray(newOptions.preprocessors)
8✔
328
        } else {
329
            newOptions.preprocessors = []
42✔
330
        }
331

332
        if (newOptions.preprocessorOptions?.maskedFields?.length > 0) {
50!
333
            newOptions.preprocessorOptions.maskedFields = dedupArray(newOptions.preprocessorOptions.maskedFields)
50✔
334
        } else if (newOptions.preprocessorOptions) {
×
335
            newOptions.preprocessorOptions.maskedFields = []
×
336
        }
337

338
        if (newOptions.styles) {
50✔
339
            for (let key in newOptions.styles) {
49✔
340
                if (newOptions.styles[key].length > 0) {
109!
341
                    newOptions.styles[key] = dedupArray(newOptions.styles[key])
109✔
342
                }
343
            }
344
        } else {
345
            newOptions.styles = {}
1✔
346
        }
347

348
        this.uncaughtExceptions = newOptions.uncaughtExceptions
50✔
349
        this.unhandledRejections = newOptions.unhandledRejections
50✔
350

351
        this._options = newOptions
50✔
352
        parser.options = newOptions
50✔
353
    }
354
}
355

356
// Exports...
357
export = Anyhow.Instance
3✔
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