@stonyx/logs 1.0.1-beta.11 → 1.0.1-beta.13
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/dist/color.d.ts +10 -0
- package/dist/color.js +39 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.js +174 -0
- package/package.json +20 -7
- package/src/color.js +0 -50
- package/src/index.js +0 -201
package/dist/color.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
export type ChalkColorFn = (text: string) => string;
|
|
3
|
+
export type ColorSetting = string | ChalkColorFn;
|
|
4
|
+
export default class Color {
|
|
5
|
+
types: Record<string, ChalkColorFn>;
|
|
6
|
+
getLogColor(type: string): ChalkColorFn;
|
|
7
|
+
getChalkInstance(): typeof chalk;
|
|
8
|
+
setLogColor(type: string, setting: ColorSetting): void;
|
|
9
|
+
settingToChalkColorFunction(setting: ColorSetting): ChalkColorFn;
|
|
10
|
+
}
|
package/dist/color.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
export default class Color {
|
|
3
|
+
types = {};
|
|
4
|
+
getLogColor(type) {
|
|
5
|
+
return this.types[type];
|
|
6
|
+
}
|
|
7
|
+
getChalkInstance() {
|
|
8
|
+
return chalk;
|
|
9
|
+
}
|
|
10
|
+
setLogColor(type, setting) {
|
|
11
|
+
const chalkColorFunction = this.settingToChalkColorFunction(setting);
|
|
12
|
+
this.types[type] = chalkColorFunction;
|
|
13
|
+
}
|
|
14
|
+
// retrieves chalk color function, and fully validates output
|
|
15
|
+
settingToChalkColorFunction(setting) {
|
|
16
|
+
const errorMessage = 'Invalid chalk color function. '
|
|
17
|
+
+ 'For help with color settings, see https://github.com/abofs/stonyx-logs#defining-logs--colors';
|
|
18
|
+
switch (typeof setting) {
|
|
19
|
+
case 'string':
|
|
20
|
+
const chalkColorFunction = (setting[0] === '#')
|
|
21
|
+
? chalk.hex(setting)
|
|
22
|
+
: chalk[setting];
|
|
23
|
+
if (!chalkColorFunction
|
|
24
|
+
|| typeof chalkColorFunction !== 'function'
|
|
25
|
+
|| typeof chalkColorFunction('') !== 'string') {
|
|
26
|
+
throw new Error(errorMessage);
|
|
27
|
+
}
|
|
28
|
+
return chalkColorFunction;
|
|
29
|
+
case 'function':
|
|
30
|
+
// validate that given function returns a string
|
|
31
|
+
if (typeof setting('') !== 'string') {
|
|
32
|
+
throw new Error(errorMessage);
|
|
33
|
+
}
|
|
34
|
+
return setting;
|
|
35
|
+
default:
|
|
36
|
+
throw new Error(errorMessage);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import Color, { type ColorSetting } from './color.js';
|
|
2
|
+
export interface LogOptions {
|
|
3
|
+
logToFileByDefault: boolean;
|
|
4
|
+
logTimestamp: boolean;
|
|
5
|
+
path: string;
|
|
6
|
+
prefix: string;
|
|
7
|
+
suffix: string;
|
|
8
|
+
filename: string;
|
|
9
|
+
additionalLogs: Record<string, ColorSetting>;
|
|
10
|
+
systemLogs: Record<string, ColorSetting>;
|
|
11
|
+
}
|
|
12
|
+
export default class Log {
|
|
13
|
+
options: LogOptions;
|
|
14
|
+
color: Color;
|
|
15
|
+
typeOptions: Record<string, Partial<LogOptions>>;
|
|
16
|
+
[key: string]: unknown;
|
|
17
|
+
constructor(options?: Partial<LogOptions>);
|
|
18
|
+
defineType(type: string, setting: ColorSetting, options?: Partial<LogOptions> | null): void;
|
|
19
|
+
createConvenienceMethod(type: string): void;
|
|
20
|
+
logAction(type: string, content: string, logToFile?: boolean, overwrite?: boolean): Promise<void>;
|
|
21
|
+
getOptionForType(type: string, option: keyof LogOptions): LogOptions[keyof LogOptions];
|
|
22
|
+
chalk(): ReturnType<Color['getChalkInstance']>;
|
|
23
|
+
log(content: string, type: string, logToFile: boolean, overwrite: boolean): Promise<void>;
|
|
24
|
+
debug(content: unknown, logToFile?: boolean, overwrite?: boolean): Promise<void>;
|
|
25
|
+
writeToFile(type: string, content: string, overwrite: boolean): Promise<void>;
|
|
26
|
+
resolveFilename(template: string, type: string): string;
|
|
27
|
+
validateFileAndDirectory(path: string, targetLog: string): Promise<void>;
|
|
28
|
+
sanitizePath(path: string): string;
|
|
29
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { mkdirSync, promises as fsp } from 'fs';
|
|
2
|
+
import { fileURLToPath } from 'url';
|
|
3
|
+
import { hostname } from 'os';
|
|
4
|
+
import projectPath from 'path';
|
|
5
|
+
import Color from './color.js';
|
|
6
|
+
const defaultOptions = {
|
|
7
|
+
logToFileByDefault: false,
|
|
8
|
+
logTimestamp: false,
|
|
9
|
+
path: 'logs/',
|
|
10
|
+
prefix: '',
|
|
11
|
+
suffix: '',
|
|
12
|
+
filename: '',
|
|
13
|
+
additionalLogs: {},
|
|
14
|
+
systemLogs: {
|
|
15
|
+
info: 'cyan',
|
|
16
|
+
warn: 'yellow',
|
|
17
|
+
error: 'red',
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
// used to sanitize defineType() options input
|
|
21
|
+
const optionKeys = Object.keys(defaultOptions);
|
|
22
|
+
export default class Log {
|
|
23
|
+
options;
|
|
24
|
+
color;
|
|
25
|
+
typeOptions = {};
|
|
26
|
+
constructor(options = {}) {
|
|
27
|
+
const merged = {
|
|
28
|
+
...defaultOptions,
|
|
29
|
+
...options,
|
|
30
|
+
};
|
|
31
|
+
this.options = merged;
|
|
32
|
+
this.options.path = this.sanitizePath(this.options.path);
|
|
33
|
+
const { additionalLogs, systemLogs } = merged;
|
|
34
|
+
const logs = {
|
|
35
|
+
...systemLogs,
|
|
36
|
+
...additionalLogs,
|
|
37
|
+
};
|
|
38
|
+
this.color = new Color();
|
|
39
|
+
this.typeOptions = {};
|
|
40
|
+
// create direct convenience methods for logging
|
|
41
|
+
for (const type of Object.keys(logs)) {
|
|
42
|
+
this.defineType(type, logs[type]);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
// records setting and options for log type, and creates convenience method ie: log.info()
|
|
46
|
+
defineType(type, setting, options = null) {
|
|
47
|
+
this.color.setLogColor(type, setting);
|
|
48
|
+
// create convenience method if it doesn't exist
|
|
49
|
+
if (!this[type])
|
|
50
|
+
this.createConvenienceMethod(type);
|
|
51
|
+
if (!options)
|
|
52
|
+
return;
|
|
53
|
+
if (typeof options !== 'object')
|
|
54
|
+
throw new Error('The options param must be an object.');
|
|
55
|
+
for (const option of Object.keys(options)) {
|
|
56
|
+
if (!optionKeys.includes(option)) {
|
|
57
|
+
throw new Error(`${option} is not a valid configuration object.`
|
|
58
|
+
+ '\n For a list of available options, see https://github.com/abofs/stonyx-logs#configuration');
|
|
59
|
+
}
|
|
60
|
+
// sanitize path input
|
|
61
|
+
if (option === 'path') {
|
|
62
|
+
options[option] = this.sanitizePath(options[option]);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
this.typeOptions[type] = options;
|
|
66
|
+
}
|
|
67
|
+
// proxy through `logAction` method in order to set defaults based on argument presence
|
|
68
|
+
createConvenienceMethod(type) {
|
|
69
|
+
this[type] = (content, logToFile, overwrite = false) => this.logAction(type, content, logToFile, overwrite);
|
|
70
|
+
}
|
|
71
|
+
// validates params and sets configuration-based defaults for logging
|
|
72
|
+
logAction(type, content, logToFile, overwrite) {
|
|
73
|
+
// set logToFile default based on class options when not set
|
|
74
|
+
if (logToFile === undefined)
|
|
75
|
+
logToFile = this.getOptionForType(type, 'logToFileByDefault');
|
|
76
|
+
// treat overwrite default as true for log type "debug"
|
|
77
|
+
if (type === 'debug' && overwrite === undefined)
|
|
78
|
+
overwrite = true;
|
|
79
|
+
return this.log(content, type, logToFile, overwrite ?? false);
|
|
80
|
+
}
|
|
81
|
+
// retrieves option setting for given type, default to global
|
|
82
|
+
getOptionForType(type, option) {
|
|
83
|
+
const options = this.typeOptions[type];
|
|
84
|
+
if (!options || !options[option])
|
|
85
|
+
return this.options[option];
|
|
86
|
+
return options[option];
|
|
87
|
+
}
|
|
88
|
+
// exposes chalk for custom color options via defineType
|
|
89
|
+
chalk() {
|
|
90
|
+
return this.color.getChalkInstance();
|
|
91
|
+
}
|
|
92
|
+
// logs to console, and conditionally to file
|
|
93
|
+
async log(content, type, logToFile, overwrite) {
|
|
94
|
+
const logTimestamp = this.getOptionForType(type, 'logTimestamp');
|
|
95
|
+
const timestamp = `[${new Date().toLocaleString('en-US')}]`;
|
|
96
|
+
const chalkColorFunction = this.color.getLogColor(type);
|
|
97
|
+
let prefix = this.getOptionForType(type, 'prefix');
|
|
98
|
+
let suffix = this.getOptionForType(type, 'suffix');
|
|
99
|
+
if (logTimestamp)
|
|
100
|
+
prefix += `${timestamp} `;
|
|
101
|
+
if (prefix)
|
|
102
|
+
prefix = chalkColorFunction(prefix);
|
|
103
|
+
if (suffix)
|
|
104
|
+
suffix = chalkColorFunction(suffix);
|
|
105
|
+
const coloredLog = chalkColorFunction(content);
|
|
106
|
+
console.log(`${prefix}${coloredLog}${suffix}`); // eslint-disable-line no-console
|
|
107
|
+
if (!logToFile)
|
|
108
|
+
return;
|
|
109
|
+
await this.writeToFile(type, `${timestamp} ${content}\n`, overwrite);
|
|
110
|
+
}
|
|
111
|
+
// direct hardcoded debug method (log to file functionality is limited)
|
|
112
|
+
async debug(content, logToFile = false, overwrite = true) {
|
|
113
|
+
console.dir(content, { depth: 6 }); // eslint-disable-line no-console
|
|
114
|
+
if (!logToFile)
|
|
115
|
+
return;
|
|
116
|
+
await this.writeToFile('debug', JSON.stringify(content, null, 2), overwrite);
|
|
117
|
+
}
|
|
118
|
+
async writeToFile(type, content, overwrite) {
|
|
119
|
+
const path = this.getOptionForType(type, 'path');
|
|
120
|
+
const filenameTemplate = this.getOptionForType(type, 'filename');
|
|
121
|
+
const resolvedName = this.resolveFilename(filenameTemplate, type);
|
|
122
|
+
const targetLog = `${path}${resolvedName}`;
|
|
123
|
+
await this.validateFileAndDirectory(path, targetLog);
|
|
124
|
+
const fileAction = overwrite ? fsp.writeFile : fsp.appendFile;
|
|
125
|
+
await fileAction(targetLog, content);
|
|
126
|
+
}
|
|
127
|
+
// resolves template variables in a filename string
|
|
128
|
+
resolveFilename(template, type) {
|
|
129
|
+
// default to '{type}.log' when no template is configured
|
|
130
|
+
if (!template)
|
|
131
|
+
return `${type}.log`;
|
|
132
|
+
const now = new Date();
|
|
133
|
+
const yyyy = now.getFullYear();
|
|
134
|
+
const mm = String(now.getMonth() + 1).padStart(2, '0');
|
|
135
|
+
const dd = String(now.getDate()).padStart(2, '0');
|
|
136
|
+
const variables = {
|
|
137
|
+
date: `${yyyy}-${mm}-${dd}`,
|
|
138
|
+
type,
|
|
139
|
+
pid: process.pid,
|
|
140
|
+
hostname: hostname(),
|
|
141
|
+
};
|
|
142
|
+
const resolved = template.replace(/\{(\w+)\}/g, (match, key) => {
|
|
143
|
+
return variables[key] !== undefined ? String(variables[key]) : match;
|
|
144
|
+
});
|
|
145
|
+
// sanitize: prevent path traversal and disallow directory separators
|
|
146
|
+
return resolved.replace(/\.\./g, '').replace(/[/\\]/g, '');
|
|
147
|
+
}
|
|
148
|
+
// attempts to create file and/or directory if they don't already exist
|
|
149
|
+
async validateFileAndDirectory(path, targetLog) {
|
|
150
|
+
const errorMethod = this.error || console.error;
|
|
151
|
+
mkdirSync(path, { recursive: true });
|
|
152
|
+
await fsp.access(targetLog).catch(() => {
|
|
153
|
+
fsp.writeFile(targetLog, '').catch(() => {
|
|
154
|
+
errorMethod(`Failed to create log file: ${targetLog}.`
|
|
155
|
+
+ '\n Verify that the application runner has write permissions');
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
// method to conditionally sanitize user configuration input
|
|
160
|
+
sanitizePath(path) {
|
|
161
|
+
const moduleDir = projectPath.dirname(fileURLToPath(import.meta.url));
|
|
162
|
+
const delim = moduleDir.includes('node_modules') ? 'node_modules' : 'src';
|
|
163
|
+
const splitDir = moduleDir.split(delim);
|
|
164
|
+
if (splitDir.length < 2)
|
|
165
|
+
throw new Error('Failed to locate your project\'s root directory.');
|
|
166
|
+
// use project root directory behind path
|
|
167
|
+
path = projectPath.resolve(splitDir[0], path);
|
|
168
|
+
// force path property to contain a trailing "/"
|
|
169
|
+
if (path[path.length - 1] !== '/') {
|
|
170
|
+
path += '/';
|
|
171
|
+
}
|
|
172
|
+
return path;
|
|
173
|
+
}
|
|
174
|
+
}
|
package/package.json
CHANGED
|
@@ -1,15 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stonyx/logs",
|
|
3
|
-
"version": "1.0.1-beta.
|
|
3
|
+
"version": "1.0.1-beta.13",
|
|
4
4
|
"description": "Simplified logging for node applications",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"main": "
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
7
8
|
"exports": {
|
|
8
|
-
".":
|
|
9
|
-
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"default": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./color": {
|
|
14
|
+
"types": "./dist/color.d.ts",
|
|
15
|
+
"default": "./dist/color.js"
|
|
16
|
+
}
|
|
10
17
|
},
|
|
11
18
|
"files": [
|
|
12
|
-
"
|
|
19
|
+
"dist",
|
|
13
20
|
"README.md"
|
|
14
21
|
],
|
|
15
22
|
"publishConfig": {
|
|
@@ -43,13 +50,19 @@
|
|
|
43
50
|
"chalk": "^5.3.0"
|
|
44
51
|
},
|
|
45
52
|
"devDependencies": {
|
|
53
|
+
"@types/node": "^25.5.2",
|
|
54
|
+
"@types/qunit": "^2.19.13",
|
|
55
|
+
"@types/sinon": "^21.0.1",
|
|
46
56
|
"eslint": "^8.27.0",
|
|
47
57
|
"eslint-plugin-node": "^11.1.0",
|
|
48
58
|
"qunit": "^2.19.3",
|
|
49
|
-
"sinon": "^17.0.0"
|
|
59
|
+
"sinon": "^17.0.0",
|
|
60
|
+
"typescript": "^5.8.3"
|
|
50
61
|
},
|
|
51
62
|
"scripts": {
|
|
52
|
-
"
|
|
63
|
+
"build": "tsc",
|
|
64
|
+
"build:test": "tsc -p tsconfig.test.json",
|
|
65
|
+
"test": "pnpm build && pnpm build:test && qunit 'dist-test/test/unit/**/*-test.js'",
|
|
53
66
|
"lint": "eslint . --fix"
|
|
54
67
|
}
|
|
55
68
|
}
|
package/src/color.js
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import chalk from 'chalk';
|
|
2
|
-
|
|
3
|
-
export default class Color {
|
|
4
|
-
constructor() {
|
|
5
|
-
this.types = [];
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
// retrieves configured color function for log type
|
|
9
|
-
getLogColor(type) {
|
|
10
|
-
return this.types[type];
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
getChalkInstance() {
|
|
14
|
-
return chalk;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
setLogColor(type, setting) {
|
|
18
|
-
const chalkColorFunction = this.settingToChalkColorFunction(setting);
|
|
19
|
-
this.types[type] = chalkColorFunction;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
// retrieves chalk color function, and fully validates output
|
|
23
|
-
settingToChalkColorFunction(setting) {
|
|
24
|
-
const errorMessage = 'Invalid chalk color function.'
|
|
25
|
-
+ 'For help with color settings, see https://github.com/abofs/stonyx-logs#defining-logs--colors';
|
|
26
|
-
|
|
27
|
-
switch (typeof setting) {
|
|
28
|
-
case 'string':
|
|
29
|
-
const chalkColorFunction = (setting[0] === '#') ? chalk.hex(setting) : chalk[setting];
|
|
30
|
-
if (!chalkColorFunction
|
|
31
|
-
|| typeof chalkColorFunction !== 'function'
|
|
32
|
-
|| typeof chalkColorFunction('') !== 'string') {
|
|
33
|
-
throw errorMessage;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
return chalkColorFunction;
|
|
37
|
-
|
|
38
|
-
case 'function':
|
|
39
|
-
// validate that given function returns a string
|
|
40
|
-
if (typeof setting('') !== 'string') {
|
|
41
|
-
throw errorMessage;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
return setting;
|
|
45
|
-
|
|
46
|
-
default:
|
|
47
|
-
throw errorMessage;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
}
|
package/src/index.js
DELETED
|
@@ -1,201 +0,0 @@
|
|
|
1
|
-
import { mkdirSync, promises as fsp } from 'fs';
|
|
2
|
-
import { fileURLToPath } from 'url';
|
|
3
|
-
import { hostname } from 'os';
|
|
4
|
-
import projectPath from 'path';
|
|
5
|
-
import Color from './color.js';
|
|
6
|
-
|
|
7
|
-
const defaultOptions = {
|
|
8
|
-
logToFileByDefault: false, // default setting (overridable by logToFile param)
|
|
9
|
-
logTimestamp: false, // option to include timestamp in console logs
|
|
10
|
-
path: 'logs/', // default log directory (relative to main project root directory)
|
|
11
|
-
prefix: '',
|
|
12
|
-
suffix: '',
|
|
13
|
-
filename: '', // template for log file name (e.g. 'error-{date}.log'), defaults to '{type}.log'
|
|
14
|
-
|
|
15
|
-
// log types with corresponding color settings
|
|
16
|
-
additionalLogs: {},
|
|
17
|
-
systemLogs: {
|
|
18
|
-
info: 'cyan',
|
|
19
|
-
warn: 'yellow',
|
|
20
|
-
error: 'red',
|
|
21
|
-
},
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
// used to sanitize defineType() options input
|
|
25
|
-
const optionKeys = Object.keys(defaultOptions);
|
|
26
|
-
|
|
27
|
-
export default class Log {
|
|
28
|
-
constructor(options = defaultOptions) {
|
|
29
|
-
options = {
|
|
30
|
-
...defaultOptions,
|
|
31
|
-
...options,
|
|
32
|
-
};
|
|
33
|
-
this.options = options;
|
|
34
|
-
this.options.path = this.sanitizePath(this.options.path);
|
|
35
|
-
|
|
36
|
-
const { additionalLogs, systemLogs } = options;
|
|
37
|
-
const logs = {
|
|
38
|
-
...systemLogs,
|
|
39
|
-
...additionalLogs,
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
this.color = new Color();
|
|
43
|
-
this.typeOptions = [];
|
|
44
|
-
|
|
45
|
-
// create direct convenience methods for logging
|
|
46
|
-
for (const type of Object.keys(logs)) {
|
|
47
|
-
this.defineType(type, logs[type]);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// records setting and options for log type, and creates convenience method ie: log.info()
|
|
52
|
-
defineType(type, setting, options = null) {
|
|
53
|
-
this.color.setLogColor(type, setting);
|
|
54
|
-
|
|
55
|
-
// create convenience method if it doesn't exist
|
|
56
|
-
if (!this[type]) this.createConvenienceMethod(type);
|
|
57
|
-
|
|
58
|
-
if (!options) return;
|
|
59
|
-
if (typeof options !== 'object') throw 'The options param must be an object.';
|
|
60
|
-
|
|
61
|
-
for (let option of Object.keys(options)) {
|
|
62
|
-
if (!optionKeys.includes(option)) {
|
|
63
|
-
throw `${option} is not a valid configuration object.`
|
|
64
|
-
+ '\n For a list of available options, see https://github.com/abofs/stonyx-logs#configuration';
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// sanitize path input
|
|
68
|
-
if (option === 'path') options[option] = this.sanitizePath(options[option]);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
this.typeOptions[type] = options;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// proxy through `logAction` method in order to set defaults based on argument presence
|
|
75
|
-
createConvenienceMethod(type) {
|
|
76
|
-
this[type] = (content, logToFile, overwrite = false) =>
|
|
77
|
-
this.logAction(type, content, logToFile, overwrite);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// validates params and sets configuration-based defaults for logging
|
|
81
|
-
logAction(type, content, logToFile, overwrite) {
|
|
82
|
-
// set logToFile default based on class options when not set
|
|
83
|
-
if (arguments[2] === undefined) logToFile = this.getOptionForType(type, 'logToFileByDefault');
|
|
84
|
-
|
|
85
|
-
// treat overwrite default as true for log type "debug"
|
|
86
|
-
if (type === 'debug' && arguments[3] === undefined) overwrite = true;
|
|
87
|
-
|
|
88
|
-
return this.log(content, type, logToFile, overwrite);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// retrieves option setting for given type, default to global
|
|
92
|
-
getOptionForType(type, option) {
|
|
93
|
-
const options = this.typeOptions[type];
|
|
94
|
-
if (!options || !options[option]) return this.options[option];
|
|
95
|
-
|
|
96
|
-
return options[option];
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// exposes chalk for custom color options via defineType
|
|
100
|
-
chalk() {
|
|
101
|
-
return this.color.getChalkInstance();
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// logs to console, and conditionally to file
|
|
105
|
-
async log(content, type, logToFile, overwrite) {
|
|
106
|
-
const logTimestamp = this.getOptionForType(type, 'logTimestamp');
|
|
107
|
-
const timestamp = `[${new Date().toLocaleString('en-US')}]`;
|
|
108
|
-
const chalkColorFunction = this.color.getLogColor(type);
|
|
109
|
-
let prefix = this.getOptionForType(type, 'prefix');
|
|
110
|
-
let suffix = this.getOptionForType(type, 'suffix');
|
|
111
|
-
if (logTimestamp) prefix += `${timestamp} `;
|
|
112
|
-
if (prefix) prefix = chalkColorFunction(prefix);
|
|
113
|
-
if (suffix) suffix = chalkColorFunction(suffix);
|
|
114
|
-
const coloredLog = chalkColorFunction(content);
|
|
115
|
-
|
|
116
|
-
console.log(`${prefix}${coloredLog}${suffix}`); // eslint-disable-line no-console
|
|
117
|
-
|
|
118
|
-
if (!logToFile) return;
|
|
119
|
-
|
|
120
|
-
return this.writeToFile(type, `${timestamp} ${content}\n`, overwrite);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// direct hardcoded debug method (log to file functionality is limited)
|
|
124
|
-
async debug(content, logToFile = false, overwrite = true) {
|
|
125
|
-
console.dir(content, { depth: 6 }); // eslint-disable-line no-console
|
|
126
|
-
|
|
127
|
-
if (!logToFile) return;
|
|
128
|
-
|
|
129
|
-
return this.writeToFile('debug', JSON.stringify(content, null, 2), overwrite);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
async writeToFile(type, content, overwrite) {
|
|
133
|
-
const path = this.getOptionForType(type, 'path');
|
|
134
|
-
const filenameTemplate = this.getOptionForType(type, 'filename');
|
|
135
|
-
const resolvedName = this.resolveFilename(filenameTemplate, type);
|
|
136
|
-
const targetLog = `${path}${resolvedName}`;
|
|
137
|
-
await this.validateFileAndDirectory(path, targetLog);
|
|
138
|
-
|
|
139
|
-
const fileAction = overwrite ? fsp.writeFile : fsp.appendFile;
|
|
140
|
-
|
|
141
|
-
return fileAction(targetLog, content);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// resolves template variables in a filename string
|
|
145
|
-
resolveFilename(template, type) {
|
|
146
|
-
// default to '{type}.log' when no template is configured
|
|
147
|
-
if (!template) return `${type}.log`;
|
|
148
|
-
|
|
149
|
-
const now = new Date();
|
|
150
|
-
const yyyy = now.getFullYear();
|
|
151
|
-
const mm = String(now.getMonth() + 1).padStart(2, '0');
|
|
152
|
-
const dd = String(now.getDate()).padStart(2, '0');
|
|
153
|
-
|
|
154
|
-
const variables = {
|
|
155
|
-
date: `${yyyy}-${mm}-${dd}`,
|
|
156
|
-
type,
|
|
157
|
-
pid: process.pid,
|
|
158
|
-
hostname: hostname(),
|
|
159
|
-
};
|
|
160
|
-
|
|
161
|
-
const resolved = template.replace(/\{(\w+)\}/g, (match, key) => {
|
|
162
|
-
return variables[key] !== undefined ? variables[key] : match;
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
// sanitize: prevent path traversal and disallow directory separators
|
|
166
|
-
return resolved.replace(/\.\./g, '').replace(/[/\\]/g, '');
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// attempts to create file and/or directory if they don't already exist
|
|
170
|
-
async validateFileAndDirectory(path, targetLog) {
|
|
171
|
-
const errorMethod = this.error || console.error; // prefer native method unless removed by user
|
|
172
|
-
|
|
173
|
-
mkdirSync(path, { recursive: true });
|
|
174
|
-
|
|
175
|
-
await fsp.access(targetLog).catch(() => {
|
|
176
|
-
fsp.writeFile(targetLog, '').catch(() => {
|
|
177
|
-
errorMethod(`Failed to create log file: ${targetLog}.`
|
|
178
|
-
+ '\n Verify that the application runner has write permissions');
|
|
179
|
-
});
|
|
180
|
-
});
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// method to conditionally sanitize user configuration input
|
|
184
|
-
sanitizePath(path) {
|
|
185
|
-
const moduleDir = projectPath.dirname(fileURLToPath(import.meta.url));
|
|
186
|
-
const delim = moduleDir.includes('node_modules') ? 'node_modules' : 'src';
|
|
187
|
-
const splitDir = moduleDir.split(delim);
|
|
188
|
-
|
|
189
|
-
if (splitDir.length < 2) throw ('Failed to locate your project\'s root directory.');
|
|
190
|
-
|
|
191
|
-
// use project root directory behind path
|
|
192
|
-
path = projectPath.resolve(splitDir[0], path);
|
|
193
|
-
|
|
194
|
-
// force path property to contain a trailing "/"
|
|
195
|
-
if (path[path.length - 1] !== '/') {
|
|
196
|
-
path += '/';
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
return path;
|
|
200
|
-
}
|
|
201
|
-
}
|