kayvee 3.17.0 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +147 -202
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +29 -0
- package/dist/kayvee.d.ts +12 -0
- package/dist/kayvee.d.ts.map +1 -0
- package/dist/kayvee.js +50 -0
- package/dist/logger/logger.d.ts +49 -0
- package/dist/logger/logger.d.ts.map +1 -0
- package/dist/logger/logger.js +237 -0
- package/dist/middleware.d.ts +23 -0
- package/dist/middleware.d.ts.map +1 -0
- package/dist/middleware.js +196 -0
- package/dist/package.json +89 -0
- package/dist/router/index.d.ts +23 -0
- package/dist/router/index.d.ts.map +1 -0
- package/dist/router/index.js +184 -0
- package/package.json +64 -24
- package/.circleci/config.yml +0 -25
- package/.eslintrc.yml +0 -44
- package/.nvmrc +0 -1
- package/.prettierrc.json +0 -1
- package/Makefile +0 -56
- package/benchmarks/data/.keep +0 -1
- package/benchmarks/data/corpus-basic.json +0 -22
- package/benchmarks/data/corpus-pathological.json +0 -22
- package/benchmarks/data/corpus-realistic.json +0 -22
- package/benchmarks/data/kvconfig-basic.yml +0 -7
- package/benchmarks/data/kvconfig-pathological.yml +0 -222
- package/benchmarks/data/kvconfig-realistic.yml +0 -39
- package/benchmarks/routing.js +0 -116
- package/build/lib/kayvee.js +0 -67
- package/build/lib/logger/helpers.js +0 -0
- package/build/lib/logger/logger.js +0 -221
- package/build/lib/middleware.js +0 -302
- package/build/lib/router/index.js +0 -198
- package/build/package.json +0 -49
- package/build/test/context_logger.js +0 -77
- package/build/test/kayvee.js +0 -36
- package/build/test/logger_test.js +0 -334
- package/build/test/middleware.js +0 -557
- package/build/test/router.js +0 -311
- package/index.js +0 -7
- package/lib/kayvee.ts +0 -73
- package/lib/logger/helpers.ts +0 -0
- package/lib/logger/logger.ts +0 -296
- package/lib/middleware.ts +0 -317
- package/lib/router/index.ts +0 -234
- package/lib/router/schema_definitions.json +0 -158
- package/test/context_logger.ts +0 -76
- package/test/kayvee.ts +0 -50
- package/test/kvconfig.yml +0 -14
- package/test/logger_test.ts +0 -378
- package/test/middleware.ts +0 -632
- package/test/router.ts +0 -558
- package/test/static/empty.css +0 -0
- package/test/static/js/empty.js +0 -0
- package/test/tests.json +0 -100
- package/tsconfig.json +0 -10
- package/tslint.json +0 -134
- /package/{build/lib → dist}/router/schema_definitions.json +0 -0
package/lib/middleware.ts
DELETED
|
@@ -1,317 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Module dependencies.
|
|
3
|
-
* @private
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
var fs = require("fs");
|
|
7
|
-
var path = require("path");
|
|
8
|
-
|
|
9
|
-
var kayvee = require("../lib/kayvee");
|
|
10
|
-
var KayveeLogger = require("../lib/logger/logger");
|
|
11
|
-
var morgan = require("morgan");
|
|
12
|
-
var _ = require("underscore");
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* all relative files path in a directory
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
function walkDirSync(dir, files = []) {
|
|
19
|
-
const list = fs.readdirSync(dir);
|
|
20
|
-
list.forEach((file) => {
|
|
21
|
-
const f = path.join(dir, file);
|
|
22
|
-
if (fs.statSync(path.join(dir, file)).isDirectory()) {
|
|
23
|
-
walkDirSync(f, files);
|
|
24
|
-
} else {
|
|
25
|
-
files.push(f);
|
|
26
|
-
}
|
|
27
|
-
});
|
|
28
|
-
return files.map((f) => path.relative(dir, f));
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* returns a middleware function that checks if path exists in dir.
|
|
33
|
-
*
|
|
34
|
-
* Files in the directory are prefixed by base_path and compared to
|
|
35
|
-
* req.path
|
|
36
|
-
*/
|
|
37
|
-
|
|
38
|
-
function skip_path(dir, base_path = "/") {
|
|
39
|
-
let files = walkDirSync(dir);
|
|
40
|
-
files = files.map((file) => path.join(base_path, file));
|
|
41
|
-
console.error(
|
|
42
|
-
`KayveeMiddleware: Skipping successful requests for files in ${dir} at ${base_path}`,
|
|
43
|
-
);
|
|
44
|
-
return (req, res) => _(files).contains(req.path) && res.statusCode < 400;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* request path
|
|
49
|
-
*/
|
|
50
|
-
|
|
51
|
-
function getBaseUrl(req) {
|
|
52
|
-
var url = req.originalUrl || req.url;
|
|
53
|
-
var parsed = require("url").parse(url, true);
|
|
54
|
-
return parsed.pathname;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* request query params
|
|
59
|
-
*/
|
|
60
|
-
|
|
61
|
-
function getQueryParams(req) {
|
|
62
|
-
var url = req.originalUrl || req.url;
|
|
63
|
-
var parsed = require("url").parse(url, true);
|
|
64
|
-
var parsedQueryString = require("qs").parse(parsed.search, {
|
|
65
|
-
allowPrototypes: false,
|
|
66
|
-
ignoreQueryPrefix: true,
|
|
67
|
-
});
|
|
68
|
-
return `?${require("qs").stringify(parsedQueryString)}`;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* response size
|
|
73
|
-
*/
|
|
74
|
-
|
|
75
|
-
function getResponseSize(res) {
|
|
76
|
-
var result = undefined;
|
|
77
|
-
var headers = res.headers || res._headers;
|
|
78
|
-
if (headers && headers["content-length"]) {
|
|
79
|
-
result = Number(headers["content-length"]);
|
|
80
|
-
} else if (res.data) {
|
|
81
|
-
result = res.data.length;
|
|
82
|
-
}
|
|
83
|
-
return result;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* response time in nanoseconds
|
|
88
|
-
*/
|
|
89
|
-
|
|
90
|
-
function getResponseTimeNs(req, res) {
|
|
91
|
-
if (!req._startAt || !res._startAt) {
|
|
92
|
-
// missing request and/or response start time
|
|
93
|
-
return undefined;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// calculate diff
|
|
97
|
-
var ns = (res._startAt[0] - req._startAt[0]) * 1e9 + (res._startAt[1] - req._startAt[1]);
|
|
98
|
-
return ns;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* IP address that sent the request.
|
|
103
|
-
*
|
|
104
|
-
* `req.ip` is defined in Express: http://expressjs.com/en/api.html#req.ip
|
|
105
|
-
*/
|
|
106
|
-
function getIp(req) {
|
|
107
|
-
var remoteAddress = req.connection ? req.connection.remoteAddress : undefined;
|
|
108
|
-
return req.ip || remoteAddress;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Log level
|
|
113
|
-
*/
|
|
114
|
-
function getLogLevel(req, res) {
|
|
115
|
-
const statusCode = res.statusCode;
|
|
116
|
-
let result;
|
|
117
|
-
if (statusCode >= 499) {
|
|
118
|
-
result = KayveeLogger.Error;
|
|
119
|
-
} else {
|
|
120
|
-
result = KayveeLogger.Info;
|
|
121
|
-
}
|
|
122
|
-
return result;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/*
|
|
126
|
-
* Default handlers
|
|
127
|
-
*/
|
|
128
|
-
var defaultHandlers = [
|
|
129
|
-
// Request method
|
|
130
|
-
(req) => ({ method: req.method }),
|
|
131
|
-
// Path (URL without query params)
|
|
132
|
-
(req) => ({ path: getBaseUrl(req) }),
|
|
133
|
-
// Query params
|
|
134
|
-
(req) => ({ params: getQueryParams(req) }),
|
|
135
|
-
// Response size
|
|
136
|
-
(req, res) => ({ "response-size": getResponseSize(res) }),
|
|
137
|
-
// Response time (ns)
|
|
138
|
-
(req, res) => ({ "response-time": getResponseTimeNs(req, res) }),
|
|
139
|
-
// Status code
|
|
140
|
-
(req, res) => ({ "status-code": res.statusCode }),
|
|
141
|
-
// Ip address
|
|
142
|
-
(req) => ({ ip: getIp(req) }),
|
|
143
|
-
// Via -- what library/code produced this log?
|
|
144
|
-
() => ({ via: "kayvee-middleware" }),
|
|
145
|
-
|
|
146
|
-
// Kayvee's reserved fields
|
|
147
|
-
// Log level
|
|
148
|
-
(req, res) => ({ level: getLogLevel(req, res) }),
|
|
149
|
-
// Source -- which app emitted this log?
|
|
150
|
-
// -> Gets passed in among `options` during library initialization
|
|
151
|
-
// Title
|
|
152
|
-
() => ({ title: "request-finished" }),
|
|
153
|
-
];
|
|
154
|
-
|
|
155
|
-
const defaultContextHandlers = [];
|
|
156
|
-
|
|
157
|
-
function handlerData(handlers, ...args) {
|
|
158
|
-
const data = {};
|
|
159
|
-
handlers.forEach((h) => {
|
|
160
|
-
try {
|
|
161
|
-
const handler_data = h(...args);
|
|
162
|
-
_.extend(data, handler_data);
|
|
163
|
-
} catch (e) {
|
|
164
|
-
// ignore invalid handler
|
|
165
|
-
}
|
|
166
|
-
});
|
|
167
|
-
return data;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
class ContextLogger {
|
|
171
|
-
logger = null;
|
|
172
|
-
handlers = [];
|
|
173
|
-
args = [];
|
|
174
|
-
|
|
175
|
-
constructor(logger, handlers, ...args) {
|
|
176
|
-
this.logger = logger;
|
|
177
|
-
this.handlers = handlers;
|
|
178
|
-
this.args = args;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
_contextualData(data) {
|
|
182
|
-
return _.extend(handlerData(this.handlers, ...this.args), data);
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
for (const func of KayveeLogger.LEVELS) {
|
|
187
|
-
ContextLogger.prototype[func] = function (title) {
|
|
188
|
-
this[`${func}D`](title, {});
|
|
189
|
-
};
|
|
190
|
-
ContextLogger.prototype[`${func}D`] = function (title, data) {
|
|
191
|
-
this.logger[`${func}D`](title, this._contextualData(data));
|
|
192
|
-
};
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
for (const func of KayveeLogger.METRICS) {
|
|
196
|
-
ContextLogger.prototype[func] = function (title, value) {
|
|
197
|
-
this[`${func}D`](title, value, {});
|
|
198
|
-
};
|
|
199
|
-
ContextLogger.prototype[`${func}D`] = function (title, value, data) {
|
|
200
|
-
this.logger[`${func}D`](title, value, this._contextualData(data));
|
|
201
|
-
};
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
/*
|
|
205
|
-
* User configuration is passed via an `options` object.
|
|
206
|
-
* Results from configuration are prioritized such that (`base_handlers` > `handlers` > `headers`).
|
|
207
|
-
*
|
|
208
|
-
* // `headers` - logs these request headers, if they exist
|
|
209
|
-
*
|
|
210
|
-
* headers: e.g. ['X-Request-Id', 'Host']
|
|
211
|
-
*
|
|
212
|
-
* // `handlers` - an array of functions of that return dicts to be logged.
|
|
213
|
-
*
|
|
214
|
-
* handlers: e.g. [function(request, response) { return {"key":"val"}]
|
|
215
|
-
*
|
|
216
|
-
* // `base_handlers` - an array of functions of that return dicts to be logged.
|
|
217
|
-
* // Barring exceptional circumstances, `base_handlers` should not be overriden by the user.
|
|
218
|
-
* // `base_handlers` defaults to a core set of handlers to run... see `defaultHandlers`.
|
|
219
|
-
* //
|
|
220
|
-
* // Separating `base_handlers` from `handlers` is done to ensure that reserved keys
|
|
221
|
-
* // don't accidentally get overriden by custom handlers. This can now only happen if
|
|
222
|
-
* // the user explicitly overrides `base_handlers`.
|
|
223
|
-
*
|
|
224
|
-
* base_handlers: e.g. [function(request, response) { return {"key":"val"}]
|
|
225
|
-
*
|
|
226
|
-
*/
|
|
227
|
-
|
|
228
|
-
var formatLine = (options_arg) => {
|
|
229
|
-
var options = options_arg || {};
|
|
230
|
-
|
|
231
|
-
// `source` is the one required field
|
|
232
|
-
if (!options.source) {
|
|
233
|
-
throw Error("Missing required config for 'source' in Kayvee middleware 'options'");
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
const router = KayveeLogger.getGlobalRouter();
|
|
237
|
-
|
|
238
|
-
return (tokens, req, res) => {
|
|
239
|
-
// Build a dict of data to log
|
|
240
|
-
var data = { _kvmeta: undefined }; // Adding _kvmeta here to make typescript compile happy
|
|
241
|
-
|
|
242
|
-
// Add user-configured request headers
|
|
243
|
-
var custom_headers = options.headers || [];
|
|
244
|
-
var header_data = {};
|
|
245
|
-
custom_headers.forEach((h) => {
|
|
246
|
-
// Header field names are case insensitive, so let's be consistent
|
|
247
|
-
var lc = h.toLowerCase();
|
|
248
|
-
header_data[lc] = req.headers[lc];
|
|
249
|
-
});
|
|
250
|
-
_.extend(data, header_data);
|
|
251
|
-
|
|
252
|
-
// Run user-configured handlers; add custom data
|
|
253
|
-
var custom_handlers = options.handlers || [];
|
|
254
|
-
|
|
255
|
-
// Allow user to override `base_handlers`; provide sane default set of handlers
|
|
256
|
-
var base_handlers = options.base_handlers || defaultHandlers;
|
|
257
|
-
base_handlers = base_handlers.concat([() => ({ source: options.source })]);
|
|
258
|
-
|
|
259
|
-
// Execute custom-handlers THEN base-handlers
|
|
260
|
-
const all_handlers = custom_handlers.concat(base_handlers);
|
|
261
|
-
_.extend(data, handlerData(all_handlers, req, res));
|
|
262
|
-
|
|
263
|
-
if (router) {
|
|
264
|
-
data._kvmeta = router.route(data);
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
return kayvee.format(data);
|
|
268
|
-
};
|
|
269
|
-
};
|
|
270
|
-
|
|
271
|
-
const defaultContextLoggerOpts = {
|
|
272
|
-
enabled: true,
|
|
273
|
-
handlers: defaultContextHandlers,
|
|
274
|
-
};
|
|
275
|
-
|
|
276
|
-
/**
|
|
277
|
-
* Module exports.
|
|
278
|
-
* @public
|
|
279
|
-
*/
|
|
280
|
-
|
|
281
|
-
if (process.env.NODE_ENV === "test") {
|
|
282
|
-
module.exports = (clever_options, morgan_options = { skip: null }) => {
|
|
283
|
-
if (clever_options.ignore_dir) {
|
|
284
|
-
morgan_options.skip = skip_path(
|
|
285
|
-
clever_options.ignore_dir.directory,
|
|
286
|
-
clever_options.ignore_dir.path,
|
|
287
|
-
);
|
|
288
|
-
}
|
|
289
|
-
return morgan(formatLine(clever_options), morgan_options);
|
|
290
|
-
};
|
|
291
|
-
module.exports.ContextLogger = ContextLogger;
|
|
292
|
-
} else {
|
|
293
|
-
module.exports = (clever_options, context_logger_options = defaultContextLoggerOpts) => {
|
|
294
|
-
// `source` is the one required field
|
|
295
|
-
if (!clever_options.source) {
|
|
296
|
-
throw new Error("Missing required config for 'source' in Kayvee middleware 'options'");
|
|
297
|
-
}
|
|
298
|
-
const logger = new KayveeLogger(clever_options.source);
|
|
299
|
-
const morgan_options = {
|
|
300
|
-
stream: process.stderr,
|
|
301
|
-
skip: null,
|
|
302
|
-
};
|
|
303
|
-
if (clever_options.ignore_dir) {
|
|
304
|
-
morgan_options.skip = skip_path(
|
|
305
|
-
clever_options.ignore_dir.directory,
|
|
306
|
-
clever_options.ignore_dir.path,
|
|
307
|
-
);
|
|
308
|
-
}
|
|
309
|
-
const morgan_logger = morgan(formatLine(clever_options), morgan_options);
|
|
310
|
-
return (req, res, next) => {
|
|
311
|
-
if (context_logger_options.enabled) {
|
|
312
|
-
req.log = new ContextLogger(logger, context_logger_options.handlers, req);
|
|
313
|
-
}
|
|
314
|
-
morgan_logger(req, res, next);
|
|
315
|
-
};
|
|
316
|
-
};
|
|
317
|
-
}
|
package/lib/router/index.ts
DELETED
|
@@ -1,234 +0,0 @@
|
|
|
1
|
-
var fs = require("fs");
|
|
2
|
-
var jsonschema = require("jsonschema");
|
|
3
|
-
var schema = require("./schema_definitions");
|
|
4
|
-
var yaml = require("js-yaml");
|
|
5
|
-
var _ = require("underscore");
|
|
6
|
-
|
|
7
|
-
var packageJson = require("../../package.json");
|
|
8
|
-
const kvVersion = packageJson.version;
|
|
9
|
-
const teamName = process.env._TEAM_OWNER || "UNSET";
|
|
10
|
-
|
|
11
|
-
const reEnvvarTokens = new RegExp("\\$\\{(.+?)\\}", "g");
|
|
12
|
-
const reFieldTokens = new RegExp("%\\{(.+?)\\}", "g");
|
|
13
|
-
|
|
14
|
-
// For performance reason this code is intentionally redundant and not-inlined.
|
|
15
|
-
// Removing redundancy and inlining this function some how makes performance worst.
|
|
16
|
-
function substituteEnvVars(obj, subber) {
|
|
17
|
-
const rtn = {};
|
|
18
|
-
const replacer = (s) => s.replace(reEnvvarTokens, (__, p1) => subber(p1));
|
|
19
|
-
|
|
20
|
-
for (const key in obj) {
|
|
21
|
-
const val = obj[key];
|
|
22
|
-
|
|
23
|
-
if (Array.isArray(val)) {
|
|
24
|
-
const updatedVals = Array(val.length);
|
|
25
|
-
for (let i = 0; i < val.length; i++) {
|
|
26
|
-
updatedVals[i] = replacer(val[i]);
|
|
27
|
-
}
|
|
28
|
-
rtn[key] = updatedVals;
|
|
29
|
-
} else {
|
|
30
|
-
rtn[key] = replacer(val);
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
return rtn;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function deepKey(obj, key) {
|
|
38
|
-
const path = key.split(".");
|
|
39
|
-
|
|
40
|
-
let idx = 0;
|
|
41
|
-
let val = obj;
|
|
42
|
-
do {
|
|
43
|
-
val = val[path[idx++]];
|
|
44
|
-
} while (val && idx < path.length);
|
|
45
|
-
|
|
46
|
-
return val;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function fieldMatches(obj, field, values) {
|
|
50
|
-
const val = obj[field] || deepKey(obj, field);
|
|
51
|
-
|
|
52
|
-
if (val == null || val === "") {
|
|
53
|
-
return false;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
if (values[0] === "*") {
|
|
57
|
-
return true;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
for (let i = 0; i < values.length; i++) {
|
|
61
|
-
if (values[i] === val) {
|
|
62
|
-
return true;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
return false;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
class Rule {
|
|
70
|
-
name = null;
|
|
71
|
-
matchers = null;
|
|
72
|
-
output = null;
|
|
73
|
-
|
|
74
|
-
constructor(name, matchers, output) {
|
|
75
|
-
this.name = name;
|
|
76
|
-
this.matchers = matchers;
|
|
77
|
-
|
|
78
|
-
const envMissing = [];
|
|
79
|
-
this.output = substituteEnvVars(output, (k) => {
|
|
80
|
-
const val = process.env[k];
|
|
81
|
-
if (val == null) {
|
|
82
|
-
envMissing.push(k);
|
|
83
|
-
}
|
|
84
|
-
return val;
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
if (envMissing.length > 0) {
|
|
88
|
-
throw new Error(`Missing env var(s): ${envMissing.join(", ")}`);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
Object.keys(matchers).forEach((field) => {
|
|
92
|
-
const fieldVals = matchers[field];
|
|
93
|
-
if (fieldVals.indexOf("*") !== -1 && fieldVals.length > 1) {
|
|
94
|
-
throw new Error(
|
|
95
|
-
`Invalid matcher values in ${name}.${field}.\n` +
|
|
96
|
-
"Wildcard matcher can't co-exist with other matchers.",
|
|
97
|
-
);
|
|
98
|
-
}
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
if (this.output.type === "alerts" || this.output.type === "metrics") {
|
|
102
|
-
this.output.value_field = this.output.value_field || "value";
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
this.output.rule = this.name;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// matches returns true if `msg` matches against this rule
|
|
109
|
-
matches(msg) {
|
|
110
|
-
for (const field in this.matchers) {
|
|
111
|
-
if (!fieldMatches(msg, field, this.matchers[field])) {
|
|
112
|
-
return false;
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
return true;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// returns the output with kv substitutions performed
|
|
120
|
-
outputFor(msg) {
|
|
121
|
-
const rtn = {};
|
|
122
|
-
const subst = (__, k) => msg[k] || deepKey(msg, k) || "KEY_NOT_FOUND";
|
|
123
|
-
const replacer = (s) => s.replace(reFieldTokens, subst);
|
|
124
|
-
|
|
125
|
-
for (const key in this.output) {
|
|
126
|
-
const val = this.output[key];
|
|
127
|
-
|
|
128
|
-
if (Array.isArray(val)) {
|
|
129
|
-
const updatedVals = Array(val.length);
|
|
130
|
-
for (let i = 0; i < val.length; i++) {
|
|
131
|
-
updatedVals[i] = replacer(val[i]);
|
|
132
|
-
}
|
|
133
|
-
rtn[key] = updatedVals;
|
|
134
|
-
} else {
|
|
135
|
-
rtn[key] = replacer(val);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
return rtn;
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// validateKVConfig ensures that `routes` matches the config schema. We have this
|
|
144
|
-
// function instead of just doing a plain jsonschema.validate in order to get
|
|
145
|
-
// better error messages for the "output" object (by default jsonschema would
|
|
146
|
-
// just tell you that the output block doesn't match any of the known output
|
|
147
|
-
// formats, but won't tell you what's wrong because it doesn't let you
|
|
148
|
-
// condition on the output.type property).
|
|
149
|
-
function validateKVConfig(config) {
|
|
150
|
-
const validator = new jsonschema.Validator();
|
|
151
|
-
const results = validator.validate(config, schema);
|
|
152
|
-
|
|
153
|
-
return {
|
|
154
|
-
valid: results.valid,
|
|
155
|
-
errors: results.errors.map((err) => err.stack),
|
|
156
|
-
};
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
// parseConfig parses and validates the configuration passed as a string. It
|
|
160
|
-
// returns an object of the form {valid, rules, errors}, where valid is true if
|
|
161
|
-
// it was successfully parsed, rules is an array of rules, and errors is an
|
|
162
|
-
// array of errors.
|
|
163
|
-
function parseConfig(fileString) {
|
|
164
|
-
let config;
|
|
165
|
-
try {
|
|
166
|
-
config = yaml.safeLoad(fileString);
|
|
167
|
-
} catch (e) {
|
|
168
|
-
return { valid: false, rules: [], errors: [e] };
|
|
169
|
-
}
|
|
170
|
-
const validateRes = validateKVConfig(config);
|
|
171
|
-
if (!validateRes.valid) {
|
|
172
|
-
return _.assign(validateRes, { rules: [] });
|
|
173
|
-
}
|
|
174
|
-
try {
|
|
175
|
-
const rulesObj = _.mapObject(
|
|
176
|
-
config.routes,
|
|
177
|
-
(elem, name) => new Rule(name, elem.matchers, elem.output),
|
|
178
|
-
);
|
|
179
|
-
const rules = _.values(rulesObj);
|
|
180
|
-
return { valid: true, rules, errors: [] };
|
|
181
|
-
} catch (e) {
|
|
182
|
-
return { valid: false, rules: [], errors: [e] };
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
class Router {
|
|
187
|
-
rules = null;
|
|
188
|
-
|
|
189
|
-
constructor(rules) {
|
|
190
|
-
this.rules = rules || [];
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
// loadConfig reads in the config located at `filename` and sets the routing
|
|
194
|
-
// rules to what it finds there. It should be a YAML-formatted file with
|
|
195
|
-
// routing rules placed under the `routes` key on the root object.
|
|
196
|
-
loadConfig(filename) {
|
|
197
|
-
const data = fs.readFileSync(filename, "utf8");
|
|
198
|
-
this._loadConfigString(data);
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
_loadConfigString(configStr) {
|
|
202
|
-
const parsedRules = parseConfig(configStr);
|
|
203
|
-
if (!parsedRules.valid) {
|
|
204
|
-
throw new Error(parsedRules.errors);
|
|
205
|
-
}
|
|
206
|
-
this.rules = parsedRules.rules;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
// route matches the log line `msg` against all loaded rules and returns a
|
|
210
|
-
// metadata object describing the outputs it should be sent to based on that
|
|
211
|
-
// matching. logger.ts will attach this to log lines under the `_kvmeta`
|
|
212
|
-
// property.
|
|
213
|
-
route(msg) {
|
|
214
|
-
const outputs = [];
|
|
215
|
-
for (let i = 0; i < this.rules.length; i++) {
|
|
216
|
-
const rule = this.rules[i];
|
|
217
|
-
if (rule.matches(msg)) {
|
|
218
|
-
outputs.push(rule.outputFor(msg));
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
return {
|
|
223
|
-
team: teamName,
|
|
224
|
-
kv_version: kvVersion,
|
|
225
|
-
kv_language: "js",
|
|
226
|
-
routes: outputs,
|
|
227
|
-
};
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
module.exports = {
|
|
232
|
-
Router,
|
|
233
|
-
Rule,
|
|
234
|
-
};
|
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"description": "Last modified: 02/08/2017",
|
|
3
|
-
"required": ["routes"],
|
|
4
|
-
"properties": {
|
|
5
|
-
"routes": { "$ref": "#/definitions/routes" }
|
|
6
|
-
},
|
|
7
|
-
"definitions": {
|
|
8
|
-
"routes": {
|
|
9
|
-
"type": "object",
|
|
10
|
-
"additionalProperties": false,
|
|
11
|
-
"patternProperties": {
|
|
12
|
-
"^[a-zA-Z0-9-_]+$": { "$ref": "#/definitions/rule" }
|
|
13
|
-
}
|
|
14
|
-
},
|
|
15
|
-
"rule": {
|
|
16
|
-
"title": "Rule",
|
|
17
|
-
"type": "object",
|
|
18
|
-
"required": ["matchers", "output"],
|
|
19
|
-
"additionalProperties": false,
|
|
20
|
-
"properties": {
|
|
21
|
-
"matchers": { "$ref": "#/definitions/matchers" },
|
|
22
|
-
"output": { "$ref": "#/definitions/output" }
|
|
23
|
-
}
|
|
24
|
-
},
|
|
25
|
-
"matchers": {
|
|
26
|
-
"type": "object",
|
|
27
|
-
"minProperties": 1,
|
|
28
|
-
"additionalProperties": false,
|
|
29
|
-
"patternProperties": {
|
|
30
|
-
"^[^%\\${}]+$": { "$ref": "#/definitions/matcherArr" }
|
|
31
|
-
}
|
|
32
|
-
},
|
|
33
|
-
"output": {
|
|
34
|
-
"type": "object",
|
|
35
|
-
"oneOf": [
|
|
36
|
-
{ "$ref": "#/definitions/metricsOutput" },
|
|
37
|
-
{ "$ref": "#/definitions/alertsOutput" },
|
|
38
|
-
{ "$ref": "#/definitions/analyticsOutput" },
|
|
39
|
-
{ "$ref": "#/definitions/notificationsOutput" }
|
|
40
|
-
]
|
|
41
|
-
},
|
|
42
|
-
"metricsOutput": {
|
|
43
|
-
"title": "Metrics Output",
|
|
44
|
-
"type": "object",
|
|
45
|
-
"oneOf": [
|
|
46
|
-
{
|
|
47
|
-
"additionalProperties": false,
|
|
48
|
-
"required": ["type", "series", "dimensions"],
|
|
49
|
-
"properties": {
|
|
50
|
-
"type": { "type": "string", "pattern": "^metrics$" },
|
|
51
|
-
"series": { "$ref": "#/definitions/envVarSubstValue" },
|
|
52
|
-
"dimensions": { "$ref": "#/definitions/flatValueArr" }
|
|
53
|
-
}
|
|
54
|
-
},
|
|
55
|
-
{
|
|
56
|
-
"additionalProperties": false,
|
|
57
|
-
"required": ["type", "series", "dimensions", "value_field"],
|
|
58
|
-
"properties": {
|
|
59
|
-
"type": { "type": "string", "pattern": "^metrics$" },
|
|
60
|
-
"series": { "$ref": "#/definitions/envVarSubstValue" },
|
|
61
|
-
"dimensions": { "$ref": "#/definitions/flatValueArr" },
|
|
62
|
-
"value_field": { "$ref": "#/definitions/flatValue" }
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
]
|
|
66
|
-
},
|
|
67
|
-
"alertsOutput": {
|
|
68
|
-
"type": "object",
|
|
69
|
-
"oneOf": [
|
|
70
|
-
{
|
|
71
|
-
"additionalProperties": false,
|
|
72
|
-
"required": ["type", "series", "dimensions", "stat_type"],
|
|
73
|
-
"properties": {
|
|
74
|
-
"type": { "type": "string", "pattern": "^alerts$" },
|
|
75
|
-
"series": { "$ref": "#/definitions/envVarSubstValue" },
|
|
76
|
-
"dimensions": {
|
|
77
|
-
"type": "array",
|
|
78
|
-
"uniqueItems": true,
|
|
79
|
-
"items": { "$ref": "#/definitions/flatValue" }
|
|
80
|
-
},
|
|
81
|
-
"stat_type": { "type": "string", "enum": ["counter", "gauge"] }
|
|
82
|
-
}
|
|
83
|
-
},
|
|
84
|
-
{
|
|
85
|
-
"additionalProperties": false,
|
|
86
|
-
"required": ["type", "series", "dimensions", "stat_type", "value_field"],
|
|
87
|
-
"properties": {
|
|
88
|
-
"type": { "type": "string", "pattern": "^alerts$" },
|
|
89
|
-
"series": { "$ref": "#/definitions/envVarSubstValue" },
|
|
90
|
-
"dimensions": {
|
|
91
|
-
"type": "array",
|
|
92
|
-
"uniqueItems": true,
|
|
93
|
-
"items": { "$ref": "#/definitions/flatValue" }
|
|
94
|
-
},
|
|
95
|
-
"stat_type": { "type": "string", "enum": ["counter", "gauge"] },
|
|
96
|
-
"value_field": { "$ref": "#/definitions/flatValue" }
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
]
|
|
100
|
-
},
|
|
101
|
-
"analyticsOutput": {
|
|
102
|
-
"type": "object",
|
|
103
|
-
"additionalProperties": false,
|
|
104
|
-
"required": ["type", "series"],
|
|
105
|
-
"properties": {
|
|
106
|
-
"type": {
|
|
107
|
-
"type": "string",
|
|
108
|
-
"pattern": "^analytics$"
|
|
109
|
-
},
|
|
110
|
-
"series": { "$ref": "#/definitions/envVarSubstValue" }
|
|
111
|
-
}
|
|
112
|
-
},
|
|
113
|
-
"notificationsOutput": {
|
|
114
|
-
"type": "object",
|
|
115
|
-
"additionalProperties": false,
|
|
116
|
-
"required": ["type", "channel", "icon", "message", "user"],
|
|
117
|
-
"properties": {
|
|
118
|
-
"type": {
|
|
119
|
-
"type": "string",
|
|
120
|
-
"pattern": "^notifications$"
|
|
121
|
-
},
|
|
122
|
-
"channel": { "$ref": "#/definitions/kvSubstValue" },
|
|
123
|
-
"icon": { "$ref": "#/definitions/envVarSubstValue" },
|
|
124
|
-
"message": { "$ref": "#/definitions/kvSubstValue" },
|
|
125
|
-
"user": { "$ref": "#/definitions/envVarSubstValue" }
|
|
126
|
-
}
|
|
127
|
-
},
|
|
128
|
-
"flatValue": {
|
|
129
|
-
"type": "string",
|
|
130
|
-
"pattern": "^[^%\\${}]+$"
|
|
131
|
-
},
|
|
132
|
-
"flatValueArr": {
|
|
133
|
-
"type": "array",
|
|
134
|
-
"minItems": 1,
|
|
135
|
-
"uniqueItems": true,
|
|
136
|
-
"items": { "$ref": "#/definitions/flatValue" }
|
|
137
|
-
},
|
|
138
|
-
"matcherArr": {
|
|
139
|
-
"type": "array",
|
|
140
|
-
"minItems": 1,
|
|
141
|
-
"uniqueItems": true,
|
|
142
|
-
"items": {
|
|
143
|
-
"oneOf": [
|
|
144
|
-
{ "$ref": "#/definitions/flatValue" },
|
|
145
|
-
{ "type": "boolean" }
|
|
146
|
-
]
|
|
147
|
-
}
|
|
148
|
-
},
|
|
149
|
-
"envVarSubstValue": {
|
|
150
|
-
"type": "string",
|
|
151
|
-
"pattern": "^([^\\$%{}]|\\$\\{[^%\\${}]+\\})+$"
|
|
152
|
-
},
|
|
153
|
-
"kvSubstValue": {
|
|
154
|
-
"type": "string",
|
|
155
|
-
"pattern": "^([^\\$%{}]|\\$\\{[^%\\${}]+\\}|%\\{[^%\\${}]+\\})+$"
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
}
|