@viplance/nestjs-logger 0.4.5 → 0.4.8

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 (49) hide show
  1. package/dist/log.module.js +9 -5
  2. package/dist/log.module.js.map +1 -1
  3. package/dist/services/log.service.d.ts +1 -1
  4. package/dist/services/log.service.js +49 -7
  5. package/dist/services/log.service.js.map +1 -1
  6. package/dist/services/memory-db.service.d.ts +6 -0
  7. package/dist/services/memory-db.service.js +85 -1
  8. package/dist/services/memory-db.service.js.map +1 -1
  9. package/dist/services/ws.service.d.ts +2 -4
  10. package/dist/services/ws.service.js +61 -32
  11. package/dist/services/ws.service.js.map +1 -1
  12. package/package.json +1 -1
  13. package/public/index.html +3 -1
  14. package/public/scripts/common.js +143 -33
  15. package/public/scripts/details-popup.js +17 -2
  16. package/public/scripts/local-storage.js +6 -6
  17. package/public/scripts/ws.js +36 -7
  18. package/public/styles/index.css +177 -2
  19. package/src/log.module.ts +10 -6
  20. package/src/services/log.service.ts +64 -8
  21. package/src/services/memory-db.service.ts +90 -3
  22. package/src/services/ws.service.ts +33 -36
  23. package/dist/db.service.d.ts +0 -8
  24. package/dist/db.service.js +0 -51
  25. package/dist/db.service.js.map +0 -1
  26. package/dist/exception.filter.d.ts +0 -4
  27. package/dist/exception.filter.js +0 -34
  28. package/dist/exception.filter.js.map +0 -1
  29. package/dist/log.interceptor.d.ts +0 -9
  30. package/dist/log.interceptor.js +0 -43
  31. package/dist/log.interceptor.js.map +0 -1
  32. package/dist/log.service.d.ts +0 -12
  33. package/dist/log.service.js +0 -66
  34. package/dist/log.service.js.map +0 -1
  35. package/dist/logger.module.d.ts +0 -7
  36. package/dist/logger.module.js +0 -47
  37. package/dist/logger.module.js.map +0 -1
  38. package/dist/logger.service.d.ts +0 -8
  39. package/dist/logger.service.js +0 -35
  40. package/dist/logger.service.js.map +0 -1
  41. package/dist/services/db.service.d.ts +0 -9
  42. package/dist/services/db.service.js +0 -65
  43. package/dist/services/db.service.js.map +0 -1
  44. package/dist/types/db.type.d.ts +0 -1
  45. package/dist/types/db.type.js +0 -3
  46. package/dist/types/db.type.js.map +0 -1
  47. package/dist/types.d.ts +0 -7
  48. package/dist/types.js +0 -12
  49. package/dist/types.js.map +0 -1
@@ -11,6 +11,10 @@ const logTypes = Object.keys(selectedLogTypes).filter((key) => key !== `all`);
11
11
 
12
12
  let logs = [];
13
13
  let text = '';
14
+ let currentPage = 1;
15
+ let isLoading = false;
16
+ let hasMore = true;
17
+ const limit = 10;
14
18
 
15
19
  connectWebSocket();
16
20
 
@@ -36,7 +40,9 @@ document.addEventListener(`click`, (e) => {
36
40
  });
37
41
  }
38
42
 
39
- renderLogs();
43
+ currentPage = 1;
44
+ hasMore = true;
45
+ getLogs(1);
40
46
 
41
47
  return;
42
48
  }
@@ -53,7 +59,9 @@ document.addEventListener(`click`, (e) => {
53
59
  selectedLogTypes[`all`] = false;
54
60
  unsetSelectorActive(document.querySelector(`li.all`));
55
61
 
56
- getLogs();
62
+ currentPage = 1;
63
+ hasMore = true;
64
+ getLogs(1);
57
65
 
58
66
  return;
59
67
  }
@@ -141,24 +149,9 @@ function getLogHtmlElement(log) {
141
149
  function renderLogs(logList = logs) {
142
150
  let html = '';
143
151
 
144
- logList
145
- .filter((log) => {
146
- return selectedLogTypes['all'] || selectedLogTypes[log.type];
147
- })
148
- .filter((log) => {
149
- if (text === '') return true;
150
-
151
- return (
152
- log.message.toLowerCase().includes(text) ||
153
- log.trace?.toLowerCase().includes(text) ||
154
- JSON.stringify(log.context || {})
155
- .toLowerCase()
156
- .includes(text)
157
- );
158
- })
159
- .forEach((log) => {
160
- html += getLogHtmlElement(log);
161
- });
152
+ logList.forEach((log) => {
153
+ html += getLogHtmlElement(log);
154
+ });
162
155
 
163
156
  document.getElementById('logs').innerHTML = html;
164
157
  }
@@ -166,39 +159,79 @@ function renderLogs(logList = logs) {
166
159
  async function checkElementsVisibility(logList = logs) {
167
160
  if (logList.length === 0) {
168
161
  document.getElementById('no-logs').style.display = 'block';
169
- document.getElementById('search').style.display = 'none';
170
162
  document.querySelector('.table-header').style.display = 'none';
171
- document.querySelector('nav').style.display = 'none';
163
+ document.querySelector('nav').style.display = 'flex';
172
164
  } else {
173
165
  document.getElementById('no-logs').style.display = 'none';
174
- document.getElementById('search').style.display = 'inline-block';
175
166
  document.querySelector('.table-header').style.display = 'flex';
176
167
  document.querySelector('nav').style.display = 'flex';
177
168
  }
178
169
  }
179
170
 
180
- async function getLogs() {
181
- const { origin, pathname, search } = window.location;
182
- const searchParams = new URLSearchParams(search);
171
+ async function getLogs(page = 1) {
172
+ if (isLoading && page > 1) return;
173
+
174
+ if (page > 1 && !hasMore) return;
175
+
176
+ isLoading = true;
177
+ currentPage = page;
178
+ document.getElementById('loader').style.display = 'block';
179
+
180
+ const { origin, pathname, search: urlSearch } = window.location;
181
+ const searchParams = new URLSearchParams(urlSearch);
183
182
  const key = searchParams.get('key');
184
183
 
185
- if (!!socket) {
184
+ const types = selectedLogTypes.all
185
+ ? []
186
+ : Object.keys(selectedLogTypes).filter(
187
+ (key) => selectedLogTypes[key] && key !== 'all',
188
+ );
189
+
190
+ if (!!socket && socket.readyState === WebSocket.OPEN) {
186
191
  socket.send(
187
192
  JSON.stringify({
188
193
  action: 'getLogs',
189
194
  key,
190
- })
195
+ page,
196
+ limit,
197
+ search: text,
198
+ types,
199
+ }),
191
200
  );
192
201
  } else {
193
- const res = await fetch(`${origin}${pathname}api${search}`);
202
+ const apiParams = new URLSearchParams(urlSearch);
203
+ apiParams.set('page', page);
204
+ apiParams.set('limit', limit);
205
+
206
+ if (text) {
207
+ apiParams.set('search', text);
208
+ }
209
+
210
+ if (types.length > 0) {
211
+ apiParams.set('types', types.join(','));
212
+ }
213
+
214
+ const res = await fetch(`${origin}${pathname}api?${apiParams.toString()}`);
194
215
 
195
216
  if (res.ok) {
196
- logs = await res.json();
217
+ const newLogs = await res.json();
197
218
 
198
- checkElementsVisibility();
219
+ if (page === 1) {
220
+ logs = newLogs;
221
+ } else {
222
+ logs = logs.concat(newLogs);
223
+ }
224
+
225
+ hasMore = newLogs.length === limit;
226
+ isLoading = false;
227
+ document.getElementById('loader').style.display = 'none';
199
228
 
229
+ checkElementsVisibility();
200
230
  renderLogs();
231
+ checkAndUpdatePopup();
201
232
  } else {
233
+ isLoading = false;
234
+ document.getElementById('loader').style.display = 'none';
202
235
  alert('An error occurred while fetching logs.');
203
236
  }
204
237
  }
@@ -220,7 +253,7 @@ async function deleteLog(_id) {
220
253
  data: {
221
254
  _id,
222
255
  },
223
- })
256
+ }),
224
257
  );
225
258
  closePopup();
226
259
  getLogs();
@@ -231,7 +264,7 @@ async function deleteLog(_id) {
231
264
  `${origin}${pathname}api?${searchParamsWithId.toString()}`,
232
265
  {
233
266
  method: 'DELETE',
234
- }
267
+ },
235
268
  );
236
269
 
237
270
  if (res.ok) {
@@ -243,8 +276,85 @@ async function deleteLog(_id) {
243
276
  }
244
277
  }
245
278
 
279
+ let searchTimeout;
246
280
  function search(event) {
247
281
  text = event.target.value.toLowerCase();
248
282
 
283
+ clearTimeout(searchTimeout);
284
+ searchTimeout = setTimeout(() => {
285
+ currentPage = 1;
286
+ hasMore = true;
287
+ getLogs(1);
288
+ }, 300);
289
+ }
290
+
291
+ // Infinite scrolling
292
+ const observer = new IntersectionObserver(
293
+ (entries) => {
294
+ if (entries[0].isIntersecting && !isLoading && hasMore) {
295
+ getLogs(currentPage + 1);
296
+ }
297
+ },
298
+ { threshold: 1.0 },
299
+ );
300
+
301
+ document.addEventListener('DOMContentLoaded', () => {
302
+ const scrollAnchor = document.getElementById('scroll-anchor');
303
+ if (scrollAnchor) {
304
+ observer.observe(scrollAnchor);
305
+ }
306
+ });
307
+
308
+ function matchesFilter(log) {
309
+ // Check types
310
+ if (!selectedLogTypes['all'] && !selectedLogTypes[log.type]) {
311
+ return false;
312
+ }
313
+
314
+ // Check search text
315
+ if (text !== '') {
316
+ const matches =
317
+ log.message.toLowerCase().includes(text) ||
318
+ log.trace?.toLowerCase().includes(text) ||
319
+ JSON.stringify(log.context || {})
320
+ .toLowerCase()
321
+ .includes(text);
322
+
323
+ if (!matches) return false;
324
+ }
325
+
326
+ return true;
327
+ }
328
+
329
+ function handleWsInsert(log) {
330
+ if (matchesFilter(log)) {
331
+ logs.unshift(log);
332
+ checkElementsVisibility();
333
+ renderLogs();
334
+ }
335
+ }
336
+
337
+ function handleWsUpdate(updatedLog) {
338
+ const idx = logs.findIndex((l) => l._id === updatedLog._id);
339
+ if (idx > -1) {
340
+ logs.splice(idx, 1);
341
+ }
342
+
343
+ if (matchesFilter(updatedLog)) {
344
+ logs.unshift(updatedLog);
345
+ }
346
+
347
+ checkElementsVisibility();
249
348
  renderLogs();
349
+ checkAndUpdatePopup();
350
+ }
351
+
352
+ function handleWsDelete(id) {
353
+ const idx = logs.findIndex((l) => l._id === id);
354
+ if (idx > -1) {
355
+ logs.splice(idx, 1);
356
+ checkElementsVisibility();
357
+ renderLogs();
358
+ checkAndUpdatePopup();
359
+ }
250
360
  }
@@ -1,6 +1,9 @@
1
1
  const popup = document.getElementById('popup');
2
+ let selectedLogId = null;
2
3
 
3
4
  function showLogDetails(log) {
5
+ selectedLogId = log._id;
6
+
4
7
  const context = getObject(log.context);
5
8
  const breadcrumbs = getObject(log.breadcrumbs);
6
9
 
@@ -18,8 +21,8 @@ function showLogDetails(log) {
18
21
  )}.   First seen: ${getDate(log.createdAt)}`;
19
22
 
20
23
  popup.innerHTML = `
21
- <div id="drag-handle" style="margin: -2rem -2rem 1rem -2rem; height: 1.5rem; background-color: #f1f1f1; border-bottom: 1px solid #ddd; cursor: grab; display: flex; align-items: center; justify-content: center;" title="Drag to move">
22
- <div style="width: 50px; height: 5px; background-color: #ccc; border-radius: 5px;"></div>
24
+ <div id="drag-handle" title="Drag to move">
25
+ <div class="handle-indicator"></div>
23
26
  </div>
24
27
  <div class="content center">
25
28
  <div class="container">
@@ -117,9 +120,21 @@ function getTrace(trace) {
117
120
  }
118
121
 
119
122
  function closePopup() {
123
+ selectedLogId = null;
120
124
  popup.style.display = 'none';
121
125
  }
122
126
 
127
+ function checkAndUpdatePopup() {
128
+ if (selectedLogId) {
129
+ const log = logs.find((log) => log._id === selectedLogId);
130
+ if (log) {
131
+ showLogDetails(log);
132
+ } else {
133
+ closePopup();
134
+ }
135
+ }
136
+ }
137
+
123
138
  function setPopupLeft(left) {
124
139
  if (popup?.style.display === 'block') {
125
140
  if (left < 0) {
@@ -1,10 +1,14 @@
1
1
  const LS_KEY = 'nestjs-logger';
2
2
 
3
+ const defaultSettings = {
4
+ popupLeft: `${window.innerWidth / 4}px`,
5
+ };
6
+
3
7
  function getLs(propertyName) {
4
8
  const item = localStorage.getItem(LS_KEY);
5
9
 
6
10
  if (!item) {
7
- return;
11
+ return defaultSettings[propertyName];
8
12
  }
9
13
 
10
14
  return JSON.parse(item)[propertyName];
@@ -12,12 +16,8 @@ function getLs(propertyName) {
12
16
 
13
17
  function setLs(propertyName, value) {
14
18
  const item = localStorage.getItem(LS_KEY);
19
+ const ls = item ? JSON.parse(item) : { ...defaultSettings };
15
20
 
16
- if (!item) {
17
- localStorage.setItem(LS_KEY, JSON.stringify({}));
18
- }
19
-
20
- const ls = JSON.parse(item);
21
21
  ls[propertyName] = value;
22
22
  localStorage.setItem(LS_KEY, JSON.stringify(ls));
23
23
  }
@@ -1,15 +1,24 @@
1
1
  // WebSocket connection
2
2
  let socket;
3
+ let connected = false;
4
+ let connectionAttempts = 0;
3
5
  let frozen = false;
4
6
 
5
7
  async function connectWebSocket() {
8
+ connectionAttempts++;
9
+
10
+ if (connectionAttempts > 3) {
11
+ alert('Failed to connect to WebSocket. Check the `key` url parameter.');
12
+ return;
13
+ }
14
+
6
15
  const { hostname, origin, pathname, search } = window.location;
7
16
 
8
17
  const res = await fetch(`${origin}${pathname}settings${search}`);
9
18
 
10
19
  if (!res.ok) {
11
20
  alert(
12
- 'An error occurred while fetching settings. Check the `key` url parameter.'
21
+ 'An error occurred while fetching settings. Check the `key` url parameter.',
13
22
  );
14
23
  return;
15
24
  }
@@ -39,11 +48,14 @@ async function connectWebSocket() {
39
48
  };
40
49
 
41
50
  socket.onopen = (event) => {
51
+ connected = true;
52
+ connectionAttempts = 0;
42
53
  getLogs();
43
54
  };
44
55
 
45
56
  socket.onclose = (event) => {
46
- console.log(event);
57
+ connected = false;
58
+ console.error(event);
47
59
  setTimeout(connectWebSocket, 5000);
48
60
  };
49
61
 
@@ -53,22 +65,38 @@ async function connectWebSocket() {
53
65
  if (data['action'] && !frozen) {
54
66
  switch (data['action']) {
55
67
  case 'list':
56
- logs = data['data'];
68
+ if (currentPage === 1) {
69
+ logs = data['data'];
70
+ } else {
71
+ logs = logs.concat(data['data']);
72
+ }
73
+ hasMore = data['data'].length === limit;
74
+ isLoading = false;
75
+ document.getElementById('loader').style.display = 'none';
57
76
  checkElementsVisibility(logs);
58
77
  renderLogs(logs);
78
+ checkAndUpdatePopup();
59
79
  break;
60
80
  case 'insert':
61
- getLogs();
81
+ if (currentPage === 1) {
82
+ handleWsInsert(data['data']);
83
+ }
62
84
  break;
63
85
  case 'update':
64
- getLogs();
65
- return;
86
+ handleWsUpdate(data['data']);
87
+ break;
66
88
  case 'delete':
67
- return;
89
+ handleWsDelete(data['data']._id);
90
+ break;
68
91
  }
69
92
  }
70
93
  };
94
+
95
+ setTimeout(() => {
96
+ if (!connected) connectWebSocket(); // fix for Safari browser
97
+ }, 300);
71
98
  }
99
+
72
100
  function sendMessage(message) {
73
101
  socket.send(JSON.stringify(message));
74
102
  }
@@ -84,5 +112,6 @@ function toggleFreeze() {
84
112
  } else {
85
113
  button.classList.remove('light');
86
114
  button.classList.add('white');
115
+ getLogs();
87
116
  }
88
117
  }
@@ -54,7 +54,7 @@ h3 {
54
54
  }
55
55
 
56
56
  #logs {
57
- margin-top: 4rem;
57
+ margin-top: 7rem;
58
58
  }
59
59
 
60
60
  #no-logs {
@@ -63,6 +63,18 @@ h3 {
63
63
  text-align: center;
64
64
  }
65
65
 
66
+ #loader {
67
+ display: none;
68
+ text-align: center;
69
+ padding: 1rem;
70
+ color: var(--teal);
71
+ font-weight: bold;
72
+ }
73
+
74
+ #scroll-anchor {
75
+ height: 20px;
76
+ }
77
+
66
78
  /* buttons */
67
79
  button {
68
80
  display: flex;
@@ -316,7 +328,27 @@ nav ul li:hover {
316
328
  overflow-y: scroll;
317
329
  z-index: 1;
318
330
  opacity: 0.97;
319
- box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
331
+ box-shadow:
332
+ 0 4px 8px 0 rgba(0, 0, 0, 0.2),
333
+ 0 6px 20px 0 rgba(0, 0, 0, 0.19);
334
+ }
335
+
336
+ #drag-handle {
337
+ margin: -2rem -2rem 1rem -2rem;
338
+ height: 1.5rem;
339
+ background-color: #f1f1f1;
340
+ border-bottom: 1px solid #ddd;
341
+ cursor: grab;
342
+ display: flex;
343
+ align-items: center;
344
+ justify-content: center;
345
+ }
346
+
347
+ #drag-handle .handle-indicator {
348
+ width: 50px;
349
+ height: 5px;
350
+ background-color: #ccc;
351
+ border-radius: 5px;
320
352
  }
321
353
 
322
354
  /* JSON viewer */
@@ -347,3 +379,146 @@ nav ul li:hover {
347
379
  box-shadow: none;
348
380
  }
349
381
  }
382
+
383
+ @media (max-width: 768px) {
384
+ /* Allow header to flow naturally */
385
+ header {
386
+ position: relative !important;
387
+ padding-bottom: 1rem;
388
+ height: auto;
389
+ }
390
+
391
+ /* Reset logo from previous media query */
392
+ .logo {
393
+ width: 100%;
394
+ margin: 0 !important;
395
+ justify-content: center;
396
+ }
397
+
398
+ /* Show title again for branding */
399
+ .logo h2 {
400
+ display: block !important;
401
+ margin-left: 0.5rem;
402
+ font-size: 1.2rem;
403
+ }
404
+
405
+ /* Wrap controls */
406
+ .controls {
407
+ flex-wrap: wrap;
408
+ padding: 0 1rem;
409
+ }
410
+
411
+ /* Nav */
412
+ nav {
413
+ width: 100%;
414
+ order: 2;
415
+ overflow-x: auto;
416
+ margin-bottom: 1rem;
417
+ -webkit-overflow-scrolling: touch;
418
+ margin-top: 1rem;
419
+ height: 2rem;
420
+ }
421
+
422
+ nav ul {
423
+ width: max-content;
424
+ padding: 0 0.5rem;
425
+ gap: 1.5rem;
426
+ justify-content: flex-start;
427
+ }
428
+
429
+ /* Search */
430
+ #search {
431
+ order: 3;
432
+ width: 100%;
433
+ margin-bottom: 1rem;
434
+ box-sizing: border-box;
435
+ }
436
+
437
+ /* Buttons */
438
+ #refresh,
439
+ #freeze {
440
+ order: 4;
441
+ width: 48%;
442
+ }
443
+
444
+ #refresh button,
445
+ #freeze button {
446
+ width: 100%;
447
+ justify-content: center;
448
+ padding: 10px;
449
+ margin-bottom: 1rem;
450
+ }
451
+
452
+ /* Logs Container */
453
+ #logs {
454
+ margin-top: 0;
455
+ }
456
+
457
+ /* Hide Table Header */
458
+ .table-header {
459
+ display: none;
460
+ }
461
+
462
+ /* Row Layout */
463
+ .row {
464
+ flex-wrap: wrap;
465
+ height: auto;
466
+ padding: 0.8rem 0;
467
+ border-bottom: 1px solid var(--light);
468
+ align-items: flex-start;
469
+ }
470
+
471
+ .row:hover {
472
+ border-left: 2px solid transparent;
473
+ background-color: transparent;
474
+ }
475
+
476
+ .row:active {
477
+ background-color: #f5f5f5;
478
+ }
479
+
480
+ /* Type */
481
+ .row > :first-child {
482
+ flex: 0 0 3.5rem;
483
+ font-size: 0.75rem;
484
+ text-transform: uppercase;
485
+ font-weight: bold;
486
+ padding-left: 0.5rem;
487
+ text-align: left;
488
+ }
489
+
490
+ /* Info */
491
+ .row > :nth-child(2) {
492
+ flex: 1 1 auto;
493
+ max-width: calc(100% - 6.5rem);
494
+ padding: 0 0.5rem;
495
+ }
496
+
497
+ .log-info {
498
+ white-space: normal;
499
+ display: -webkit-box;
500
+ line-clamp: 2;
501
+ -webkit-line-clamp: 2;
502
+ -webkit-box-orient: vertical;
503
+ max-height: 2.8rem;
504
+ margin-bottom: 0.2rem;
505
+ }
506
+
507
+ /* Hide Context */
508
+ .row > :nth-child(3) {
509
+ display: none;
510
+ }
511
+
512
+ /* Count */
513
+ .row > :last-child {
514
+ flex: 0 0 2rem;
515
+ font-size: 0.8rem;
516
+ color: #aaa;
517
+ text-align: right;
518
+ padding-right: 0.5rem;
519
+ }
520
+
521
+ #drag-handle {
522
+ display: none;
523
+ }
524
+ }
package/src/log.module.ts CHANGED
@@ -27,10 +27,8 @@ export class LogModule {
27
27
  app: any,
28
28
  options?: LogModuleOptions
29
29
  ): Promise<void> {
30
- app.resolve(LogService);
31
-
32
- const logService: LogService = await app.resolve(LogService);
33
- const wsService: WsService = await app.resolve(WsService);
30
+ const logService: LogService = await app.get(LogService);
31
+ const wsService: WsService = await app.get(WsService);
34
32
  const logAccessGuard: LogAccessGuard = await app.get(LogAccessGuard);
35
33
 
36
34
  if (options) {
@@ -66,11 +64,17 @@ export class LogModule {
66
64
  }
67
65
  );
68
66
 
69
- // get all logs endpoint
67
+ // get logs endpoint
70
68
  httpAdapter.get(join(options.path, 'api'), async (req: any, res: any) => {
71
69
  logAccessGuard.canActivate(req);
72
70
 
73
- res.json(await logService.getAll());
71
+ const params = querystring.parse(req.url.split('?')[1]);
72
+ const page = params.page ? parseInt(params.page.toString()) : 1;
73
+ const limit = params.limit ? parseInt(params.limit.toString()) : 50;
74
+ const search = params.search ? params.search.toString() : '';
75
+ const types = params.types ? params.types.toString().split(',') : [];
76
+
77
+ res.json(await logService.getAll(page, limit, search, types));
74
78
  });
75
79
 
76
80
  // delete log endpoint