| # Encoding structured records |
| |
| Fuchsia is in the process of migrating to a wire format for structured logs inspired by its |
| [trace format]. This new transmission format for logs allows consumption and propagation of |
| arbitrary data structures as log records. |
| |
| <!-- TODO(fxbug.dev/34899) update this when more implementations have landed --> |
| |
| ## Validation |
| |
| <!-- TODO(fxbug.dev/50240) update when limit relaxed --> |
| |
| The size of a log record is currently capped at 32kb as a result of the number of bits used for |
| recording size. |
| |
| Writers that send oversize or invalid records to the diagnostics service will have their streams |
| closed and an error recorded by the diagnostics service. |
| |
| ## Primitives |
| |
| Integers have little endian encoding. Signed integers are twos-complement. |
| |
| Timestamps are signed 64-bit integers measured in nanoseconds, as recorded by |
| [`zx_clock_get_monotonic`]. |
| |
| Strings are denoted by a 16-bit "string ref". If the most significant bit (MSB) is 0, the string is |
| empty. If the MSB is 1, the remaining bits of the ref indicate the length of the subsequent UTF-8 |
| byte stream. All string refs with an MSB of 0 other than the empty string are reserved for future |
| extensions. Strings are padded with zeroes until 8-byte aligned. |
| |
| Records consist of a number of 8-byte words and are 8-byte aligned. |
| |
| ## Records |
| |
| Implementors are encouraged to use FIDL bindings from |
| [/sdk/fidl/fuchsia.diagnostics.stream/record.fidl] as the "reference" API for records when testing |
| their encoder. These types allow for cross-validating interoperability of log readers and writers. |
| |
| ### Header |
| |
| The required metadata for a log record are a record type, the overall length of the record, and a |
| timestamp. These are encoded in the first 16 bytes of a record: |
| |
| <!-- TODO(fxbug.dev/50239) update diagrams when we use more bits for size --> |
| <!-- TODO(fxbug.dev/50517) finalize bits used for severity --> |
| ``` |
| .---------------------------------------------------------------. |
| | |1|1|1|1|1|2|2|2|2|2|3|3|3|3|3|4|4|4|4|4|5|5|5|5|5|6|6|6| |
| |2|4|6|8|0|2|4|6|8|0|2|4|6|8|0|2|4|6|8|0|2|4|6|8|0|2|4|6|8|0|2|4| |
| |---+-----------+-----------------------------------------------| |
| | T | SizeWords | Reserved | Severity| |
| |---------------------------------------------------------------| |
| | TimestampNanos | |
| '---------------------------------------------------------------' |
| |
| T (type) = {0,3} must be 9 |
| SizeWords = {4,15} includes header word |
| Reserved = {16,55} must be 0 |
| Severity = {56,63} severity of the log record |
| TimestampNanos = {64,127} |
| ``` |
| |
| Currently all records are expected to have type=9. This was chosen to mirror the [trace format] but |
| may require a change before these records can be processed by tracing tools. |
| Values for severity are defined in [/sdk/fidl/fuchsia.diagnostics.stream/source.fidl] |
| |
| ## Arguments |
| |
| Log record data is conveyed via a list of typed key-value pairs. The keys are always non-empty |
| strings, and the values can have several types. Each record can have up to 15 arguments. |
| |
| <!-- TODO(fxbug.dev/50240) update docs when limit on arg count relaxed --> |
| |
| ### Argument header |
| |
| Each argument has an 8-byte header, followed by the argument name, followed by the argument's value. |
| The name is padded with zeroes to 8-byte alignment before the argument's content is written. |
| |
| <!-- TODO(fxbug.dev/50239) update diagrams when we use more bits for size --> |
| |
| ``` |
| .---------------------------------------------------------------. |
| | |1|1|1|1|1|2|2|2|2|2|3|3|3|3|3|4|4|4|4|4|5|5|5|5|5|6|6|6| |
| |2|4|6|8|0|2|4|6|8|0|2|4|6|8|0|2|4|6|8|0|2|4|6|8|0|2|4|6|8|0|2|4| |
| |---+-----------+-----------------------------------------------| |
| | T | SizeWords | NameRef | Varies | |
| |---------------------------------------------------------------| |
| | Name (1+ words) | |
| '---------------------------------------------------------------' |
| |
| T (type) = {0,3} see table below |
| SizeWords = {4,15} includes header word |
| NameRef = {16,31} string ref for the argument name |
| Varies = {32,63} varies by argument type, must be 0 if unused |
| NameLen = 64*NameRef |
| Name = {64,64+NameLen} name of the argument, padded to 8-byte alignment |
| ``` |
| |
| The first 4 bits of the argument header determine which type the argument has: |
| |
| T (type) | name |
| ---------|-------------------------- |
| `3` | signed 64-bit integers |
| `4` | unsigned 64-bit integers |
| `5` | double-precision floats |
| `6` | UTF-8 strings |
| |
| ### Signed 64-bit integer arguments |
| |
| Signed integers are appended after the argument name is terminated. |
| |
| ``` |
| .---------------------------------------------------------------. |
| | |1|1|1|1|1|2|2|2|2|2|3|3|3|3|3|4|4|4|4|4|5|5|5|5|5|6|6|6| |
| |2|4|6|8|0|2|4|6|8|0|2|4|6|8|0|2|4|6|8|0|2|4|6|8|0|2|4|6|8|0|2|4| |
| |---+-----------+-----------------------------------------------| |
| | 3 | SizeWords | NameRef | Reserved | |
| |---------------------------------------------------------------| |
| | Name (1+ words) | |
| '---------------------------------------------------------------' |
| | Value | |
| '---------------------------------------------------------------' |
| |
| T (type) = {0,3} must be 3 |
| SizeWords = {4,15} includes header word |
| NameRef = {16,31} string ref for the argument name |
| Reserved = {32,63} must be 0 |
| NameEnd = 64+(64*NameRef) |
| Name = {64,NameEnd} name of the argument, padded to 8-byte alignment |
| Value = {NameEnd+1,SizeWords*64} |
| ``` |
| |
| ### Unsigned 64-bit integer arguments |
| |
| Unsigned integers are appended after the argument name is terminated. |
| |
| ``` |
| .---------------------------------------------------------------. |
| | |1|1|1|1|1|2|2|2|2|2|3|3|3|3|3|4|4|4|4|4|5|5|5|5|5|6|6|6| |
| |2|4|6|8|0|2|4|6|8|0|2|4|6|8|0|2|4|6|8|0|2|4|6|8|0|2|4|6|8|0|2|4| |
| |---+-----------+-----------------------------------------------| |
| | 4 | SizeWords | NameRef | Reserved | |
| |---------------------------------------------------------------| |
| | Name (1+ words) | |
| '---------------------------------------------------------------' |
| | Value | |
| '---------------------------------------------------------------' |
| |
| T (type) = {0,3} must be 4 |
| SizeWords = {4,15} includes header word |
| NameRef = {16,31} string ref for the argument name |
| Reserved = {32,63} must be 0 |
| NameEnd = 64+(64*NameRef) |
| Name = {64,NameEnd} name of the argument, padded to 8-byte alignment |
| Value = {NameEnd+1,SizeWords*64} |
| ``` |
| |
| ### 64-bit floating point arguments |
| |
| Floats are appended after the argument name is terminated. |
| |
| ``` |
| .---------------------------------------------------------------. |
| | |1|1|1|1|1|2|2|2|2|2|3|3|3|3|3|4|4|4|4|4|5|5|5|5|5|6|6|6| |
| |2|4|6|8|0|2|4|6|8|0|2|4|6|8|0|2|4|6|8|0|2|4|6|8|0|2|4|6|8|0|2|4| |
| |---+-----------+-----------------------------------------------| |
| | 5 | SizeWords | NameRef | Reserved | |
| |---------------------------------------------------------------| |
| | Name (1+ words) | |
| '---------------------------------------------------------------' |
| | Value | |
| '---------------------------------------------------------------' |
| |
| T (type) = {0,3} must be 5 |
| SizeWords = {4,15} includes header word |
| NameRef = {16,31} string ref for the argument name |
| Reserved = {32,63} must be 0 |
| NameEnd = 64+(64*NameRef) |
| Name = {64,NameEnd} name of the argument, padded to 8-byte alignment |
| Value = {NameEnd+1,SizeWords*64} |
| ``` |
| |
| ### String arguments |
| |
| Strings are encoded in UTF-8, padded with zeroes until 8-byte aligned, and appended after the |
| argument name. |
| |
| ``` |
| .---------------------------------------------------------------. |
| | |1|1|1|1|1|2|2|2|2|2|3|3|3|3|3|4|4|4|4|4|5|5|5|5|5|6|6|6| |
| |2|4|6|8|0|2|4|6|8|0|2|4|6|8|0|2|4|6|8|0|2|4|6|8|0|2|4|6|8|0|2|4| |
| |---+-----------+-----------------------------------------------| |
| | 6 | SizeWords | NameRef | ValueRef | Reserved | |
| |---------------------------------------------------------------| |
| | Name (1+ words) | |
| '---------------------------------------------------------------' |
| | Value (1+ words) | |
| '---------------------------------------------------------------' |
| |
| T (type) = {0,3} must be 6 |
| SizeWords = {4,15} includes header word |
| NameRef = {16,31} string ref for the argument name |
| ValueRef = {32,47} string ref for the argument value |
| Reserved = {48,63} must be 0 |
| NameEnd = 64+(64*NameRef) |
| Name = {64,NameEnd} name of the argument, padded to 8-byte alignment |
| Value = {NameEnd+1,SizeWords*64} |
| ``` |
| --- |
| |
| # Encoding "legacy" format messages |
| |
| Components that call [`LogSink.Connect`] are expected to pass a socket in "datagram" mode (as |
| opposed to "streaming") and to write the ["legacy" wire format] into the it. This uses little endian |
| integers and a mix of length-prefixed and null-terminated UTF-8 strings. |
| |
| [`zx_clock_get_monotonic`]: /docs/reference/syscalls/clock_get_monotonic.md |
| [`LogSink.Connect`]: https://fuchsia.dev/reference/fidl/fuchsia.logger#Connect |
| ["legacy" wire format]: /zircon/system/ulib/syslog/include/lib/syslog/wire_format.h |
| [trace format]: /docs/reference/tracing/trace-format.md |