@akc42/server-utils 3.4.2 → 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.
- package/README.md +100 -41
- package/base32-utils.js +89 -0
- package/debug-helper.js +60 -0
- package/debug.js +245 -66
- package/message-formatter.js +84 -0
- package/package.json +4 -3
- package/responder.js +1 -12
- package/server-utils.js +38 -0
- package/utils.js +17 -25
- package/version.js +2 -14
- package/logger.js +0 -77
package/README.md
CHANGED
|
@@ -1,50 +1,109 @@
|
|
|
1
1
|
# server-utils
|
|
2
2
|
A Set of Utilities that I generally use on SPA projects for the server side of the project
|
|
3
3
|
|
|
4
|
-
It consists of 4 separate packages
|
|
4
|
+
It consists of 4 separate packages 8 entry points.
|
|
5
5
|
|
|
6
6
|
The packages are:-
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
This is designed to be used during testing of the server side of the app so that nothing is logged. LOG_NO_DATE omits the date and time from
|
|
10
|
-
the logged output. This is generally used when another logger (e.g PM2 log output) is also adding date/time. Finally LOG_HIDDEN_IP is used
|
|
11
|
-
to say to try and anonomise client ip addresses (see below). `logger` is called so `logger([clientip,] level, ...messages);`.
|
|
8
|
+
## The Debug Suite
|
|
12
9
|
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
This package is a complete debugging solution. The basic concept is that messages get written to a database to be
|
|
11
|
+
available for examination in the future. Messages can be immediately written to the console (that is called `logging`),
|
|
12
|
+
Also if the message was a "crash", the previous `<n>` (where `<n>` is determined from the `DEBUG_CACHE_SIZE` environment
|
|
13
|
+
if it exists, else 100) is re-read from the database and also written to the console.
|
|
15
14
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
separated list of topics to be logged), then this is output. Regardless, all
|
|
39
|
-
debug calls are stored in a 50 line cache, and will be output (newest first) on a call
|
|
40
|
-
to `dumpDebugCache`
|
|
41
|
-
|
|
42
|
-
Breaking change as of 3.0.0 logger is now an async function returning a promise fulfilled when (if set) a log file entry is made
|
|
43
|
-
|
|
44
|
-
both `Debug` and `logger` have had their file logging removed as its causing more issues that its worth
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
These are installed with as many of few of the items that you want like so:-
|
|
48
|
-
```
|
|
49
|
-
import {logger,Responder,Debug} from '@akc42/server-utils';
|
|
15
|
+
The **COLOURS** constant is an object contains a set of predefined colours (using the `npm chalk` package) for its properties of `app`,`db`,`api`,`client`, `log`, `mail`, `auth` and `error`. A *colourspec* is defined as one of those predefined colours or a hex string (hex digits preceeded by a `#`) or an rgb value (a string of three comma separated numbers between 0 and 255) and that will be used to colour the message itself when (and if) is is written to the console.
|
|
16
|
+
|
|
17
|
+
If the *shortdate* is defined (defaults to false) then the message, when eventually written to the console, is formatted as "YYYY-MM-DD hh:mm" otherwise it is
|
|
18
|
+
formatted as "YYYY-MM-DD hh:mm:ss.sss" (ie to millisecond accuracy).
|
|
19
|
+
|
|
20
|
+
The *immediate* parameter says to immediately, after logging to the database, to format the message and output it.
|
|
21
|
+
|
|
22
|
+
When called the **Debug** function returns a function which is the actual *logger*. This function can then be called with any number of parameters. The first three, if present, are checked to match the requirement, but if not are assumed not to be present. These are
|
|
23
|
+
|
|
24
|
+
- *crash* the literal string "crash" - see above for its meaning. In this case the *colourspec* is ignored and the
|
|
25
|
+
message is printed in white on a red background.
|
|
26
|
+
- *logtime* A unix timestamp with millisecond accuracy (e.g the result from `Date.now()`), Its only considered valid if
|
|
27
|
+
it is for today, although it can be earlier than the current time.
|
|
28
|
+
- *ipaddress* An ipv4 address as a string.
|
|
29
|
+
- *...messages* Any number of parameters following which are joined together with a space.
|
|
30
|
+
|
|
31
|
+
Formally **Debug** is called like this:-
|
|
32
|
+
|
|
33
|
+
```javascript
|
|
34
|
+
const debug = Debug(topic,colourspec, shortdate, immediate);
|
|
35
|
+
|
|
36
|
+
debug([crash,][,logtime][ipaddress,]...messages);
|
|
50
37
|
```
|
|
38
|
+
|
|
39
|
+
This `debug` instance also remembers the time between calls and this time is logged (and subsequently printed) as a "gap". This is printed in milliseconds unless it was a "shortdate" in which case it is printed in minutes.
|
|
40
|
+
|
|
41
|
+
**Logger** is a function that is a wrapper for *Debug* where `shortdate` and `immediate` are both true.
|
|
42
|
+
|
|
43
|
+
**messageFormatter** is the routine that formats the raw message that has been written to the database.
|
|
44
|
+
|
|
45
|
+
It is called with the following parameters in order:-
|
|
46
|
+
|
|
47
|
+
- *logid* The `logid` (the primary key) of the message in the database (only used in the message output if the item was
|
|
48
|
+
a crash).
|
|
49
|
+
- *logtime* This is either a unix timestamp *or* a string with the date and/or time in it. It should be in the same
|
|
50
|
+
format as being formatted (see above).
|
|
51
|
+
- *crash* a 0 or 1 dependant on if this message was a crash or not.
|
|
52
|
+
- *shortdate* a 0 or 1 dependant on if this message has a short date or not,
|
|
53
|
+
- *ipaddress* should be a valid ip address or `null`.
|
|
54
|
+
- *topic*
|
|
55
|
+
- *message* Just a single string
|
|
56
|
+
- *colourspec*
|
|
57
|
+
- *gap* Gap in milliseconds (this routine does the conversion to minutes if a `shortdate`).
|
|
58
|
+
|
|
59
|
+
It returns an Object with 4 properties
|
|
60
|
+
|
|
61
|
+
- *dayoutput* If the first message of the day, text with the date (only) in it, otherwise a zero length string.
|
|
62
|
+
- *message* The complete formatted message
|
|
63
|
+
- *logid* The `logid` the formatter was called with.
|
|
64
|
+
- *ip* The `ipaddress` the formatter was called with.
|
|
65
|
+
|
|
66
|
+
**DebugHelper** is a helper function for *Debug* and performs most of its work. It is called with the same parameters as *Debug* plus an additional one; *writer*. *writer* should be a callback function that can do something with the message and then return the return object that a debug call does. In the use by *Debug* this function is the one writes the data to the database, but other writers can be provided. For instance the *Debug* function is the `@akc42/app-utils` package uses the writer to send the message from the client to the server.
|
|
67
|
+
|
|
68
|
+
*writer* is called with the following parameters (all described above for *debug*, although in this case they *must* be supplied)
|
|
69
|
+
|
|
70
|
+
- *logtime* (unix timestamp)
|
|
71
|
+
- *crash* (0 or 1)
|
|
72
|
+
- *shortdate* (0 or 1)
|
|
73
|
+
- *ipaddress* (or `null`)
|
|
74
|
+
- *topic*
|
|
75
|
+
- *colourspec*
|
|
76
|
+
- *gap*
|
|
77
|
+
- *immediate* (`true` or `false`)
|
|
78
|
+
|
|
79
|
+
## Responder
|
|
80
|
+
|
|
81
|
+
**Responder** is a class to provide the ability to stream JSON responses to a node js http request. It is instanciated
|
|
82
|
+
with `new Responder(response);` and the resultant object has three methods;
|
|
83
|
+
|
|
84
|
+
- *addSection* called like
|
|
85
|
+
|
|
86
|
+
```javascript
|
|
87
|
+
addSection(name [,value])
|
|
88
|
+
```
|
|
89
|
+
which creates a new *Object property* in the response of the given name, with an optional value (which should be the entirety of a section).
|
|
90
|
+
|
|
91
|
+
- *write* allows you add an array row to an existing open section (one where *addSection* is called without a value). It
|
|
92
|
+
will return a promise which resolves when any blockage is cleared. It is recommended to use this when an array of
|
|
93
|
+
database rows will return more than a very limited number.
|
|
94
|
+
- *end* when signifies the end of stream. Any attempt to call the other two methods after this has been called will throw an error.
|
|
95
|
+
|
|
96
|
+
## Version
|
|
97
|
+
|
|
98
|
+
**getVersion** is an async function with a single parameter, the path to your project root.
|
|
99
|
+
|
|
100
|
+
It ultimately resolves to an object which has two fields. `version` which is the version string and `year` which is the
|
|
101
|
+
copyright year. The project root is where either the `.git` directory exists (in which case `version` will ask git for
|
|
102
|
+
the version and calculate the copyright year from the last git log entry) or where a `release.info` file is sitting (in
|
|
103
|
+
which case `version` will expect that to contain a version string and have a modification time from which the copyright
|
|
104
|
+
year can be derived). If neither of those possibilities exist it will try to get the version info from the
|
|
105
|
+
`package.json` file.
|
|
106
|
+
|
|
107
|
+
## Utils
|
|
108
|
+
|
|
109
|
+
**nullif0len** is the only function currently in this package. It is a function that takes a single parameter. If that parameters is either undefined or a string that has zero length it returns null. Otherwise it returns what was input.
|
package/base32-utils.js
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
@licence
|
|
3
|
+
Copyright (c) 2025 Alan Chandler, all rights reserved
|
|
4
|
+
|
|
5
|
+
This file is part of PASv5, an implementation of the Patient Administration
|
|
6
|
+
System used to support Accuvision's Laser Eye Clinics.
|
|
7
|
+
|
|
8
|
+
PASv5 is licenced to Accuvision (and its successors in interest) free of royality payments
|
|
9
|
+
and in perpetuity in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
|
|
10
|
+
implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. Accuvision
|
|
11
|
+
may modify, or employ an outside party to modify, any of the software provided that
|
|
12
|
+
this modified software is only used as part of Accuvision's internal business processes.
|
|
13
|
+
|
|
14
|
+
The software may be run on either Accuvision's own computers or on external computing
|
|
15
|
+
facilities provided by a third party, provided that the software remains soley for use
|
|
16
|
+
by Accuvision (or by potential or existing customers in interacting with Accuvision).
|
|
17
|
+
*/
|
|
18
|
+
export function hexToBase32(input) {
|
|
19
|
+
let output = '';
|
|
20
|
+
let next = 0;
|
|
21
|
+
let lshift = 0;
|
|
22
|
+
for(let i = 0; i< input.length; i++) {
|
|
23
|
+
let digit = parseInt(input.charAt(i), 16);
|
|
24
|
+
if (!Number.isInteger(digit)) throw new RangeError('Expected string to include only HEX digits');
|
|
25
|
+
if (lshift > 0) {
|
|
26
|
+
const rshift = 4 - lshift;
|
|
27
|
+
const remainder = digit & (2**rshift - 1);
|
|
28
|
+
next |= digit >>> rshift;
|
|
29
|
+
output += alphabet.charAt(next);
|
|
30
|
+
lshift = (lshift + 1) % 5;
|
|
31
|
+
next = remainder << lshift;
|
|
32
|
+
} else {
|
|
33
|
+
lshift++;
|
|
34
|
+
next = digit << lshift;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if (lshift > 0) output += alphabet.charAt(next);
|
|
38
|
+
return output;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/*
|
|
42
|
+
|
|
43
|
+
The code below was obtained from elsewhere with the attached notice
|
|
44
|
+
|
|
45
|
+
MIT License
|
|
46
|
+
|
|
47
|
+
Copyright (c) 2016-2021 Linus Unnebäck
|
|
48
|
+
|
|
49
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
50
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
51
|
+
in the Software without restriction, including without limitation the rights
|
|
52
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
53
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
54
|
+
furnished to do so, subject to the following conditions:
|
|
55
|
+
|
|
56
|
+
The above copyright notice and this permission notice shall be included in all
|
|
57
|
+
copies or substantial portions of the Software.
|
|
58
|
+
|
|
59
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
60
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
61
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
62
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
63
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
64
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
65
|
+
SOFTWARE.
|
|
66
|
+
*/
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'
|
|
70
|
+
|
|
71
|
+
export function base32ToHex (input) {
|
|
72
|
+
input = input.replace(/=+$/, '')
|
|
73
|
+
const length = input.length
|
|
74
|
+
let bits = 0;
|
|
75
|
+
let value = 0;
|
|
76
|
+
let index = 0;
|
|
77
|
+
const output = new Uint8Array(Math.ceil(length * 5 / 8) | 0)
|
|
78
|
+
for (var i = 0; i < length; i++) {
|
|
79
|
+
value = (value << 5) | alphabet.indexOf(input[i].toUpperCase())
|
|
80
|
+
bits += 5
|
|
81
|
+
if (bits >= 8) {
|
|
82
|
+
output[index++] = (value >>> (bits - 8)) & 255
|
|
83
|
+
bits -= 8
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return Buffer.from(output).toString('hex');
|
|
87
|
+
|
|
88
|
+
}
|
|
89
|
+
|
package/debug-helper.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
@licence
|
|
3
|
+
Copyright (c) 2025 Alan Chandler, all rights reserved
|
|
4
|
+
|
|
5
|
+
This file is part of PASv5, an implementation of the Patient Administration
|
|
6
|
+
System used to support Accuvision's Laser Eye Clinics.
|
|
7
|
+
|
|
8
|
+
PASv5 is licenced to Accuvision (and its successors in interest) free of royality payments
|
|
9
|
+
and in perpetuity in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
|
|
10
|
+
implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. Accuvision
|
|
11
|
+
may modify, or employ an outside party to modify, any of the software provided that
|
|
12
|
+
this modified software is only used as part of Accuvision's internal business processes.
|
|
13
|
+
|
|
14
|
+
The software may be run on either Accuvision's own computers or on external computing
|
|
15
|
+
facilities provided by a third party, provided that the software remains soley for use
|
|
16
|
+
by Accuvision (or by potential or existing customers in interacting with Accuvision).
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
export function DebugHelper(topic, colourspec, shortdate, immediate, writer) {
|
|
20
|
+
const t = topic;
|
|
21
|
+
const cs = (colourspec in COLOURS.COLOURS) || COLOURS.hexmatch.test(colourspec) || COLOURS.rgbmatch.test(colourspec) ? colourspec : null;
|
|
22
|
+
const sd = shortdate? 1:0;
|
|
23
|
+
let timestamp = Date.now();
|
|
24
|
+
const i = immediate;
|
|
25
|
+
return function (c, logtime ,ip, ...args) {
|
|
26
|
+
let crash = 1;
|
|
27
|
+
if (c !== 'crash') {
|
|
28
|
+
if (ip !== undefined) {
|
|
29
|
+
if (Array.isArray(ip)) args = ip.concat(args); else args.unshift(ip);
|
|
30
|
+
}
|
|
31
|
+
ip = logtime;
|
|
32
|
+
logtime = c;
|
|
33
|
+
crash = 0;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const fromDate = new Date(); //logtime is only possible if later than midnight last night.
|
|
37
|
+
const from = fromDate.setHours(0,0,0,0)
|
|
38
|
+
if (!(Number.isInteger(logtime) && logtime > from)) {
|
|
39
|
+
if (ip !== undefined) {
|
|
40
|
+
if (Array.isArray(ip)) args = ip.concat(args); else args.unshift(ip);
|
|
41
|
+
}
|
|
42
|
+
ip = logtime;
|
|
43
|
+
logtime = Date.now();
|
|
44
|
+
}
|
|
45
|
+
if (!ipmatch.test(ip)) {
|
|
46
|
+
if (ip !== undefined){
|
|
47
|
+
if (Array.isArray(ip)) args = ip.concat(args); else args.unshift(ip);
|
|
48
|
+
}
|
|
49
|
+
ip = null
|
|
50
|
+
}
|
|
51
|
+
const now = Date.now();
|
|
52
|
+
const gap = now - timestamp;
|
|
53
|
+
timestamp = now;
|
|
54
|
+
const message = args.reduce((cum, arg) => {
|
|
55
|
+
if (arg === undefined) return cum;
|
|
56
|
+
return `${cum} ${arg}`.trim();
|
|
57
|
+
},'');
|
|
58
|
+
return writer(logtime, crash, sd, ip, t, message, cs, gap,i)
|
|
59
|
+
}
|
|
60
|
+
};
|
package/debug.js
CHANGED
|
@@ -1,85 +1,264 @@
|
|
|
1
1
|
/**
|
|
2
|
-
@licence
|
|
3
|
-
Copyright (c)
|
|
2
|
+
@licence
|
|
3
|
+
Copyright (c) 2025 Alan Chandler, all rights reserved
|
|
4
4
|
|
|
5
|
-
This file is part of
|
|
5
|
+
This file is part of PASv5, an implementation of the Patient Administration
|
|
6
|
+
System used to support Accuvision's Laser Eye Clinics.
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
PASv5 is licenced to Accuvision (and its successors in interest) free of royality payments
|
|
9
|
+
and in perpetuity in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
|
|
10
|
+
implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. Accuvision
|
|
11
|
+
may modify, or employ an outside party to modify, any of the software provided that
|
|
12
|
+
this modified software is only used as part of Accuvision's internal business processes.
|
|
11
13
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
The software may be run on either Accuvision's own computers or on external computing
|
|
15
|
+
facilities provided by a third party, provided that the software remains soley for use
|
|
16
|
+
by Accuvision (or by potential or existing customers in interacting with Accuvision).
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { EventEmitter } from 'node:events';
|
|
20
|
+
import { setTimeout } from 'node:timers/promises';
|
|
21
|
+
import chalk from "chalk";
|
|
22
|
+
import {openDatabase} from '@akc42/sqlite-db';
|
|
23
|
+
import { DebugHelper, messageFormatter, COLOURS } from './debug-utils.js';
|
|
16
24
|
|
|
17
|
-
|
|
18
|
-
|
|
25
|
+
class DebugLogEvents extends EventEmitter {}
|
|
26
|
+
/*
|
|
27
|
+
The following events are emitted by the debug log
|
|
28
|
+
|
|
29
|
+
'log-write': (<formatted message>, <day boundary>, <logid>, <crash>, <ipaddress>, <topic>, <colourspec> )
|
|
30
|
+
<logid> is current message unless <day boundary> is true, in which case it is the logid of the last message of the
|
|
31
|
+
previous day, or 0 if its the first message since startup. If <day-boundary> is true the remaining parameters will be
|
|
32
|
+
null,
|
|
33
|
+
<crash> a true of false setting which says if it was crash or not,
|
|
34
|
+
<ipaddress> if not null is the ip address of the client that caused the message (as a string)
|
|
35
|
+
<topic> the topic of the message.
|
|
36
|
+
<colourspec> One of name of standard colors [app,db,api,client,log,mail,auth,error] else ''
|
|
37
|
+
|
|
38
|
+
'log-raw' (<log object>)
|
|
39
|
+
<log-object> contains the following fields
|
|
40
|
+
logid, This is the same logid as <logid> above, except the <day-boundary> message is not emitted in the raw feed
|
|
41
|
+
logtime, The number of milliseconds since Unix Epoch, or as a datetime string with optional milliseconds
|
|
42
|
+
logmin, The number of millisecons since Unix Epoch of logtime rounded to the minute boundary before it.
|
|
43
|
+
crash, If 1, this entry is a crash report, otherwise its a normal message
|
|
44
|
+
shortdate, If 1. the request is only to log to the nearest minute, rather than in milliseconds
|
|
45
|
+
ipaddress, If not null, is the ip address of the client that caused the message (as a string)
|
|
46
|
+
topic, The topic of this message
|
|
47
|
+
message, The text of the message is self
|
|
48
|
+
colourspec, One of name of standard colors [app,db,api,client,log,mail,auth,error], a hex color string, an rgb,
|
|
49
|
+
comma seperated, string of three values 0-255
|
|
50
|
+
gap gap in milliseconds since the last message of the same topic.
|
|
19
51
|
*/
|
|
52
|
+
export const DebugLog = new DebugLogEvents();
|
|
53
|
+
|
|
54
|
+
const db = await openDatabase(`${process.env.SQLITE_DB_NAME}-log`)
|
|
55
|
+
db.exec(`
|
|
56
|
+
CREATE TABLE IF NOT EXISTS Log (
|
|
57
|
+
logid INTEGER PRIMARY KEY,
|
|
58
|
+
logtime DATETIME NOT NULL DEFAULT (datetime('now','subsec')),
|
|
59
|
+
logmin INTEGER AS (CAST(round((unixepoch(logtime)/60) - 0.5) AS INT)) STORED,
|
|
60
|
+
crash BOOLEAN NOT NULL DEFAULT 0,
|
|
61
|
+
shortdate BOOLEAN NOT NULL DEFAULT 0,
|
|
62
|
+
ipaddress TEXT,
|
|
63
|
+
topic TEXT,
|
|
64
|
+
message TEXT,
|
|
65
|
+
colourspec TEXT,
|
|
66
|
+
gap INTEGER
|
|
67
|
+
);
|
|
68
|
+
CREATE INDEX IF NOT EXISTS IX_Log_topic ON Log(topic);
|
|
69
|
+
CREATE INDEX IF NOT EXISTS IX_Log_logmin ON Log(logmin);
|
|
70
|
+
CREATE INDEX IF NOT EXISTS IX_Log_crash ON Log(crash);
|
|
71
|
+
PRAGMA journal_mode=WAL;
|
|
72
|
+
`);
|
|
20
73
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
let enabled = false;
|
|
31
|
-
if (config) {
|
|
32
|
-
const topics = config.split(':');
|
|
33
|
-
if (topics.includes(t)) enabled = true;
|
|
74
|
+
|
|
75
|
+
export async function awaitTransaction() {
|
|
76
|
+
return new Promise((resolve) => {
|
|
77
|
+
if (db.inTransaction) {
|
|
78
|
+
if (timerAbort.signal.aborted) {
|
|
79
|
+
setTimeout(200,'awaittransaction').then(resolve);
|
|
80
|
+
} else {
|
|
81
|
+
timerAbort.signal.once('abort',resolve);
|
|
82
|
+
timerAbort.abort();
|
|
34
83
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
84
|
+
} else {
|
|
85
|
+
resolve();
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
let timerAbort = new AbortController();
|
|
91
|
+
|
|
92
|
+
const insertLogTime = db.prepare(`INSERT INTO Log (logtime,crash,shortdate,ipaddress, topic,message,colourspec,gap) VALUES
|
|
93
|
+
(datetime(?,'unixepoch','subsec'),?,?,?,?,?,?,?)`);
|
|
94
|
+
|
|
95
|
+
const insertLogNoTime = db.prepare(`INSERT INTO LOG(crash,shortdate,ipaddress, topic,message,colourspec,gap) VALUES (?,?,?,?,?,?,?)`);
|
|
96
|
+
|
|
97
|
+
const getLogTime = db.prepare('SELECT logtime FROM Log WHERE logid = ?');
|
|
98
|
+
|
|
99
|
+
/*
|
|
100
|
+
Writes the output to the log, pretty much assumed to be raw
|
|
101
|
+
logtime is in milliseconds since epoch (ie what date gives from Date.getTime())
|
|
102
|
+
*/
|
|
103
|
+
let shuttingDown = false;
|
|
104
|
+
|
|
105
|
+
export function logWriter(logtime, crash, shortdate,ipaddress, topic, message, colourspec,gap) {
|
|
106
|
+
if (shuttingDown) return messageFormatter(0,logtime, crash, shortdate,ipaddress, topic, message, colourspec,gap);
|
|
107
|
+
if (!db.isOpen) db.open();
|
|
108
|
+
if (!db.inTransaction) {
|
|
109
|
+
db.exec('BEGIN TRANSACTION;');
|
|
110
|
+
timerAbort = new AbortController();
|
|
111
|
+
setTimeout(1000,'logwriter',timerAbort.signal).then(() => {
|
|
112
|
+
if (db.inTransaction) {
|
|
113
|
+
db.exec('COMMIT;');
|
|
54
114
|
}
|
|
55
|
-
|
|
56
|
-
cache.push(`${time} ${output}`);
|
|
57
|
-
if (cache.length > cachelimit) cache.splice(0,cache.length - cachelimit); //prevent it getting too big
|
|
115
|
+
})
|
|
58
116
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
117
|
+
|
|
118
|
+
let logid;
|
|
119
|
+
|
|
120
|
+
if (logtime) {
|
|
121
|
+
const {lastInsertRowid} = insertLogTime.run((logtime/1000), crash, shortdate,ipaddress, topic, message, colourspec,gap);
|
|
122
|
+
logid = lastInsertRowid;
|
|
123
|
+
|
|
124
|
+
} else {
|
|
125
|
+
const {lastInsertRowid} = insertLogNoTime.run(crash, shortdate,ipaddress, topic, message, colourspec,gap);
|
|
126
|
+
logid = lastInsertRowid;
|
|
127
|
+
const logtimereq = getLogTime.get(logid);
|
|
128
|
+
logtime = logtimereq.logtime;
|
|
67
129
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
130
|
+
if (crash === 1) timerAbort.abort(); //force the transaction to close;
|
|
131
|
+
|
|
132
|
+
const output = messageFormatter(logid,logtime, crash, shortdate,ipaddress, topic, message, colourspec,gap);
|
|
133
|
+
if (output.dayoutput.length > 0) {
|
|
134
|
+
DebugLog.emit('log-day', output.dayoutput);
|
|
135
|
+
}
|
|
136
|
+
delete output.dayoutput;
|
|
137
|
+
DebugLog.emit(`log-write`,output.message, logid, (crash === 1), ipaddress, topic, (colourspec in COLOURS.COLOURS)? colourspec: '');
|
|
138
|
+
return output;
|
|
139
|
+
|
|
71
140
|
};
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
141
|
+
|
|
142
|
+
function logWrapper(logtime, crash, shortdate,ipaddress, topic, message, colourspec,gap, i) {
|
|
143
|
+
const output = logWriter(logtime, crash, shortdate, ipaddress, topic, message, colourspec, gap);
|
|
144
|
+
if (i) console.log(output.message);
|
|
145
|
+
return output;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/*
|
|
149
|
+
getDebugLog
|
|
150
|
+
|
|
151
|
+
will read the debug log that occured just before the provided logid, and for each row returned will call the callback
|
|
152
|
+
function. The callback function may be asynchronous. the "no" parameter is how many to fetch. The logger uses the
|
|
153
|
+
DEBUG_CACHE_SIZE environment variable to specify this.
|
|
154
|
+
*/
|
|
155
|
+
|
|
156
|
+
async function getDebugLog(callback, loid, no, ip) {
|
|
157
|
+
const lid = loid;
|
|
158
|
+
const limit = no
|
|
159
|
+
const ipadd = ip;
|
|
160
|
+
db.open();
|
|
161
|
+
try {
|
|
162
|
+
await awaitTransaction(); //make sure we have all the info committed before looking for it.
|
|
163
|
+
//we are looking from log entries from the crash backwards in time
|
|
164
|
+
const getLogtime = db.prepare(`SELECT unixepoch(logtime,'subsec') AS logtime FROM Log WHERE logid = ?`)
|
|
165
|
+
const {logtime:lt } = getLogtime.get(lid)??{logtime:0}
|
|
166
|
+
if (lt > 0) {
|
|
167
|
+
const fetchRecords = db.prepare(`SELECT logid, logtime,crash,shortdate,ipaddress, topic,message,colourspec,gap FROM Log
|
|
168
|
+
WHERE (unixepoch(logtime,'subsec')) * 1000 <= ? AND logid <> ? AND ipaddress = ? ORDER BY unixepoch(logtime,'subsec') DESC LIMIT ?`)
|
|
169
|
+
if (!db.inTransaction) db.exec('BEGIN TRANSACTION');
|
|
170
|
+
for (const {logid,logtime,crash,shortdate,ipaddress,topic,message,colourspec,gap} of fetchRecords.iterate(lt, lid, ipadd??null, limit)) {
|
|
171
|
+
const output = messageFormatter(logid,logtime,crash,shortdate,ipaddress,topic,message,colourspec,gap)
|
|
172
|
+
await callback(output.logid, output.message);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
} else {
|
|
176
|
+
console.log(chalk.white.bgBlue('The transaction provided has not yet cleared its transaction, so no records to list.'));
|
|
177
|
+
}
|
|
178
|
+
} catch(e) {
|
|
179
|
+
console.log(chalk.white.bgRed('failed with error'), e.stack);
|
|
180
|
+
throw e;
|
|
181
|
+
} finally {
|
|
182
|
+
if (db.inTransaction) db.exec('ROLLBACK'); //make sure callback hasn't changed anything
|
|
183
|
+
db.close();
|
|
79
184
|
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
/*
|
|
190
|
+
Debug creates an instance of a debug function
|
|
191
|
+
|
|
192
|
+
parameters:
|
|
193
|
+
topic - a value that can be searched for. Useful for dividing into different sections
|
|
194
|
+
colourspec - One of name of standard colors [app,db,api,client,log,mail,auth,error], a hex color string, an rgb,
|
|
195
|
+
comma seperated, string of three values 0-255
|
|
196
|
+
shortdate - if true, then dates will be output as YYYY-MM-DD hh:mm else YYYY-MM-DD hh:mm:ss.ms
|
|
197
|
+
|
|
198
|
+
immediate - if set, the message is output (formatted) to the console.
|
|
199
|
+
|
|
200
|
+
Returns a function that will write a row into the log, using the parameters above and some optional extra values
|
|
201
|
+
these extra parameters are
|
|
202
|
+
|
|
203
|
+
crash - the literal word "crash". if set, then this will be highlighted in the output. Don't provide this as
|
|
204
|
+
the first parameter if a normal call
|
|
205
|
+
logtime - a unix millisecond timestamp. If provided if must be for today, otherwise it will be as
|
|
206
|
+
though it were not provided. If provided it will be the logtime, otherwise "Now" will be used.
|
|
207
|
+
ipaddress - an optional parameter container a string representation of an ip address. Ignored if not
|
|
208
|
+
a valid adddress. If provided its value will be highlighted and surrounded in "[]"
|
|
209
|
+
...messages - As many parameters containing parts of the message. The message will be joined together
|
|
210
|
+
with a space separation and displayed with the colourspec parameter.
|
|
211
|
+
*/
|
|
212
|
+
|
|
213
|
+
export function Debug (topic, colourspec, shortdate, immediate = false) {
|
|
214
|
+
return DebugHelper(topic, colourspec, shortdate, immediate, logWrapper);
|
|
80
215
|
};
|
|
81
216
|
|
|
82
217
|
|
|
83
218
|
|
|
219
|
+
/*
|
|
220
|
+
Logger is like Debug (indeed its a wrapper for it) except
|
|
221
|
+
|
|
222
|
+
- It doesn't need short date, or immediate parameters as thats whats assumed
|
|
223
|
+
|
|
224
|
+
- If a crash, all the messages just before the crash (especially the debug ones that were not output before), are also
|
|
225
|
+
printed to the consolein reverse order
|
|
226
|
+
*/
|
|
227
|
+
|
|
228
|
+
export function Logger(topic, colourspec) {
|
|
229
|
+
const debug = Debug(topic, colourspec, 1,1);
|
|
230
|
+
return function(c, ip, ...args) {
|
|
231
|
+
const crash = (c === 'crash');
|
|
232
|
+
const output = debug(c,ip,args);
|
|
233
|
+
console.log(output.message);
|
|
234
|
+
if (crash) {
|
|
235
|
+
let lt;
|
|
236
|
+
getDebugLog(async(logid,message) => {
|
|
237
|
+
if (lt === undefined) lt=message.substring(0,24);
|
|
238
|
+
console.log(chalk.whiteBright(logid), message)
|
|
239
|
+
},output.logid,Number(process.env.DEBUG_CACHE_SIZE??100), output.ip).then(() => {
|
|
240
|
+
console.log(chalk.whiteBright(lt),chalk.white.bgBlue('Above are all the debug calls (most recent first) which lead up to the error above') )
|
|
241
|
+
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/*
|
|
248
|
+
Closes the database and ensures any partial transactions are written
|
|
249
|
+
*/
|
|
250
|
+
|
|
251
|
+
export function close() {
|
|
252
|
+
shuttingDown = true;
|
|
253
|
+
DebugLog.emit('close');
|
|
254
|
+
if (db.inTransaction) {
|
|
255
|
+
db.exec('COMMIT;');
|
|
256
|
+
}
|
|
257
|
+
if(db.isOpen) db.close();
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
|
|
84
263
|
|
|
85
264
|
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
@licence
|
|
3
|
+
Copyright (c) 2026 Alan Chandler, all rights reserved
|
|
4
|
+
|
|
5
|
+
This file is part of Server Utils.
|
|
6
|
+
|
|
7
|
+
Server Utils is free software: you can redistribute it and/or modify
|
|
8
|
+
it under the terms of the GNU General Public License as published by
|
|
9
|
+
the Free Software Foundation, either version 3 of the License, or
|
|
10
|
+
(at your option) any later version.
|
|
11
|
+
|
|
12
|
+
Server Utils is distributed in the hope that it will be useful,
|
|
13
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
14
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
15
|
+
GNU General Public License for more details.
|
|
16
|
+
|
|
17
|
+
You should have received a copy of the GNU General Public License
|
|
18
|
+
along with Server Utils. If not, see <http://www.gnu.org/licenses/>.
|
|
19
|
+
*/
|
|
20
|
+
import chalk from "chalk";
|
|
21
|
+
export const COLOURS = {
|
|
22
|
+
hexmatch: /^#(?:[0-9a-fA-F]{3}){1,2}$/,
|
|
23
|
+
rgbmatch: /^(\d{1,3}), ?(\d{1,3}), ?(\d{1,3})$/,
|
|
24
|
+
COLOURS: {
|
|
25
|
+
app: chalk.rgb(255, 136, 0).bold, //orange,
|
|
26
|
+
db: chalk.greenBright,
|
|
27
|
+
api: chalk.magentaBright,
|
|
28
|
+
client: chalk.redBright,
|
|
29
|
+
log: chalk.hex('#ff651d'),
|
|
30
|
+
mail: chalk.cyanBright,
|
|
31
|
+
//error like have backGround colouring
|
|
32
|
+
auth: chalk.whiteBright.bgBlue,
|
|
33
|
+
error: chalk.whiteBright.bgHex('#ff1165')
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
const ipmatch = /^((25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)(\.|$)){4}$/;
|
|
37
|
+
const datematch =/^(\d{4})-([01]\d)-([0-3]\d) ([0-2]\d):([0-5]\d)(:([0-5]\d)(\.(\d{1,3}))?)?$/;
|
|
38
|
+
let lastdate = '';
|
|
39
|
+
|
|
40
|
+
export function messageFormatter(logid,logtime, crash, shortdate, ipaddress, topic, message, colourspec, gap) {
|
|
41
|
+
let matches;
|
|
42
|
+
if (typeof logtime === 'string') {
|
|
43
|
+
matches = logtime.match(datematch);
|
|
44
|
+
} else {
|
|
45
|
+
matches = [logtime];
|
|
46
|
+
}
|
|
47
|
+
const logdate = new Date(matches[0]);
|
|
48
|
+
logdate.setMinutes(logdate.getMinutes() + logdate.getTimezoneOffset());
|
|
49
|
+
const displaydate = `${logdate.getFullYear()}-${(logdate.getMonth() + 1).toString().padStart(2,'0')}-${logdate.getDate().toString().padStart(2,'0')}`;
|
|
50
|
+
const displaytime = `${logdate.getHours().toString().padStart(2,'0')}:${logdate.getMinutes().toString().padStart(2,'0')}:${
|
|
51
|
+
logdate.getSeconds().toString().padStart(2,'0')}.${
|
|
52
|
+
(typeof logtime === 'string')? (matches[9]??'').toString().padStart(3,'0') : logdate.getMilliseconds().toString().padStart(3,'0')}`;
|
|
53
|
+
const d = chalk.blueBright(`${displaydate} ${shortdate === 1? displaytime.slice(0,-7): displaytime}`);
|
|
54
|
+
const ip = ipmatch.test(ipaddress)? chalk.red(` [${ipaddress}]`) : '';
|
|
55
|
+
const t = chalk.greenBright(`(${topic})`);
|
|
56
|
+
let m;
|
|
57
|
+
let l = '';
|
|
58
|
+
if (crash === 1) {
|
|
59
|
+
l = chalk.whiteBright(` ${logid}`)
|
|
60
|
+
m = chalk.white.bgRed(message);
|
|
61
|
+
} else if (colourspec in COLOURS.COLOURS) {
|
|
62
|
+
m = COLOURS.COLOURS[colourspec](message);
|
|
63
|
+
} else if (COLOURS.hexmatch.test(colourspec)) {
|
|
64
|
+
m = chalk.hex(colourspec)(message);
|
|
65
|
+
} else if (COLOURS.rgbmatch.test(colourspec)) {
|
|
66
|
+
const matches = COLOURS.rgbmatch.exec(colourspec);
|
|
67
|
+
m = chalk.rgb(matches[1], matches[2], matches[3])(message);
|
|
68
|
+
} else {
|
|
69
|
+
m = chalk.cyan(message)
|
|
70
|
+
}
|
|
71
|
+
const g = Number.isInteger(gap)?chalk.whiteBright(` gap: ${shortdate? Math.round(gap/60000) + ' mins': gap + 'ms'}`):'';
|
|
72
|
+
let dayoutput = '';
|
|
73
|
+
if (lastdate !== displaydate) {
|
|
74
|
+
dayoutput = `${chalk.whiteBright(displaydate)}:\n`
|
|
75
|
+
lastdate = displaydate;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
dayoutput: dayoutput,
|
|
80
|
+
message:`${d}${l}${ip} ${t} ${m}${g}`,
|
|
81
|
+
logid: logid,
|
|
82
|
+
ip: ipaddress
|
|
83
|
+
}
|
|
84
|
+
};
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@akc42/server-utils",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "A Set of Utilities to use on the Server Side of a Project",
|
|
6
|
-
"main": "./utils.js",
|
|
6
|
+
"main": "./server-utils.js",
|
|
7
7
|
"scripts": {
|
|
8
8
|
"test": "echo \"Error: no test specified\" && exit 1"
|
|
9
9
|
},
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
},
|
|
23
23
|
"homepage": "https://github.com/akc42/server-utils#readme",
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"
|
|
25
|
+
"@akc42/sqlite-db": "3.0.6",
|
|
26
|
+
"chalk": "^5.6.2"
|
|
26
27
|
}
|
|
27
28
|
}
|
package/responder.js
CHANGED
|
@@ -18,13 +18,8 @@
|
|
|
18
18
|
along with Server Utils. If not, see <http://www.gnu.org/licenses/>.
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const debug = Debug('responder');
|
|
24
|
-
|
|
25
|
-
export default class Responder {
|
|
21
|
+
export class Responder {
|
|
26
22
|
constructor(response) {
|
|
27
|
-
debug('Starting responder');
|
|
28
23
|
this.response = response;
|
|
29
24
|
this.doneFirstRow = false;
|
|
30
25
|
this.doneFirstSection = false;
|
|
@@ -50,11 +45,9 @@ export default class Responder {
|
|
|
50
45
|
if (typeof value !== 'undefined') {
|
|
51
46
|
this.response.write(JSON.stringify(value));
|
|
52
47
|
this.inSection = false;
|
|
53
|
-
debug('Write conplete section',name, 'with' , JSON.stringify(value));
|
|
54
48
|
} else {
|
|
55
49
|
this.response.write('[');
|
|
56
50
|
this.inSection = true;
|
|
57
|
-
debug('Started Section',name);
|
|
58
51
|
}
|
|
59
52
|
this.doneFirstSection = true;
|
|
60
53
|
this.doneFirstRow = false;
|
|
@@ -80,15 +73,12 @@ export default class Responder {
|
|
|
80
73
|
if (reply) {
|
|
81
74
|
return Promise.resolve();
|
|
82
75
|
}
|
|
83
|
-
debug('False reply from write so need return the promise of a drain');
|
|
84
76
|
if (!this.awaitingDrain) {
|
|
85
77
|
this.awaitingDrain = true;
|
|
86
78
|
const self = this;
|
|
87
|
-
debug('create a drain promise as we do not have one');
|
|
88
79
|
this.drainPromise = new Promise(resolve => {
|
|
89
80
|
self.response.once('drain', () => {
|
|
90
81
|
self.awaitingDrain = false;
|
|
91
|
-
debug('drained so resolve promise of drain');
|
|
92
82
|
resolve();
|
|
93
83
|
});
|
|
94
84
|
});
|
|
@@ -98,7 +88,6 @@ export default class Responder {
|
|
|
98
88
|
return Promise.reject(); //mark as blocked
|
|
99
89
|
}
|
|
100
90
|
end() {
|
|
101
|
-
debug('End Responder');
|
|
102
91
|
if (!this.ended) {
|
|
103
92
|
if (this.inSection) {
|
|
104
93
|
this.response.write(']');
|
package/server-utils.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
@licence
|
|
3
|
+
Copyright (c) 2026 Alan Chandler, all rights reserved
|
|
4
|
+
|
|
5
|
+
This file is part of Server Utils.
|
|
6
|
+
|
|
7
|
+
Server Utils is free software: you can redistribute it and/or modify
|
|
8
|
+
it under the terms of the GNU General Public License as published by
|
|
9
|
+
the Free Software Foundation, either version 3 of the License, or
|
|
10
|
+
(at your option) any later version.
|
|
11
|
+
|
|
12
|
+
Server Utils is distributed in the hope that it will be useful,
|
|
13
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
14
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
15
|
+
GNU General Public License for more details.
|
|
16
|
+
|
|
17
|
+
You should have received a copy of the GNU General Public License
|
|
18
|
+
along with Server Utils. If not, see <http://www.gnu.org/licenses/>.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
import {getVersion}from './version.js';
|
|
24
|
+
import {Responder} from './responder.js';
|
|
25
|
+
import { messageFormatter, COLOURS } from './message-formatter.js';
|
|
26
|
+
import { DebugHelper } from './debug-helper.js';
|
|
27
|
+
import { Debug, Logger} from './debug.js';
|
|
28
|
+
import { nullif0len } from './utils.js';
|
|
29
|
+
export {
|
|
30
|
+
COLOURS,
|
|
31
|
+
Debug,
|
|
32
|
+
DebugHelper,
|
|
33
|
+
getVersion,
|
|
34
|
+
Logger,
|
|
35
|
+
messageFormatter,
|
|
36
|
+
nullif0len,
|
|
37
|
+
Responder
|
|
38
|
+
};
|
package/utils.js
CHANGED
|
@@ -1,35 +1,27 @@
|
|
|
1
1
|
/**
|
|
2
2
|
@licence
|
|
3
|
-
|
|
3
|
+
Copyright (c) 2026 Alan Chandler, all rights reserved
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
This file is part of Server Utils.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
Server Utils is free software: you can redistribute it and/or modify
|
|
8
|
+
it under the terms of the GNU General Public License as published by
|
|
9
|
+
the Free Software Foundation, either version 3 of the License, or
|
|
10
|
+
(at your option) any later version.
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
Server Utils is distributed in the hope that it will be useful,
|
|
13
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
14
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
15
|
+
GNU General Public License for more details.
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
You should have received a copy of the GNU General Public License
|
|
18
|
+
along with Server Utils. If not, see <http://www.gnu.org/licenses/>.
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
export function nullif0len (str) {
|
|
23
|
+
if (typeof str === 'undefined') return null;
|
|
24
|
+
if (typeof str === 'string' && str.length === 0) return null;
|
|
25
|
+
return str;
|
|
26
|
+
};
|
|
26
27
|
|
|
27
|
-
|
|
28
|
-
export {
|
|
29
|
-
logger,
|
|
30
|
-
Version,
|
|
31
|
-
Responder,
|
|
32
|
-
Debug,
|
|
33
|
-
dumpDebugCache,
|
|
34
|
-
setDebugConfig
|
|
35
|
-
}
|
package/version.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
@licence
|
|
3
|
-
Copyright (c)
|
|
3
|
+
Copyright (c) 2026 Alan Chandler, all rights reserved
|
|
4
4
|
|
|
5
5
|
This file is part of Server Utils.
|
|
6
6
|
|
|
@@ -17,16 +17,11 @@
|
|
|
17
17
|
You should have received a copy of the GNU General Public License
|
|
18
18
|
along with Server Utils. If not, see <http://www.gnu.org/licenses/>.
|
|
19
19
|
*/
|
|
20
|
-
|
|
21
|
-
import {Debug} from './debug.js';
|
|
22
20
|
import {access, readFile, stat} from 'node:fs/promises';
|
|
23
21
|
import { resolve } from 'node:path';
|
|
24
22
|
import { exec } from 'node:child_process';
|
|
25
23
|
|
|
26
|
-
const debug = Debug('version');
|
|
27
|
-
|
|
28
24
|
async function shCmd(cmd, root) {
|
|
29
|
-
debug('About to execute Command ', cmd);
|
|
30
25
|
return new Promise(async (accept, reject) => {
|
|
31
26
|
exec(cmd, { cwd: root }, (err, stdout, stderr) => {
|
|
32
27
|
if (stderr) {
|
|
@@ -34,21 +29,16 @@ async function shCmd(cmd, root) {
|
|
|
34
29
|
reject(err);
|
|
35
30
|
} else {
|
|
36
31
|
const out = stdout.trim();
|
|
37
|
-
debug('Command ', cmd, 'Success with ', out);
|
|
38
32
|
accept(out);
|
|
39
33
|
}
|
|
40
34
|
});
|
|
41
35
|
});
|
|
42
36
|
}
|
|
43
|
-
export
|
|
44
|
-
|
|
37
|
+
export async function getVersion(root) {
|
|
45
38
|
let version;
|
|
46
39
|
let vtime;
|
|
47
|
-
|
|
48
40
|
try {
|
|
49
|
-
debug('Look for git')
|
|
50
41
|
await access(resolve(root, '.git'));
|
|
51
|
-
debug('Git found, so use it to get data')
|
|
52
42
|
//we get here if there is a git directory, so we can look up version and latest commit from them
|
|
53
43
|
version = await shCmd('git describe --abbrev=0 --tags');
|
|
54
44
|
//git is installed and we found a tag
|
|
@@ -60,7 +50,6 @@ export default async function(root) {
|
|
|
60
50
|
} catch (e) {
|
|
61
51
|
//no git, or no tag, so we must look for a version file
|
|
62
52
|
try {
|
|
63
|
-
debug('Git approach failed, so look for release info');
|
|
64
53
|
version = await readFile(resolve(root, 'release.info'), 'utf8');
|
|
65
54
|
try {
|
|
66
55
|
const { mtime } = await stat(resolve(root, 'release.info'));
|
|
@@ -88,7 +77,6 @@ export default async function(root) {
|
|
|
88
77
|
} finally {
|
|
89
78
|
const finalversion = version.replace(/\s+/g, ' ').trim(); //trim out new lines and multiple spaces just one.
|
|
90
79
|
const copyrightTime = new Date(vtime);
|
|
91
|
-
debug('Resolving with Git copyright Year is ', copyrightTime.getUTCFullYear());
|
|
92
80
|
return({ version: finalversion, year: copyrightTime.getUTCFullYear() });
|
|
93
81
|
}
|
|
94
82
|
};
|
package/logger.js
DELETED
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
@licence
|
|
3
|
-
Copyright (c) 2021 Alan Chandler, all rights reserved
|
|
4
|
-
|
|
5
|
-
This file is part of Server Utils.
|
|
6
|
-
|
|
7
|
-
Server Utils is free software: you can redistribute it and/or modify
|
|
8
|
-
it under the terms of the GNU General Public License as published by
|
|
9
|
-
the Free Software Foundation, either version 3 of the License, or
|
|
10
|
-
(at your option) any later version.
|
|
11
|
-
|
|
12
|
-
Server Utils is distributed in the hope that it will be useful,
|
|
13
|
-
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
14
|
-
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
15
|
-
GNU General Public License for more details.
|
|
16
|
-
|
|
17
|
-
You should have received a copy of the GNU General Public License
|
|
18
|
-
along with Server Utils. If not, see <http://www.gnu.org/licenses/>.
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
import chalk from 'chalk';
|
|
22
|
-
import { isIP } from 'node:net';
|
|
23
|
-
|
|
24
|
-
const COLOURS = {
|
|
25
|
-
app: chalk.rgb(255, 136, 0).bold, //orange,
|
|
26
|
-
db: chalk.greenBright,
|
|
27
|
-
api: chalk.magentaBright,
|
|
28
|
-
client: chalk.redBright,
|
|
29
|
-
log: chalk.yellowBright,
|
|
30
|
-
mail: chalk.cyanBright,
|
|
31
|
-
//error like have backGround colouring
|
|
32
|
-
auth: chalk.black.bgCyan,
|
|
33
|
-
err: chalk.white.bgBlue,
|
|
34
|
-
error: chalk.white.bgRed
|
|
35
|
-
|
|
36
|
-
};
|
|
37
|
-
function cyrb53 (str, seed = 0) {
|
|
38
|
-
let h1 = 0xdeadbeef ^ seed, h2 = 0x41c6ce57 ^ seed;
|
|
39
|
-
for (let i = 0, ch; i < str.length; i++) {
|
|
40
|
-
ch = str.charCodeAt(i);
|
|
41
|
-
h1 = Math.imul(h1 ^ ch, 2654435761);
|
|
42
|
-
h2 = Math.imul(h2 ^ ch, 1597334677);
|
|
43
|
-
}
|
|
44
|
-
h1 = Math.imul(h1 ^ h1 >>> 16, 2246822507) ^ Math.imul(h2 ^ h2 >>> 13, 3266489909);
|
|
45
|
-
h2 = Math.imul(h2 ^ h2 >>> 16, 2246822507) ^ Math.imul(h1 ^ h1 >>> 13, 3266489909);
|
|
46
|
-
return 4294967296 * (2097151 & h2) + (h1 >>> 0);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export default function logger(ip,level, ...messages) {
|
|
50
|
-
if (process.env.LOG_NONE === undefined) {
|
|
51
|
-
let logLine = '';
|
|
52
|
-
if (typeof process.env.LOG_NO_DATE === 'undefined') {
|
|
53
|
-
if (typeof process.env.LOG_ISO_DATE !== 'undefined') {
|
|
54
|
-
logLine += new Date().toISOString() + ': ';
|
|
55
|
-
} else {
|
|
56
|
-
logLine += new Date().toISOString().substring(0,10) + ' ' + new Date().toLocaleTimeString() + ': ';
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
let message;
|
|
60
|
-
let logcolor;
|
|
61
|
-
if (isIP(ip) === 0 ) {
|
|
62
|
-
logcolor = ip;
|
|
63
|
-
message = level + messages.join(' ');
|
|
64
|
-
} else {
|
|
65
|
-
const client = typeof process.env.LOG_IP_HIDDEN !== 'undefined' ? cyrb53(ip): ip;
|
|
66
|
-
logLine += COLOURS.client(client + ': ');
|
|
67
|
-
logcolor = level
|
|
68
|
-
message = messages.join(' ');
|
|
69
|
-
}
|
|
70
|
-
logLine += COLOURS[logcolor](message);
|
|
71
|
-
//eslint-disable-next-line no-console
|
|
72
|
-
console.log(logLine.trim());
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|