@serhii.mazur/directus-gu-logs 1.0.6 → 1.0.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.
- package/README.MD +88 -20
- package/dist/index.js +36 -14
- package/package.json +1 -1
- package/src/index.ts +45 -14
package/README.MD
CHANGED
|
@@ -8,31 +8,32 @@ A lightweight utility class for logging errors, creating activity logs, and send
|
|
|
8
8
|
|
|
9
9
|
You need to create a collection named `logs` with the following fields:
|
|
10
10
|
|
|
11
|
-
| Field Name | Type
|
|
12
|
-
| --------------- |
|
|
13
|
-
| `collection` | string
|
|
14
|
-
| `date_created` | datetime
|
|
15
|
-
| `extension` | string
|
|
16
|
-
| `function_name` | string
|
|
17
|
-
| `error` | code
|
|
11
|
+
| Field Name | Type | Description |
|
|
12
|
+
| --------------- | --------- | ---------------------------- |
|
|
13
|
+
| `collection` | string | Name of the collection |
|
|
14
|
+
| `date_created` | datetime | Timestamp of the log |
|
|
15
|
+
| `extension` | string | Extension identifier |
|
|
16
|
+
| `function_name` | string | Name of the function |
|
|
17
|
+
| `error` | text/code | Error message or stack trace |
|
|
18
18
|
|
|
19
19
|
### 2. Global Collection (global)
|
|
20
20
|
|
|
21
21
|
Add the following field to the `global` collection:
|
|
22
22
|
|
|
23
|
-
| Field Name
|
|
24
|
-
|
|
|
25
|
-
| `
|
|
23
|
+
| Field Name | Type | Description |
|
|
24
|
+
| ------------------------ | ------------------------------- | ---------------------------------- |
|
|
25
|
+
| `error_notice_recipient` | m2m → directus_users (junction) | Recipients for error notifications |
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
All linked users from this field will receive internal notifications when errors occur.
|
|
28
28
|
|
|
29
29
|
## 🚀 Features
|
|
30
30
|
|
|
31
|
-
-
|
|
32
|
-
|
|
33
|
-
-
|
|
34
|
-
|
|
35
|
-
-
|
|
31
|
+
- Save structured error logs to a collection
|
|
32
|
+
- Create Directus activity records
|
|
33
|
+
- Send internal Directus notifications
|
|
34
|
+
- Automatic recipient resolution from global settings
|
|
35
|
+
- Support for manual recipient override
|
|
36
|
+
- Includes project metadata in notifications (environment, URL, timestamp)
|
|
36
37
|
|
|
37
38
|
## 📦 Installation
|
|
38
39
|
|
|
@@ -45,10 +46,77 @@ npm i @serhii.mazur/directus-gu-logs
|
|
|
45
46
|
```ts
|
|
46
47
|
import { Logs } from "@serhii.mazur/directus-gu-logs";
|
|
47
48
|
|
|
48
|
-
const logs = new Logs(context, "my-extension"
|
|
49
|
+
const logs = new Logs(context, "my-extension");
|
|
50
|
+
|
|
51
|
+
// Save error log
|
|
52
|
+
await logs.printLogs("myFunction", "Something went wrong");
|
|
49
53
|
|
|
50
|
-
|
|
51
|
-
await logs.createActivity("create", "
|
|
54
|
+
// Create activity record
|
|
55
|
+
await logs.createActivity("create", "collection_name", "item_id");
|
|
56
|
+
|
|
57
|
+
// Send notification (auto recipients from global settings)
|
|
52
58
|
await logs.createNotification("An error occurred");
|
|
53
|
-
|
|
59
|
+
|
|
60
|
+
// Send notification with custom subject
|
|
61
|
+
await logs.createNotification("An error occurred", "Custom Subject");
|
|
62
|
+
|
|
63
|
+
// Send notification to specific recipient
|
|
64
|
+
await logs.createNotification("An error occurred", "Custom Subject", "user_id");
|
|
65
|
+
|
|
66
|
+
// Send notification with related collection + item
|
|
67
|
+
await logs.createNotification("An error occurred", "Custom Subject", null, "collection_name", "item_id");
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## ⚙️ Constructor
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
constructor(
|
|
76
|
+
context: ApiExtensionContext,
|
|
77
|
+
extension: string,
|
|
78
|
+
collectionName: string = "logs"
|
|
79
|
+
)
|
|
54
80
|
```
|
|
81
|
+
|
|
82
|
+
| Param | Type | Description |
|
|
83
|
+
| ---------------- | ------------------- | ------------------------------------ |
|
|
84
|
+
| `context` | ApiExtensionContext | Directus extension context |
|
|
85
|
+
| `extension` | string | Name of your extension |
|
|
86
|
+
| `collectionName` | string (optional) | Logs collection name (default: logs) |
|
|
87
|
+
|
|
88
|
+
## 🧠 How It Works
|
|
89
|
+
|
|
90
|
+
### Logging
|
|
91
|
+
|
|
92
|
+
- Saves structured logs into the logs collection
|
|
93
|
+
- Automatically attaches timestamp and extension name
|
|
94
|
+
- Fallback to console if DB write fails
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
### Activity Tracking
|
|
99
|
+
|
|
100
|
+
- Uses Directus ActivityService
|
|
101
|
+
- Automatically includes:
|
|
102
|
+
- user
|
|
103
|
+
- IP address
|
|
104
|
+
- user agent
|
|
105
|
+
- origin
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
### Notifications
|
|
110
|
+
|
|
111
|
+
- Resolves recipients in this order:
|
|
112
|
+
1. Explicit recipientOverride
|
|
113
|
+
2. global.notice_recipient
|
|
114
|
+
3. global.developer_notice_recipient
|
|
115
|
+
- Automatically:
|
|
116
|
+
- Deduplicates recipients
|
|
117
|
+
- Includes project metadata:
|
|
118
|
+
- Project name
|
|
119
|
+
- Backend URL
|
|
120
|
+
- Environment (branch)
|
|
121
|
+
- UTC timestamp
|
|
122
|
+
- Sends one notification per recipient
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
function extractRecipientIds(rows) {
|
|
2
|
+
const recipients = rows
|
|
3
|
+
?.map(row => {
|
|
4
|
+
const user = row?.directus_users_id;
|
|
5
|
+
if (typeof user === "string")
|
|
6
|
+
return user;
|
|
7
|
+
return typeof user?.id === "string" ? user.id : null;
|
|
8
|
+
})
|
|
9
|
+
.filter((value) => Boolean(value)) ?? [];
|
|
10
|
+
return Array.from(new Set(recipients));
|
|
11
|
+
}
|
|
1
12
|
export class Logs {
|
|
2
13
|
constructor(context, extension, collectionName = "logs") {
|
|
3
14
|
this.context = context;
|
|
@@ -59,14 +70,23 @@ export class Logs {
|
|
|
59
70
|
try {
|
|
60
71
|
const schema = await this.getSchema();
|
|
61
72
|
const { database, services } = this.context;
|
|
73
|
+
const globalService = new services.ItemsService("global", {
|
|
74
|
+
database,
|
|
75
|
+
schema,
|
|
76
|
+
});
|
|
62
77
|
// Check for passed recipient, fallback to global settings
|
|
63
|
-
let
|
|
64
|
-
if (
|
|
65
|
-
|
|
66
|
-
|
|
78
|
+
let recipients = [];
|
|
79
|
+
if (recipientOverride) {
|
|
80
|
+
recipients = [recipientOverride];
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
const globalSettings = await globalService.readSingleton({
|
|
84
|
+
fields: ["error_notice_recipient.directus_users_id.id"],
|
|
85
|
+
});
|
|
86
|
+
recipients = extractRecipientIds(globalSettings?.error_notice_recipient);
|
|
67
87
|
}
|
|
68
|
-
if (
|
|
69
|
-
this.printLogs(this.extension, "No
|
|
88
|
+
if (recipients.length === 0) {
|
|
89
|
+
this.printLogs(this.extension, "No recipients defined (override or global settings)");
|
|
70
90
|
return;
|
|
71
91
|
}
|
|
72
92
|
const notificationService = new services.NotificationsService({ schema });
|
|
@@ -95,14 +115,16 @@ ${message}<br><br>
|
|
|
95
115
|
<strong>Backend URL:</strong> <a href="${backendUrl}" target="_blank">${backendUrl}</a><br>
|
|
96
116
|
<strong>Date/Time (UTC):</strong> ${timestamp}
|
|
97
117
|
`.trim();
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
118
|
+
for (const recipient of recipients) {
|
|
119
|
+
await notificationService.createOne({
|
|
120
|
+
recipient,
|
|
121
|
+
sender: recipient,
|
|
122
|
+
subject,
|
|
123
|
+
message: fullMessage,
|
|
124
|
+
collection,
|
|
125
|
+
item,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
106
128
|
}
|
|
107
129
|
catch (error) {
|
|
108
130
|
console.error("❌ Failed to create notification:", error);
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -9,6 +9,23 @@ interface LogEntry {
|
|
|
9
9
|
error: string;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
+
interface RecipientLinkRow {
|
|
13
|
+
directus_users_id?: string | { id?: string | null } | null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function extractRecipientIds(rows: RecipientLinkRow[] | undefined): string[] {
|
|
17
|
+
const recipients =
|
|
18
|
+
rows
|
|
19
|
+
?.map(row => {
|
|
20
|
+
const user = row?.directus_users_id;
|
|
21
|
+
if (typeof user === "string") return user;
|
|
22
|
+
return typeof user?.id === "string" ? user.id : null;
|
|
23
|
+
})
|
|
24
|
+
.filter((value): value is string => Boolean(value)) ?? [];
|
|
25
|
+
|
|
26
|
+
return Array.from(new Set(recipients));
|
|
27
|
+
}
|
|
28
|
+
|
|
12
29
|
export class Logs {
|
|
13
30
|
protected context: ApiExtensionContext;
|
|
14
31
|
protected extension: string;
|
|
@@ -88,16 +105,26 @@ export class Logs {
|
|
|
88
105
|
try {
|
|
89
106
|
const schema = await this.getSchema();
|
|
90
107
|
const { database, services } = this.context;
|
|
108
|
+
const globalService = new services.ItemsService("global", {
|
|
109
|
+
database,
|
|
110
|
+
schema,
|
|
111
|
+
});
|
|
91
112
|
|
|
92
113
|
// Check for passed recipient, fallback to global settings
|
|
93
|
-
let
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
114
|
+
let recipients: string[] = [];
|
|
115
|
+
|
|
116
|
+
if (recipientOverride) {
|
|
117
|
+
recipients = [recipientOverride];
|
|
118
|
+
} else {
|
|
119
|
+
const globalSettings = await globalService.readSingleton({
|
|
120
|
+
fields: ["error_notice_recipient.directus_users_id.id"],
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
recipients = extractRecipientIds(globalSettings?.error_notice_recipient);
|
|
97
124
|
}
|
|
98
125
|
|
|
99
|
-
if (
|
|
100
|
-
this.printLogs(this.extension, "No
|
|
126
|
+
if (recipients.length === 0) {
|
|
127
|
+
this.printLogs(this.extension, "No recipients defined (override or global settings)");
|
|
101
128
|
return;
|
|
102
129
|
}
|
|
103
130
|
|
|
@@ -105,9 +132,11 @@ export class Logs {
|
|
|
105
132
|
|
|
106
133
|
// Project Data
|
|
107
134
|
const settings = await database.select("project_name").from("directus_settings").first();
|
|
135
|
+
|
|
108
136
|
const projectName = settings?.project_name || "Unknown Project";
|
|
109
137
|
const backendUrl = process.env.BACKEND_URL || this.context.env?.PUBLIC_URL || "Unknown URL";
|
|
110
138
|
const environment = process.env.BRANCH || "dev";
|
|
139
|
+
|
|
111
140
|
const now = new Date();
|
|
112
141
|
const timestamp = new Intl.DateTimeFormat("en-US", {
|
|
113
142
|
month: "2-digit",
|
|
@@ -131,14 +160,16 @@ ${message}<br><br>
|
|
|
131
160
|
<strong>Date/Time (UTC):</strong> ${timestamp}
|
|
132
161
|
`.trim();
|
|
133
162
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
163
|
+
for (const recipient of recipients) {
|
|
164
|
+
await notificationService.createOne({
|
|
165
|
+
recipient,
|
|
166
|
+
sender: recipient,
|
|
167
|
+
subject,
|
|
168
|
+
message: fullMessage,
|
|
169
|
+
collection,
|
|
170
|
+
item,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
142
173
|
} catch (error) {
|
|
143
174
|
console.error("❌ Failed to create notification:", error);
|
|
144
175
|
}
|