@hyperwatch/hyperwatch 4.3.1 → 5.0.1

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 (45) hide show
  1. package/README.md +9 -4
  2. package/config/apache_syslog_example.js +1 -1
  3. package/config/default.js +1 -1
  4. package/config/example.js +4 -4
  5. package/config/express_websocket_example.js +1 -1
  6. package/config/websocket_client_example.js +52 -0
  7. package/docs/configuration.md +67 -6
  8. package/docs/express-embedding.md +137 -0
  9. package/docs/input.md +50 -24
  10. package/docs/tutorials/apache_input.md +16 -14
  11. package/docs/tutorials/express_input.md +10 -8
  12. package/package.json +16 -14
  13. package/scripts/fetch-anthropic-ips.js +60 -0
  14. package/src/app/api.js +129 -3
  15. package/src/app/index.js +8 -4
  16. package/src/app/mount.js +115 -0
  17. package/src/app/websocket.js +12 -9
  18. package/src/app/ws-server.js +123 -0
  19. package/src/constants.js +8 -5
  20. package/src/data/amazon-searchbot-ips.json +304 -0
  21. package/src/data/amazonbot-ips.json +775 -1
  22. package/src/data/chatgpt-user-ips.json +115 -112
  23. package/src/data/claude-bot-ips.json +28 -0
  24. package/src/data/cloudfront-ips.json +14 -0
  25. package/src/data/gptbot-ips.json +0 -3
  26. package/src/data/openai-searchbot-ips.json +4 -0
  27. package/src/index.js +6 -1
  28. package/src/input/http.js +4 -0
  29. package/src/input/syslog.js +5 -1
  30. package/src/input/websocket.js +57 -21
  31. package/src/lib/aggregator.js +112 -19
  32. package/src/lib/formatter.js +10 -1
  33. package/src/lib/log-buffer.js +45 -0
  34. package/src/lib/persistence.js +11 -1
  35. package/src/lib/pipeline.js +140 -14
  36. package/src/lib/recent-map.js +23 -0
  37. package/src/modules/address.js +59 -2
  38. package/src/modules/dnsbl.js +11 -1
  39. package/src/modules/history.js +4 -28
  40. package/src/modules/hostname.js +12 -4
  41. package/src/modules/identity.js +92 -9
  42. package/src/modules/index.js +14 -5
  43. package/src/modules/signature.js +49 -14
  44. package/src/modules/sparkline.js +7 -3
  45. package/src/modules/status.js +6 -1
@@ -1,9 +1,10 @@
1
- const { is, Set } = require('immutable');
1
+ const { is } = require('immutable');
2
2
 
3
3
  const api = require('../app/api');
4
- const { Aggregator } = require('../lib/aggregator');
4
+ const { Aggregator, lastSeen, statusCount } = require('../lib/aggregator');
5
5
  const { Formatter } = require('../lib/formatter');
6
6
  const pipeline = require('../lib/pipeline');
7
+ const { touch, prune, countRecent } = require('../lib/recent-map');
7
8
  const {
8
9
  aggregateCount,
9
10
  aggregateSum,
@@ -40,6 +41,9 @@ function normalisedIdentityHeader(headers) {
40
41
  return obj;
41
42
  }
42
43
 
44
+ const addressCount15m = (entry) => countRecent(entry.get('addresses'), 15 * 60);
45
+ const addressCount24h = (entry) => countRecent(entry.get('addresses'));
46
+
43
47
  function computeSignature(headers) {
44
48
  const string = Object.keys(headers)
45
49
  .map((key) => [key, headers[key]].join(':'))
@@ -65,8 +69,10 @@ function init() {
65
69
  pipeline.getNode('main').map(augment).registerNode('main');
66
70
  }
67
71
 
72
+ let _aggregator;
73
+
68
74
  function start() {
69
- const aggregator = new Aggregator();
75
+ const aggregator = (_aggregator = new Aggregator());
70
76
 
71
77
  aggregator.setIdentifier((log) => log.getIn(['signature', 'id']));
72
78
 
@@ -75,15 +81,24 @@ function start() {
75
81
  signatureFormatter.setFormats([
76
82
  ['signature', (entry) => entry.getIn(['signature', 'id'])],
77
83
  ['identity', (entry) => entry.get('identity')],
78
- ['addressCount', (entry) => entry.get('addresses').size],
84
+ ['addressCount15m', addressCount15m],
85
+ ['addressCount24h', addressCount24h],
79
86
  [
80
87
  'addresses',
81
88
  (entry) =>
82
- entry
83
- .get('addresses')
84
- .map((address) => address.get('value'))
85
- .slice(0, 10)
86
- .join('<br>'),
89
+ entry.has('addresses')
90
+ ? entry.get('addresses').keySeq().slice(0, 10).join('<br>')
91
+ : '',
92
+ ],
93
+ [
94
+ 'lastAddress',
95
+ (entry) => {
96
+ const addr = entry.get('lastAddress');
97
+ if (!addr) {
98
+ return '';
99
+ }
100
+ return addr.get('hostname') || addr.get('value') || '';
101
+ },
87
102
  ],
88
103
 
89
104
  [
@@ -96,9 +111,15 @@ function start() {
96
111
  },
97
112
  ],
98
113
 
114
+ ['lastSeen', lastSeen],
99
115
  ['count15m', (entry) => aggregateCount(entry, 'per_minute')],
100
116
  ['count24h', (entry) => aggregateCount(entry, 'per_hour')],
101
117
 
118
+ ['2xx15m', statusCount('2xx_per_minute')],
119
+ ['2xx24h', statusCount('2xx_per_hour')],
120
+ ['4xx15m', statusCount('4xx_per_minute')],
121
+ ['4xx24h', statusCount('4xx_per_hour')],
122
+
102
123
  [
103
124
  'execTime15m',
104
125
  (entry) => formatDuration(aggregateSum(entry, 'per_minute')),
@@ -121,10 +142,12 @@ function start() {
121
142
  }
122
143
 
123
144
  const address = log.get('address');
124
- if (!entry.has('addresses')) {
125
- entry = entry.set('addresses', new Set([address]));
126
- } else if (!entry.get('addresses').has(address)) {
127
- entry = entry.update('addresses', (set) => set.add(address));
145
+ entry = entry.set('lastAddress', address);
146
+ // Distinct IPs with last-seen time, pruned to 24h; backs both the 15m
147
+ // and 24h counts
148
+ const value = address && address.get('value');
149
+ if (value) {
150
+ entry = entry.update('addresses', (map) => touch(map, value));
128
151
  }
129
152
 
130
153
  return entry;
@@ -132,7 +155,16 @@ function start() {
132
155
 
133
156
  aggregator.setEnricher(enricher);
134
157
 
135
- pipeline.getNode('main').map((log) => aggregator.processLog(log));
158
+ aggregator.setEntryGc((entry) =>
159
+ entry.has('addresses') ? entry.update('addresses', prune) : entry
160
+ );
161
+
162
+ aggregator.sorters.addressCount15m = addressCount15m;
163
+ aggregator.sorters.addressCount24h = addressCount24h;
164
+
165
+ pipeline
166
+ .getNode('main')
167
+ .map((log) => aggregator.processLog(log), 'aggregator');
136
168
 
137
169
  api.registerAggregator('signatures', aggregator);
138
170
  }
@@ -140,4 +172,7 @@ function start() {
140
172
  module.exports = {
141
173
  init,
142
174
  start,
175
+ get aggregator() {
176
+ return _aggregator;
177
+ },
143
178
  };
@@ -1,6 +1,10 @@
1
1
  const aggregator = require('../lib/aggregator');
2
2
 
3
- const sparkline = (entry, key) => {
3
+ const sparkline = (entry, key, output) => {
4
+ if (output === 'text') {
5
+ return;
6
+ }
7
+
4
8
  const id = entry.get('id');
5
9
 
6
10
  const points = entry
@@ -22,8 +26,8 @@ sparkline ('${id}', ${JSON.stringify(points)}, '#797979', 14, 5);
22
26
  };
23
27
 
24
28
  function init() {
25
- aggregator.defaultFormatter.insertFormat('activity', (entry) =>
26
- sparkline(entry, 'per_minute')
29
+ aggregator.defaultFormatter.insertFormat('activity', (entry, output) =>
30
+ sparkline(entry, 'per_minute', output)
27
31
  );
28
32
  }
29
33
 
@@ -23,10 +23,15 @@ function mapper(entry, format) {
23
23
  }
24
24
 
25
25
  function start() {
26
- api.get('/status(.:format(txt|json))?', (req, res) => {
26
+ api.get('/status{.:format}', (req, res) => {
27
27
  const raw = req.query.raw ? true : false;
28
28
  const format = req.params.format || (raw ? 'json' : null);
29
29
 
30
+ if (format && !['json', 'txt'].includes(format)) {
31
+ res.sendStatus(404);
32
+ return;
33
+ }
34
+
30
35
  let rawData = monitoring.getAllComputed();
31
36
 
32
37
  if (req.query.type) {