TextFormatter
TextFormatter
Section titled “TextFormatter”Canonical source:
protos/lib/logging/TextFormatter.protosat locked revisione99b712ecf658a527b1fe82c8beba2c1286d8dc9.
// THE LICENSED WORK IS PROVIDED UNDER THE TERMS OF THE ADAPTIVE PUBLIC LICENSE// ("LICENSE") AS FIRST COMPLETED BY: Guillermo Adrián Molina. ANY USE, PUBLIC// DISPLAY, PUBLIC PERFORMANCE, REPRODUCTION OR DISTRIBUTION OF, OR PREPARATION OF// DERIVATIVE WORKS BASED ON, THE LICENSED WORK CONSTITUTES RECIPIENT'S ACCEPTANCE// OF THIS LICENSE AND ITS TERMS, WHETHER OR NOT SUCH RECIPIENT READS THE TERMS OF// THE LICENSE. "LICENSED WORK" AND "RECIPIENT" ARE DEFINED IN THE LICENSE. A COPY// OF THE LICENSE IS LOCATED IN THE TEXT FILE ENTITLED "LICENSE.TXT" ACCOMPANYING// THE CONTENTS OF THIS FILE. IF A COPY OF THE LICENSE DOES NOT ACCOMPANY THIS// FILE, A COPY OF THE LICENSE MAY ALSO BE OBTAINED AT THE FOLLOWING WEB SITE:// https://github.com/guillermomolina/protos//// Software distributed under the License is distributed on an "AS IS" basis,// WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for// the specific language governing rights and limitations under the License.
//! Deterministic plain-text rendering of `std:logging/LogEvent` values. Each//! event renders as exactly one line with no line terminator, source metadata,//! color, or terminal control sequence://!//! ```text//! LINE = [TIMESTAMP " "] LEVEL " " STRING [" " MAP] [" error=true"]//! VALUE = "null" | "true" | "false" | INTEGER | FLOAT | STRING | ARRAY | MAP//! ARRAY = "[" [VALUE (", " VALUE)*] "]"//! MAP = "{" [STRING "=" VALUE (", " STRING "=" VALUE)*] "}"//! ```//!//! For example `ERROR "request failed" {"requestId"="r-7"} error=true`.//!//! `TIMESTAMP` appears exactly when the event has a timestamp and is//! `std:datetime/ISO8601.formatInstant` of it, in UTC with the `Z` designator,//! for example `1970-01-01T00:00:00.000000001Z DEBUG "tick"`. An instant//! outside the civil range that format supports signals an Error; there is no//! other timestamp spelling.//!//! The fields Map is omitted when empty, and ` error=true` appears exactly when//! an Error is attached; the attached Error is never inspected, so every Error//! renders the same marker, distinct from a field named `"error"`.//!//! Strings, including the message and Map keys, are double-quoted. `"`, `\`,//! LF, CR, and TAB render as `\"`, `\\`, `\n`, `\r`, and `\t`; the remaining//! C0 controls, DEL, the C1 controls U+0080 to U+009F, U+2028, and U+2029//! render as `\u{HEX}` with uppercase hexadecimal digits and no leading zeros.//! Every other Unicode scalar is kept exactly, without normalization.//!//! Map entries render in ascending order of their keys' Unicode scalar//! sequences: the first differing scalar decides, and a proper prefix comes//! first. The same order applies at every depth; Map iteration order never//! matters.//!//! Integers render in canonical base 10. Floats render as `NaN`, `Infinity`,//! `-Infinity`, `0.0`, `-0.0`, or, for other values, from the decimal with the//! fewest significant digits that round-trips to the same binary64 value (the//! closest such decimal; an exact tie takes the even last digit). With digits//! `d` (`k` of them) and value `d * 10^(n - k)`, the layout is `d` followed by//! `n - k` zeros and `.0` when `k <= n <= 21`; `d` with a `.` after its first//! `n` digits when `0 < n <= 21`; `0.`, `-n` zeros, and `d` when//! `-6 < n <= 0`; and otherwise the first digit, `.` and the remaining digits//! when `k > 1`, then `e`, the sign `+` or `-`, and `|n - 1|`. A negative value//! is prefixed with `-`. Every finite Float therefore renders with a `.` or an//! exponent and never looks like an Integer.
/// Answers the plain-text line of `event`, which must be recognized by/// `std:logging/LogEvent`; any other argument, or a timestamp that/// `std:datetime/ISO8601.formatInstant` cannot write, signals an Error./// Rendering invokes no behavior stored in the event.format: ((numeric) => (event) => { LogEvent: import("std:logging/LogEvent") ISO8601: import("std:datetime/ISO8601") Arrays: import("std:collections/Array")
LogEvent.recognizes(event).ifFalse(() => { Error().signal() })
output: Encoding.UTF8.encode("")
appendAscii: (text) => { Encoding.UTF8.encode(text).each((octet) => { output.add(octet) }) }
// Digits of a non-negative Integer, most significant first, in `radix` // with uppercase letters above nine. appendDigits: (magnitude, radix) => { reversed: Encoding.UTF8.encode("") remaining: magnitude (remaining == 0).ifTrue(() => { reversed.add(48) }) (() => remaining > 0).whileTrue(() => { digit: remaining.mod(radix) (digit < 10).ifTrue(() => { reversed.add(48 + digit) }) (digit >= 10).ifTrue(() => { reversed.add(55 + digit) }) remaining = remaining.div(radix) }) index: reversed.size() - 1 (() => index >= 0).whileTrue(() => { output.add(reversed[index]) index = index - 1 }) }
appendInteger: (value) => { (value < 0).ifTrue(() => { output.add(45) appendDigits(0 - value, 10) }) (value >= 0).ifTrue(() => { appendDigits(value, 10) }) }
appendZeros: (count) => { remaining: count (() => remaining > 0).whileTrue(() => { output.add(48) remaining = remaining - 1 }) }
appendFloat: (value) => { positiveInfinity: 1.0 / 0.0 negativeInfinity: 0.0 - positiveInfinity handled: false
(value == value).ifFalse(() => { appendAscii("NaN") handled = true }) ((handled == false) && (value == positiveInfinity)).ifTrue(() => { appendAscii("Infinity") handled = true }) ((handled == false) && (value == negativeInfinity)).ifTrue(() => { appendAscii("-Infinity") handled = true }) ((handled == false) && (value == 0.0)).ifTrue(() => { ((1.0 / value) == negativeInfinity).ifTrue(() => { output.add(45) }) appendAscii("0.0") handled = true })
handled.ifFalse(() => { (value < 0.0).ifTrue(() => { output.add(45) }) decimal: numeric.shortestDecimal(value) digits: Encoding.UTF8.encode("") reversed: Encoding.UTF8.encode("") remaining: decimal[0] (() => remaining > 0).whileTrue(() => { reversed.add(48 + remaining.mod(10)) remaining = remaining.div(10) }) index: reversed.size() - 1 (() => index >= 0).whileTrue(() => { digits.add(reversed[index]) index = index - 1 })
k: digits.size() n: k + decimal[1] appendRange: (start, end) => { position: start (() => position < end).whileTrue(() => { output.add(digits[position]) position = position + 1 }) }
fixed: (n <= 21) && (n > -6) (fixed && (k <= n)).ifTrue(() => { appendRange(0, k) appendZeros(n - k) appendAscii(".0") }) (fixed && (n > 0) && (n < k)).ifTrue(() => { appendRange(0, n) output.add(46) appendRange(n, k) }) (fixed && (n <= 0)).ifTrue(() => { appendAscii("0.") appendZeros(0 - n) appendRange(0, k) }) fixed.ifFalse(() => { appendRange(0, 1) (k > 1).ifTrue(() => { output.add(46) appendRange(1, k) }) output.add(101) ((n - 1) < 0).ifTrue(() => { output.add(45) appendDigits(1 - n, 10) }) ((n - 1) >= 0).ifTrue(() => { output.add(43) appendDigits(n - 1, 10) }) }) }) }
// Scalars are decoded from the UTF-8 octets of `text`; every scalar that // needs no escape is copied as its original octets. appendString: (text) => { output.add(34) octets: Encoding.UTF8.encode(text) size: octets.size() index: 0 (() => index < size).whileTrue(() => { lead: octets[index] length: 1 scalar: lead (lead >= 240).ifTrue(() => { length = 4 scalar = lead - 240 }) ((lead >= 224) && (lead < 240)).ifTrue(() => { length = 3 scalar = lead - 224 }) ((lead >= 192) && (lead < 224)).ifTrue(() => { length = 2 scalar = lead - 192 }) offset: 1 (() => offset < length).whileTrue(() => { scalar = scalar * 64 + (octets[index + offset] - 128) offset = offset + 1 })
escaped: true (scalar == 34).ifTrue(() => { appendAscii("\\\"") }) (scalar == 92).ifTrue(() => { appendAscii("\\\\") }) (scalar == 10).ifTrue(() => { appendAscii("\\n") }) (scalar == 13).ifTrue(() => { appendAscii("\\r") }) (scalar == 9).ifTrue(() => { appendAscii("\\t") }) short: (scalar == 34) || (scalar == 92) || (scalar == 10) || (scalar == 13) || (scalar == 9) control: (scalar < 32) || ((scalar >= 127) && (scalar <= 159)) || (scalar == 8232) || (scalar == 8233) ((short == false) && control).ifTrue(() => { appendAscii("\\u{") appendDigits(scalar, 16) output.add(125) }) escaped = short || control escaped.ifFalse(() => { offset = 0 (() => offset < length).whileTrue(() => { output.add(octets[index + offset]) offset = offset + 1 }) }) index = index + length }) output.add(34) }
// UTF-8 octet order is Unicode scalar order, and a proper prefix of an // encoding is the encoding of a proper scalar prefix. precedes: (left, right) => { leftSize: left.size() rightSize: right.size() index: 0 decided: false result: false (() => (decided == false) && (index < leftSize) && (index < rightSize)).whileTrue(() => { (left[index] == right[index]).ifFalse(() => { result = left[index] < right[index] decided = true }) index = index + 1 }) decided.ifFalse(() => { result = leftSize < rightSize }) result }
appendValue: null appendValue = (value) => { handled: false (value === null).ifTrue(() => { appendAscii("null") handled = true }) (value === true).ifTrue(() => { appendAscii("true") handled = true }) (value === false).ifTrue(() => { appendAscii("false") handled = true }) ((handled == false) && String.recognizes(value)).ifTrue(() => { appendString(value) handled = true }) ((handled == false) && Integer.recognizes(value)).ifTrue(() => { appendInteger(value) handled = true }) ((handled == false) && Float.recognizes(value)).ifTrue(() => { appendFloat(value) handled = true }) ((handled == false) && Array.recognizes(value)).ifTrue(() => { output.add(91) first: true value.each((element) => { first.ifFalse(() => { appendAscii(", ") }) first = false appendValue(element) }) output.add(93) handled = true }) // A recognized event holds only Maps beyond this point. handled.ifFalse(() => { entries: [] value.each((key, item) => { entries = Array(...entries, [Encoding.UTF8.encode(key), key, item]) }) sorted: Arrays.sort(entries, (left, right) => precedes(left[0], right[0])) output.add(123) first: true sorted.each((entry) => { first.ifFalse(() => { appendAscii(", ") }) first = false appendString(entry[1]) output.add(61) appendValue(entry[2]) }) output.add(125) }) }
(event.timestamp === null).ifFalse(() => { appendAscii(ISO8601.formatInstant(event.timestamp)) output.add(32) }) appendAscii(event.level) output.add(32) appendString(event.message) (event.fields.size() > 0).ifTrue(() => { output.add(32) appendValue(event.fields) }) (event.error === null).ifFalse(() => { appendAscii(" error=true") }) Encoding.UTF8.decode(output)})(_logTextFacility)
// The runtime-provisioned numeric facility is captured privately above; it is// not module surface.context.removeSlot("_logTextFacility")