UNPKG

7.45 kBJavaScriptView Raw
1// Copyright (c) 2015 Uber Technologies, Inc.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the "Software"), to deal
5// in the Software without restriction, including without limitation the rights
6// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7// copies of the Software, and to permit persons to whom the Software is
8// furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in
11// all copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19// THE SOFTWARE.
20
21var extend = require('xtend');
22var EventEmitter = require('events').EventEmitter;
23var inherits = require('inherits');
24var collectParallel = require('collect-parallel/object');
25
26var parallelWrite = require('./lib/parallel-write.js');
27var defaultLevels = require('./default-levels.js');
28var serializableErrorTransform =
29 require('./transforms/serialize-error.js');
30var safeSerializeMeta =
31 require('./transforms/safe-serialize-meta.js');
32var writePidAndHost = require('./transforms/pid-and-host.js');
33var errors = require('./errors.js');
34var makeLogMethod = require('./log-method');
35var ChildLogger = require('./child-logger');
36
37function Logger(opts) {
38 if (!(this instanceof Logger)) {
39 return new Logger(opts);
40 }
41 var self = this;
42
43 if (!opts) {
44 throw errors.OptsRequired();
45 }
46
47 if (!opts.meta) {
48 throw errors.MetaRequired();
49 }
50
51 if (!opts.backends) {
52 throw errors.BackendsRequired();
53 }
54
55 EventEmitter.call(this);
56
57 var meta = this.meta = opts.meta;
58 var transforms = opts.transforms || [];
59 var basemetaTransforms = opts.basemetaTransforms || [];
60
61 basemetaTransforms.forEach(function initTransforms(transform) {
62 transforms.push(transform(meta));
63 });
64
65 transforms.push(safeSerializeMeta);
66 transforms.push(serializableErrorTransform);
67 transforms.push(writePidAndHost(meta));
68
69 this.statsd = opts.statsd;
70
71 this.path = opts.path = "";
72
73 // Performs a deep copy of the default log levels, overlaying the
74 // configured levels and filtering nulled levels.
75 var levels = this.levels = {};
76 var configuredLevels = extend(defaultLevels, opts.levels || {});
77 Object.keys(configuredLevels)
78 .forEach(function copyDefaultLevel(levelName) {
79 // Setting a level in opts.levels to null disables that level.
80 if (!configuredLevels[levelName]) {
81 return;
82 }
83 // Each log level will contain an array of transforms by default,
84 // that will be suffixed with globally configured transforms.
85 var level = extend({transforms: []}, configuredLevels[levelName]);
86 level.transforms = level.transforms.concat(transforms);
87 levels[levelName] = level;
88 });
89
90 // Create a log level method, e.g., info(message, meta, cb?), for every
91 // configured log level.
92 Object.keys(levels)
93 .forEach(function makeMethodForLevel(levelName) {
94 self[levelName] = makeLogMethod(levelName);
95 });
96
97 // Create a stream for each of the configured backends, indexed by backend
98 // name.
99 // streams: Object<backendName, Stream>
100 var streams = this.streams = Object.keys(opts.backends)
101 .reduce(function accumulateStreams(streamByBackend, backendName) {
102 var backend = opts.backends[backendName];
103 if (!backend) {
104 return streamByBackend;
105 }
106
107 streamByBackend[backendName] = backend.createStream(meta, {
108 highWaterMark: opts.highWaterMark || 1000
109 });
110 return streamByBackend;
111 }, {});
112
113 // Creates an index of all the streams that each log level will write to,
114 // keyed by log level.
115 // The index is used by the writeEntry method to look up all the target
116 // streams for the given level.
117 // The parallel write method uses the backend name to annotate errors.
118 // _streamsByLevel: Object<logLevel, Array<Object<backendName, Stream>>>
119 this._streamsByLevel = Object.keys(levels)
120 .reduce(function accumulateStreamsByLevel(streamsByLevel, levelName) {
121 if (!levels[levelName]) {
122 return streamsByLevel;
123 }
124
125 var level = levels[levelName];
126
127 streamsByLevel[levelName] = level.backends
128 .reduce(function accumulateStreamsByBackend(
129 levelStreams,
130 backendName
131 ) {
132 if (streams[backendName]) {
133 levelStreams.push({
134 name: backendName,
135 stream: streams[backendName]
136 });
137 }
138 return levelStreams;
139 }, []);
140
141 return streamsByLevel;
142 }, {});
143}
144
145inherits(Logger, EventEmitter);
146
147Logger.prototype.instrument = function instrument() { };
148
149Logger.prototype.close = function close(callback) {
150 collectParallel(this.streams, closeEachStream, finish);
151
152 function closeEachStream(stream, i, done) {
153 if (stream && stream.close) {
154 stream.close(done);
155 }
156 }
157
158 function finish(err, results) {
159 for (var i = 0; i < results; i++) {
160 if (results[i].err) {
161 callback(results[i].err);
162 return;
163 }
164 }
165 callback(null);
166 }
167};
168
169Logger.prototype.destroy = function destroy() {
170 Object.keys(this.streams).forEach(function destroyStreamForLevel(name) {
171 var stream = this.streams[name];
172 if (stream && stream.destroy) {
173 stream.destroy();
174 }
175 }, this);
176};
177
178Logger.prototype.writeEntry = function writeEntry(entry, callback) {
179 var levelName = entry.level;
180 var level = this.levels[levelName];
181 var logStreams = this._streamsByLevel[levelName];
182 var logger = this;
183 if (this.statsd && typeof this.statsd.increment === 'function') {
184 this.statsd.increment('logtron.logged.' + levelName);
185 }
186
187 for (var i=0; i<level.transforms.length; ++i) {
188 entry = level.transforms[i](entry);
189 }
190
191 parallelWrite(logStreams, entry, function (err) {
192 if (!err) {
193 if (callback) {
194 callback(null);
195 }
196 return;
197 }
198
199 if (callback && typeof callback === 'function') {
200 return callback(err);
201 }
202
203 logger.emit('error', err);
204 });
205};
206
207Logger.prototype.createChild = function createChild(path, levels, opts) {
208 opts = opts || {};
209
210 return new ChildLogger({
211 mainLogger: this,
212 path: path,
213 levels: levels,
214 extendMeta: opts.extendMeta,
215 meta: opts.meta,
216 strict: opts.strict,
217 metaFilter: opts.metaFilter
218 });
219};
220
221module.exports = Logger;