From 051f725b468a92af0d6df5cdb749ba3269e65917 Mon Sep 17 00:00:00 2001 From: Anne van Kesteren Date: Mon, 17 Jun 2024 13:18:05 +0200 Subject: [PATCH] Review Draft Publication: June 2024 --- index.bs | 2 +- review-drafts/2024-06.bs | 589 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 590 insertions(+), 1 deletion(-) create mode 100644 review-drafts/2024-06.bs diff --git a/index.bs b/index.bs index db579fb..81405ba 100644 --- a/index.bs +++ b/index.bs @@ -3,7 +3,7 @@ Group: WHATWG H1: Console Shortname: console Text Macro: TWITTER consolelog -Text Macro: LATESTRD 2022-12 +Text Macro: LATESTRD 2024-06 Abstract: This specification defines APIs for console debugging facilities. Translation: ja https://triple-underscore.github.io/console-ja.html Indent: 2 diff --git a/review-drafts/2024-06.bs b/review-drafts/2024-06.bs new file mode 100644 index 0000000..74ecf93 --- /dev/null +++ b/review-drafts/2024-06.bs @@ -0,0 +1,589 @@ +
+Group: WHATWG
+Status: RD
+Date: 2024-06-17
+H1: Console
+Shortname: console
+Text Macro: TWITTER consolelog
+Text Macro: LATESTRD 2024-06
+Abstract: This specification defines APIs for console debugging facilities.
+Translation: ja https://triple-underscore.github.io/console-ja.html
+Indent: 2
+Markup Shorthands: markdown yes
+
+ +
+urlPrefix: https://tc39.github.io/ecma262/#; spec: ECMASCRIPT
+  type: abstract-op
+    text: ObjectCreate; url: sec-objectcreate
+    text: Type; url: sec-ecmascript-data-types-and-values
+    text: ToString; url: sec-tostring
+    text: Call; url: sec-call
+  type: interface
+    text: %ObjectPrototype%; url: sec-properties-of-the-object-prototype-object
+  type: constructor
+    text: %String%; url: sec-string-constructor
+  type: dfn
+    text: %parseFloat%; url: sec-parsefloat-string
+    text: %parseInt%; url: sec-parseint-string-radix
+
+ +

Status

+ +This specification is an early work in progress that welcomes feedback to refine toward more +precise and compatible definitions. It is also the editors' first specification, so please be kind +and constructive. + +Please join us in the issue tracker for more +discussion. + +

Namespace {{console}}

+ +
+[Exposed=*]
+namespace console { // but see namespace object requirements below
+  // Logging
+  undefined assert(optional boolean condition = false, any... data);
+  undefined clear();
+  undefined debug(any... data);
+  undefined error(any... data);
+  undefined info(any... data);
+  undefined log(any... data);
+  undefined table(optional any tabularData, optional sequence<DOMString> properties);
+  undefined trace(any... data);
+  undefined warn(any... data);
+  undefined dir(optional any item, optional object? options);
+  undefined dirxml(any... data);
+
+  // Counting
+  undefined count(optional DOMString label = "default");
+  undefined countReset(optional DOMString label = "default");
+
+  // Grouping
+  undefined group(any... data);
+  undefined groupCollapsed(any... data);
+  undefined groupEnd();
+
+  // Timing
+  undefined time(optional DOMString label = "default");
+  undefined timeLog(optional DOMString label = "default", any... data);
+  undefined timeEnd(optional DOMString label = "default");
+};
+
+ +

+ For historical reasons, {{console}} is lowercased. +

+ +

+ It is important that {{console}} is always visible and usable to scripts, even if the developer + console has not been opened or does not exist. +

+ +For historical web-compatibility reasons, the namespace object for {{console}} must have as +its \[[Prototype]] an empty object, created as if by +ObjectCreate({{%ObjectPrototype%}}), instead of {{%ObjectPrototype%}}. + +

Logging functions

+ +

assert(|condition|, ...|data|)

+ +1. If |condition| is true, return. +1. Let |message| be a string without any formatting specifiers indicating generically an assertion + failure (such as "Assertion failed"). +1. If |data| is [=list/is empty|empty=], [=list/append=] |message| to |data|. +1. Otherwise: + 1. Let |first| be |data|[0]. + 1. If Type(|first|) is not String, then [=list/prepend=] |message| to |data|. + 1. Otherwise: + 1. Let |concat| be the concatenation of |message|, U+003A (:), U+0020 SPACE, and |first|. + 1. Set |data|[0] to |concat|. +1. Perform Logger("assert", |data|). + +

clear()

+ +1. [=stack/Empty=] the appropriate group stack. +1. If possible for the environment, clear the console. (Otherwise, do nothing.) + +

debug(...|data|)

+ +1. Perform Logger("debug", |data|). + +

error(...|data|)

+ +1. Perform Logger("error", |data|). + +

info(...|data|)

+ +1. Perform Logger("info", |data|). + +

log(...|data|)

+ +1. Perform Logger("log", |data|). + +

table(|tabularData|, |properties|)

+ +Try to construct a table with the columns of the properties of |tabularData| (or use +|properties|) and rows of |tabularData| and log it with a logLevel of "log". Fall +back to just logging the argument if it can't be parsed as tabular. + +

TODO: This will need a good algorithm.

+ +

trace(...|data|)

+ +1. Let |trace| be some implementation-specific, potentially-interactive representation of the + callstack from where this function was called. +1. Optionally, let |formattedData| be the result of Formatter(|data|), and + incorporate |formattedData| as a label for |trace|. +1. Perform Printer("trace", « |trace| »). + +

+ The identifier of a function printed in a stack trace is implementation-dependant. It is also not + guaranteed to be the same identifier that would be seen in `new Error().stack`. +

+ +

warn(...|data|)

+ +1. Perform Logger("warn", |data|). + +

dir(|item|, |options|)

+ +1. Let |object| be |item| with generic JavaScript object formatting applied. +1. Perform Printer("dir", « |object| », |options|). + +

dirxml(...|data|)

+ +1. Let |finalList| be a new [=/list=], initially empty. +1. [=list/For each=] |item| of |data|: + 1. Let |converted| be a DOM tree representation of |item| if possible; otherwise let + |converted| be |item| with optimally useful formatting applied. + 1. Append |converted| to |finalList|. +1. Perform Logger("dirxml", |finalList|). + +

Counting functions

+ +Each {{console}} namespace object has an associated count map, which is a map of +strings to numbers, initially empty. + +

count(|label|)

+ +1. Let |map| be the associated count map. +1. If |map|[|label|] [=map/exists=], [=map/set=] |map|[|label|] to |map|[|label|] + 1. +1. Otherwise, [=map/set=] |map|[|label|] to 1. +1. Let |concat| be the concatenation of |label|, U+003A (:), U+0020 SPACE, and + ToString(|map|[|label|]). +1. Perform Logger("count", « |concat| »). + +

countReset(|label|)

+ +1. Let |map| be the associated count map. +1. If |map|[|label|] [=map/exists=], [=map/set=] |map|[|label|] to 0. +1. Otherwise: + 1. Let |message| be a string without any formatting specifiers indicating generically that the + given label does not have an associated count. + 1. Perform Logger("countReset", « |message| »); + +

Grouping functions

+ +A group is an implementation-specific, potentially-interactive view +for output produced by calls to Printer, with one further level of indentation +than its parent. Each {{console}} namespace object has an associated group stack, which +is a stack, initially empty. Only the last group in a group stack will host +output produced by calls to Printer. + +

group(...|data|)

+ +1. Let |group| be a new group. +1. If |data| is not [=list/is empty|empty=], let |groupLabel| be the result of + Formatter(|data|). Otherwise, let |groupLabel| be an implementation-chosen + label representing a group. +1. Incorporate |groupLabel| as a label for |group|. +1. Optionally, if the environment supports interactive groups, |group| should be expanded by + default. +1. Perform Printer("group", « |group| »). +1. [=stack/Push=] |group| onto the appropriate group stack. + +

groupCollapsed(...|data|)

+ +1. Let |group| be a new group. +1. If |data| is not empty, let |groupLabel| be the result of + Formatter(|data|). Otherwise, let |groupLabel| be an implementation-chosen + label representing a group. +1. Incorporate |groupLabel| as a label for |group|. +1. Optionally, if the environment supports interactive groups, |group| should be collapsed by + default. +1. Perform Printer("groupCollapsed", « |group| »). +1. [=stack/Push=] |group| onto the appropriate group stack. + +

groupEnd()

+ +1. Pop the last group from the group stack. + +

Timing functions

+ +Each {{console}} namespace object has an associated timer table, which is a map of +strings to times, initially empty. + +

time(|label|)

+ +1. If the associated timer table [=map/contains=] an entry with key |label|, return, + optionally reporting a warning to the console indicating that a timer with label |label| has + already been started. +1. Otherwise, [=map/set=] the value of the entry with key |label| in the associated + timer table to the current time. + +

timeLog(|label|, ...|data|)

+ +1. Let |timerTable| be the associated timer table. +1. Let |startTime| be |timerTable|[|label|]. +1. Let |duration| be a string representing the difference between the current time and + |startTime|, in an implementation-defined format. +

"4650", "4650.69 ms", "5 seconds", and "00:05" + are all reasonable ways of displaying a 4650.69 ms duration.

+1. Let |concat| be the concatenation of |label|, U+003A (:), U+0020 SPACE, and |duration|. +1. [=list/prepend|Prepend=] |concat| to |data|. +1. Perform Printer("timeLog", data). + +
+ The |data| parameter in calls to {{console/timeLog()}} is included in the call to + Logger to make it easier for users to supply intermediate timer logs with + some extra data throughout the life of a timer. For example: + +

+    console.time("MyTimer");
+    console.timeLog("MyTimer", "Starting application up…");
+    // Perhaps some code runs to bootstrap a complex app
+    // ...
+    console.timeLog("MyTimer", "UI is setup, making API calls now");
+    // Perhaps some fetch()'s here filling the app with data
+    // ...
+    console.timeEnd("MyTimer");
+  
+
+ +

timeEnd(|label|)

+ +1. Let |timerTable| be the associated timer table. +1. Let |startTime| be |timerTable|[|label|]. +1. [=map/Remove=] |timerTable|[|label|]. +1. Let |duration| be a string representing the difference between the current time and + |startTime|, in an implementation-defined format. +1. Let |concat| be the concatenation of |label|, U+003A (:), U+0020 SPACE, and |duration|. +1. Perform Printer("timeEnd", « |concat| »). + +

See whatwg/console#134 +for plans to make {{console/timeEnd()}} and {{console/timeLog()}} formally report warnings to the +console when a given |label| does not exist in the associated timer table. +

+ +

Supporting abstract operations

+ +

Logger(|logLevel|, |args|)

+ +The logger operation accepts a log level and a [=/list=] of other arguments. Its main output is the +implementation-defined side effect of printing the result to the console. This specification +describes how it processes format specifiers while doing so. + +1. If |args| is [=list/is empty|empty=], return. +1. Let |first| be |args|[0]. +1. Let |rest| be all elements following |first| in |args|. +1. If |rest| is [=list/is empty|empty=], perform + Printer(|logLevel|, « |first| ») and return. +1. Otherwise, perform Printer(|logLevel|, Formatter(|args|)). +1. Return *undefined*. + +
+ It's important that the printing occurs before returning from the algorithm. Many developer + consoles print the result of the last operation entered into them. In such consoles, when a + developer enters `console.log("hello!")`, this will first print "hello!", then the + undefined return value from the console.log call. + + Indicating that printing is done before return +
+ +

Formatter(|args|)

+ +The formatter operation tries to format the first argument provided, using the other arguments. It +will try to format the input until no formatting specifiers are left in the first argument, or no +more arguments are left. It returns a [=/list=] of objects suitable for printing. + +1. If |args|'s [=list/size=] is 1, return |args|. +1. Let |target| be the first element of |args|. +1. Let |current| be the second element of |args|. +1. Find the first possible format specifier |specifier|, from the left to the right in |target|. +1. If no format specifier was found, return |args|. +1. Otherwise: + 1. If |specifier| is `%s`, let |converted| be the result of + [$Call$](%String%, **undefined**, « |current| »). + 1. If |specifier| is `%d` or `%i`: + 1. If [$Type$](|current|) is Symbol, let |converted| be `NaN` + 1. Otherwise, let |converted| be the result of + [$Call$]([=%parseInt%=], **undefined**, « |current|, 10 »). + 1. If |specifier| is `%f`: + 1. If [$Type$](|current|) is Symbol, let |converted| be `NaN` + 1. Otherwise, let |converted| be the result of + [$Call$]([=%parseFloat%=], **undefined**, « |current| »). + 1. If |specifier| is `%o`, optionally let |converted| be |current| with + optimally useful formatting applied. + 1. If |specifier| is `%O`, optionally let |converted| be |current| with + generic JavaScript object formatting applied. + 1.

TODO: process %c

+ 1. If any of the previous steps set |converted|, replace |specifier| in |target| with + |converted|. +1. Let |result| be a [=/list=] containing |target| together with the elements of |args| starting + from the third onward. +1. Return Formatter(|result|). + +

Summary of formatting specifiers

+ +The following is an informative summary of the format specifiers processed by the above algorithm. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SpecifierPurposeType Conversion
`%s`Element which substitutes is converted to a string%String%(|element|)
`%d` or `%i`Element which substitutes is converted to an integer%parseInt%(|element|, 10)
`%f`Element which substitutes is converted to a float%parseFloat%(|element|, 10)
`%o`Element is displayed with optimally useful formattingn/a
`%O`Element is displayed with generic JavaScript object formattingn/a
`%c`Applies provided CSSn/a
+ +

Printer(|logLevel|, |args|[, |options|])

+ +The printer operation is implementation-defined. It accepts a log level indicating severity, a List +of arguments to print, and an optional object of implementation-specific formatting options. +Elements appearing in |args| will be one of the following: + +- JavaScript objects of any type. +- Implementation-specific representations of printable things such as a stack trace or group. +- Objects with either generic JavaScript object formatting or + optimally useful formatting applied. + +If the |options| object is passed, and is not undefined or null, implementations may use |options| +to apply implementation-specific formatting to the elements in |args|. + +How the implementation prints |args| is up to the implementation, but implementations should +separate the objects by a space or something similar, as that has become a developer expectation. + +By the time the printer operation is called, all format specifiers will have been taken into +account, and any arguments that are meant to be consumed by format specifiers will not be present in +|args|. The implementation's job is simply to print the List. The output produced by calls to +Printer should appear only within the last group on the appropriate group stack if the +group stack is not empty, or elsewhere in the console otherwise. + +If the console is not open when the printer operation is called, implementations should buffer +messages to show them in the future up to an implementation-chosen limit (typically on the order of +at least 100). + +

Indicating |logLevel| severity

+ +Each {{console}} function uses a unique value for the |logLevel| parameter when calling +Printer, allowing implementations to customize each printed message depending on the function from +which it originated. However, it is common practice to group together certain functions and treat +their output similarly, in four broad categories. This table summarizes these common groupings: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Grouping{{console}} functionsDescription
log + {{console/log()}}, {{console/trace()}}, {{console/dir()}}, {{console/dirxml()}}, + {{console/group()}}, {{console/groupCollapsed()}}, {{console/debug()}}, {{console/timeLog()}} + + A generic log +
info{{console/count()}}, {{console/info()}}, {{console/timeEnd()}} + An informative log +
warn + {{console/warn()}}, {{console/countReset()}} + + A log warning the user of something indicated by the message +
error{{console/error()}}, {{console/assert()}} + A log indicating an error to the user +
+ +These groupings are meant to document common practices, and do not constrain implementations from +providing special behavior for each function, as in the following examples: + +
+ Here you can see one implementation chose to make output produced by calls to + {{console/timeEnd()}} blue, while leaving {{console/info()}} a more neutral color. + + A demonstration of timeEnd and info formatting differences +
+ +
+ Calls to {{console/count()}} might not always print new output, but instead could update + previously-output counts. + + A demonstration of count behavior +
+ +

Printer user experience innovation

+ +Since Printer is implementation-defined, it is common to see UX innovations in +its implementations. The following is a non-exhaustive list of potential UX enhancements: + + + +

Common object formats

+ +Typically objects will be printed in a format that is suitable for their context. This section +describes common ways in which objects are formatted to be most useful in their context. It should +be noted that the formatting described in this section is applied to implementation-specific object +representations that will eventually be passed into Printer, where the actual +side effect of formatting will be seen. + +An object with generic JavaScript object formatting is a potentially expandable +representation of a generic JavaScript object. An object with +optimally useful formatting is an implementation-specific, potentially-interactive +representation of an object judged to be maximally useful and informative. + +

Example printer in Node.js

+ +
+ The simplest way to implement the printer operation on the Node.js platform is to join the + previously formatted arguments separated by a space and write the output to `stdout` + or `stderr`. + + Example implementation in Node.js using [[!ECMASCRIPT]]: + +

+    const util = require('util');
+
+    function print(logLevel, ...args) {
+      const message = util.format(...args);
+
+      if (logLevel === 'error') {
+        process.stderr.write(message + '\n');
+      } else if (logLevel === 'log' || logLevel === 'info' || logLevel === 'warn') {
+        process.stdout.write(message + '\n');
+      }
+    }
+  
+ + Here a lot of the work is done by the `util.format` function. It stringifies nested objects, and + converts non-string arguments into a readable string version, e.g. undefined becomes the string + "undefined" and false becomes "false": + +

+    print('log', 'duck', [{foo: 'bar'}]);     // prints: `duck [ { foo: 'bar' } ]\n` on stdout
+    print('log', 'duck', false);              // prints: `duck false\n` on stdout
+    print('log', 'duck', undefined);          // prints: `duck undefined\n` on stdout
+  
+
+ +

Reporting warnings to the console

+ +To report a warning to the console given a generic description of a warning +|description|, implementations must run these steps: + +1. Let |warning| be an implementation-defined string derived from |description|. +1. Perform Printer("reportWarning", « |warning| »). + +

Acknowledgments

+ +The editors would like to thank +Boris Zbarsky, +Brent S.A. Cowgill, +Brian Grinstead, +Corey Farwell, +Ian Kilpatrick, +Jeff Carpenter, +Joseph Pecoraro, +Justin Woo, +Luc Martin, +Noah Bass, +Paul Irish, +Raphaël, and +Victor Costan +for their contributions to this specification. You are awesome! + +This standard is written by +Terin Stock +(terin@terinstock.com), +Robert Kowalski +(rok@kowalski.gd), and +Dominic Farolino +(domfarolino@gmail.com) +with major help from Domenic Denicola +(Google, +d@domenic.me).