UNPKG

13 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3// tslint:disable:jsdoc-format
4const timers_1 = require("timers");
5const config = require("config");
6const bunyan = require("bunyan");
7const PrettyStream = require("bunyan-prettystream");
8const path = require("path");
9const RotatingFileStream = require("bunyan-rotating-file-stream");
10const RedisTransport = require("bunyan-redis");
11const os = require("os");
12const fs = require("fs");
13const stream = require("stream");
14const stringify = require("json-stringify-safe");
15const NewrelicUtil_1 = require("../newrelic/NewrelicUtil");
16const ErrorUtil_1 = require("../util/ErrorUtil");
17class Logger {
18}
19exports.Logger = Logger;
20class LevelStringifyTransform extends stream.Transform {
21 static levelSerialiser(level) {
22 switch (level) {
23 case bunyan.TRACE:
24 return 'TRACE';
25 case bunyan.DEBUG:
26 return 'DEBUG';
27 case bunyan.INFO:
28 return 'INFO';
29 case bunyan.WARN:
30 return 'WARN';
31 case bunyan.ERROR:
32 return 'ERROR';
33 case bunyan.FATAL:
34 return 'FATAL';
35 default:
36 return 'N/A';
37 }
38 }
39 constructor() {
40 super({ writableObjectMode: true, readableObjectMode: true });
41 }
42 // tslint:disable-next-line:prefer-function-over-method
43 _flush(cb) {
44 cb();
45 }
46 // tslint:disable-next-line:prefer-function-over-method
47 _transform(data, encoding, callback) {
48 data.levelStr = data.level ? LevelStringifyTransform.levelSerialiser(data.level) : 'NA';
49 callback(null, data);
50 }
51}
52class CloseableLevelStringifyTransform extends LevelStringifyTransform {
53}
54class StringifyTransform extends stream.Transform {
55 constructor() {
56 super({ writableObjectMode: true, readableObjectMode: false });
57 }
58 // tslint:disable-next-line:prefer-function-over-method
59 _flush(cb) {
60 cb();
61 }
62 // tslint:disable-next-line:prefer-function-over-method
63 _transform(data, encoding, callback) {
64 callback(null, `${stringify(data)}\n`);
65 }
66}
67class ConsoleRawStream extends stream.Writable {
68 constructor() {
69 super({ objectMode: true });
70 }
71 _write(rec, encoding, cb) {
72 if (rec.level < bunyan.WARN) {
73 return process.stdout.write(`${stringify(rec)}\n`, encoding, cb);
74 }
75 return process.stderr.write(`${stringify(rec)}\n`, encoding, cb);
76 }
77}
78class LogManagerInternal {
79 constructor() {
80 this.streamCache = new Map();
81 this.appName = config.get('app.name', 'na');
82 }
83 static beSmartOnThePath(thePath) {
84 // Let's see if we can find an ancestor called 'src'
85 const srcLoc = thePath.indexOf(`${path.sep}src${path.sep}`);
86 if (srcLoc >= 0) {
87 return thePath.substr(srcLoc + 5);
88 }
89 const builtLoc = thePath.indexOf(`${path.sep}dist${path.sep}`);
90 if (builtLoc >= 0) {
91 return thePath.substr(builtLoc + 6);
92 }
93 if (thePath.startsWith(process.cwd())) {
94 const fromCwd = thePath.substr(process.cwd().length);
95 return fromCwd.startsWith(path.sep) ? fromCwd.substr(1) : fromCwd;
96 }
97 return thePath;
98 }
99 static getEffectiveLevel(loggerName, streamName, configuredLevel) {
100 let overrideEnv = `LOG_${loggerName}_${streamName}`.replace('/[^a-zA-z0-9_]+/', '_').replace('/[_]{2,}', '_').toUpperCase();
101 if (process.env[overrideEnv]) {
102 return process.env[overrideEnv];
103 }
104 overrideEnv = `LOG_${loggerName}`.replace('/[^a-zA-z0-9_]+/', '_').replace('/[_]{2,}', '_').toUpperCase();
105 if (process.env[overrideEnv]) {
106 return process.env[overrideEnv];
107 }
108 return configuredLevel;
109 }
110 static mkdirpSync(thePath, mode) {
111 if (fs.existsSync(thePath)) {
112 return true;
113 }
114 else if (this.mkdirpSync(path.dirname(thePath), mode)) {
115 fs.mkdirSync(path.basename(thePath), mode);
116 return true;
117 }
118 return false;
119 }
120 static getRedisStream(redisConfig) {
121 return new RedisTransport({
122 container: redisConfig.key || 'phplog',
123 host: process.env.REDIS_HOST || redisConfig.host || '127.0.0.1',
124 port: process.env.REDIS_PORT || redisConfig.port || 6379,
125 db: 0,
126 });
127 }
128 static resolvePath(thePath) {
129 const basePath = process.env.LOG_DIR || (config.has('Logger.dir') && config.get('Logger.dir')) || os.tmpdir();
130 const finalPath = path.resolve(basePath, thePath);
131 if (!fs.existsSync(path.dirname(finalPath))) {
132 LogManagerInternal.mkdirpSync(path.dirname(finalPath), 0o766);
133 }
134 // eslint-disable-next-line no-console
135 // tslint:disable-next-line:no-console
136 console.log(`Logging to file: ${finalPath}`);
137 return finalPath;
138 }
139 getLogger(filePath) {
140 // console.log(`Got here with filePath: ${filePath}`);
141 const thePath = removeExtension(filePath || _getCallerFile());
142 if (thePath.substr(0, 1) !== '/') {
143 // It's not a full file path.
144 return this.getLoggerInternal(thePath);
145 }
146 return this.getLoggerInternal(LogManagerInternal.beSmartOnThePath(thePath));
147 }
148 scheduleShutdown() {
149 timers_1.setTimeout(() => this.closeStreams(), 1000);
150 }
151 closeStreams() {
152 this.streamCache.forEach((streamToClose) => {
153 if (streamToClose instanceof CloseableLevelStringifyTransform) {
154 streamToClose.end();
155 }
156 });
157 }
158 setAppName(appName) {
159 this.appName = appName;
160 }
161 getAppName() {
162 return this.appName;
163 }
164 getLoggerInternal(loggerPath) {
165 if (!config.has('logging.loggers')) {
166 throw new Error("Couldn't find loggers configuration!!! Your logging config is wrong!");
167 }
168 const loggers = config.get('logging.loggers');
169 const candidates = loggers.filter((logger) => loggerPath.startsWith(logger.name));
170 if (!candidates || candidates.length === 0) {
171 candidates.push(loggers.filter((logger) => logger.name === 'ROOT')[0]);
172 }
173 else {
174 candidates.sort((a, b) => {
175 if (a.length > b.length) {
176 return -1;
177 }
178 if (a.length < b.length) {
179 return 1;
180 }
181 return 0;
182 });
183 }
184 const loggerConfig = candidates[0];
185 const loggerName = loggerConfig.name;
186 const streamNames = loggerConfig.streams;
187 const streams = Object.keys(streamNames)
188 .filter((streamName) => {
189 const level = LogManagerInternal.getEffectiveLevel(loggerName, streamName, streamNames[streamName]);
190 return level && level.toUpperCase() !== 'OFF';
191 })
192 .map((streamName) => this.getStreamConfig(streamName, LogManagerInternal.getEffectiveLevel(loggerName, streamName, streamNames[streamName])));
193 return bunyan.createLogger({ name: loggerPath, streams, serializers: bunyan.stdSerializers, appName: this.appName ? this.appName.toLowerCase() : 'appNameNotAvailable' });
194 }
195 getStreamConfig(streamName, level) {
196 const configKey = `logging.streams.${streamName}`;
197 if (!config.has(configKey)) {
198 throw new Error(`Couldn't find stream with name ${streamName}`);
199 }
200 const streamConfig = config.get(configKey);
201 switch (streamConfig.type) {
202 case 'console':
203 return {
204 stream: this.getUnderlyingStream(streamName),
205 level,
206 name: streamConfig.name,
207 };
208 case 'json':
209 return {
210 type: 'raw',
211 stream: this.getUnderlyingStream(streamName),
212 closeOnExit: false,
213 level,
214 };
215 case 'file':
216 return {
217 type: 'raw',
218 stream: this.getUnderlyingStream(streamName),
219 closeOnExit: true,
220 level,
221 };
222 case 'redis':
223 return {
224 type: 'raw',
225 level,
226 stream: this.getUnderlyingStream(streamName),
227 };
228 default:
229 throw new Error('Unknown log stream type');
230 }
231 }
232 getUnderlyingStream(streamName) {
233 if (!this.streamCache.has(streamName)) {
234 const configKey = `logging.streams.${streamName}`;
235 if (!config.has(configKey)) {
236 throw new Error(`Couldn't find stream with name ${streamName}`);
237 }
238 const streamConfig = config.get(configKey);
239 switch (streamConfig.type) {
240 case 'console':
241 {
242 const prettyStream = new PrettyStream();
243 prettyStream.pipe(process.stdout);
244 this.streamCache.set(streamName, prettyStream);
245 }
246 break;
247 case 'json':
248 {
249 const levelStringifyTransform = new LevelStringifyTransform();
250 levelStringifyTransform.pipe(new ConsoleRawStream());
251 this.streamCache.set(streamName, levelStringifyTransform);
252 }
253 break;
254 case 'file':
255 {
256 const levelStringifyTransform = new LevelStringifyTransform();
257 levelStringifyTransform.pipe(new StringifyTransform()).pipe(new RotatingFileStream({
258 path: LogManagerInternal.resolvePath(streamConfig.path),
259 period: streamConfig.period || '1d',
260 count: streamConfig.count || 14,
261 }));
262 this.streamCache.set(streamName, levelStringifyTransform);
263 }
264 break;
265 case 'redis':
266 {
267 const levelStringifyTransform = new CloseableLevelStringifyTransform();
268 const redisStream = LogManagerInternal.getRedisStream(streamConfig);
269 redisStream['end'] = () => null;
270 levelStringifyTransform.pipe(redisStream);
271 levelStringifyTransform.on('end', () => {
272 redisStream._client.quit();
273 });
274 this.streamCache.set(streamName, levelStringifyTransform);
275 }
276 break;
277 default:
278 throw new Error('Unknown log stream type');
279 }
280 }
281 return this.streamCache.get(streamName);
282 }
283}
284exports.LogManagerInternal = LogManagerInternal;
285exports.LogManager = new LogManagerInternal();
286const baseLogger = exports.LogManager.getLogger('ROOT');
287process.on('unhandledRejection', (reason, promise) => {
288 if (NewrelicUtil_1.NewrelicUtil.isNewrelicAvailable()) {
289 NewrelicUtil_1.NewrelicUtil.getNewrelicIfAvailable().noticeError(reason, { source: 'unhandledRejection' });
290 }
291 // eslint-disable-next-line no-underscore-dangle
292 baseLogger.fatal(reason, `Unhandled promise: ${reason} ${promise && promise._trace && promise._trace.stack ? promise._trace.stack : ''}`);
293});
294process.on('uncaughtException', (err) => {
295 if (NewrelicUtil_1.NewrelicUtil.isNewrelicAvailable()) {
296 NewrelicUtil_1.NewrelicUtil.getNewrelicIfAvailable().noticeError(err, { source: 'unhandledException' });
297 }
298 baseLogger.fatal(err, `Uncaught exception: ${err} | ${err.stack}`);
299});
300process.on('warning', (err) => {
301 baseLogger.warn(err, `Uncaught warning: ${err} | ${err.stack}`);
302});
303function _getCallerFile() {
304 let callerFile;
305 const err = new ErrorUtil_1.ExtendedError('Getting Stack');
306 const structuredStackTrace = err.getStructuredStackTrace();
307 if (structuredStackTrace) {
308 // console.log(structuredStackTrace.map((cs) => cs.getFileName()));
309 const currentFile = structuredStackTrace.shift().getFileName();
310 callerFile = currentFile;
311 while (structuredStackTrace.length && currentFile === callerFile) {
312 callerFile = structuredStackTrace.shift().getFileName();
313 }
314 if (!callerFile) {
315 return '/na';
316 }
317 return callerFile;
318 }
319 return '/na';
320}
321function removeExtension(thePath) {
322 if (['.js', '.ts'].indexOf(thePath.substr(-3)) >= 0) {
323 return thePath.substring(0, thePath.length - 3);
324 }
325 return thePath;
326}
327//# sourceMappingURL=LogManager.js.map
\No newline at end of file