@viplance/nestjs-logger 0.3.7 → 0.4.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 +14 -1
- package/dist/entities/log.entity.d.ts +2 -2
- package/dist/entities/log.entity.js +3 -2
- package/dist/entities/log.entity.js.map +1 -1
- package/dist/log.module.js +28 -2
- package/dist/log.module.js.map +1 -1
- package/dist/services/log.service.d.ts +10 -4
- package/dist/services/log.service.js +81 -14
- package/dist/services/log.service.js.map +1 -1
- package/dist/services/ws.service.d.ts +18 -0
- package/dist/services/ws.service.js +106 -0
- package/dist/services/ws.service.js.map +1 -0
- package/dist/types/options.type.d.ts +6 -0
- package/dist/utils/entity2table.d.ts +2 -0
- package/dist/utils/entity2table.js +35 -0
- package/dist/utils/entity2table.js.map +1 -0
- package/package.json +11 -4
- package/public/index.html +7 -2
- package/public/scripts/common.js +70 -32
- package/public/scripts/details-popup.js +25 -6
- package/public/scripts/ws.js +83 -0
- package/public/styles/index.css +5 -0
- package/src/entities/log.entity.ts +7 -3
- package/src/log.module.ts +35 -2
- package/src/services/log.service.ts +102 -11
- package/src/services/ws.d.ts +1 -0
- package/src/services/ws.service.ts +110 -0
- package/src/types/options.type.ts +6 -0
- package/src/utils/entity2table.ts +33 -0
package/public/scripts/common.js
CHANGED
|
@@ -12,9 +12,7 @@ const logTypes = Object.keys(selectedLogTypes).filter((key) => key !== `all`);
|
|
|
12
12
|
let logs = [];
|
|
13
13
|
let text = "";
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
getLogs();
|
|
17
|
-
});
|
|
15
|
+
connectWebSocket();
|
|
18
16
|
|
|
19
17
|
document.addEventListener(`click`, (e) => {
|
|
20
18
|
const target = e.target;
|
|
@@ -69,7 +67,14 @@ document.addEventListener(`click`, (e) => {
|
|
|
69
67
|
(target.classList?.contains(`row`) && target.id);
|
|
70
68
|
|
|
71
69
|
if (logId) {
|
|
72
|
-
|
|
70
|
+
let id = logId;
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
if (Number(id) > 0) id = Number(id); // SQL DB numeric index
|
|
74
|
+
} catch (e) {}
|
|
75
|
+
|
|
76
|
+
const log = logs.find((log) => log._id === id);
|
|
77
|
+
|
|
73
78
|
showLogDetails(log);
|
|
74
79
|
}
|
|
75
80
|
});
|
|
@@ -133,10 +138,10 @@ function getLogHtmlElement(log) {
|
|
|
133
138
|
</div>`;
|
|
134
139
|
}
|
|
135
140
|
|
|
136
|
-
function renderLogs() {
|
|
141
|
+
function renderLogs(logList = logs) {
|
|
137
142
|
let html = "";
|
|
138
143
|
|
|
139
|
-
|
|
144
|
+
logList
|
|
140
145
|
.filter((log) => {
|
|
141
146
|
return selectedLogTypes["all"] || selectedLogTypes[log.type];
|
|
142
147
|
})
|
|
@@ -158,50 +163,83 @@ function renderLogs() {
|
|
|
158
163
|
document.getElementById("logs").innerHTML = html;
|
|
159
164
|
}
|
|
160
165
|
|
|
166
|
+
async function checkElementsVisibility(logList = logs) {
|
|
167
|
+
if (logList.length === 0) {
|
|
168
|
+
document.getElementById("no-logs").style.display = "block";
|
|
169
|
+
document.getElementById("search").style.display = "none";
|
|
170
|
+
document.querySelector(".table-header").style.display = "none";
|
|
171
|
+
document.querySelector("nav").style.display = "none";
|
|
172
|
+
} else {
|
|
173
|
+
document.getElementById("no-logs").style.display = "none";
|
|
174
|
+
document.getElementById("search").style.display = "inline-block";
|
|
175
|
+
document.querySelector(".table-header").style.display = "flex";
|
|
176
|
+
document.querySelector("nav").style.display = "flex";
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
161
180
|
async function getLogs() {
|
|
162
181
|
const { origin, pathname, search } = window.location;
|
|
182
|
+
const searchParams = new URLSearchParams(search);
|
|
183
|
+
const key = searchParams.get("key");
|
|
184
|
+
|
|
185
|
+
if (!!socket) {
|
|
186
|
+
socket.send(
|
|
187
|
+
JSON.stringify({
|
|
188
|
+
action: "getLogs",
|
|
189
|
+
key,
|
|
190
|
+
})
|
|
191
|
+
);
|
|
192
|
+
} else {
|
|
193
|
+
const res = await fetch(`${origin}${pathname}api${search}`);
|
|
163
194
|
|
|
164
|
-
|
|
195
|
+
if (res.ok) {
|
|
196
|
+
logs = await res.json();
|
|
165
197
|
|
|
166
|
-
|
|
167
|
-
logs = await res.json();
|
|
198
|
+
checkElementsVisibility();
|
|
168
199
|
|
|
169
|
-
|
|
170
|
-
document.getElementById("no-logs").style.display = "block";
|
|
171
|
-
document.querySelector(".table-header").style.display = "none";
|
|
172
|
-
document.querySelector("nav").style.display = "none";
|
|
200
|
+
renderLogs();
|
|
173
201
|
} else {
|
|
174
|
-
|
|
175
|
-
document.querySelector(".table-header").style.display = "flex";
|
|
176
|
-
document.querySelector("nav").style.display = "flex";
|
|
202
|
+
alert("An error occurred while fetching logs.");
|
|
177
203
|
}
|
|
178
|
-
|
|
179
|
-
renderLogs();
|
|
180
|
-
} else {
|
|
181
|
-
alert("An error occurred while fetching logs.");
|
|
182
204
|
}
|
|
183
205
|
}
|
|
184
206
|
|
|
185
|
-
async function deleteLog(
|
|
207
|
+
async function deleteLog(_id) {
|
|
186
208
|
if (!confirm("Are you sure? It can't be undone.")) return;
|
|
187
209
|
|
|
188
210
|
const { origin, pathname, search: searchParams } = window.location;
|
|
189
211
|
|
|
190
212
|
const searchParamsWithId = new URLSearchParams(searchParams);
|
|
191
|
-
searchParamsWithId.
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
213
|
+
const key = searchParamsWithId.get("key");
|
|
214
|
+
|
|
215
|
+
if (!!socket) {
|
|
216
|
+
socket.send(
|
|
217
|
+
JSON.stringify({
|
|
218
|
+
action: "delete",
|
|
219
|
+
key,
|
|
220
|
+
data: {
|
|
221
|
+
_id,
|
|
222
|
+
},
|
|
223
|
+
})
|
|
224
|
+
);
|
|
201
225
|
closePopup();
|
|
202
226
|
getLogs();
|
|
203
227
|
} else {
|
|
204
|
-
|
|
228
|
+
searchParamsWithId.set("id", _id);
|
|
229
|
+
|
|
230
|
+
const res = await fetch(
|
|
231
|
+
`${origin}${pathname}api?${searchParamsWithId.toString()}`,
|
|
232
|
+
{
|
|
233
|
+
method: "DELETE",
|
|
234
|
+
}
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
if (res.ok) {
|
|
238
|
+
closePopup();
|
|
239
|
+
getLogs();
|
|
240
|
+
} else {
|
|
241
|
+
alert("An error occurred while deleting log.");
|
|
242
|
+
}
|
|
205
243
|
}
|
|
206
244
|
}
|
|
207
245
|
|
|
@@ -1,11 +1,22 @@
|
|
|
1
1
|
function showLogDetails(log) {
|
|
2
2
|
const popup = document.getElementById(`popup`);
|
|
3
|
+
const context = getObject(log.context);
|
|
4
|
+
const breadcrumbs = getObject(log.breadcrumbs);
|
|
5
|
+
|
|
6
|
+
const timeInfo =
|
|
7
|
+
log.updatedAt === log.createdAt
|
|
8
|
+
? getDate(log.updatedAt)
|
|
9
|
+
: `Updated: ${getDate(log.updatedAt)}. First seen: ${getDate(
|
|
10
|
+
log.createdAt
|
|
11
|
+
)}`;
|
|
3
12
|
|
|
4
13
|
popup.innerHTML = `
|
|
5
14
|
<div class="content center">
|
|
6
15
|
<div class="container">
|
|
7
|
-
<h2 class="popup-title ${log.type}">${log.type}: ${log.message}
|
|
8
|
-
|
|
16
|
+
<h2 class="popup-title ${log.type}">${log.type}: ${log.message} (${
|
|
17
|
+
log.count
|
|
18
|
+
})</h2>
|
|
19
|
+
<div class="mt-05">${timeInfo}</div>
|
|
9
20
|
${
|
|
10
21
|
log.trace
|
|
11
22
|
? `
|
|
@@ -15,18 +26,18 @@ function showLogDetails(log) {
|
|
|
15
26
|
: ""
|
|
16
27
|
}
|
|
17
28
|
${
|
|
18
|
-
|
|
29
|
+
context
|
|
19
30
|
? `
|
|
20
31
|
<h3 class="mt-15">Context</h3>
|
|
21
|
-
<p>${jsonViewer(
|
|
32
|
+
<p>${jsonViewer(context)}</p>
|
|
22
33
|
`
|
|
23
34
|
: ""
|
|
24
35
|
}
|
|
25
36
|
${
|
|
26
|
-
|
|
37
|
+
breadcrumbs && breadcrumbs.length > 0
|
|
27
38
|
? `
|
|
28
39
|
<h3 class="mt-15">Breadcrumbs</h3>
|
|
29
|
-
<p>${jsonViewer(
|
|
40
|
+
<p>${jsonViewer(breadcrumbs)}</p>
|
|
30
41
|
`
|
|
31
42
|
: ""
|
|
32
43
|
}
|
|
@@ -42,6 +53,14 @@ function showLogDetails(log) {
|
|
|
42
53
|
popup.style.display = "block";
|
|
43
54
|
}
|
|
44
55
|
|
|
56
|
+
function getObject(context) {
|
|
57
|
+
if (typeof context === "string") {
|
|
58
|
+
return JSON.parse(context);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return context;
|
|
62
|
+
}
|
|
63
|
+
|
|
45
64
|
function getTrace(trace) {
|
|
46
65
|
return trace.replace(new RegExp(String.fromCharCode(10), "g"), "<br />");
|
|
47
66
|
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// WebSocket connection
|
|
2
|
+
let socket;
|
|
3
|
+
let frozen = false;
|
|
4
|
+
|
|
5
|
+
async function connectWebSocket() {
|
|
6
|
+
const { hostname, origin, pathname, search } = window.location;
|
|
7
|
+
|
|
8
|
+
const res = await fetch(`${origin}${pathname}settings${search}`);
|
|
9
|
+
|
|
10
|
+
if (!res.ok) {
|
|
11
|
+
alert('An error occurred while fetching settings.');
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const settings = await res.json();
|
|
16
|
+
|
|
17
|
+
if (!settings.websocket) {
|
|
18
|
+
getLogs();
|
|
19
|
+
document.getElementById('refresh').style.display = 'block';
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
document.getElementById('freeze').style.display = 'block';
|
|
23
|
+
|
|
24
|
+
if (!settings.websocket?.port) {
|
|
25
|
+
alert('WebSocket port is not configured.');
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const socketUrl = `ws://${hostname}:${settings.websocket.port}`;
|
|
30
|
+
socket = new WebSocket(socketUrl, 'json');
|
|
31
|
+
|
|
32
|
+
socket.onerror = (error) => {
|
|
33
|
+
console.error(error);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
socket.onopen = (event) => {
|
|
37
|
+
getLogs();
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
socket.onclose = (event) => {
|
|
41
|
+
console.log(event);
|
|
42
|
+
setTimeout(connectWebSocket, 5000);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
socket.onmessage = (event) => {
|
|
46
|
+
const data = JSON.parse(event.data.toString());
|
|
47
|
+
|
|
48
|
+
if (data['action'] && !frozen) {
|
|
49
|
+
switch (data['action']) {
|
|
50
|
+
case 'list':
|
|
51
|
+
logs = data['data'];
|
|
52
|
+
checkElementsVisibility(logs);
|
|
53
|
+
renderLogs(logs);
|
|
54
|
+
break;
|
|
55
|
+
case 'insert':
|
|
56
|
+
getLogs();
|
|
57
|
+
break;
|
|
58
|
+
case 'update':
|
|
59
|
+
getLogs();
|
|
60
|
+
return;
|
|
61
|
+
case 'delete':
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
function sendMessage(message) {
|
|
68
|
+
socket.send(JSON.stringify(message));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function toggleFreeze() {
|
|
72
|
+
frozen = !frozen;
|
|
73
|
+
const button = document.querySelector('#freeze button');
|
|
74
|
+
button.innerHTML = frozen ? 'Frozen' : 'Freeze';
|
|
75
|
+
|
|
76
|
+
if (frozen) {
|
|
77
|
+
button.classList.remove('white');
|
|
78
|
+
button.classList.add('light');
|
|
79
|
+
} else {
|
|
80
|
+
button.classList.remove('light');
|
|
81
|
+
button.classList.add('white');
|
|
82
|
+
}
|
|
83
|
+
}
|
package/public/styles/index.css
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
|
-
import { EntitySchema } from "typeorm";
|
|
1
|
+
import { DataSourceOptions, EntitySchema } from "typeorm";
|
|
2
2
|
|
|
3
|
-
export function createLogEntity(
|
|
3
|
+
export function createLogEntity(
|
|
4
|
+
name: string,
|
|
5
|
+
dbType: DataSourceOptions["type"] | "memory"
|
|
6
|
+
) {
|
|
4
7
|
return new EntitySchema({
|
|
5
8
|
name,
|
|
6
9
|
columns: {
|
|
7
10
|
_id: {
|
|
8
|
-
type: String,
|
|
11
|
+
type: dbType === "mongodb" ? String : Number,
|
|
9
12
|
objectId: true,
|
|
10
13
|
primary: true,
|
|
14
|
+
generated: true,
|
|
11
15
|
},
|
|
12
16
|
type: { type: String },
|
|
13
17
|
message: { type: String },
|
package/src/log.module.ts
CHANGED
|
@@ -8,12 +8,19 @@ import querystring from "node:querystring";
|
|
|
8
8
|
import { ApplicationConfig } from "@nestjs/core";
|
|
9
9
|
import { join } from "node:path";
|
|
10
10
|
import { LogAccessGuard } from "./guards/access.guard";
|
|
11
|
+
import { WsService } from "./services/ws.service";
|
|
11
12
|
|
|
12
13
|
@Global()
|
|
13
14
|
@Module({
|
|
14
15
|
imports: [TypeOrmModule],
|
|
15
|
-
providers: [
|
|
16
|
-
|
|
16
|
+
providers: [
|
|
17
|
+
ApplicationConfig,
|
|
18
|
+
LogAccessGuard,
|
|
19
|
+
LogService,
|
|
20
|
+
MemoryDbService,
|
|
21
|
+
WsService,
|
|
22
|
+
],
|
|
23
|
+
exports: [TypeOrmModule, LogService, MemoryDbService, WsService],
|
|
17
24
|
})
|
|
18
25
|
export class LogModule {
|
|
19
26
|
public static async init(
|
|
@@ -23,6 +30,7 @@ export class LogModule {
|
|
|
23
30
|
app.resolve(LogService);
|
|
24
31
|
|
|
25
32
|
const logService: LogService = await app.resolve(LogService);
|
|
33
|
+
const wsService: WsService = await app.resolve(WsService);
|
|
26
34
|
const logAccessGuard: LogAccessGuard = await app.get(LogAccessGuard);
|
|
27
35
|
|
|
28
36
|
if (options) {
|
|
@@ -38,6 +46,26 @@ export class LogModule {
|
|
|
38
46
|
|
|
39
47
|
const httpAdapter = app.getHttpAdapter();
|
|
40
48
|
|
|
49
|
+
// frontend settings endpoint
|
|
50
|
+
httpAdapter.get(
|
|
51
|
+
join(options.path, "settings"),
|
|
52
|
+
async (req: any, res: any) => {
|
|
53
|
+
logAccessGuard.canActivate(req);
|
|
54
|
+
|
|
55
|
+
const result: { [key: string]: any } = {};
|
|
56
|
+
|
|
57
|
+
if (options?.websocket) {
|
|
58
|
+
result.websocket = {
|
|
59
|
+
namespace: options.websocket?.namespace,
|
|
60
|
+
port: options.websocket?.port,
|
|
61
|
+
host: options.websocket?.host || req.headers?.host.split(":")[0],
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
res.json(result);
|
|
66
|
+
}
|
|
67
|
+
);
|
|
68
|
+
|
|
41
69
|
// get all logs endpoint
|
|
42
70
|
httpAdapter.get(join(options.path, "api"), async (req: any, res: any) => {
|
|
43
71
|
logAccessGuard.canActivate(req);
|
|
@@ -60,6 +88,11 @@ export class LogModule {
|
|
|
60
88
|
res.json(await logService.delete(params.id.toString()));
|
|
61
89
|
}
|
|
62
90
|
);
|
|
91
|
+
|
|
92
|
+
// set up WebSocket connection
|
|
93
|
+
if (options?.websocket) {
|
|
94
|
+
wsService.setupConnection(options.websocket, options.key);
|
|
95
|
+
}
|
|
63
96
|
}
|
|
64
97
|
|
|
65
98
|
if (options?.database) {
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
Injectable,
|
|
3
|
+
LoggerService,
|
|
4
|
+
OnApplicationShutdown,
|
|
5
|
+
Scope,
|
|
6
|
+
} from "@nestjs/common";
|
|
2
7
|
import { MemoryDbService } from "./memory-db.service";
|
|
3
8
|
import { defaultTable } from "../defaults";
|
|
4
9
|
import { Context, LogModuleOptions, LogType } from "../types";
|
|
@@ -11,24 +16,40 @@ import {
|
|
|
11
16
|
import { createLogEntity } from "../entities/log.entity";
|
|
12
17
|
import { ExecutionContextHost } from "@nestjs/core/helpers/execution-context-host";
|
|
13
18
|
import { setInterval } from "timers";
|
|
19
|
+
import { entity2table } from "../utils/entity2table";
|
|
20
|
+
import { WsService } from "./ws.service";
|
|
21
|
+
import { Subscription } from "rxjs";
|
|
14
22
|
|
|
15
23
|
@Injectable({ scope: Scope.TRANSIENT })
|
|
16
|
-
export class LogService implements LoggerService {
|
|
24
|
+
export class LogService implements LoggerService, OnApplicationShutdown {
|
|
17
25
|
static connection: DataSource;
|
|
18
26
|
static options: LogModuleOptions;
|
|
19
|
-
static Log: EntitySchema = createLogEntity(defaultTable);
|
|
27
|
+
static Log: EntitySchema = createLogEntity(defaultTable, "memory");
|
|
20
28
|
static timer: ReturnType<typeof setInterval>;
|
|
29
|
+
static subscription: Subscription;
|
|
21
30
|
|
|
22
31
|
breadcrumbs: any[] = [];
|
|
23
32
|
|
|
24
|
-
constructor(
|
|
33
|
+
constructor(
|
|
34
|
+
private readonly memoryDbService: MemoryDbService,
|
|
35
|
+
private readonly wsService: WsService
|
|
36
|
+
) {}
|
|
37
|
+
|
|
38
|
+
onApplicationShutdown() {
|
|
39
|
+
if (LogService.timer) {
|
|
40
|
+
clearInterval(LogService.timer);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
25
43
|
|
|
26
44
|
async connectDb(options: LogModuleOptions): Promise<DataSource> {
|
|
27
45
|
LogService.Log = createLogEntity(
|
|
28
|
-
options.database?.collection || options.database?.table || defaultTable
|
|
46
|
+
options.database?.collection || options.database?.table || defaultTable,
|
|
47
|
+
options.database?.type || "mongodb"
|
|
29
48
|
);
|
|
30
49
|
|
|
31
|
-
|
|
50
|
+
if (!LogService.options) {
|
|
51
|
+
this.setOptions(options);
|
|
52
|
+
}
|
|
32
53
|
|
|
33
54
|
const dataSourceOptions = {
|
|
34
55
|
type: options.database?.type,
|
|
@@ -39,9 +60,22 @@ export class LogService implements LoggerService {
|
|
|
39
60
|
} as DataSourceOptions;
|
|
40
61
|
|
|
41
62
|
LogService.connection = new DataSource(dataSourceOptions);
|
|
42
|
-
|
|
43
63
|
await LogService.connection.initialize();
|
|
44
64
|
|
|
65
|
+
if (dataSourceOptions.type !== "mongodb") {
|
|
66
|
+
const queryRunner = LogService.connection.createQueryRunner();
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
await queryRunner.connect();
|
|
70
|
+
|
|
71
|
+
const table = entity2table(LogService.Log);
|
|
72
|
+
|
|
73
|
+
await queryRunner.createTable(table, true);
|
|
74
|
+
} finally {
|
|
75
|
+
await queryRunner.release();
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
45
79
|
if (LogService.timer) {
|
|
46
80
|
clearInterval(LogService.timer);
|
|
47
81
|
}
|
|
@@ -53,6 +87,24 @@ export class LogService implements LoggerService {
|
|
|
53
87
|
|
|
54
88
|
setOptions(options: LogModuleOptions) {
|
|
55
89
|
LogService.options = options;
|
|
90
|
+
|
|
91
|
+
if (options.websocket && !LogService.subscription) {
|
|
92
|
+
LogService.subscription = this.wsService.onMessage.subscribe(
|
|
93
|
+
async (message) => {
|
|
94
|
+
switch (message.action) {
|
|
95
|
+
case "getLogs":
|
|
96
|
+
this.wsService.sendMessage({
|
|
97
|
+
action: "list",
|
|
98
|
+
data: await this.getAll(),
|
|
99
|
+
});
|
|
100
|
+
break;
|
|
101
|
+
case "delete":
|
|
102
|
+
this.delete(message.data._id);
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
);
|
|
107
|
+
}
|
|
56
108
|
}
|
|
57
109
|
|
|
58
110
|
addBreadcrumb(breadcrumb: any) {
|
|
@@ -121,8 +173,12 @@ export class LogService implements LoggerService {
|
|
|
121
173
|
});
|
|
122
174
|
}
|
|
123
175
|
|
|
124
|
-
async delete(
|
|
125
|
-
|
|
176
|
+
async delete(_id: string) {
|
|
177
|
+
this.wsService.sendMessage({
|
|
178
|
+
action: "delete",
|
|
179
|
+
data: { _id },
|
|
180
|
+
});
|
|
181
|
+
return this.getConnection().delete(LogService.Log, _id);
|
|
126
182
|
}
|
|
127
183
|
|
|
128
184
|
private async smartInsert(data: {
|
|
@@ -146,16 +202,31 @@ export class LogService implements LoggerService {
|
|
|
146
202
|
const context = data.context ? this.parseContext(data.context) : undefined;
|
|
147
203
|
|
|
148
204
|
if (log) {
|
|
149
|
-
|
|
205
|
+
const updatedLog = {
|
|
150
206
|
context,
|
|
151
207
|
trace: data.trace,
|
|
152
208
|
breadcrumbs: this.breadcrumbs,
|
|
153
209
|
count: log.count + 1,
|
|
154
210
|
updatedAt: currentDate,
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
this.wsService.sendMessage({
|
|
214
|
+
action: "update",
|
|
215
|
+
data: { ...log, ...updatedLog },
|
|
155
216
|
});
|
|
217
|
+
|
|
218
|
+
await connection.update(LogService.Log, log["_id"], {
|
|
219
|
+
context,
|
|
220
|
+
trace: data.trace,
|
|
221
|
+
breadcrumbs: this.breadcrumbs,
|
|
222
|
+
count: log.count + 1,
|
|
223
|
+
updatedAt: currentDate,
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
return { ...log, ...updatedLog };
|
|
156
227
|
}
|
|
157
228
|
|
|
158
|
-
|
|
229
|
+
const insertedLog = {
|
|
159
230
|
type: data.type,
|
|
160
231
|
message: data.message,
|
|
161
232
|
context,
|
|
@@ -164,7 +235,27 @@ export class LogService implements LoggerService {
|
|
|
164
235
|
count: 1,
|
|
165
236
|
createdAt: currentDate,
|
|
166
237
|
updatedAt: currentDate,
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
const res = await connection.insert(LogService.Log, insertedLog);
|
|
241
|
+
const _id = this.getNewObjectId(res);
|
|
242
|
+
|
|
243
|
+
this.wsService.sendMessage({
|
|
244
|
+
action: "insert",
|
|
245
|
+
data: { _id, ...insertedLog },
|
|
167
246
|
});
|
|
247
|
+
|
|
248
|
+
return { _id, ...insertedLog };
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
private getNewObjectId(result: any): string | number {
|
|
252
|
+
if (result.identifiers) {
|
|
253
|
+
return result.identifiers[0]._id;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
console.log(result);
|
|
257
|
+
|
|
258
|
+
return result._id;
|
|
168
259
|
}
|
|
169
260
|
|
|
170
261
|
private getConnection(): EntityManager {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
declare module "ws";
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { Injectable } from "@nestjs/common";
|
|
2
|
+
import WebSocket, { WebSocketServer } from "ws";
|
|
3
|
+
import { LogModuleOptions } from "../types";
|
|
4
|
+
import { Subject } from "rxjs";
|
|
5
|
+
|
|
6
|
+
@Injectable()
|
|
7
|
+
export class WsService {
|
|
8
|
+
public onMessage: Subject<any> = new Subject();
|
|
9
|
+
private ws: WebSocket | null = null;
|
|
10
|
+
private connected: boolean = false;
|
|
11
|
+
private connectionTimeout: number = 500;
|
|
12
|
+
private options: LogModuleOptions["websocket"] = {
|
|
13
|
+
port: 8080,
|
|
14
|
+
host: "localhost",
|
|
15
|
+
};
|
|
16
|
+
private key: string = "";
|
|
17
|
+
|
|
18
|
+
setupConnection(options: LogModuleOptions["websocket"], key = "") {
|
|
19
|
+
this.options = {
|
|
20
|
+
...this.options,
|
|
21
|
+
...options,
|
|
22
|
+
};
|
|
23
|
+
this.key = key;
|
|
24
|
+
|
|
25
|
+
// Set up Web Socket server
|
|
26
|
+
if (this.ws) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const wsServer = new WebSocketServer({
|
|
31
|
+
retryCount: 1,
|
|
32
|
+
reconnectInterval: 1,
|
|
33
|
+
handshakeTimeout: this.connectionTimeout,
|
|
34
|
+
port: this.options?.port,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
console.log(
|
|
38
|
+
`Logs WebSocket server is listening on port ${this.options.port}`
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
wsServer.on("error", this.handleError);
|
|
42
|
+
wsServer.on("open", () => this.handleOpenConnection());
|
|
43
|
+
wsServer.on("ping", () => this.ping(this.ws));
|
|
44
|
+
wsServer.on("close", () => this.closeConnection(this.ws));
|
|
45
|
+
wsServer.on("message", this.handleMessage);
|
|
46
|
+
wsServer.on("connection", (connection: WebSocket) => {
|
|
47
|
+
this.ws = connection;
|
|
48
|
+
connection.onmessage = this.handleMessage;
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
sendMessage(message: any) {
|
|
53
|
+
this.ws?.send(JSON.stringify(message));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
private handleError = () => {
|
|
57
|
+
const serverUrl = this.getServerUrl();
|
|
58
|
+
console.error(`Server ${serverUrl} is not available.`);
|
|
59
|
+
|
|
60
|
+
setTimeout(this.setupConnection, this.connectionTimeout);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
private closeConnection = (connection: any) => {
|
|
64
|
+
clearTimeout(connection.pingTimeout);
|
|
65
|
+
|
|
66
|
+
if (this.connected) {
|
|
67
|
+
console.log("Connection has been closed by server.");
|
|
68
|
+
this.connected = false;
|
|
69
|
+
this.handleError();
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
private ping = (connection: any) => {
|
|
74
|
+
console.log("Ping remote server.");
|
|
75
|
+
clearTimeout(connection.pingTimeout);
|
|
76
|
+
|
|
77
|
+
connection.pingTimeout = setTimeout(() => {
|
|
78
|
+
connection.terminate();
|
|
79
|
+
}, 30000 + this.connectionTimeout);
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
private handleMessage = (message: any) => {
|
|
83
|
+
try {
|
|
84
|
+
const data = JSON.parse((message.data || message).toString());
|
|
85
|
+
|
|
86
|
+
if (this.key !== "" && data.key !== this.key) {
|
|
87
|
+
throw new Error("WebSocket unauthorized");
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (this.options)
|
|
91
|
+
if (data.action) {
|
|
92
|
+
this.onMessage.next(data);
|
|
93
|
+
}
|
|
94
|
+
} catch (err) {
|
|
95
|
+
console.error(err);
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
private getServerUrl = (): string => {
|
|
100
|
+
return `${this.options?.secure ? "wss" : "ws"}://${this.options?.host}:${
|
|
101
|
+
this.options?.port
|
|
102
|
+
}`;
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
private handleOpenConnection = async () => {
|
|
106
|
+
this.connected = true;
|
|
107
|
+
const serverUrl = this.getServerUrl();
|
|
108
|
+
console.log(`${serverUrl} has been connected.`);
|
|
109
|
+
};
|
|
110
|
+
}
|