@suffolklitlab/alkiln 5.13.3 → 5.13.4
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/.github/workflows/github_server.yml +7 -7
- package/.github/workflows/playground.yml +1 -1
- package/CHANGELOG.md +24 -0
- package/action.yml +70 -28
- package/action_for_github_server/action.yml +61 -29
- package/docs/4_environments.md +243 -0
- package/docs/artifacts_folder_name.md +135 -0
- package/docs/constrained_random_tests.md +320 -0
- package/docs/decisions/006_which_log_level_to_pick_2024_09_5.md +70 -0
- package/docs/decisions/007_getting_and_storing_logs.md +179 -0
- package/docs/decisions/move_runtime_config_to_sources_folder.md +3 -1
- package/docs/decisions/reports_and_logs_2024_08_11.md +161 -0
- package/docs/kinds_of_errors.md +26 -0
- package/lib/docassemble/docassemble_api_interface.js +77 -51
- package/lib/docassemble/server_install.js +32 -9
- package/lib/docassemble/setup.js +40 -25
- package/lib/docassemble/takedown.js +33 -10
- package/lib/parse.js +36 -0
- package/lib/run_cucumber.js +115 -36
- package/lib/scope.js +60 -75
- package/lib/steps.js +70 -42
- package/lib/utils/Log.js +982 -0
- package/lib/utils/files.js +18 -11
- package/lib/utils/get_sources_paths.js +162 -0
- package/lib/utils/parse.js +1 -0
- package/lib/utils/proccess_command_line_args.js +267 -0
- package/lib/utils/reports.js +64 -17
- package/lib/utils/session_vars.js +34 -21
- package/lib/utils/time.js +44 -0
- package/package.json +1 -1
- package/tests/log_codes/check_codes.sh +2 -1
- package/tests/log_codes/log_code_expected_instances.txt +33 -9
- package/tests/unit_tests/getAllFields.test.js +5 -0
- package/tests/unit_tests/getMatchingRows.test.js +5 -0
- package/tests/unit_tests/log.test.js +581 -0
- package/tests/unit_tests/normalizeTable.test.js +5 -0
- package/lib/utils/log.js +0 -96
package/lib/utils/Log.js
ADDED
|
@@ -0,0 +1,982 @@
|
|
|
1
|
+
const fs = require(`fs`);
|
|
2
|
+
const util = require(`node:util`);
|
|
3
|
+
const session_vars = require( `./session_vars` );
|
|
4
|
+
const time = require(`./time`);
|
|
5
|
+
const files = require(`./files`);
|
|
6
|
+
|
|
7
|
+
// ? 🌈 💡 🔎 🤕 🐛
|
|
8
|
+
// Alternatives: 🩹 ❤️🩹 🚑 🖊️
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Future issues:
|
|
12
|
+
* - [ ] Save logs from GitHub actions as well? Users can download them themselves, but this
|
|
13
|
+
* might not be clear to them
|
|
14
|
+
* - [ ] Capture cucumber ProgressFormatter logs instead of making them ourselves?
|
|
15
|
+
* - [ ] keeping report separate for now (can just be an issue to integrate)
|
|
16
|
+
* */
|
|
17
|
+
|
|
18
|
+
class Log {
|
|
19
|
+
/** This class includes methods to save meaningful messages with additional
|
|
20
|
+
* metadata to the debug files while tests run. Most of the rest of `Log`'s
|
|
21
|
+
* methods similarly save to the debug files and also log to the console. We
|
|
22
|
+
* use them only during processes that wrap the core framework code[^1],
|
|
23
|
+
* like setup and takedown. All these outputs let both us and authors debug
|
|
24
|
+
* their interviews, their tests, and this framework's code.
|
|
25
|
+
*
|
|
26
|
+
* Errors: For functionality that wraps core code[^1], `Log` also has a
|
|
27
|
+
* `.throw()` method, which is like a regular log method and then also
|
|
28
|
+
* throws an error. `.throw()` isn't for test failures, though. Meanwhile,
|
|
29
|
+
* `Log` tries to avoid throwing its own errors. That way, authors don't
|
|
30
|
+
* have to deal with this non-critical functionality. We save those errors
|
|
31
|
+
* to the debug files. See the docs about errors.
|
|
32
|
+
*
|
|
33
|
+
* The different log files:
|
|
34
|
+
* - debug_log.txt contains detailed info for internal debugging.
|
|
35
|
+
* - unexpected_results.txt contains detailed messages only about warnings and
|
|
36
|
+
* failures. It will only exist if an unexpected result happens.
|
|
37
|
+
* - To integrate:
|
|
38
|
+
* - report.txt is a pretty file that is only complete at the end of all
|
|
39
|
+
* tests.
|
|
40
|
+
* - report_log.txt is not formatted nicely. It saves the same info as the
|
|
41
|
+
* report while tests are running in case tests end early.
|
|
42
|
+
*
|
|
43
|
+
* Discuss: pros and cons of integrating saving other kinds of files using
|
|
44
|
+
* `Log`, like screenshots, downloaded files, etc.
|
|
45
|
+
*
|
|
46
|
+
* The tests have the most detailed examples.
|
|
47
|
+
*
|
|
48
|
+
* [^1]: Core code is code that handles the Steps that authors write in their
|
|
49
|
+
* `.feature` files.
|
|
50
|
+
* */
|
|
51
|
+
|
|
52
|
+
/** Debug filenames. */
|
|
53
|
+
debug_log_filename = `debug_log.txt`; // Should always exist
|
|
54
|
+
// This is only here so we can add it to the final unexpected results file
|
|
55
|
+
temp_unexpected_debug_log_filename = `temp_unexpected_results_debug_log.txt`; // Only created if needed
|
|
56
|
+
|
|
57
|
+
/** Report filenames. Reports aren't integrated yet. This is a good central
|
|
58
|
+
* location for all this information and for future development. */
|
|
59
|
+
report_log_filename = `report_log.txt`; // Not integrated, but used
|
|
60
|
+
// Regular report filename
|
|
61
|
+
report_filename = `report.txt`; // Not integrated, but used
|
|
62
|
+
|
|
63
|
+
/** Will have all unexpected results - from report and debug logs. */
|
|
64
|
+
unexpected_filename = `unexpected_results.txt`; // Only created if needed
|
|
65
|
+
|
|
66
|
+
constructor({ path, context=`` } = {}) {
|
|
67
|
+
/** Makes sure the right directory exists and instantiates class properties.
|
|
68
|
+
* GitHub actions will pass in a path that may already exist. Other
|
|
69
|
+
* processes, like ALKilnInThePlayground, don't have a path yet.
|
|
70
|
+
*
|
|
71
|
+
* ALKiln runs in multiple different environments that use a different order
|
|
72
|
+
* of operations to run the tests. See the documentation about ALKiln's 4
|
|
73
|
+
* environments. ALKiln's current GitHub composite action must be able to
|
|
74
|
+
* force a specific path for the artifacts folder so that the GitHb
|
|
75
|
+
* setup, run, and takedown process' `Log`s all store their logs in the
|
|
76
|
+
* same directory. This is the GitHub composite action flow:
|
|
77
|
+
*
|
|
78
|
+
* 1. Make a path name.
|
|
79
|
+
* 2. Pass the path to the setup script, which makes a `Log` for the first
|
|
80
|
+
* time using that path name. `Log`, in here creates the folder.
|
|
81
|
+
* 3. Pass the path to the run script, which makes a `Log` using that path
|
|
82
|
+
* name. The folder already exists, so `Log` uses that folder. The run
|
|
83
|
+
* script makes sure the ALKiln core code knows what folder to use.
|
|
84
|
+
* 4. Pass the path to the takedown script, which makes a `Log` using that
|
|
85
|
+
* path name. The `Log` uses that same folder.
|
|
86
|
+
*
|
|
87
|
+
* ALKiln's other environments only use the run script for each test run.
|
|
88
|
+
* The run process tells the core code what directory to use.
|
|
89
|
+
*
|
|
90
|
+
* The tests can only run sequentially right now, not in parallel, so there
|
|
91
|
+
* is no need to worry about locking the file and creating queues for
|
|
92
|
+
* logs from different tests to keep them from getting mixed together.
|
|
93
|
+
*
|
|
94
|
+
* @param { Object } options
|
|
95
|
+
* @param { string } options.path - Optional. The directory where it should
|
|
96
|
+
* store the final logs. We currently only use this argument with GitHub
|
|
97
|
+
* actions.
|
|
98
|
+
* @param { string } options.context - Optional. Info about what is creating
|
|
99
|
+
* this Log. For example, "setup".
|
|
100
|
+
*
|
|
101
|
+
* @example
|
|
102
|
+
* const Log = require("./lib/utils/Log.js");
|
|
103
|
+
* const log = new Log({ path: "_alkiln-123", context: "setup" });
|
|
104
|
+
* */
|
|
105
|
+
|
|
106
|
+
// GitHub actions will pass in a path that may already exist
|
|
107
|
+
if ( path ) { files.make_artifacts_folder( path ); }
|
|
108
|
+
// Other processes, like ALKilnInThePlayground, don't have a path
|
|
109
|
+
else { path = files.make_artifacts_folder(); }
|
|
110
|
+
|
|
111
|
+
this.path = path;
|
|
112
|
+
session_vars.save_artifacts_path_name( this.path );
|
|
113
|
+
|
|
114
|
+
// Has this `Log` ever failed to append to the debug file?
|
|
115
|
+
this.fs_append_to_debug_file_failed = false;
|
|
116
|
+
|
|
117
|
+
// Add `log` to the context and tell the author what the path is
|
|
118
|
+
if ( context ) { context = context + ` log`; }
|
|
119
|
+
else { context = `log`; }
|
|
120
|
+
this.info({ code: `ALK0214`, context, },
|
|
121
|
+
`Saving files to "${ this.path }"`
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
_console( console_opts = {}, ...logs ) {
|
|
126
|
+
/** `_console` should be called internally by other methods of Log. We use it
|
|
127
|
+
* in functionality that wraps around test runs, specifically setup,
|
|
128
|
+
* takedown, run_cucumber (before and after running the tests), and API calls
|
|
129
|
+
* that interact with the server at those times. It writes to the console at
|
|
130
|
+
* different levels and throws errors. If it's not throwing a given error, it
|
|
131
|
+
* returns the combined metadata and the logs as a formatted string.
|
|
132
|
+
*
|
|
133
|
+
* The `_console` method will:
|
|
134
|
+
* 1. Save the data to files with `log.debug()`
|
|
135
|
+
* 2. If `do_throw` is true, write data to the console and throw the error
|
|
136
|
+
* 3. Otherwise, write data to the console at the right level
|
|
137
|
+
* 4. Return a formatted string of the metadata and the given data
|
|
138
|
+
*
|
|
139
|
+
* Flow summary:
|
|
140
|
+
* .debug() ─► write to the console ─► throw or return
|
|
141
|
+
*
|
|
142
|
+
* Complex flow diagram:
|
|
143
|
+
* ┌───────────────────────────────────────┐
|
|
144
|
+
* │ ._console() │
|
|
145
|
+
* ├───────────────────────────────────────┤
|
|
146
|
+
* .throw() ──┐ │ .debug() (all) (formatted logs str) │
|
|
147
|
+
* │ │ get metadata string (all) │
|
|
148
|
+
* │ │ │
|
|
149
|
+
* ├────► get safe Error │
|
|
150
|
+
* ├────► console.error() with extra info │
|
|
151
|
+
* └────► throw Error │
|
|
152
|
+
* .success() ──┐ │ │
|
|
153
|
+
* │ │ │
|
|
154
|
+
* .info() ──╘════► console.info() logs ════════════╗ │
|
|
155
|
+
* │ ║ │
|
|
156
|
+
* .warn() ───────► console.warn() logs─────┐ ║ │
|
|
157
|
+
* │ ▼ ║ │
|
|
158
|
+
* │ return formatted logs string ◄══╝ │
|
|
159
|
+
* └───────────────────────────────────────┘
|
|
160
|
+
*
|
|
161
|
+
* @param { Object } console_opts - Required. Metadata and functional flags
|
|
162
|
+
* and data
|
|
163
|
+
* @param { success|info|warn|error } console_opts.level - Required. The log
|
|
164
|
+
* level. Functional and metadata. Defined by the internal caller.
|
|
165
|
+
* @param { string } console_opts.before - Optional. Text to add before the
|
|
166
|
+
* metadata of the log. Metadata. Default "".
|
|
167
|
+
* @param { string } console_opts.icon - Required. Unicode char put at the
|
|
168
|
+
* start of metadata. Makes the level more easy to see quickly. Defined by
|
|
169
|
+
* the internal caller.
|
|
170
|
+
* @param { string } console_opts.code - Required. Unique consecutive log id
|
|
171
|
+
* with the format ALK####. Defined by the original caller.
|
|
172
|
+
* @param { string } console_opts.context - Optional. Info about the context
|
|
173
|
+
* where the log was called. Metadata. Examples: "setup", "run",
|
|
174
|
+
* "takedown". Default "". Defined by the original caller.
|
|
175
|
+
* @param { Error|any } console_opts.error - Optional. Only include if
|
|
176
|
+
* `do_throw` is true. Otherwise, `error` must be `undefined`. The error
|
|
177
|
+
* or data to throw if `do_throw` is `true`. Before that throw, `_console`
|
|
178
|
+
* will write the before, metadata, and logs. Try to use an actual
|
|
179
|
+
* instance of Error. Otherwise, the stack trace will include this
|
|
180
|
+
* function, which is confusing. Avoid that. Default `undefined`. Defined
|
|
181
|
+
* by the original caller.
|
|
182
|
+
* @param { boolean } console_opts.do_throw - Optional. If `true` caller wants
|
|
183
|
+
* to throw an error error. Default `false`. Defined by the original
|
|
184
|
+
* caller.
|
|
185
|
+
* @param { ...any } logs - Optional. The logs to write to the console. Joined
|
|
186
|
+
* by " " or "\n" depending on the type of the log. Default `undefined`.
|
|
187
|
+
* Defined by the original caller.
|
|
188
|
+
*
|
|
189
|
+
* @returns { string } Formatted metadata, logs, and, if given, the error.
|
|
190
|
+
*
|
|
191
|
+
* @example
|
|
192
|
+
* log._console();
|
|
193
|
+
* // * ALK000c LOG [2024-08-16 00:30:39UTC]:
|
|
194
|
+
*
|
|
195
|
+
* @example
|
|
196
|
+
* log._console({
|
|
197
|
+
* level: "info",
|
|
198
|
+
* icon: "&",
|
|
199
|
+
* code: "ALK00d1",
|
|
200
|
+
* context: "demo",
|
|
201
|
+
* }, "demo log", "demo log 2");
|
|
202
|
+
* // & ALK00d1 demo INFO [2024-08-16 00:30:39UTC]: demo log demo log 2
|
|
203
|
+
*
|
|
204
|
+
* @example
|
|
205
|
+
* // Logged in the console, but not returned
|
|
206
|
+
* try {
|
|
207
|
+
* let causes_error = undefined_value + 5;
|
|
208
|
+
* } catch ( demo_error ) {
|
|
209
|
+
* log._console({
|
|
210
|
+
* level: "error",
|
|
211
|
+
* before: "===\n===\n",
|
|
212
|
+
* icon: "X"
|
|
213
|
+
* code: "ALK00d2"
|
|
214
|
+
* context: "err_demo"
|
|
215
|
+
* error: new Error("This is an error"),
|
|
216
|
+
* do_throw: true,
|
|
217
|
+
* }, "error demo log", { demo: "obj" } );
|
|
218
|
+
* }
|
|
219
|
+
* // ===
|
|
220
|
+
* // ===
|
|
221
|
+
* // X ALK00d2 err_demo ERROR [2024-08-16 00:30:39UTC]: error demo log
|
|
222
|
+
* // {
|
|
223
|
+
* // demo: "obj"
|
|
224
|
+
* // }
|
|
225
|
+
* // Error: This is an error
|
|
226
|
+
* // <the stack trace>
|
|
227
|
+
*
|
|
228
|
+
* For more examples, see the tests.
|
|
229
|
+
* */
|
|
230
|
+
|
|
231
|
+
// It's not any skin off our nose if these values are undefined and I think
|
|
232
|
+
// a warning for that would be too much noise. Internal developers should
|
|
233
|
+
// have enough information to be able to figure out what these mean and
|
|
234
|
+
// correct problems if needed.
|
|
235
|
+
let { // Defaults
|
|
236
|
+
// Metadata
|
|
237
|
+
level = `log`, // Usually defined by the internal caller, e.g. `.warn()`
|
|
238
|
+
before = ``, // Sometimes undefined
|
|
239
|
+
icon = `*`, // Usually defined by the internal caller, e.g. `.warn()`
|
|
240
|
+
code = `ALK000c`, // Usually defined by the original caller
|
|
241
|
+
context = ``, // Sometimes undefined
|
|
242
|
+
// Error functionality
|
|
243
|
+
error, // Sometimes undefined
|
|
244
|
+
do_throw = false, // Sometimes undefined
|
|
245
|
+
} = console_opts;
|
|
246
|
+
|
|
247
|
+
let formatted_str_to_return = this.debug({ level, before, icon, code, context, error, do_throw }, ...logs );
|
|
248
|
+
|
|
249
|
+
let metadata_str = this._format_metadata({ level, icon, code, context });
|
|
250
|
+
|
|
251
|
+
// Throw whatever we can throw
|
|
252
|
+
if ( do_throw ) {
|
|
253
|
+
error = this._get_Error_safely({ maybe_error: error });
|
|
254
|
+
// WARNING: If someone catches the error we throw, the console logs that
|
|
255
|
+
// get logged here before catching will be confusing.
|
|
256
|
+
try {
|
|
257
|
+
logs = logs || [];
|
|
258
|
+
console.error( `${before}${metadata_str}:`, ...logs );
|
|
259
|
+
} catch ( pre_throw_error ) {
|
|
260
|
+
this.debug({
|
|
261
|
+
icon: `🖊️`, code: `ALK0215`, context: `internal log`, level: `note`,
|
|
262
|
+
}, `Skipped logging metadata before throwing the error`, pre_throw_error );
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if ( error instanceof Error ) {
|
|
266
|
+
throw error;
|
|
267
|
+
} else {
|
|
268
|
+
// non-error messages will get in here if do_throw is `true` Ensure the
|
|
269
|
+
// call stack gets created at this point.
|
|
270
|
+
// Discuss: Should we include the metadata in this error since we don't
|
|
271
|
+
// need to preserve a stack trace? That's inconsistent, though.
|
|
272
|
+
throw new Error( error );
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Avoid using custom-formatted logs and error here.
|
|
277
|
+
// Let the console show them as it wants.
|
|
278
|
+
try {
|
|
279
|
+
// log at the given level if console has that method (`log` by default).
|
|
280
|
+
// If a non-standard level was passed in, like `success`, use `info` by
|
|
281
|
+
// default.
|
|
282
|
+
if ( !console[ level ]) { level = `info`; }
|
|
283
|
+
console[ level ]( `${before}${metadata_str}:`, ...logs );
|
|
284
|
+
|
|
285
|
+
} catch ( console_log_error ) {
|
|
286
|
+
// Fail silently
|
|
287
|
+
this.debug({
|
|
288
|
+
icon: `🖊️`, code: `ALK0211`, context: `internal log`, level: `note`,
|
|
289
|
+
}, `Skipped saving a ${ level } message to the debug log`, console_log_error );
|
|
290
|
+
} finally {
|
|
291
|
+
return formatted_str_to_return;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
debug( debug_opts = {}, ...logs ) {
|
|
296
|
+
/** Saves metadata and any logs in the debug file. When appropriate, also
|
|
297
|
+
* saves to the unexpected results file. Includes an Error if needed.
|
|
298
|
+
* Returns a formatted string version of the log with its metadata. Avoids
|
|
299
|
+
* throwing internal errors while giving the max info possible. Avoids
|
|
300
|
+
* writing to the visible console. Every log should come through here.
|
|
301
|
+
*
|
|
302
|
+
* Tries to make saved logs look as similar as possible to console logs.
|
|
303
|
+
*
|
|
304
|
+
* `.debug()` has the same signature as `._console()`, but saves to files
|
|
305
|
+
* instead of writing to the console.
|
|
306
|
+
*
|
|
307
|
+
* @params - Similar to `._console()`, except we expect to use `.debug()`
|
|
308
|
+
* directly as well as internally, while we expect to only use
|
|
309
|
+
* `._console()` internally.
|
|
310
|
+
*
|
|
311
|
+
* @returns { string } Same as `._console()`
|
|
312
|
+
*
|
|
313
|
+
* Discuss: Log warnings with stack trace? function stackTrace() { return
|
|
314
|
+
* (new Error()).stack; } and https://stackoverflow.com/a/41820537/14144258
|
|
315
|
+
* var mystring=require("util").inspect(error_object);
|
|
316
|
+
*
|
|
317
|
+
* Flow:
|
|
318
|
+
* get metadata
|
|
319
|
+
* get formatted logs as string (maybe with error)
|
|
320
|
+
* combine those
|
|
321
|
+
* save those to debug log
|
|
322
|
+
* maybe save those to unexpected results log
|
|
323
|
+
* return the formatted string
|
|
324
|
+
*
|
|
325
|
+
* @example
|
|
326
|
+
* log.debug()
|
|
327
|
+
* // 🐛 ALK000d DEBUG [2024-08-16 00:38:49UTC]:
|
|
328
|
+
* */
|
|
329
|
+
|
|
330
|
+
let {
|
|
331
|
+
level= `debug`,
|
|
332
|
+
before = ``,
|
|
333
|
+
icon = `🐛`,
|
|
334
|
+
code = `ALK000d`,
|
|
335
|
+
context = ``,
|
|
336
|
+
error, // Sometimes undefined
|
|
337
|
+
do_throw = false,
|
|
338
|
+
} = debug_opts;
|
|
339
|
+
|
|
340
|
+
// "warning" level and icon for for internal devs we use to avoid disrupting
|
|
341
|
+
// the info authors are looking for
|
|
342
|
+
if ( level === `note` ) { icon = `🖊️`; }
|
|
343
|
+
|
|
344
|
+
let formatted = `Skipped formatting all logs.`;
|
|
345
|
+
try {
|
|
346
|
+
|
|
347
|
+
let metadata_str = this._format_metadata({ level, icon, code, context });
|
|
348
|
+
|
|
349
|
+
// Get the whole formatted log message
|
|
350
|
+
formatted = `${ metadata_str }: Skipped formatting logs.`;
|
|
351
|
+
let logging_errors_msgs = [];
|
|
352
|
+
try {
|
|
353
|
+
|
|
354
|
+
// Use `with_error` to include the error in the debug and unexpected
|
|
355
|
+
// results logs
|
|
356
|
+
let with_error = this._combine_logs_and_possible_error({
|
|
357
|
+
logs, maybe_error: error, do_throw,
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
let stringified = this._stringify_logs( ...with_error );
|
|
361
|
+
|
|
362
|
+
// Question: Does `formatted` double log the error in the console?
|
|
363
|
+
// Answer: `._console` doesn't use `formatted`, just returns it.
|
|
364
|
+
formatted = `${ before }${ metadata_str }: ${ stringified }`;
|
|
365
|
+
} catch ( formatted_log_error ) {
|
|
366
|
+
formatted = `🖊️ ALK0212 internal debug NOTE [${ Date.now() }]: Skipped formatting debug logs.`;
|
|
367
|
+
this._save_to_debug_safely({ unexpected: true, text: formatted });
|
|
368
|
+
this._save_to_debug_safely({ unexpected: true, text: formatted_log_error.stack });
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
let unexpected = `warn error note`.includes( level );
|
|
372
|
+
this._save_to_debug_safely({ unexpected, text: formatted });
|
|
373
|
+
|
|
374
|
+
} catch ( debug_error ) {
|
|
375
|
+
formatted = `🖊️ ALK0217 internal debug NOTE [${ Date.now() }]: Skipped log.debug()\n${ debug_error.stack }`;
|
|
376
|
+
this._save_to_debug_safely({ unexpected: true, text: formatted });
|
|
377
|
+
this._save_to_debug_safely({ unexpected: true, text: debug_error.stack });
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
return formatted;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
_format_metadata( metadata_opts = {} ) {
|
|
384
|
+
/** Formats and returns the log's metadata. Catch ANY error.
|
|
385
|
+
*
|
|
386
|
+
* @param { Object } metadata_opts - Required. Custom options for metadata
|
|
387
|
+
* @param { string } metadata_opts.level - Required. Level of the log (info,
|
|
388
|
+
* warn, etc).
|
|
389
|
+
* @param { string } metadata_opts.icon - Required. Icon that makes the
|
|
390
|
+
* level of the log more visible.
|
|
391
|
+
* @param { string } metadata_opts.code - Required. The unique id of the
|
|
392
|
+
* log.
|
|
393
|
+
* @param { string } metadata_opts.context - Optional. Default is `""`. More
|
|
394
|
+
* context about the caller. For example, "setup" or "takedown".
|
|
395
|
+
*
|
|
396
|
+
* @returns { string } The log's metadata.
|
|
397
|
+
*
|
|
398
|
+
* @example
|
|
399
|
+
* log._format_metadata({
|
|
400
|
+
* level: "fax",
|
|
401
|
+
* icon: "%",
|
|
402
|
+
* code: "ALK00d7",
|
|
403
|
+
* context: "demo"
|
|
404
|
+
* });
|
|
405
|
+
* // % ALK00d7 demo FAX [2024-08-16 00:30:39UTC]
|
|
406
|
+
* */
|
|
407
|
+
let {
|
|
408
|
+
level = `log`,
|
|
409
|
+
icon = `?`,
|
|
410
|
+
code = `ALK000m`,
|
|
411
|
+
context = ``,
|
|
412
|
+
} = metadata_opts;
|
|
413
|
+
|
|
414
|
+
let metadata_str = `Formatted log string placeholder: `;
|
|
415
|
+
try {
|
|
416
|
+
|
|
417
|
+
// For elapsed time, see
|
|
418
|
+
// https://nodejs.org/api/console.html#consoletimelabel
|
|
419
|
+
// Should this metadata use a raw timestamp?
|
|
420
|
+
|
|
421
|
+
// Transform the level name if necessary
|
|
422
|
+
let metadata_level = level;
|
|
423
|
+
if ( level === `warn` ) { metadata_level = `warning`; }
|
|
424
|
+
let level_caps = metadata_level.toUpperCase();
|
|
425
|
+
// Ignore items that are empty strings before combining everything
|
|
426
|
+
let metadata_list = [];
|
|
427
|
+
for ( let item of [ icon, code, context, level_caps, `[${ time.log_date() }]` ]) {
|
|
428
|
+
if ( item !== `` ) { metadata_list.push(item); }
|
|
429
|
+
}
|
|
430
|
+
metadata_str = metadata_list.join(` `);
|
|
431
|
+
|
|
432
|
+
} catch ( metadata_error ) {
|
|
433
|
+
this._save_to_debug_safely({ unexpected: true, text: `🖊️ ALK0219 internal log metadata NOTE [${ Date.now() }]: Skipped creating metadata.` });
|
|
434
|
+
this._save_to_debug_safely({ unexpected: true, text: metadata_error.stack });
|
|
435
|
+
}
|
|
436
|
+
return metadata_str;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
_stringify_logs(...logs) {
|
|
440
|
+
/** Returns a string of the combined and formatted version of the list of
|
|
441
|
+
* logs by stringifying non-strings and joining all lots with separators.
|
|
442
|
+
* An error will show its full stack. A bit different than `console.log()`.
|
|
443
|
+
*
|
|
444
|
+
* @param { ...string } - Optional. Default is `""`. Logs to combine.
|
|
445
|
+
*
|
|
446
|
+
* @returns { string } Combined and formatted logs.
|
|
447
|
+
*
|
|
448
|
+
* @example
|
|
449
|
+
* log._stringify_logs("apples", "oranges", { obj_log: "bananas" }, "bears");
|
|
450
|
+
* // apples oranges
|
|
451
|
+
* // { obj_log: 'bananas' }
|
|
452
|
+
* // bears
|
|
453
|
+
* */
|
|
454
|
+
|
|
455
|
+
let stringified_logs = ``;
|
|
456
|
+
let prev_line_type = null;
|
|
457
|
+
|
|
458
|
+
try {
|
|
459
|
+
for ( let log of logs ) {
|
|
460
|
+
// Consider refactor:
|
|
461
|
+
// let start = _start_of_stringified_log({ prev_type, log })
|
|
462
|
+
// stringified_logs = start + inspect(...) or start + _get_safe_val() or ???
|
|
463
|
+
|
|
464
|
+
if ( log === null || typeof log !== `object` ) {
|
|
465
|
+
stringified_logs += this._stringify_inline({ prev_line_type, inline_log: log });
|
|
466
|
+
prev_line_type = `inline`;
|
|
467
|
+
|
|
468
|
+
} else {
|
|
469
|
+
// Objects, including errors
|
|
470
|
+
stringified_logs += this._stringify_object({ prev_line_type, obj_log: log });
|
|
471
|
+
prev_line_type = `block`;
|
|
472
|
+
} // ends typeof log
|
|
473
|
+
} // Ends for logs
|
|
474
|
+
} catch ( stringify_error ) {
|
|
475
|
+
|
|
476
|
+
try {
|
|
477
|
+
// Discuss: Should the above `stringify_error` get recorded even when
|
|
478
|
+
// `this._inspect()` works?
|
|
479
|
+
stringified_logs = this._inspect({ value: logs });
|
|
480
|
+
} catch ( util_error ) {
|
|
481
|
+
if ( stringified_logs.length > 0 ) {
|
|
482
|
+
stringified_logs += `Skipped stringifying logs. Got:\n${ stringified_logs }\n${ util_error.stack }\n${ stringify_error.stack }`;
|
|
483
|
+
} else {
|
|
484
|
+
stringified_logs = `Skipped stringifying logs.\n${ util_error.stack }\n${ stringify_error.stack }`;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
return stringified_logs;
|
|
491
|
+
} // Ends Log._stringify_logs()
|
|
492
|
+
|
|
493
|
+
_stringify_inline({ prev_line_type, inline_log }) {
|
|
494
|
+
/** Formats the start of an inline log based on the previous log and returns
|
|
495
|
+
* that combined string.
|
|
496
|
+
*
|
|
497
|
+
* @param { "inline"|"block"|null } prev_line_type - Required. The type of
|
|
498
|
+
* the previous log.
|
|
499
|
+
* @param { null|non-obj } inline_log - Required. The log.
|
|
500
|
+
*
|
|
501
|
+
* @returns { string } The formatted log.
|
|
502
|
+
*
|
|
503
|
+
* @example
|
|
504
|
+
* // Backticks are to make the surrounding white space more clear
|
|
505
|
+
* log._stringify_inline({ prev_line_type: null, inline_log: "apple" });
|
|
506
|
+
* // `apple`
|
|
507
|
+
* log._stringify_inline({ prev_line_type: 'inline', inline_log: "apple" });
|
|
508
|
+
* // ` apple`
|
|
509
|
+
* log._stringify_inline({ prev_line_type: 'block', inline_log: "apple" });
|
|
510
|
+
* // `
|
|
511
|
+
* // apple`
|
|
512
|
+
* */
|
|
513
|
+
// An inline log's string starts with nothing if it's the first string...
|
|
514
|
+
let start = ``;
|
|
515
|
+
// ...starts after a space if its next to an inline string...
|
|
516
|
+
if ( prev_line_type === `inline` ) { start = ` `; }
|
|
517
|
+
// ...or starts on a new line if it's after a block of text, like an error
|
|
518
|
+
// or object.
|
|
519
|
+
else if ( prev_line_type === `block` ) { start = `\n`; }
|
|
520
|
+
return `${ start }${ inline_log }`;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
_stringify_object({ prev_line_type, obj_log }) {
|
|
524
|
+
/** Return a string - either the object as a string or a default value with
|
|
525
|
+
* a "block"-type separator at the start. Can handle circular references.
|
|
526
|
+
* Catch ANY error.
|
|
527
|
+
*
|
|
528
|
+
* @param { Object } obj_log - Required. The object to format.
|
|
529
|
+
*
|
|
530
|
+
* @returns { string } The obj as a string plus a separator
|
|
531
|
+
*
|
|
532
|
+
* @example
|
|
533
|
+
* // Quotes are to make the surrounding white space more clear
|
|
534
|
+
* log._stringify_object({ obj_log: { fruit: "bananas" } });
|
|
535
|
+
* //
|
|
536
|
+
* // { fruit: 'bananas' }
|
|
537
|
+
* */
|
|
538
|
+
let start = ``;
|
|
539
|
+
if ( prev_line_type === `inline` || prev_line_type === `block` ) {
|
|
540
|
+
start = `\n`;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
try {
|
|
544
|
+
// Try to stringify the object.
|
|
545
|
+
return `${ start }${ this._inspect({ value: obj_log }) }`;
|
|
546
|
+
|
|
547
|
+
// Discuss: record these errors?
|
|
548
|
+
} catch ( outer_error ) {
|
|
549
|
+
// If that fails, see if it has its own way of stringifying
|
|
550
|
+
try {
|
|
551
|
+
return `${ start }${ obj_log }`;
|
|
552
|
+
} catch ( inner_error ) {
|
|
553
|
+
// If everything else fails, give at least some information
|
|
554
|
+
return `${ start }Skipped logging this ${ typeof obj_log } which is an instance of ${ obj_log.constructor.name }`;
|
|
555
|
+
} // ends inner try
|
|
556
|
+
} // ends outer try
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
_combine_logs_and_possible_error({ logs=[], do_throw=false, maybe_error }) {
|
|
560
|
+
/** Returns a list of combined values to be logged. If we need to include
|
|
561
|
+
* an error, ensure it's an error. Catch ANY internal errors.
|
|
562
|
+
*
|
|
563
|
+
* @param { array } logs - Required. List of logs.
|
|
564
|
+
* @param { boolean } do_throw - Optional. Default is `false`. If `true`
|
|
565
|
+
* caller wants to throw an error.
|
|
566
|
+
* @param { Error|undefined } maybe_error - Optional. Default is
|
|
567
|
+
* `undefined`. A value that might be. turned into an Error and is
|
|
568
|
+
* definitely _safe_ to turn into an Error. For example, no circular
|
|
569
|
+
* references.
|
|
570
|
+
*
|
|
571
|
+
* @returns { array } List of all values that should be logged.
|
|
572
|
+
*
|
|
573
|
+
* @example
|
|
574
|
+
* let with_error = log._combine_logs_and_possible_error({
|
|
575
|
+
* [ "a", "b" ], do_throw: true, maybe_error: "A string."
|
|
576
|
+
* });
|
|
577
|
+
* // [ "a", "b", <An error with its stack and the message "A string."> ]
|
|
578
|
+
* @example
|
|
579
|
+
* let with_error = log._combine_logs_and_possible_error({
|
|
580
|
+
* [ "a", "b" ], do_throw: false, maybe_error: new Error("An error.")
|
|
581
|
+
* });
|
|
582
|
+
* // [ "a", "b" ]
|
|
583
|
+
* */
|
|
584
|
+
let combined = [ ...logs ];
|
|
585
|
+
try {
|
|
586
|
+
// If we're supposed to throw, make sure the error gets an Error value.
|
|
587
|
+
if ( do_throw ) {
|
|
588
|
+
combined.push( this._get_Error_safely({ maybe_error }) );
|
|
589
|
+
|
|
590
|
+
// Otherwise error should be `undefined`. If not, we get a warning.
|
|
591
|
+
} else if ( maybe_error !== undefined ) {
|
|
592
|
+
this._save_to_debug_safely({ unexpected: true, text: `🖊️ ALK0223 internal debug NOTE [${ Date.now() }]: Only logs that throw should give a value to \`error\`. Skipping the value.` });
|
|
593
|
+
}
|
|
594
|
+
} catch ( combining_error_error ) {
|
|
595
|
+
this._save_to_debug_safely({ unexpected: true, text: `🖊️ ALK0226 internal debug NOTE [${ Date.now() }]: Skipped combining the logs.` });
|
|
596
|
+
this._save_to_debug_safely({ unexpected: true, text: combining_error_error.stack });
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
return combined;
|
|
600
|
+
} // Ends _combine_logs_and_possible_error()
|
|
601
|
+
|
|
602
|
+
_get_Error_safely({ maybe_error }) {
|
|
603
|
+
/** Given anything, return an Error. Avoid throwing any errors.
|
|
604
|
+
*
|
|
605
|
+
* @param { Object } opts
|
|
606
|
+
* @param { * } opts.maybe_error - Optional. Can be anything.
|
|
607
|
+
*
|
|
608
|
+
* @returns { Error }
|
|
609
|
+
* */
|
|
610
|
+
if ( maybe_error instanceof Error ) {
|
|
611
|
+
return maybe_error;
|
|
612
|
+
} else {
|
|
613
|
+
if ( this._json_does_error( maybe_error )) {
|
|
614
|
+
maybe_error = this._get_inspected_val_safely( maybe_error );
|
|
615
|
+
}
|
|
616
|
+
// The stack trace will be less helpful, but it's all we can do if the
|
|
617
|
+
// caller hasn't already created the error.
|
|
618
|
+
return new Error( maybe_error );
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
_json_does_error( risky_value ) {
|
|
623
|
+
try {
|
|
624
|
+
|
|
625
|
+
// Possibly trigger an error (e.g. circular reference error) (new Error()
|
|
626
|
+
// with a circular reference can't be caught, it seems)
|
|
627
|
+
JSON.stringify( risky_value );
|
|
628
|
+
return false; // Did not trigger an error
|
|
629
|
+
|
|
630
|
+
} catch ( maybe_error_stringify_error ) {
|
|
631
|
+
this._save_to_debug_safely({ unexpected: true, text: `🖊️ ALK0224 internal log NOTE [${ Date.now() }]: Skipped JSON.stringify().` });
|
|
632
|
+
this._save_to_debug_safely({ unexpected: true, text: maybe_error_stringify_error.stack });
|
|
633
|
+
return true; // Triggered an error
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
_get_inspected_val_safely( risky_value ) {
|
|
638
|
+
|
|
639
|
+
let safe_val = `Skipped safe val.`;
|
|
640
|
+
try {
|
|
641
|
+
// If stringifying did create an error, try modifying it to make it
|
|
642
|
+
// safe. Not as good as the original, but better than nothing.
|
|
643
|
+
let inspected = this._inspect({ value: risky_value });
|
|
644
|
+
safe_val = inspected;
|
|
645
|
+
|
|
646
|
+
} catch ( risky_val_inspect_error ) {
|
|
647
|
+
this._save_to_debug_safely({ unexpected: true, text: `🖊️ ALK0222 internal log NOTE [${ Date.now() }]: Skipped inspecting risky value.` });
|
|
648
|
+
this._save_to_debug_safely({ unexpected: true, text: risky_val_inspect_error.stack });
|
|
649
|
+
// If even that failed, we can only return a vague message
|
|
650
|
+
safe_val = `ALKiln was unable to parse the value of \`error\`. Skipped parsing it.`;
|
|
651
|
+
|
|
652
|
+
} // ends try inspect
|
|
653
|
+
|
|
654
|
+
return safe_val;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
_inspect({ value }) {
|
|
658
|
+
try {
|
|
659
|
+
return util.inspect( value, { depth: 8, maxArrayLength: null, maxStringLength: null, });
|
|
660
|
+
} catch ( direct_inspect_err ) {
|
|
661
|
+
return `🖊️ ALK0228 inspect log NOTE [${ Date.now() }]: Skipped util.inspect. ${ direct_inspect_err.stack }`;
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
/** Note: Functions that call `._console()` like those below only get used by
|
|
666
|
+
* setup, takedown, and run_cucumber, or a few exceptions, like unexpected
|
|
667
|
+
* behavior that gets in here that we cannot record any other way. */
|
|
668
|
+
|
|
669
|
+
success( success_opts = {}, ...logs ) {
|
|
670
|
+
/** Log a major successful operation to the console using additional
|
|
671
|
+
* metadata and the given logs. Use it sparingly. Use it in test suite
|
|
672
|
+
* wrapper functionality, like setup and takedown.
|
|
673
|
+
*
|
|
674
|
+
* @param success_opts { Object }
|
|
675
|
+
* @param success_opts.code { string } - Required. Unique log id of the
|
|
676
|
+
* format "ALK####", where "#" is a digit.
|
|
677
|
+
* @param success_opts.before { string } - Optional. Default is `""`.
|
|
678
|
+
* Decoration to log before the metadata.
|
|
679
|
+
* @param success_opts.context { string } - Optional. Default is `""`. Info
|
|
680
|
+
* about the origin of the logs.
|
|
681
|
+
* @param logs { ...string } - Optional. Default is `undefined`. Explanation
|
|
682
|
+
* of what happened.
|
|
683
|
+
*
|
|
684
|
+
* @returns { string } Formatted metadata, logs, and, if given, the error.
|
|
685
|
+
*
|
|
686
|
+
* @example
|
|
687
|
+
* log.warn({
|
|
688
|
+
* before: "~~~",
|
|
689
|
+
* code: "ALK00d3",
|
|
690
|
+
* context: "demo",
|
|
691
|
+
* }, "demo log");
|
|
692
|
+
* // ~~~🌈 ALK00d3 demo SUCCESS [2024-08-16 00:30:39UTC]: demo log
|
|
693
|
+
* */
|
|
694
|
+
// Discuss: do we want to log some successes as "💡 ... SUCCESS: " for
|
|
695
|
+
// smaller successful operations along the way?
|
|
696
|
+
let {
|
|
697
|
+
code = `ALK000s`, before = ``, context = ``
|
|
698
|
+
} = success_opts;
|
|
699
|
+
return this._console({
|
|
700
|
+
level: `success`, icon: `🌈`,
|
|
701
|
+
before, code, context,
|
|
702
|
+
}, ...logs);
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
info( info_opts = {}, ...logs ) {
|
|
706
|
+
/** Log informational messages to the console using additional metadata and
|
|
707
|
+
* the given logs. Use it in test suite wrapper functionality, like setup
|
|
708
|
+
* and takedown.
|
|
709
|
+
*
|
|
710
|
+
* @param info_opts { Object }
|
|
711
|
+
* @param info_opts.code { string } - Required. Unique log id of the format
|
|
712
|
+
* "ALK####", where "#" is a digit.
|
|
713
|
+
* @param info_opts.before { string } - Optional. Default is `""`.
|
|
714
|
+
* Decoration to log before the metadata.
|
|
715
|
+
* @param info_opts.context { string } - Optional. Default is `""`. Info
|
|
716
|
+
* about the origin of the logs.
|
|
717
|
+
* @param logs { ...string } - Optional. Default is `undefined`. Explanation
|
|
718
|
+
* of what happened.
|
|
719
|
+
*
|
|
720
|
+
* @returns { string } Formatted metadata, logs, and, if given, the error.
|
|
721
|
+
*
|
|
722
|
+
* @example
|
|
723
|
+
* log.warn({
|
|
724
|
+
* before: "~~~",
|
|
725
|
+
* code: "ALK00d4",
|
|
726
|
+
* context: "demo",
|
|
727
|
+
* }, "demo log");
|
|
728
|
+
* // ~~~💡 ALK00d4 demo INFO [2024-08-16 00:30:39UTC]: demo log
|
|
729
|
+
* */
|
|
730
|
+
let {
|
|
731
|
+
code = `ALK000i`, before = ``, context = ``,
|
|
732
|
+
} = info_opts;
|
|
733
|
+
return this._console({
|
|
734
|
+
level: `info`, icon: `💡`,
|
|
735
|
+
before, code, context,
|
|
736
|
+
}, ...logs);
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
warn( warning_opts = {}, ...logs ) {
|
|
740
|
+
/** Log warnings to the console using additional metadata and the given
|
|
741
|
+
* logs. Use it in test suite wrapper functionality, like setup and
|
|
742
|
+
* takedown.
|
|
743
|
+
*
|
|
744
|
+
* @param warning_opts { Object }
|
|
745
|
+
* @param warning_opts.code { string } - Required. Unique log id of the
|
|
746
|
+
* format "ALK####", where "#" is a digit.
|
|
747
|
+
* @param warning_opts.before { string } - Optional. Default is `""`.
|
|
748
|
+
* Decoration to log before the metadata.
|
|
749
|
+
* @param warning_opts.context { string } - Optional. Default is `""`. Info
|
|
750
|
+
* about the origin of the logs.
|
|
751
|
+
* @param logs { ...string } - Optional. Default is `undefined`. Explanation
|
|
752
|
+
* of what happened.
|
|
753
|
+
*
|
|
754
|
+
* @returns { string } Formatted metadata, logs, and, if given, the error.
|
|
755
|
+
*
|
|
756
|
+
* @example
|
|
757
|
+
* log.warn({
|
|
758
|
+
* before: "~~~",
|
|
759
|
+
* code: "ALK00d5",
|
|
760
|
+
* context: "demo",
|
|
761
|
+
* }, "demo log");
|
|
762
|
+
* // ~~~🔎 ALK00d5 demo WARNING [2024-08-16 00:30:39UTC]: demo log
|
|
763
|
+
* */
|
|
764
|
+
let {
|
|
765
|
+
code = `ALK000w`, before = ``, context = ``
|
|
766
|
+
} = warning_opts;
|
|
767
|
+
return this._console({
|
|
768
|
+
level: `warn`, icon: `🔎`,
|
|
769
|
+
before, code, context,
|
|
770
|
+
}, ...logs);
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
throw( throw_opts = {}, ...logs ){
|
|
774
|
+
/** Throw the given error option after writing metadata and logs to the
|
|
775
|
+
* console. Use it in test suite wrapper functionality, like setup and
|
|
776
|
+
* takedown. Helps us give more meaningful error messages and logs.
|
|
777
|
+
*
|
|
778
|
+
* @param throw_opts { Object }
|
|
779
|
+
* @param throw_opts.code { string } - Required. Unique log id of the format
|
|
780
|
+
* "ALK####", where "#" is a digit.
|
|
781
|
+
* @param throw_opts.before { string } - Optional. Default is `""`.
|
|
782
|
+
* Decoration to log before the metadata.
|
|
783
|
+
* @param throw_opts.context { string } - Optional. Default is `""`. Info
|
|
784
|
+
* about the origin of the logs.
|
|
785
|
+
* @param throw_opts.error { Error|any } - Required. Ideally, an `Error`. An
|
|
786
|
+
* `Error` keeps the stack trace from the line where the code created
|
|
787
|
+
* it, so the error message will be more clear. If this value is not an
|
|
788
|
+
* `Error`, `Log` will turn it into an `Error` before throwing it, which
|
|
789
|
+
* will make the stack trace harder to read.
|
|
790
|
+
* @param logs { ...string } - Optional. Default is `undefined`. Explanation
|
|
791
|
+
* of what happened.
|
|
792
|
+
*
|
|
793
|
+
* @returns { None } Throws an error, no return value.
|
|
794
|
+
*
|
|
795
|
+
* @example
|
|
796
|
+
* try {
|
|
797
|
+
* let causes_error = undefined_value + 5;
|
|
798
|
+
* } catch ( demo_error ) {
|
|
799
|
+
* log.throw({
|
|
800
|
+
* code: "ALK00d6"
|
|
801
|
+
* context: "err_demo"
|
|
802
|
+
* error: demo_error,
|
|
803
|
+
* }, "error demo log" );
|
|
804
|
+
* }
|
|
805
|
+
* // Logged in the console, but not returned
|
|
806
|
+
* // 🤕 ALK00d6 err_demo ERROR [2024-08-16 00:30:39UTC]: error demo log
|
|
807
|
+
* // Uncaught ReferenceError: undefined_value is not defined
|
|
808
|
+
* // <the stack trace>
|
|
809
|
+
* */
|
|
810
|
+
let {
|
|
811
|
+
code = `ALK000t`, before = ``, context = ``, error = `ALKiln default error`,
|
|
812
|
+
} = throw_opts;
|
|
813
|
+
|
|
814
|
+
this._console({
|
|
815
|
+
level: `error`, icon: `🤕`, do_throw: true,
|
|
816
|
+
before, code, context, error,
|
|
817
|
+
}, ...logs);
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
_save_to_debug_safely( save_debug_opts = {} ) {
|
|
821
|
+
/** Save to the debug file.
|
|
822
|
+
*
|
|
823
|
+
* @param { Object } save_debug_opts
|
|
824
|
+
* @param { string } save_debug_opts.text - Optional. Default is `""`. Str
|
|
825
|
+
* to save to the debug file.
|
|
826
|
+
* @param { boolean } save_debug_opts.unexpected - Optional. Default is
|
|
827
|
+
* false. Whether or not to save to the unexpected results file.
|
|
828
|
+
*
|
|
829
|
+
* @returns { string } The same value as given.
|
|
830
|
+
* */
|
|
831
|
+
let { text = ``, unexpected = false } = save_debug_opts;
|
|
832
|
+
|
|
833
|
+
// If we have ever failed to append to the debug log in the past, avoid
|
|
834
|
+
// doing so again and avoid logging an error message about it again.
|
|
835
|
+
try {
|
|
836
|
+
if ( this.fs_append_to_debug_file_failed ) {
|
|
837
|
+
console.info( text );
|
|
838
|
+
return text;
|
|
839
|
+
}
|
|
840
|
+
} catch ( console_log_failed_error ) {
|
|
841
|
+
this._warn_unable_to_log( console_log_failed_error );
|
|
842
|
+
return text;
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
try {
|
|
846
|
+
fs.appendFileSync( `${ this.path }/${ this.debug_log_filename }`, text + `\n` );
|
|
847
|
+
} catch ( fs_append_error ) {
|
|
848
|
+
// If this is our first time failing to write to the debug log, console
|
|
849
|
+
// warn about it.
|
|
850
|
+
this.fs_append_to_debug_file_failed = true;
|
|
851
|
+
console.warn(`🖊️ ALK0213 internal debug NOTE [${ Date.now() }]: Skipped appending to debug file.`);
|
|
852
|
+
console.warn( fs_append_error );
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
try {
|
|
856
|
+
if ( unexpected ) {
|
|
857
|
+
this._save_to_temp_unexpected_file({ text });
|
|
858
|
+
}
|
|
859
|
+
} catch ( error_sending_to_unexpected ) {
|
|
860
|
+
// Do nothing. These values will be in the debug log
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
return text;
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
_warn_unable_to_log( final_error ) {
|
|
867
|
+
/** The last ditch effort to leave a clue that something went horribly
|
|
868
|
+
* wrong.
|
|
869
|
+
*
|
|
870
|
+
* @param { any } final_error - Anything that can be logged to the console.
|
|
871
|
+
* */
|
|
872
|
+
try {
|
|
873
|
+
console.warn(
|
|
874
|
+
`🖊️ ALK0229 internal debug NOTE [${ Date.now() }]:`,
|
|
875
|
+
`Skipped logging data to console.`,
|
|
876
|
+
final_error
|
|
877
|
+
);
|
|
878
|
+
} catch ( not_even_console_can_handle_it ) {
|
|
879
|
+
// Nothing to do about it
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
_save_to_temp_unexpected_file({ text = `` } = {}) {
|
|
884
|
+
/** Save to the unexpected results file. Intended for errors and warnings.
|
|
885
|
+
* Catch ANY error.
|
|
886
|
+
*
|
|
887
|
+
* @param { Object } options
|
|
888
|
+
* @param { string } options.text - Optional. Default is `""`. Str to save
|
|
889
|
+
* to the unexpected results file.
|
|
890
|
+
*
|
|
891
|
+
* @returns { string } The same value as given.
|
|
892
|
+
* */
|
|
893
|
+
try {
|
|
894
|
+
fs.appendFileSync(`${ this.path }/${ this.temp_unexpected_debug_log_filename }`, `${ text }\n` );
|
|
895
|
+
} catch ( error_saving_to_unexpected_results_file ) {
|
|
896
|
+
// Do nothing. At worst, we'll get the data in the debug file or console .
|
|
897
|
+
}
|
|
898
|
+
return text;
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
/** `stdout` is currently for test progress summary and reports. */
|
|
902
|
+
|
|
903
|
+
stdout(stdout_opts = {}, ...logs) {
|
|
904
|
+
/** Prints in the console inline and saves to the debug log and, in future
|
|
905
|
+
* the report log. Use for progress summary during test runs and for
|
|
906
|
+
* reports. Logs are joined with no separator.
|
|
907
|
+
*
|
|
908
|
+
* @param { Object } stdout_opts
|
|
909
|
+
* @param { boolean } stdout_opts.records_only - Optional. Default is
|
|
910
|
+
* `false`. Whether to only save logs without printing them to the
|
|
911
|
+
* console. E.g. progress summary characters like those that cucumber is
|
|
912
|
+
* already printing to the console. logs { ...string } - Optional. Each
|
|
913
|
+
* argument must be a string.
|
|
914
|
+
* @param logs { ...string } - Optional. Default is `undefined`. Text to
|
|
915
|
+
* print.
|
|
916
|
+
*
|
|
917
|
+
* @returns { string } String of combined strings.
|
|
918
|
+
*
|
|
919
|
+
* @example
|
|
920
|
+
* log.stdout({ records_only: false }, ".", "." );
|
|
921
|
+
* // ..
|
|
922
|
+
* */
|
|
923
|
+
let {
|
|
924
|
+
records_only = false,
|
|
925
|
+
} = stdout_opts;
|
|
926
|
+
let whole_log = logs.join(``);
|
|
927
|
+
|
|
928
|
+
try {
|
|
929
|
+
|
|
930
|
+
// Write to the console
|
|
931
|
+
if ( !records_only ) {
|
|
932
|
+
process.stdout.write( whole_log );
|
|
933
|
+
}
|
|
934
|
+
// Write to the debug and report files
|
|
935
|
+
this._record_stdout( whole_log );
|
|
936
|
+
} catch ( stdout_error ) {
|
|
937
|
+
this._save_to_debug_safely({ unexpected: true, text: `🖊️ ALK0227 internal stdout NOTE [${ Date.now() }]: Skipped stdout.`});
|
|
938
|
+
this._save_to_debug_safely({ unexpected: true, text: stdout_error.stack });
|
|
939
|
+
};
|
|
940
|
+
|
|
941
|
+
return whole_log;
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
_record_stdout(...logs) {
|
|
945
|
+
/** Save stdout output to various debug and report files.
|
|
946
|
+
*
|
|
947
|
+
* @param { ...string } logs - Optional. Default is `undefined`. Strings to
|
|
948
|
+
* write to debug files.
|
|
949
|
+
*
|
|
950
|
+
* @returns { string } String of combined strings.
|
|
951
|
+
*
|
|
952
|
+
* @example
|
|
953
|
+
* log._record_stdout( ".", "." );
|
|
954
|
+
* // ..
|
|
955
|
+
* */
|
|
956
|
+
// Discuss: Return `log.debug()` value?
|
|
957
|
+
this.debug({ code: `ALK0220`, context: `stdout` }, ...logs);
|
|
958
|
+
let whole_log = logs.join(``);
|
|
959
|
+
// TODO: add this after the report heading (datetime of test, etc.)
|
|
960
|
+
fs.appendFileSync( `${ this.path }/${ this.report_log_filename }`, whole_log );
|
|
961
|
+
return whole_log;
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
clear() {
|
|
965
|
+
/** Empty all files possible without deleting them.
|
|
966
|
+
*
|
|
967
|
+
* @returns { Log } Self.
|
|
968
|
+
* */
|
|
969
|
+
try { fs.writeFileSync( `${ this.path }/${ this.debug_log_filename }`, `` ); }
|
|
970
|
+
catch (error) { /* Fail without throwing. Nice to have, not crucial. */ }
|
|
971
|
+
try { fs.writeFileSync( `${ this.path }/${ this.report_log_filename }`, `` ); }
|
|
972
|
+
catch (error) { /* Fail without throwing. Nice to have, not crucial. */ }
|
|
973
|
+
try { fs.writeFileSync( `${ this.path }/${ this.report_filename }`, `` ); }
|
|
974
|
+
catch (error) { /* Fail without throwing. Nice to have, not crucial. */ }
|
|
975
|
+
try { fs.writeFileSync( `${ this.path }/${ this.unexpected_filename }`, `` ); }
|
|
976
|
+
catch (error) { /* Fail without throwing. Nice to have, not crucial. */ }
|
|
977
|
+
return this;
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
} // Ends Log{}
|
|
981
|
+
|
|
982
|
+
module.exports = Log;
|