1 | import getLogLevel, { rfcLogLevels, LevelInfo, LevelsMap } from 'rfc-log-levels'
|
2 | import getCurrentLine, { Offset, Location } from 'get-current-line'
|
3 | import { Transform } from './transform.js'
|
4 |
|
5 | /** The log entry that Caterpillar creates and forwards to its transforms */
|
6 | export interface LogEntry extends LevelInfo, Location {
|
7 | /** the iso string of when the log occured */
|
8 | date: string
|
9 | /** all the arguments that were after the log level */
|
10 | args: any[]
|
11 | }
|
12 |
|
13 | /** Configuration for the Caterpillar Logger */
|
14 | export interface LoggerOptions {
|
15 | /** Use to override the default value of {@link Logger.lineOffset} */
|
16 | lineOffset?: Offset
|
17 |
|
18 | /** Use to override the default value of {@link Logger.levels} */
|
19 | levels?: LevelsMap
|
20 |
|
21 | /** Use to override the default value of {@link Logger.defaultLevelInfo} */
|
22 | defaultLevel?: number | string
|
23 |
|
24 | /** Use to override the default value of {@link Logger.lineLevel} */
|
25 | lineLevel?: number
|
26 | }
|
27 |
|
28 | /**
|
29 | * Logger.
|
30 | * This is what we write to.
|
31 | * @example Creation
|
32 | * ``` javascript
|
33 | * // Via class
|
34 | * import { Logger } from 'caterpillar'
|
35 | * const logger = new Logger()
|
36 | * ```
|
37 | */
|
38 | export class Logger extends Transform {
|
39 | /**
|
40 | * The configuration to use for the line offset.
|
41 | * This defaults to any file path that includes `logger`, and any method that includes the word `log`.
|
42 | */
|
43 | public lineOffset: Offset = {
|
44 | file: /logger/i,
|
45 | method: /log/i,
|
46 | }
|
47 |
|
48 | /**
|
49 | * The mapping of log level names to log level numbers.
|
50 | * Defaults to the RFC Log Level configuration.
|
51 | */
|
52 | public levels: LevelsMap = rfcLogLevels
|
53 |
|
54 | /**
|
55 | * The log level information to use when the log level was unable to be determined.
|
56 | * Defaults to the info log level.
|
57 | */
|
58 | public defaultLevelInfo!: LevelInfo
|
59 |
|
60 | /** Set the default level info via a level number or name. */
|
61 | public set defaultLevel(value: number | string) {
|
62 | const levelInfo = this.getLogLevel(value)
|
63 | if (levelInfo == null) {
|
64 | throw new Error(
|
65 | `caterpillar: the intended value of ${value} for the default log level not found in the configured levels`
|
66 | )
|
67 | }
|
68 | this.defaultLevelInfo = levelInfo
|
69 | }
|
70 |
|
71 | /**
|
72 | * Only fetch line information for entries that have a log level equal to, or below this number.
|
73 | * You should only specify this if you need it, as fFetching line information for thousands of log entries, which is typical in large applications, will slow your application down dramatically.
|
74 | * If not specified, defaults to `-Infinity` which effect is to ignore gathering line information for all log levels.
|
75 | */
|
76 | public lineLevel: number = -Infinity
|
77 |
|
78 | /** Create our instance and apply our configuraiton options. */
|
79 | constructor(opts?: LoggerOptions) {
|
80 | super()
|
81 |
|
82 | // options
|
83 | if (opts?.lineOffset != null) this.lineOffset = opts.lineOffset
|
84 | if (opts?.levels != null) this.levels = opts.levels
|
85 | if (opts?.lineLevel != null) this.lineLevel = opts.lineLevel
|
86 |
|
87 | // options: default level
|
88 | this.defaultLevel = opts?.defaultLevel ?? 'info'
|
89 |
|
90 | // dereference
|
91 | this.levels = Object.assign({}, this.levels)
|
92 | }
|
93 |
|
94 | /** Alias for {@link getLogLevel} using the configured logger levels as reference. */
|
95 | getLogLevel(value: number | string) {
|
96 | return getLogLevel(value, this.levels)
|
97 | }
|
98 |
|
99 | /** Takes an arguments array and tranforms it into a log entry. */
|
100 | format(args: any): LogEntry {
|
101 | // fetch the level
|
102 | const level = args.shift()
|
103 | let levelInfo =
|
104 | level === 'default' ? this.defaultLevelInfo : this.getLogLevel(level)
|
105 | if (levelInfo == null) {
|
106 | // fallback to the default log level
|
107 | levelInfo = this.defaultLevelInfo
|
108 | // as the level (first param) was not actually a level, put it back
|
109 | args.unshift(level)
|
110 | }
|
111 |
|
112 | // fetch the date
|
113 | const date = new Date().toISOString()
|
114 |
|
115 | // fetch the line information
|
116 | const lineInfo =
|
117 | levelInfo.levelNumber <= this.lineLevel
|
118 | ? getCurrentLine(this.lineOffset)
|
119 | : {
|
120 | line: -1,
|
121 | char: -1,
|
122 | method: '',
|
123 | file: '',
|
124 | }
|
125 |
|
126 | // put it all together
|
127 | return Object.assign({ date, args }, levelInfo, lineInfo)
|
128 | }
|
129 |
|
130 | /**
|
131 | * Log the arguments into the logger stream as formatted data with debugging information.
|
132 | * Such that our transformers can deal with it intelligently.
|
133 | *
|
134 | * @example Inputs
|
135 | * ``` javascript
|
136 | * logger.log('note', 'this is working swell')
|
137 | * ```
|
138 | * ``` javascript
|
139 | * logger.log('this', 'worked', 'swell')
|
140 | * ```
|
141 | *
|
142 | * @example Results
|
143 | * ``` json
|
144 | * {
|
145 | * "args": ["this is working swell"],
|
146 | * "date": "2013-04-25T10:18:25.722Z",
|
147 | * "levelNumber": 5,
|
148 | * "levelName": "notice",
|
149 | * "line": "59",
|
150 | * "method": "Object.<anonymous>",
|
151 | * "file": "/Users/balupton/some-project/calling-file.js"
|
152 | * }
|
153 | * ```
|
154 | * ``` json
|
155 | * {
|
156 | * "args": ["this", "worked", "well"],
|
157 | * "date": "2013-04-25T10:18:26.539Z",
|
158 | * "levelNumber": 6,
|
159 | * "levelName": "info",
|
160 | * "line": "60",
|
161 | * "method": "Object.<anonymous>",
|
162 | * "file": "/Users/balupton/some-project/calling-file.js"
|
163 | * }
|
164 | * ```
|
165 | */
|
166 | log(...args: any) {
|
167 | this.write(args)
|
168 | }
|
169 |
|
170 | /** Alias for log which prefixes the error log level */
|
171 | error(...args: any) {
|
172 | this.write(['error', ...args])
|
173 | }
|
174 |
|
175 | /** Alias for log which prefixes the warn log level */
|
176 | warn(...args: any) {
|
177 | this.write(['warn', ...args])
|
178 | }
|
179 |
|
180 | /** Alias for log which prefixes the info log level */
|
181 | info(...args: any) {
|
182 | this.write(['info', ...args])
|
183 | }
|
184 |
|
185 | /** Alias for log which prefixes the debug log level */
|
186 | debug(...args: any) {
|
187 | this.write(['debug', ...args])
|
188 | }
|
189 | }
|
190 |
|
191 | export default Logger
|