Skip to content

Commit 9ed80ee

Browse files
authoredDec 5, 2024··
feat: configureConsole function for configuring log prefixing and stderr (#1065)
1 parent 4b30888 commit 9ed80ee

File tree

5 files changed

+199
-3
lines changed

5 files changed

+199
-3
lines changed
 
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
---
2+
hide_title: false
3+
hide_table_of_contents: false
4+
pagination_next: null
5+
pagination_prev: null
6+
---
7+
8+
# configureConsole
9+
10+
The **`configureConsole()`** function allows configuring the behaviour of the `console` global JS logger.
11+
12+
## Syntax
13+
14+
```js
15+
configureConsole(loggingOptions)
16+
```
17+
18+
### Parameters
19+
20+
- `loggingOptions` _: object_
21+
-
22+
- The name has to be between 1 and 254 characters inclusive.
23+
- Throws a [`TypeError`](../globals/TypeError/TypeError.mdx) if the value is not valid. I.E. The value is null, undefined, an empty string or a string with more than 254 characters.
24+
25+
## Examples
26+
27+
In this example, we disable prefixing for `console.log` and use `stderr` output for `console.error`:
28+
29+
```js
30+
import { configureConsole } from "fastly:logger";
31+
32+
configureConsole({
33+
prefixing: false,
34+
stderr: true
35+
});
36+
37+
async function handleRequest(event) {
38+
console.log(JSON.stringify(event.request.headers));
39+
const url = new URL(event.request.url);
40+
try {
41+
validate(url);
42+
} catch (e) {
43+
console.error(`Validation error: ${e}`);
44+
return new Response('Bad Request', { status: 400 });
45+
}
46+
return new Response('OK');
47+
}
48+
49+
addEventListener("fetch", (event) => event.respondWith(handleRequest(event)));
50+
```

‎integration-tests/js-compute/fixtures/app/src/logger.js

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
import { Logger } from 'fastly:logger';
1+
import { Logger, configureConsole } from 'fastly:logger';
22
import { routes, isRunningLocally } from './routes';
33

4+
configureConsole({ prefixing: false, stderr: true });
5+
46
const earlyLogger = new Logger('AnotherLog');
57

68
routes.set('/logger', () => {
@@ -10,5 +12,8 @@ routes.set('/logger', () => {
1012
earlyLogger.log('World!');
1113
}
1214

15+
console.log('LOG');
16+
console.error('ERROR');
17+
1318
return new Response();
1419
});

‎runtime/fastly/builtins/logger.cpp

+111
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,63 @@
22
#include "../../../StarlingMonkey/runtime/encode.h"
33
#include "../host-api/host_api_fastly.h"
44

5+
namespace builtins::web::console {
6+
7+
class Console : public BuiltinNoConstructor<Console> {
8+
private:
9+
public:
10+
static constexpr const char *class_name = "Console";
11+
enum LogType {
12+
Log,
13+
Info,
14+
Debug,
15+
Warn,
16+
Error,
17+
};
18+
enum Slots { Count };
19+
static const JSFunctionSpec methods[];
20+
static const JSPropertySpec properties[];
21+
};
22+
23+
bool write_stderr = false;
24+
bool write_prefix = false;
25+
26+
void builtin_impl_console_log(Console::LogType log_ty, const char *msg) {
27+
FILE *output = stdout;
28+
if (write_stderr) {
29+
if (log_ty == Console::LogType::Warn || log_ty == Console::LogType::Error) {
30+
output = stderr;
31+
}
32+
}
33+
if (write_prefix) {
34+
const char *prefix = "";
35+
switch (log_ty) {
36+
case Console::LogType::Log:
37+
prefix = "Log";
38+
break;
39+
case Console::LogType::Debug:
40+
prefix = "Debug";
41+
break;
42+
case Console::LogType::Info:
43+
prefix = "Info";
44+
break;
45+
case Console::LogType::Warn:
46+
prefix = "Warn";
47+
break;
48+
case Console::LogType::Error:
49+
prefix = "Error";
50+
break;
51+
}
52+
fprintf(output, "%s: %s\n", prefix, msg);
53+
fflush(output);
54+
} else {
55+
fprintf(output, "%s\n", msg);
56+
fflush(output);
57+
}
58+
}
59+
60+
} // namespace builtins::web::console
61+
562
namespace fastly::logger {
663

764
bool Logger::log(JSContext *cx, unsigned argc, JS::Value *vp) {
@@ -77,6 +134,50 @@ bool Logger::constructor(JSContext *cx, unsigned argc, JS::Value *vp) {
77134
return true;
78135
}
79136

137+
bool configure_console(JSContext *cx, unsigned argc, JS::Value *vp) {
138+
JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
139+
140+
// Check if we have at least one argument and it's an object
141+
if (args.length() < 1 || !args[0].isObject()) {
142+
JS_ReportErrorUTF8(cx, "configureConsole requires an options object as its argument");
143+
return false;
144+
}
145+
146+
// Get the options object
147+
JS::RootedObject options(cx, &args[0].toObject());
148+
JS::RootedValue val(cx);
149+
150+
// Handle prefixing option
151+
if (JS_GetProperty(cx, options, "prefixing", &val)) {
152+
if (!val.isUndefined()) {
153+
if (!val.isBoolean()) {
154+
JS_ReportErrorUTF8(cx, "prefixing option must be a boolean");
155+
return false;
156+
}
157+
builtins::web::console::write_prefix = val.toBoolean();
158+
}
159+
} else {
160+
return false;
161+
}
162+
163+
// Handle stderr option
164+
if (JS_GetProperty(cx, options, "stderr", &val)) {
165+
if (!val.isUndefined()) {
166+
if (!val.isBoolean()) {
167+
JS_ReportErrorUTF8(cx, "stderr option must be a boolean");
168+
return false;
169+
}
170+
builtins::web::console::write_stderr = val.toBoolean();
171+
}
172+
} else {
173+
return false;
174+
}
175+
176+
// Set the return value to undefined
177+
args.rval().setUndefined();
178+
return true;
179+
}
180+
80181
bool install(api::Engine *engine) {
81182
if (!Logger::init_class_impl(engine->cx(), engine->global())) {
82183
return false;
@@ -89,6 +190,16 @@ bool install(api::Engine *engine) {
89190
if (!JS_SetProperty(engine->cx(), logger_ns_obj, "Logger", logger_val)) {
90191
return false;
91192
}
193+
auto configure_console_fn =
194+
JS_NewFunction(engine->cx(), &configure_console, 1, 0, "configureConsole");
195+
RootedObject configure_console_obj(engine->cx(), JS_GetFunctionObject(configure_console_fn));
196+
RootedValue configure_console_val(engine->cx(), ObjectValue(*configure_console_obj));
197+
if (!JS_SetProperty(engine->cx(), logger_ns_obj, "configureConsole", configure_console_val)) {
198+
return false;
199+
}
200+
if (!JS_SetProperty(engine->cx(), logger_obj, "configureConsole", configure_console_val)) {
201+
return false;
202+
}
92203
if (!engine->define_builtin_module("fastly:logger", logger_ns_val)) {
93204
return false;
94205
}

‎src/bundle.js

+6-1
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,12 @@ export const sdkVersion = globalThis.fastly.sdkVersion;
8383
};
8484
}
8585
case 'logger': {
86-
return { contents: `export const Logger = globalThis.Logger;` };
86+
return {
87+
contents: `export const Logger = globalThis.Logger;
88+
export const configureConsole = Logger.configureConsole;
89+
delete globalThis.Logger.configureConsole;
90+
`,
91+
};
8792
}
8893
case 'kv-store': {
8994
return { contents: `export const KVStore = globalThis.KVStore;` };

‎types/logger.d.ts

+26-1
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ declare module 'fastly:logger' {
6161
* ```
6262
* </noscript>
6363
*/
64-
class Logger {
64+
export class Logger {
6565
/**
6666
* Creates a new Logger instance for the given [named log endpoint](https://developer.fastly.com/learning/integrations/logging).
6767
*
@@ -73,4 +73,29 @@ declare module 'fastly:logger' {
7373
*/
7474
log(message: any): void;
7575
}
76+
77+
interface ConsoleLoggingOptions {
78+
/**
79+
* Whether to output string prefixes "Log: " | "Debug: " | "Info: " | "Warn: " | "Error: "
80+
* before messages.
81+
*
82+
* Defaults to true.
83+
*/
84+
prefixing?: boolean;
85+
/**
86+
* Whether to use stderr for `console.warn` and `console.error` messages.
87+
*
88+
* Defaults to false.
89+
*/
90+
stderr?: boolean;
91+
}
92+
93+
/**
94+
* Configure the behaviour of `console.log` and related console logging functions.
95+
*
96+
* Currently only supports customizing prefixing and stdio output.
97+
*
98+
* @param loggingOptions The console logging options
99+
*/
100+
export function configureConsole(loggingOptions: ConsoleLoggingOptions): void;
76101
}

0 commit comments

Comments
 (0)
Please sign in to comment.