-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathlogging.ts
286 lines (246 loc) · 10.5 KB
/
logging.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
//----------------------------------------------------------------------------------------------------------------------
// Trivial Logging
//----------------------------------------------------------------------------------------------------------------------
import { inspect } from 'util';
import path from 'path';
// Pino
import pino from 'pino';
// Interfaces
import { TrivialLogger } from './lib/interfaces/logger';
import { LoggingConfig } from './lib/interfaces/config';
import { LoggableError } from './lib/interfaces/error.js';
// Null TrivialLogger
import { NullLogger } from './lib/nullLogger';
//----------------------------------------------------------------------------------------------------------------------
// Try to import pino-pretty, and if we fail, we disable that functionality.
let havePinoPretty = true;
try { require('pino-pretty'); }
catch (_) { havePinoPretty = false; }
//----------------------------------------------------------------------------------------------------------------------
/**
* The main Trivial Logging api.
*
* This is a simple wrapper around [pino](https://getpino.io), which provides some very basic convenience. It's designed
* to be a near drop-in replacement to [omega-logger][1], which in turn is based off of python's logging module.
*
* When I say a simple wrapper, I mean it's ~150 lines of code in my very verbose coding style. Mostly, it adds a
* simple way to configure [pino](https://getpino.io), as well as the ability to trivially create loggers for modules.
*
* [1]: https://github.com/Morgul/omega-logger "gh:morgul/omega-logger"
*/
export class TrivialLogging
{
constructor()
{
try
{
this.mainDir = path.dirname((require.main || {}).filename || '');
}
catch (err)
{
// If you're requiring this from an interactive session, use the current working directory instead.
this.mainDir = process.cwd();
} // end try
this._config = {
nullLogger: !!process.env.LOG_NULL,
options: {
level: (process.env.LOG_LEVEL || 'debug').toLowerCase()
}
};
// Set a root logger
this.root = this.setRootLogger();
} // end constructor
//------------------------------------------------------------------------------------------------------------------
// Properties
//------------------------------------------------------------------------------------------------------------------
/**
* Internal config.
*/
private _config : LoggingConfig;
/**
* The root directory of the project, as detected by Trivial Logging.
*/
public mainDir : string;
/**
* The root logger.
*/
public root : TrivialLogger;
//------------------------------------------------------------------------------------------------------------------
/**
* Gets a reference to the root logger.
*
* @returns A reference to the root logger.
*/
public get logger() : TrivialLogger { return this.root; }
//------------------------------------------------------------------------------------------------------------------
private _modLogger(logger : TrivialLogger) : TrivialLogger
{
if(!logger.dump)
{
logger.dump = this.dump;
} // end if
// Modify logger functions to handle errors correctly.
this._modLogFunc('warn', logger);
this._modLogFunc('error', logger);
return logger;
} // end _modLogger
private _modLogFunc(funcName, logger) : void
{
const origFunc = logger[funcName];
logger[funcName] = function(...args)
{
args = args.map((arg : LoggableError | Record<string, unknown> | string | number | boolean | null) =>
{
if(arg instanceof Error)
{
if(arg.toJSON)
{
return arg.toJSON();
}
else
{
return {
code: arg.code,
message: arg.message,
stack: arg.stack
};
} // end if
} // end if
return arg;
}); // end map
// call the original
origFunc.apply(this, args);
};
} // end _modLogFunc
//------------------------------------------------------------------------------------------------------------------
/**
* This sets up all future loggers with a default set of options, as passed by the configuration. If `config.debug`
* is `true` then we turn on pino [pretty printing][pretty]. (`pino-pretty` must be installed, or this will have no
* effect!) This allows for beautiful debug logging, without much hassle, while still letting you shut off the
* pretty output when you deploy.
*
* _Note: the `LOG_LEVEL` environment variable **always** overrides what's set in the config._
*
* [pretty]: https://getpino.io/#/docs/pretty
*
* @param config - a configuration object with the keys `debug`, `nullLogger` and/or `options`.
*/
public init(config : LoggingConfig = { options: { level: 'debug' } }) : void
{
// Build logging config
config.options = config.options || {};
// Environment variables need to override config
this._config = { ...config };
this._config.nullLogger = !!process.env.LOG_NULL || config.nullLogger || false;
this._config.options = { ...config.options };
this._config.options.level = (process.env.LOG_LEVEL || config.level || (config.debug ? 'debug' : 'info')).toLowerCase();
if(havePinoPretty)
{
this._config.options.prettyPrint = config.options.prettyPrint
|| (config.debug ? {
errorProps: '*',
levelFirst: false,
messageKey: 'msg',
timestampKey: 'time',
translateTime: 'h:MM:ss TT',
ignore: 'pid,hostname,moduleName,v'
} : false);
} // end if
// Store a generic root logger.
this.setRootLogger();
} // end init
/**
* This creates a root logger that all other loggers are children of. This is called for you if `init()` had not
* been called previously, and you call either `getLogger` or `loggerFor`. It will be initialized with defaults.
*
* @param name - the name of the root logger (default: 'root').
* @param options - a configuration object to pass to [pino](https://getpino.io/). (default: `config.options` as
* passed to `init`)
*
* @returns A logger instance.
*/
public setRootLogger(name = 'root', options ?: Record<string, unknown>) : TrivialLogger
{
this.root = this.getLogger(name, options);
return this.root;
} // end setRootLogger
/**
* This gets a new logger, overriding default configuration options with `options`.
*
* @param name - the name of the logger.
* @param options - a configuration object to pass to [pino](https://getpino.io/). (default: `config.options` as
* passed to `init`)
*
* @returns A logger instance.
*/
public getLogger(name = 'logger', options ?: Record<string, unknown>) : TrivialLogger
{
options = { ...this._config.options, ...options, name };
const logger = this._config.nullLogger ? new NullLogger() : pino(options);
return this._modLogger(logger);
} // end getLogger
/**
* This gets a new child logger of the root logger, appending the metadata to all calls.
*
* @param metadata - Additional metadata for the child logger.
*
* @returns A logger instance.
*/
public child(metadata : Record<string, unknown> | string = {}) : TrivialLogger
{
if(typeof metadata === 'string')
{
metadata = { metadata };
} // end if
const logger = this.root.child(metadata);
return this._modLogger(logger);
} // end getLogger
/**
* Creates a child logger, appending a `moduleName` object with either the name of the object, or a path to it. (It
* will attempt to get the filename of the object if the object has a `filename` property.) This makes it most
* useful for making loggers for module objects, but can be used on any arbitrary object.
*
* @param obj - an object to get a logger for, most commonly `module`, but supports any object, or a string.
*
* @returns A logger instance.
*/
public loggerFor(obj : unknown) : TrivialLogger
{
let filename;
if(obj && typeof obj === 'object' && obj.constructor.name === 'Module')
{
filename = (obj as Record<string, unknown>)?.filename;
}
else if(typeof obj === 'string')
{
filename = obj;
} // end if
// Create a child logger, specifying the module we're logging for.
const moduleName = path.relative(this.mainDir, filename);
return !this.root ? this.setRootLogger(moduleName) : this.root.child({ moduleName });
} // end loggerFor
/**
* This is basically just a wrapper around
* [util.inspect](https://nodejs.org/api/util.html#util_util_inspect_object_options). It's here for convenience,
* but doesn't have the same flexibility as `util.inspect`, so should only be used sparingly. \
*
* _Note: This is added to all logging instances for your convenience._
*
* @param obj - the object you wish to print
* @param colors - should the object be printed with color escapes (default: `true`)
* @param depth - how deeply nested should we print the properties of the object (default: `null`)
* @param showHidden - should we print hidden properties (default: `false`)
*
* @returns A formatted string version of the object.
*/
public dump(obj : Record<string, unknown>, colors = true, depth : number | null = null, showHidden = false) : string
{
return inspect(obj, { colors, depth, showHidden });
} // end dump
} // end TrivialLogger
//----------------------------------------------------------------------------------------------------------------------
const logging = new TrivialLogging();
export { TrivialLogger, LoggingConfig, NullLogger };
export default logging;
module.exports = logging;
//----------------------------------------------------------------------------------------------------------------------