kayvee 3.18.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.
Files changed (58) hide show
  1. package/README.md +147 -202
  2. package/dist/index.d.ts +4 -0
  3. package/dist/index.d.ts.map +1 -0
  4. package/dist/index.js +29 -0
  5. package/dist/kayvee.d.ts +12 -0
  6. package/dist/kayvee.d.ts.map +1 -0
  7. package/{build/lib → dist}/kayvee.js +17 -30
  8. package/dist/logger/logger.d.ts +49 -0
  9. package/dist/logger/logger.d.ts.map +1 -0
  10. package/{build/lib → dist}/logger/logger.js +91 -83
  11. package/dist/middleware.d.ts +23 -0
  12. package/dist/middleware.d.ts.map +1 -0
  13. package/dist/middleware.js +196 -0
  14. package/dist/package.json +89 -0
  15. package/dist/router/index.d.ts +23 -0
  16. package/dist/router/index.d.ts.map +1 -0
  17. package/{build/lib → dist}/router/index.js +33 -45
  18. package/package.json +63 -27
  19. package/.circleci/config.yml +0 -25
  20. package/.eslintrc.js +0 -124
  21. package/.github/workflows/notify-ci-status.yml +0 -20
  22. package/.nvmrc +0 -1
  23. package/.prettierrc.json +0 -1
  24. package/Makefile +0 -55
  25. package/benchmarks/data/.keep +0 -1
  26. package/benchmarks/data/corpus-basic.json +0 -22
  27. package/benchmarks/data/corpus-pathological.json +0 -22
  28. package/benchmarks/data/corpus-realistic.json +0 -22
  29. package/benchmarks/data/kvconfig-basic.yml +0 -7
  30. package/benchmarks/data/kvconfig-pathological.yml +0 -222
  31. package/benchmarks/data/kvconfig-realistic.yml +0 -39
  32. package/benchmarks/routing.js +0 -116
  33. package/build/lib/logger/helpers.js +0 -0
  34. package/build/lib/middleware.js +0 -274
  35. package/build/package.json +0 -53
  36. package/build/test/context_logger.js +0 -69
  37. package/build/test/kayvee.js +0 -36
  38. package/build/test/logger_test.js +0 -345
  39. package/build/test/middleware.js +0 -556
  40. package/build/test/router.js +0 -451
  41. package/index.js +0 -7
  42. package/lib/kayvee.ts +0 -73
  43. package/lib/logger/helpers.ts +0 -0
  44. package/lib/logger/logger.ts +0 -312
  45. package/lib/middleware.ts +0 -317
  46. package/lib/router/index.ts +0 -234
  47. package/lib/router/schema_definitions.json +0 -158
  48. package/test/context_logger.ts +0 -76
  49. package/test/kayvee.ts +0 -50
  50. package/test/kvconfig.yml +0 -14
  51. package/test/logger_test.ts +0 -378
  52. package/test/middleware.ts +0 -632
  53. package/test/router.ts +0 -558
  54. package/test/static/empty.css +0 -0
  55. package/test/static/js/empty.js +0 -0
  56. package/test/tests.json +0 -100
  57. package/tsconfig.json +0 -10
  58. /package/{build/lib → dist}/router/schema_definitions.json +0 -0
@@ -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
- }
@@ -1,76 +0,0 @@
1
- var assert = require("assert");
2
- var sinon = require("sinon");
3
-
4
- var middleware = require("../lib/middleware");
5
- var KayveeLogger = require("../lib/logger/logger");
6
-
7
- describe("ContextLogger", () => {
8
- const fake_req = { key1: "val1" };
9
- const fake_handler = (req) => ({ log_key1: req.key1, key2: "val2" });
10
-
11
- for (const level of KayveeLogger.LEVELS) {
12
- it(`correctly adds context to ${level} calls`, () => {
13
- const spy = sinon.spy();
14
- const stub_logger = {};
15
- stub_logger[`${level}D`] = spy;
16
- const log = new middleware.ContextLogger(stub_logger, [fake_handler], fake_req);
17
- log[level]("test_title");
18
- const expected_context = { log_key1: "val1", key2: "val2" };
19
- assert(spy.calledWithExactly("test_title", expected_context));
20
- assert.equal(spy.callCount, 1);
21
- });
22
-
23
- it(`correctly adds context to ${level}D calls`, () => {
24
- const spy = sinon.spy();
25
- const stub_logger = {};
26
- stub_logger[`${level}D`] = spy;
27
- const log = new middleware.ContextLogger(stub_logger, [fake_handler], fake_req);
28
- log[`${level}D`]("test_title", { key2: "new_value", key3: "val3" });
29
- const expected_data = {
30
- log_key1: "val1",
31
- key2: "new_value",
32
- key3: "val3",
33
- };
34
- assert(spy.calledWithExactly("test_title", expected_data));
35
- assert.equal(spy.callCount, 1);
36
- });
37
- }
38
-
39
- for (const metric of KayveeLogger.METRICS) {
40
- it(`correctly adds context to ${metric} calls`, () => {
41
- const spy = sinon.spy();
42
- const stub_logger = {};
43
- stub_logger[`${metric}D`] = spy;
44
- const log = new middleware.ContextLogger(stub_logger, [fake_handler], fake_req);
45
- log[metric]("test_title", 3);
46
- const expected_context = { log_key1: "val1", key2: "val2" };
47
- assert(spy.calledWithExactly("test_title", 3, expected_context));
48
- assert.equal(spy.callCount, 1);
49
- });
50
-
51
- it(`correctly adds context to ${metric}D calls`, () => {
52
- const spy = sinon.spy();
53
- const stub_logger = {};
54
- stub_logger[`${metric}D`] = spy;
55
- const log = new middleware.ContextLogger(stub_logger, [fake_handler], fake_req);
56
- log[`${metric}D`]("test_title", 3, { key2: "new_value", key3: "val3" });
57
- const expected_data = {
58
- log_key1: "val1",
59
- key2: "new_value",
60
- key3: "val3",
61
- };
62
- assert(spy.calledWithExactly("test_title", 3, expected_data));
63
- assert.equal(spy.callCount, 1);
64
- });
65
- }
66
-
67
- it("correctly handles being instantiated with empty list of handlers", () => {
68
- const spy = sinon.spy();
69
- const stub_logger = { infoD: spy };
70
- const log = new middleware.ContextLogger(stub_logger, [], fake_req);
71
- log.info("test_title");
72
- const expected_context = {};
73
- assert(spy.calledWithExactly("test_title", expected_context));
74
- assert.equal(spy.callCount, 1);
75
- });
76
- });
package/test/kayvee.ts DELETED
@@ -1,50 +0,0 @@
1
- var kv = require("../lib/kayvee");
2
- var assert = require("assert");
3
- var _ = require("underscore");
4
- var fs = require("fs");
5
-
6
- describe("kayvee", () => {
7
- const tests = JSON.parse(fs.readFileSync("test/tests.json"));
8
- describe(".format", () => {
9
- _.each(tests.format, (spec) => {
10
- it(spec.title, () => {
11
- const actual = kv.format(spec.input.data);
12
- const expected = spec.output;
13
- assert.deepEqual(
14
- JSON.parse(actual),
15
- _.extend({ deploy_env: "testing", wf_id: "abc" }, JSON.parse(expected)),
16
- );
17
- });
18
- });
19
- });
20
-
21
- describe(".format with Errors", () => {
22
- it("encodes Error objects", () => {
23
- const actual = kv.format({ err: Error("An Error Message") });
24
- const expected = {
25
- deploy_env: "testing",
26
- wf_id: "abc",
27
- err: "Error: An Error Message",
28
- };
29
- assert.deepEqual(JSON.parse(actual), expected);
30
- });
31
- });
32
-
33
- describe(".formatLog", () => {
34
- _.each(tests.formatLog, (spec) => {
35
- it(spec.title, () => {
36
- const actual = kv.formatLog(
37
- spec.input.source,
38
- spec.input.level,
39
- spec.input.title,
40
- spec.input.data,
41
- );
42
- const expected = spec.output;
43
- assert.deepEqual(
44
- JSON.parse(actual),
45
- _.extend({ deploy_env: "testing", wf_id: "abc" }, JSON.parse(expected)),
46
- );
47
- });
48
- });
49
- });
50
- });
package/test/kvconfig.yml DELETED
@@ -1,14 +0,0 @@
1
- routes:
2
- all-kv_middleware:
3
- matchers:
4
- via: [ "kayvee-middleware" ]
5
- output:
6
- type: "analytics"
7
- series: "requests.everything"
8
- rule-two:
9
- matchers:
10
- title: [ "foo-title" ]
11
- deploy_env: [ "testing" ]
12
- output:
13
- type: "analytics"
14
- series: "requests.everything"