Internet-Draft | Main logging schema for qlog | October 2023 |
Marx, et al. | Expires 25 April 2024 | [Page] |
This document defines qlog, an extensible high-level schema for a standardized logging format. It allows easy sharing of data, benefitting common debug and analysis methods and tooling. The high-level schema is independent of protocol; separate documents extend qlog for protocol-specific data. The schema is also independent of serialization format, allowing logs to be represented in many ways such as JSON, CSV, or protobuf.¶
Note to RFC editor: Please remove this section before publication.¶
Feedback and discussion are welcome at https://github.com/quicwg/qlog. Readers are advised to refer to the "editor's draft" at that URL for an up-to-date version of this document.¶
Concrete examples of integrations of this schema in various programming languages can be found at https://github.com/quiclog/qlog/.¶
This Internet-Draft is submitted in full conformance with the provisions of BCP 78 and BCP 79.¶
Internet-Drafts are working documents of the Internet Engineering Task Force (IETF). Note that other groups may also distribute working documents as Internet-Drafts. The list of current Internet-Drafts is at https://datatracker.ietf.org/drafts/current/.¶
Internet-Drafts are draft documents valid for a maximum of six months and may be updated, replaced, or obsoleted by other documents at any time. It is inappropriate to use Internet-Drafts as reference material or to cite them other than as "work in progress."¶
This Internet-Draft will expire on 25 April 2024.¶
Copyright (c) 2023 IETF Trust and the persons identified as the document authors. All rights reserved.¶
This document is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/license-info) in effect on the date of publication of this document. Please review these documents carefully, as they describe your rights and restrictions with respect to this document. Code Components extracted from this document must include Revised BSD License text as described in Section 4.e of the Trust Legal Provisions and are provided without warranty as described in the Revised BSD License.¶
Endpoint logging is a useful strategy for capturing and understanding how applications using network protocols are behaving, particularly where protocols have an encrypted wire image that restricts observers' ability to see what is happening.¶
Many applications implement logging using a custom, non-standard logging format. This has an effect on the tools and methods that are used to analyze the logs, for example to perform root cause analysis of an interoperability failure between distinct implementations. A lack of a common format impedes the development of common tooling that can be used by all parties that have access to logs.¶
This document defines qlog, an extensible high-level schema and harness that provides a shareable, aggregatable and structured logging format. This high-level schema is independent of protocol, with logging entries for specific protocols and use cases being defined in other documents (see for example [QLOG-QUIC] for QUIC and [QLOG-H3] for HTTP/3 and QPACK-related event definitions).¶
The goal of this high-level schema is to provide amenities and default characteristics that each logging file should contain (or should be able to contain), such that generic and reusable toolsets can be created that can deal with logs from a variety of different protocols and use cases.¶
As such, qlog provides versioning, metadata inclusion, log aggregation, event grouping and log file size reduction techniques.¶
The qlog schema can be serialized in many ways (e.g., JSON, CBOR, protobuf, etc). This document describes only how to employ [JSON], its subset [I-JSON], and its streamable derivative [JSON-Text-Sequences].¶
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals, as shown here.¶
To define events and data structures, all qlog documents use the Concise
Data Definition Language [CDDL]. This document uses the basic
syntax, the specific text
, uint
, float32
, float64
, bool
, and
any
types, as well as the .default
, .size
, and .regexp
control
operators, the ~
unwrapping operator, and the $
extension point
syntax from [CDDL].¶
Additionally, this document defines the following custom types for clarity:¶
All timestamps and time-related values (e.g., offsets) in qlog are
logged as float64
in the millisecond resolution.¶
Other qlog documents can define their own CDDL-compatible (struct) types (e.g., separately for each Packet type that a protocol supports).¶
Note to RFC editor: Please remove the following text in this section before publication.¶
The main general CDDL syntax conventions in this document a reader should be aware of for easy reading comprehension are:¶
? obj
: this object is optional¶
TypeName1 / TypeName2
: a union of these two types (object can be either type 1 OR
type 2)¶
obj: TypeName
: this object has this concrete type¶
obj: [* TypeName]
: this object is an array of this type with
minimum size of 0 elements¶
obj: [+ TypeName]
: this object is an array of this type with
minimum size of 1 element¶
TypeName = ...
: defines a new type¶
EnumName = "entry1" / "entry2" / entry3 / ...
: defines an enum¶
StructName = { ... }
: defines a new struct type¶
;
: single-line comment¶
* text => any
: special syntax to indicate 0 or more fields that
have a string key that maps to any value. Used to indicate a generic
JSON object.¶
All timestamps and time-related values (e.g., offsets) in qlog are
logged as float64
in the millisecond resolution.¶
Other qlog documents can define their own CDDL-compatible (struct) types (e.g., separately for each Packet type that a protocol supports).¶
Serialization examples in this document use JSON ([JSON]) unless otherwise indicated.¶
The main tenets for the qlog schema design are:¶
A qlog using the QlogFile schema can contain several individual traces and logs from multiple vantage points that are in some way related. The top-level element in this schema defines only a small set of "header" fields and an array of component traces, defined in Figure 2 as:¶
The required "qlog_version" field MUST have the value "0.4".¶
The optional "qlog_format" field indicates the serialization format. Its value MUST either be one of the options defined in this document (i.e., Section 10) or the field MUST be omitted entirely. When the field is omitted the default value of "JSON" applies.¶
The optional "title" and "description" fields provide additional free-text information about the file.¶
The optional "traces" field contains an array of qlog traces (Section 3.2), each of which contain metadata and an array of qlog events (Section 6).¶
In order to make it easier to parse and identify qlog files and their serialization format, the "qlog_version" and "qlog_format" fields and their values SHOULD be in the first 256 characters/bytes of the resulting log file.¶
Where a qlog file is serialized to a JSON format, one of the downsides is that it is inherently a non-streamable format. Put differently, it is not possible to simply append new qlog events to a log file without "closing" this file at the end by appending "]}]}". Without these closing tags, most JSON parsers will be unable to parse the file entirely. The alternative QlogFileSeq (Section 4) is better suited to streaming.¶
JSON serialization example:¶
It can be advantageous to group several related qlog traces together in a single file. For example, it is possible to simultaneously perform logging on the client, on the server, and on a single point on their common network path. For analysis, it is useful to aggregate these three individual traces together into a single file, so it can be uniquely stored, transferred, and annotated.¶
The QlogFile "traces" field is an array that contains a list of individual qlog traces. When capturing a qlog at a vantage point, it is expected that the traces field contains a single entry. Files can be aggregated, for example as part of a post-processing operation, by copying the traces in component to files into the combined "traces" array of a new, aggregated qlog file.¶
The exact conceptual definition of a Trace can be fluid. For example, a trace could contain all events for a single connection, for a single endpoint, for a single measurement interval, for a single protocol, etc. In the normal use case however, a trace is a log of a single data flow collected at a single location or vantage point. For example, for QUIC, a single trace only contains events for a single logical QUIC connection for either the client or the server.¶
A Trace contains some metadata in addition to qlog events, defined in Figure 4 as:¶
The optional "title" and "description" fields provide additional free-text information about the trace.¶
The optional "common_fields" field is described in Section 6.8.¶
The optional "vantage_point" field is described in Section 5.¶
The semantics and context of the trace can mainly be deduced from the entries in the "common_fields" list and "vantage_point" field.¶
JSON serialization example:¶
A TraceError indicates that an attempt to find/convert a file for inclusion in the aggregated qlog was made, but there was an error during the process. Rather than silently dropping the erroneous file, it can be explicitly included in the qlog file as an entry in the "traces" array, defined in Figure 6 as:¶
JSON serialization example:¶
Note that another way to combine events of different traces in a single qlog file is through the use of the "group_id" field, discussed in Section 6.6.¶
A qlog file using the QlogFileSeq schema can be serialized to a streamable JSON format called JSON Text Sequences (JSON-SEQ) ([RFC7464]). The top-level element in this schema defines only a small set of "header" fields and an array of component traces, defined in Figure 2 as:¶
The required "qlog_format" field MUST have the value "JSON-SEQ".¶
The required "qlog_version" field MUST have the value "0.4".¶
The optional "title" and "description" fields provide additional free-text information about the file.¶
The optional "trace" field contains a singular trace metadata. All qlog events in the file are related to this trace.¶
JSON-SEQ serialization example:¶
For further information about serialization, see Section 10.2.¶
A VantagePoint describes the vantage point from which a trace originates, defined in Figure 11 as:¶
JSON serialization examples:¶
The flow field is only required if the type is "network" (for example, the trace is generated from a packet capture). It is used to disambiguate events like "packet sent" and "packet received". This is indicated explicitly because for multiple reasons (e.g., privacy) data from which the flow direction can be otherwise inferred (e.g., IP addresses) might not be present in the logs.¶
Meaning of the different values for the flow field: * "client" indicates that this vantage point follows client data flow semantics (a "packet sent" event goes in the direction of the server). * "server" indicates that this vantage point follow server data flow semantics (a "packet sent" event goes in the direction of the client). * "unknown" indicates that the flow's direction is unknown.¶
Depending on the context, tools confronted with "unknown" values in the vantage_point can either try to heuristically infer the semantics from protocol-level domain knowledge (e.g., in QUIC, the client always sends the first packet) or give the user the option to switch between client and server perspectives manually.¶
A qlog event is specified as a generic object with a number of member fields and their associated data. Depending on the protocol and use case, the exact member field names and their formats can differ across implementations. This section lists the main, pre-defined and reserved field names with specific semantics and expected corresponding value formats.¶
An Event is defined in Figure 13 as:¶
Each qlog event MUST contain the mandatory fields: "time" (Section 6.1), "name" (Section 6.2), and "data" (Section 6.3).¶
Each qlog event MAY contain the optional fields: "time_format" (Section 6.1), "protocol_type" (Section 6.4), "trigger" (Section 6.5), and "group_id" (Section 6.6).¶
Multiple events can appear in a Trace or TraceSeq and they might contain fields with identical values. It is possible to optimize out this duplication using "common_fields" (Section 6.8).¶
The specific values for each of these fields and their semantics are defined in separate documents, depending on protocol or use case. For example: event definitions for QUIC, HTTP/3 and QPACK can be found in [QLOG-QUIC] and [QLOG-H3].¶
Events are intended to be extended with custom fields, therefore they MAY contain other fields not defined in this document. Custom fields may be known or unknown to tools. Tools SHOULD allow for the presence of unknown event fields, but their semantics depend on the context of the log usage.¶
JSON serialization:¶
An event's "time" field indicates the timestamp at which the event occurred. Its value is typically the Unix timestamp since the 1970 epoch (number of milliseconds since midnight UTC, January 1, 1970, ignoring leap seconds). However, qlog supports two more succinct timestamps formats to allow reducing file size. The employed format is indicated in the "time_format" field, which allows one of three values: "absolute", "delta" or "relative".¶
Definition:¶
The first option is good for stateless loggers, the second and third for stateful loggers. The third option is generally preferred, since it produces smaller files while being easier to reason about. An example for each option can be seen in Figure 16.¶
One of these options is typically chosen for the entire trace (put differently: each event has the same value for the "time_format" field). Each event MUST include a timestamp in the "time" field.¶
Events in each individual trace SHOULD be logged in strictly ascending timestamp order (though not necessarily absolute value, for the "delta" format). Tools MAY sort all events on the timestamp before processing them, though are not required to (as this could impose a significant processing overhead). This can be a problem especially for multi-threaded and/or streaming loggers, who could consider using a separate post-processor to order qlog events in time if a tool do not provide this feature.¶
Timestamps do not have to use the UNIX epoch timestamp as their reference. For example for privacy considerations, any initial reference timestamps (for example "endpoint uptime in ms" or "time since connection start in ms") can be chosen. Tools SHOULD NOT assume the ability to derive the absolute Unix timestamp from qlog traces, nor allow on them to relatively order events across two or more separate traces (in this case, clock drift should also be taken into account).¶
Events differ mainly in the type of metadata associated with them. The "name" field is an identifier that parsers can use to decide how to interpret the event metadata contained in the "data" field (see Section 6.3).¶
Event names indicate a category and type. The "name" field MUST contain a non-empty character sequence representing a category, followed by a colon (':'), followed by a non-empty character sequence representing a type.¶
Category allows a higher-level grouping of events per specific event type. For example for QUIC and HTTP/3, the different categories could be "quic", "http", "qpack", and "recovery". Within these categories, the event type provides additional granularity. For example for QUIC and HTTP/3, within the "quic" category, there would be "packet_sent" and "packet_received" events.¶
JSON serialization example:¶
An event's "data" field is a generic object. It contains the per-event metadata and its form and semantics are defined per specific sort of event. For example, data field value definitions for QUIC and HTTP/3 can be found in [QLOG-QUIC] and [QLOG-H3].¶
This field is defined here as a CDDL extension point (a "socket" or
"plug") named $ProtocolEventBody
. Other documents MUST properly extend
this extension point when defining new data field content options to
enable automated validation of aggregated qlog schemas.¶
The only common field defined for the data field is the trigger
field,
which is discussed in Section 6.5.¶
Definition:¶
One purely illustrative example for a QUIC "packet_sent" event is shown in Figure 19:¶
An event's "protocol_type" array field indicates to which protocols (or protocol "stacks") this event belongs. This allows a single qlog file to aggregate traces of different protocols (e.g., a web server offering both TCP+HTTP/2 and QUIC+HTTP/3 connections).¶
Definition:¶
For example, QUIC and HTTP/3 events have the "QUIC" and "HTTP3" protocol_type entry values, see [QLOG-QUIC] and [QLOG-H3].¶
Typically however, all events in a single trace are of the same few protocols, and this array field is logged once in "common_fields", see Section 6.8.¶
Sometimes, additional information is needed in the case where a single event can be caused by a variety of other events. In the normal case, the context of the surrounding log messages gives a hint as to which of these other events was the cause. However, in highly-parallel and optimized implementations, corresponding log messages might separated in time. Another option is to explicitly indicate these "triggers" in a high-level way per-event to get more fine-grained information without much additional overhead.¶
In qlog, the optional "trigger" field contains a string value describing the reason (if any) for this event instance occurring, see Section 6.3. While this "trigger" field could be a property of the qlog Event itself, it is instead a property of the "data" field instead. This choice was made because many event types do not include a trigger value, and having the field at the Event-level would cause overhead in some serializations. Additional information on the trigger can be added in the form of additional member fields of the "data" field value, yet this is highly implementation-specific, as are the trigger field's string values.¶
One purely illustrative example of some potential triggers for QUIC's "packet_dropped" event is shown in Figure 21:¶
As discussed in Section 3.2, a single qlog file can contain several traces taken from different vantage points. However, a single trace from one endpoint can also contain events from a variety of sources. For example, a server implementation might choose to log events for all incoming connections in a single large (streamed) qlog file. As such, a method for splitting up events belonging to separate logical entities is required.¶
The simplest way to perform this splitting is by associating a "group id" to each event that indicates to which conceptual "group" each event belongs. A post-processing step can then extract events per group. However, this group identifier can be highly protocol and context-specific. In the example above, the QUIC "Original Destination Connection ID" could be used to uniquely identify a connection. As such, they might add a "ODCID" field to each event. However, a middlebox logging IP or TCP traffic might rather use four-tuples to identify connections, and add a "four_tuple" field.¶
As such, to provide consistency and ease of tooling in cross-protocol and cross-context setups, qlog instead defines the common "group_id" field, which contains a string value. Implementations are free to use their preferred string serialization for this field, so long as it contains a unique value per logical group. Some examples can be seen in Figure 23.¶
Definition:¶
JSON serialization example for events grouped by four tuples and QUIC connection IDs:¶
Note that in some contexts (for example a Multipath transport protocol) it might make sense to add additional contextual per-event fields (for example "path_id"), rather than use the group_id field for that purpose.¶
Note also that, typically, a single trace only contains events belonging to a single logical group (for example, an individual QUIC connection). As such, instead of logging the "group_id" field with an identical value for each event instance, this field is typically logged once in "common_fields", see Section 6.8.¶
The "system_info" field can be used to record system-specific details related to an event. This is useful, for instance, where an application splits work across CPUs, processes, or threads and events for a single trace occur on potentially different combinations thereof. Each field is optional to support deployment diversity.¶
Definition:¶
SystemInformation = { ? processor_id: uint32 ? process_id: uint32 ? thread_id: uint32 }¶
As discussed in the previous sections, information for a typical qlog event varies in three main fields: "time", "name" and associated data. Additionally, there are also several more advanced fields that allow mixing events from different protocols and contexts inside of the same trace (for example "protocol_type" and "group_id"). In most "normal" use cases however, the values of these advanced fields are consistent for each event instance (for example, a single trace contains events for a single QUIC connection).¶
To reduce file size and making logging easier, qlog uses the "common_fields" list to indicate those fields and their values that are shared by all events in this component trace. This prevents these fields from being logged for each individual event. An example of this is shown in Figure 24.¶
An event's "common_fields" field is a generic dictionary of key-value pairs, where the key is always a string and the value can be of any type, but is typically also a string or number. As such, unknown entries in this dictionary MUST be disregarded by the user and tools (i.e., the presence of an unknown field is explicitly NOT an error).¶
The list of default qlog fields that are typically logged in common_fields (as opposed to as individual fields per event instance) are shown in the listing below:¶
Definition:¶
Tools MUST be able to deal with these fields being defined either on each event individually or combined in common_fields. Note that if at least one event in a trace has a different value for a given field, this field MUST NOT be added to common_fields but instead defined on each event individually. Good example of such fields are "time" and "data", who are divergent by nature.¶
While qlog is a high-level logging format, it also allows the inclusion of most raw wire image information, such as byte lengths and byte values. This is useful when for example investigating or tuning packetization behavior or determining encoding/framing overheads. However, these fields are not always necessary, can take up considerable space, and can have a considerable privacy and security impact (see Section 13). Where applicable, these fields are grouped in a separate, optional, field named "raw" of type RawInfo. The exact definition of entities, headers, trailers and payloads depend on the protocol used.¶
Definition:¶
The RawInfo:data field can be truncated for privacy or security purposes, see Section 10.1.2. In this case, the length and payload_length fields should still indicate the non-truncated lengths when used for debugging purposes.¶
This document does not specify explicit header_length or trailer_length fields. In protocols without trailers, header_length can be calculated by subtracting the payload_length from the length. In protocols with trailers (e.g., QUIC's AEAD tag), event definition documents SHOULD define how to support header_length calculation.¶
There are some event types and data classes that are common across protocols, applications, and use cases. This section specifies such common definitions.¶
In typical logging setups, users utilize a discrete number of well-defined logging categories, levels or severities to log freeform (string) data. This generic events category replicates this approach to allow implementations to fully replace their existing text-based logging by qlog. This is done by providing events to log generic strings for the typical well-known logging levels (error, warning, info, debug, verbose).¶
For the events defined below, the "category" is "generic" and their "type" is the name of the heading in lowercase (e.g., the "name" of the error event is "generic:error").¶
Importance: Core¶
Used to log details of an internal error that might not get reflected on the wire.¶
Definition:¶
Importance: Base¶
Used to log details of an internal warning that might not get reflected on the wire.¶
Definition:¶
Importance: Extra¶
Used mainly for implementations that want to use qlog as their one and only logging format but still want to support unstructured string messages.¶
Definition:¶
When evaluating a protocol implementation, one typically sets up a series of interoperability or benchmarking tests, in which the test situations can change over time. For example, the network bandwidth or latency can vary during the test, or the network can be fully disable for a short time. In these setups, it is useful to know when exactly these conditions are triggered, to allow for proper correlation with other events.¶
For the events defined below, the "category" is "simulation" and their "type" is the name of the heading in lowercase (e.g., the "name" of the scenario event is "simulation:scenario").¶
This document defines the main schema for the qlog format together with some common events, which on their own do not provide much logging utility. It is expected that logging is extended with specific, per-protocol event definitions that specify the name (category + type) and data needed for each individual event. Examples include the QUIC event definitions [QLOG-QUIC] and HTTP/3 and QPACK event definitions [QLOG-H3].¶
This section defines some basic annotations and concepts that SHOULD be used by event definition documents. Doing so ensures a measure of consistency that makes it easier for qlog implementers to support a wide variety of protocols.¶
There are several ways of defining qlog events. In practice, two main types of
approach have been observed: a) those that map directly to concepts seen in the
protocols (e.g., packet_sent
) and b) those that act as aggregating events that
combine data from several possible protocol behaviors or code paths into one
(e.g., parameters_set
). The latter are typically used as a means to reduce the
amount of unique event definitions, as reflecting each possible protocol event
as a separate qlog entity would cause an explosion of event types.¶
Additionally, logging duplicate data is typically prevented as much as possible.
For example, packet header values that remain consistent across many packets are
split into separate events (for example spin_bit_updated
or
connection_id_updated
for QUIC).¶
Finally, when logging additional state change events, those state changes can
often be directly inferred from data on the wire (for example flow control limit
changes). As such, if the implementation is bug-free and spec-compliant, logging
additional events is typically avoided. Exceptions have been made for common
events that benefit from being easily identifiable or individually logged (for
example packets_acked
).¶
Depending on how events are designed, it may be that several events allow the
logging of similar or overlapping data. For example the separate QUIC
connection_started
event overlaps with the more generic
connection_state_updated
. In these cases, it is not always clear which event
should be logged or used, and which event should take precedence if e.g., both are
present and provide conflicting information.¶
To aid in this decision making, each event SHOULD have an "importance indicator" with one of three values, in decreasing order of importance and expected usage:¶
The "Core" events are the events that SHOULD be present in all qlog files for a given protocol. These are typically tied to basic packet and frame parsing and creation, as well as listing basic internal metrics. Tool implementers SHOULD expect and add support for these events, though SHOULD NOT expect all Core events to be present in each qlog trace.¶
The "Base" events add additional debugging options and MAY be present in qlog files. Most of these can be implicitly inferred from data in Core events (if those contain all their properties), but for many it is better to log the events explicitly as well, making it clearer how the implementation behaves. These events are for example tied to passing data around in buffers, to how internal state machines change, and used to help show when decisions are actually made based on received data. Tool implementers SHOULD at least add support for showing the contents of these events, if they do not handle them explicitly.¶
The "Extra" events are considered mostly useful for low-level debugging of the implementation, rather than the protocol. They allow more fine-grained tracking of internal behavior. As such, they MAY be present in qlog files and tool implementers MAY add support for these, but they are not required to.¶
Note that in some cases, implementers might not want to log for example data
content details in the "Core" events due to performance or privacy considerations.
In this case, they SHOULD use (a subset of) relevant "Base" events instead to
ensure usability of the qlog output. As an example, implementations that do not
log QUIC packet_received
events and thus also not which (if any) ACK frames the
packet contains, SHOULD log packets_acked
events instead.¶
Finally, for event types whose data (partially) overlap with other event types' definitions, where necessary the event definition document should include explicit guidance on which to use in specific situations.¶
Event definition documents are free to define new category and event types, top-level fields (e.g., a per-event field indicating its privacy properties or path_id in multipath protocols), as well as values for the "trigger" property within the "data" field, or other member fields of the "data" field, as they see fit.¶
They however SHOULD NOT expect non-specialized tools to recognize or visualize this custom data. However, tools SHOULD make an effort to visualize even unknown data if possible in the specific tool's context. If they do not, they MUST ignore these unknown fields.¶
This document and other related qlog schema definitions are intentionally independent of serialization format. This means that implementers themselves can choose how to represent and serialize qlog data practically on disk or on the wire. Some examples of possible formats are JSON, CBOR, CSV, protobuf, flatbuffers, etc.¶
All these formats make certain tradeoffs between flexibility and efficiency, with textual formats like JSON typically being more flexible but also less efficient than binary formats like protocol buffers. The format choice will depend on the practical use case of the qlog user. For example, for use in day to day debugging, a plaintext readable (yet relatively large) format like JSON is probably preferred. However, for use in production, a more optimized yet restricted format can be better. In this latter case, it will be more difficult to achieve interoperability between qlog implementations of various protocol stacks, as some custom or tweaked events from one might not be compatible with the format of the other. This will also reflect in tooling: not all tools will support all formats.¶
This being said, the authors prefer JSON as the basis for storing qlog, as it retains full flexibility and maximum interoperability. Storage overhead can be managed well in practice by employing compression. For this reason, this document details how to practically transform qlog schema definitions to [JSON], its subset [I-JSON], and its streamable derivative [JSON-Text-Sequences]s. Concrete options to bring down JSON size and processing overheads are discuseed in Section 10.3.¶
As depending on the employed format different deserializers/parsers should be used, the "qlog_format" field is used to indicate the chosen serialization approach. This field is always a string, but can be made hierarchical by the use of the "." separator between entries. For example, a value of "JSON.optimizationA" can indicate that a default JSON format is being used, but that a certain optimization of type A was applied to the file as well (see also Section 10.3).¶
When mapping qlog to normal JSON, the "qlog_format" field MUST have the value "JSON". This is also the default qlog serialization and default value of this field.¶
When using normal JSON serialization, the file extension/suffix SHOULD be ".qlog" and the Media Type (if any) SHOULD be "application/qlog+json" per [RFC6839].¶
JSON files by definition ([RFC8259]) MUST utilize the UTF-8 encoding, both for the file itself and the string values.¶
While not specifically required by the JSON specification, all qlog field names in a JSON serialization MUST be lowercase.¶
In order to serialize CDDL-based qlog event and data structure definitions to JSON, the official CDDL-to-JSON mapping defined in Appendix E of [CDDL] SHOULD be employed.¶
For some use cases, it should be taken into account that not all popular JSON parsers support the full JSON format. Especially for parsers integrated with the JavaScript programming language (e.g., Web browsers, NodeJS), users are recommended to stick to a JSON subset dubbed [I-JSON] (or Internet-JSON).¶
One of the key limitations of JavaScript and thus I-JSON is that it
cannot represent full 64-bit integers in standard operating mode (i.e.,
without using BigInt extensions), instead being limited to the range of
[-(2**53)+1, (2**53)-1]
. In these circumstances, Appendix E of
[CDDL] recommends defining new CDDL types for int64 and
uint64 that limit their values to this range.¶
While this can be sensible and workable for most use cases, some protocols targeting qlog serialization (e.g., QUIC, HTTP/3), might require full uint64 variables in some (rare) circumstances. In these situations, it should be allowed to also use the string-based representation of uint64 values alongside the numerical representation. Concretely, the following definition of uint64 should override the original and (web-based) tools should take into account that a uint64 field can be either a number or string.¶
For some use cases (e.g., limiting file size, privacy), it can be
necessary not to log a full raw blob (using the hexstring
type) but
instead a truncated value (for example, only the first 100 bytes of an
HTTP response body to be able to discern which file it actually
contained). In these cases, the original byte-size length cannot be
obtained from the serialized value directly.¶
As such, all qlog schema definitions SHOULD include a separate,
length-indicating field for all fields of type hexstring
they specify,
see for example Section 7. This not only ensures the original length
can always be retrieved, but also allows the omission of any raw value
bytes of the field completely (e.g., out of privacy or security
considerations).¶
To reduce overhead however and in the case the full raw value is logged, the extra length-indicating field can be left out. As such, tools MUST be able to deal with this situation and derive the length of the field from the raw value if no separate length-indicating field is present. The main possible permutations are shown by example in Figure 35.¶
JSON Text Sequences are very similar to JSON, except that JSON objects are serialized as individual records, each prefixed by an ASCII Record Separator (<RS>, 0x1E), and each ending with an ASCII Line Feed character (\n, 0x0A). Note that each record can also contain any amount of newlines in its body, as long as it ends with a newline character before the next <RS> character.¶
Each qlog event is serialized and interpreted as an individual JSON Text Sequence record, and can simply be appended as a new object at the back of an event stream or log file. Put differently, unlike default JSON, it does not require a file to be wrapped as a full object with "{ ... }" or "[... ]".¶
For this to work, some qlog definitions have to be adjusted however. Mainly, events are no longer part of the "events" array in the Trace object, but are instead logged separately from the qlog "header", as indicated by the TraceSeq object in Figure 10. Additionally, qlog's JSON-SEQ mapping does not allow logging multiple individual traces in a single qlog file. As such, the QlogFile:traces field is replaced by the singular QlogFileSeq:trace field, see Figure 8. An example can be seen in Figure 9. Note that the "group_id" field can still be used on a per-event basis to include events from conceptually different sources in a single JSON-SEQ qlog file.¶
When using JSON-SEQ serialization, the file extension/suffix SHOULD be ".sqlog" (for "streaming" qlog) and the Media Type (if any) SHOULD be "application/qlog+json-seq" per [RFC8091].¶
While not specifically required by the JSON-SEQ specification, all qlog field names in a JSON-SEQ serialization MUST be lowercase.¶
In order to serialize all other CDDL-based qlog event and data structure definitions to JSON-SEQ, the official CDDL-to-JSON mapping defined in Appendix E of [CDDL] SHOULD still be employed.¶
Note that JSON Text Sequences are not supported in most default programming environments (unlike normal JSON). However, several custom JSON-SEQ parsing libraries exist in most programming languages that can be used and the format is easy enough to parse with existing implementations (i.e., by splitting the file into its component records and feeding them to a normal JSON parser individually, as each record by itself is a valid JSON object).¶
Both the JSON and JSON-SEQ formatting options described above are serviceable in general small to medium scale (debugging) setups. However, these approaches tend to be relatively verbose, leading to larger file sizes. Additionally, generalized JSON(-SEQ) (de)serialization performance is typically (slightly) lower than that of more optimized and predictable formats. Both aspects make these formats more challenging (though still practical) to use in large scale setups.¶
During the development of qlog, a multitude of alternative formatting and optimization options were compared. The results of this study are summarized on the qlog github repository. The rest of this section discusses some of these approaches implementations could choose and the expected gains and tradeoffs inherent therein. Tools SHOULD support mainly the compression options listed in Section 10.3.2, as they provide the largest wins for the least cost overall.¶
Over time, specific qlog formats and encodings can be created that more formally define and combine some of the discussed optimizations or add new ones. It was decided to define these schemes in separate documents to keep the main qlog definition clean and generalizable, as not all contexts require the same performance or flexibility as others and qlog is intended to be a broadly usable and extensible format (for example more flexibility is needed in earlier stages of protocol development, while more performance is typically needed in later stages). This is also the main reason why the general qlog format is the less optimized JSON instead of a more performant option.¶
To be able to easily distinguish between these options in qlog compatible tooling (without the need to have the user provide out-of-band information or to (heuristically) parse and process files in a multitude of ways, see also Section 12), it is recommended that explicit file extensions are used to indicate specific formats. As there are no standards in place for this type of extension to format mapping, a commonly used scheme is proposed: list the applied optimizations in the extension in ascending order of application (e.g., if a qlog file is first optimized with technique A and then compressed with technique B, the resulting file would have the extension ".(s)qlog.A.B"). This allows tooling to start at the back of the extension to "undo" applied optimizations to finally arrive at the expected qlog representation.¶
The first general category of optimizations is to alter the representation of data within an JSON(-SEQ) qlog file to reduce file size.¶
The first option is to employ a scheme similar to the CSV (comma separated value [RFC4180]) format, which utilizes the concept of column "headers" to prevent repeating field names for each datapoint instance. Concretely for JSON qlog, several field names are repeated with each event (i.e., time, name, data). These names could be extracted into a separate list, after which qlog events could be serialized as an array of values, as opposed to a full object. This approach was a key part of the original qlog format (prior to draft-02) using the "event_fields" field. However, tests showed that this optimization only provided a mean file size reduction of 5% (100MB to 95MB) while significantly increasing the implementation complexity, and this approach was abandoned in favor of the default JSON setup. Implementations using this format should not employ a separate file extension (as it still uses JSON), but rather employ a new value of "JSON.namedheaders" (or "JSON-SEQ.namedheaders") for the "qlog_format" field (see Section 3).¶
The second option is to replace field values and/or names with indices into a (dynamic) lookup table. This is a common compression technique and can provide significant file size reductions (up to 50% in tests, 100MB to 50MB). However, this approach is even more difficult to implement efficiently and requires either including the (dynamic) table in the resulting file (an approach taken by for example Chromium's NetLog format) or defining a (static) table up-front and sharing this between implementations. Implementations using this approach should not employ a separate file extension (as it still uses JSON), but rather employ a new value of "JSON.dictionary" (or "JSON-SEQ.dictionary") for the "qlog_format" field (see Section 3).¶
As both options either proved difficult to implement, reduced qlog file readability, and provided too little improvement compared to other more straightforward options (for example Section 10.3.2), these schemes are not inherently part of qlog.¶
The second general category of optimizations is to utilize a (generic) compression scheme for textual data. As qlog in the JSON(-SEQ) format typically contains a large amount of repetition, off-the-shelf (text) compression techniques typically succeed very well in bringing down file sizes (regularly with up to two orders of magnitude in tests, even for "fast" compression levels). As such, utilizing compression is recommended before attempting other optimization options, even though this might (somewhat) increase processing costs due to the additional compression step.¶
The first option is to use GZIP compression ([RFC1952]). This generic compression scheme provides multiple compression levels (providing a trade-off between compression speed and size reduction). Utilized at level 6 (a medium setting thought to be applicable for streaming compression of a qlog stream in commodity devices), gzip compresses qlog JSON files to 7% of their initial size on average (100MB to 7MB). For this option, the file extension .(s)qlog.gz SHOULD BE used. The "qlog_format" field should still reflect the original JSON formatting of the qlog data (e.g., "JSON" or "JSON-SEQ").¶
The second option is to use Brotli compression ([RFC7932]). While similar to gzip, this more recent compression scheme provides a better efficiency. It also allows multiple compression levels. Utilized at level 4 (a medium setting thought to be applicable for streaming compression of a qlog stream in commodity devices), brotli compresses qlog JSON files to 7% of their initial size on average (100MB to 7MB). For this option, the file extension .(s)qlog.br SHOULD BE used. The "qlog_format" field should still reflect the original JSON formatting of the qlog data (e.g., "JSON" or "JSON-SEQ").¶
Other compression algorithms of course exist (for example xz, zstd, and lz4). The gzip and brotli are recommended because of their tweakable behaviour and wide support in web-based environments, which is envisioned as the main tooling ecosystem (see also Section 12).¶
The third general category of optimizations is to use a more optimized (often binary) format instead of the textual JSON format. This approach inherently produces smaller files and often has better (de)serialization performance. However, the resultant files are no longer human readable and some formats require hard tradeoffs between flexibility for performance.¶
The first option is to use the CBOR (Concise Binary Object Representation [RFC7049]) format. For the purposes of qlog, CBOR can be viewed as a straightforward binary variant of JSON. As such, existing JSON qlog files can be trivially converted to and from CBOR (though slightly more work is needed for JSON-SEQ qlogs to convert them to CBOR-SEQ, see [RFC8742]). While CBOR thus does retain the full qlog flexibility, it only provides a 25% file size reduction (100MB to 75MB) compared to textual JSON(-SEQ). As CBOR support in programming environments is not as widespread as that of textual JSON and the format lacks human readability, CBOR was not chosen as the default qlog format. For this option, the file extension .(s)qlog.cbor SHOULD BE used. The "qlog_format" field should still reflect the original JSON formatting of the qlog data (e.g., "JSON" or "JSON-SEQ"). The media type should indicate both whether JSON or JSON Text Sequences are used, as well as whether CBOR or CBOR Sequences are used (see the table below).¶
A second option is to use a more specialized binary format, such as Protocol Buffers (protobuf). This format is battle-tested, has support for optional fields and has libraries in most programming languages. Still, it is significantly less flexible than textual JSON or CBOR, as it relies on a separate, pre-defined schema (a .proto file). As such, it it not possible to (easily) log new event types in protobuf files without adjusting this schema as well, which has its own practical challenges. As qlog is intended to be a flexible, general purpose format, this type of format was not chosen as its basic serialization. The lower flexibility does lead to significantly reduced file sizes. A straightforward mapping of the qlog main schema and QUIC/HTTP3 event types to protobuf created qlog files 24% as large as the raw JSON equivalents (100MB to 24MB). For this option, the file extension .(s)qlog.protobuf SHOULD BE used. The "qlog_format" field should reflect the different internal format, for example: "qlog_format": "protobuf".¶
Note that binary formats can (and should) also be used in conjunction with compression (see Section 10.3.2). For example, CBOR compresses well (to about 6% of the original textual JSON size (100MB to 6MB) for both gzip and brotli) and so does protobuf (5% (gzip) to 3% (brotli)). However, these gains are similar to the ones achieved by simply compression the textual JSON equivalents directly (7%, see Section 10.3.2). As such, since compression is still needed to achieve optimal file size reductions event with binary formats, the more flexible compressed textual JSON options are likely a better default for the qlog format in general.¶
In summary, textual JSON was chosen as the main qlog format due to its high flexibility and because its inefficiencies can be largely solved by the utilization of compression techniques (which are needed to achieve optimal results with other formats as well).¶
Still, qlog implementers are free to define other qlog formats depending on their needs and context of use. These formats should be described in their own documents, the discussion in this document mainly acting as inspiration and high-level guidance. Implementers are encouraged to add concrete qlog formats and definitions to the designated public repository.¶
The following table provides an overview of all the discussed qlog formatting options with examples:¶
format | qlog_format | extension | media type |
---|---|---|---|
JSON Section 10.1 | JSON | .qlog | application/qlog+json |
JSON Text Sequences Section 10.2 | JSON-SEQ | .sqlog | application/qlog+json-seq |
named headers Section 10.3.1 | JSON(-SEQ).namedheaders | .(s)qlog | application/qlog+json(-seq) |
dictionary Section 10.3.1 | JSON(-SEQ).dictionary | .(s)qlog | application/qlog+json(-seq) |
CBOR Section 10.3.3 | JSON(-SEQ) | .(s)qlog.cbor | application/qlog+json(-seq)+cbor(-seq) |
protobuf Section 10.3.3 | protobuf | .qlog.protobuf | NOT SPECIFIED BY IANA |
gzip Section 10.3.2 | no change | .gz suffix | application/gzip |
brotli Section 10.3.2 | no change | .br suffix | NOT SPECIFIED BY IANA |
As discussed in the previous sections, a qlog file can be serialized in a multitude of formats, each of which can conceivably be transformed into or from one another without loss of information. For example, a number of JSON-SEQ streamed qlogs could be combined into a JSON formatted qlog for later processing. Similarly, a captured binary qlog could be transformed to JSON for easier interpretation and sharing.¶
Secondly, other structured logging approaches contain similar (though typically not identical) data to qlog, like raw packet capture files (for example .pcap files from tcpdump) or endpoint-specific logging formats (for example the NetLog format in Google Chrome). These are sometimes the only options, if an implementation cannot or will not support direct qlog output for any reason, but does provide other internal or external (e.g., SSLKEYLOGFILE export to allow decryption of packet captures) logging options For this second category, a (partial) transformation from/to qlog can also be defined.¶
As such, when defining a new qlog serialization format or wanting to utilize qlog-compatible tools with existing codebases lacking qlog support, it is recommended to define and provide a concrete mapping from one format to default JSON-serialized qlog. Several of such mappings exist. Firstly, [pcap2qlog]((https://github.com/quiclog/pcap2qlog) transforms QUIC and HTTP/3 packet capture files to qlog. Secondly, netlog2qlog converts chromium's internal dictionary-encoded JSON format to qlog. Finally, quictrace2qlog converts the older quictrace format to JSON qlog. Tools can then easily integrate with these converters (either by incorporating them directly or for example using them as a (web-based) API) so users can provide different file types with ease. For example, the qvis toolsuite supports a multitude of formats and qlog serializations.¶
Different implementations will have different ways of generating and storing qlogs. However, there is still value in defining a few default ways in which to steer this generation and access of the results.¶
To provide users control over where and how qlog files are created, two environment variables are defined. The first, QLOGFILE, indicates a full path to where an individual qlog file should be stored. This path MUST include the full file extension. The second, QLOGDIR, sets a general directory path in which qlog files should be placed. This path MUST include the directory separator character at the end.¶
In general, QLOGDIR should be preferred over QLOGFILE if an endpoint is prone to generate multiple qlog files. This can for example be the case for a QUIC server implementation that logs each QUIC connection in a separate qlog file. An alternative that uses QLOGFILE would be a QUIC server that logs all connections in a single file and uses the "group_id" field (Section 6.6) to allow post-hoc separation of events.¶
Implementations SHOULD provide support for QLOGDIR and MAY provide support for QLOGFILE.¶
When using QLOGDIR, it is up to the implementation to choose an appropriate naming scheme for the qlog files themselves. The chosen scheme will typically depend on the context or protocols used. For example, for QUIC, it is recommended to use the Original Destination Connection ID (ODCID), followed by the vantage point type of the logging endpoint. Examples of all options for QUIC are shown in Figure 36.¶
Tools ingestion qlog MUST indicate which qlog version(s), qlog format(s), compression methods and potentially other input file formats (for example .pcap) they support. Tools SHOULD at least support .qlog files in the default JSON format (Section 10.1). Additionally, they SHOULD indicate exactly which values for and properties of the name (category and type) and data fields they look for to execute their logic. Tools SHOULD perform a (high-level) check if an input qlog file adheres to the expected qlog schema. If a tool determines a qlog file does not contain enough supported information to correctly execute the tool's logic, it SHOULD generate a clear error message to this effect.¶
Tools MUST NOT produce breaking errors for any field names and/or values in the qlog format that they do not recognize. Tools SHOULD indicate even unknown event occurrences within their context (e.g., marking unknown events on a timeline for manual interpretation by the user).¶
Tool authors should be aware that, depending on the logging implementation, some events will not always be present in all traces. For example, using a circular logging buffer of a fixed size, it could be that the earliest events (e.g., connection setup events) are later overwritten by "newer" events. Alternatively, some events can be intentionally omitted out of privacy or file size considerations. Tool authors are encouraged to make their tools robust enough to still provide adequate output for incomplete logs.¶
Protocols such as TLS [RFC8446] and QUIC [RFC9000] provide varying degrees of secure protection for the wire image [RFC8546]. There is inevitably tension between security and observability, when logging can reveal aspects of the wire image, that would ordinarily be protected. This tension equally applies to any privacy considerations that build on security properties, especially if data can be correlated across data sources.¶
qlog operators and implementers should be mindful of the security and privacy risks inherent in handling qlog data. This includes but is not limited to logging, storing, or using the data. Data might be considered as non-sensitive, potentially-sensitive, or sensitive; applying the considerations in this section may produce different risks depending on the nature of the data itself, or its handling. However, in many cases the largest risk factors arise from data that can be considered as potenially-sensitive or sensitive.¶
The following is a non-exhaustive list of such fields and types of data that can be carried in qlog data:¶
The simplest and most extreme form of protection against abuse of this information is the complete deletion of a given field, which is equivalent to not logging the field(s) in question. While deletion completely protects the data in the deleted fields from the risk of compromise, it also reduces the utility of the dataset as a whole. As such, a balance should be found between logging these fields and the potential risks inherent in their (involuntary) disclosure. This balance depends on the use case at hand (e.g., research datasets might have different requirements to live operational troubleshooting). Capturing the minimal amount of data required for a specific purpose can help to minimize the risks associated with data usage. qlog implementations that provide fine-grained control over the inclusion of data fields, ideally on a per-use-case or per-connection basis, improve the ability to minimize data.¶
Any data that is determined to be necessary for a use case at hand could be logged or captured. As per [RFC6973], operators must be aware that such data will be at risk of compromise. As such, measures should be taken to firstly reduce the risk of compromise and secondly reduce the risk of abuse of compromised data. While a full discussion of both aspects is out of scope for this document, the following paragraphs discuss high-level considerations that can be applied to qlog data.¶
To reduce the risk of compromise, operators can take measures such as: limiting the length of time that data is stored, encrypting data in transit and at rest, limiting access rights to the data, and auditing data usage practices. qlog deployments that provide integrated options for automated or manual data deletion and (aggressive) aggregation, improve the ability to minimize the risk of compromise.¶
To reduce the risk of data abuse after compromise, data can be anonymized, pseudonymized, otherwise permutated/replaced, truncated, (re-)encrypted, or aggregated. A partial discussion of applicable techniques (especially for IP address information) can be found in Appendix B of [DNS-PRIVACY]. Operators should, however, be aware that many of these techniques have been shown to be insufficient to safeguard user privacy and/or to protect user identity, especially if a qlog data set is large or easily correlated against other data sources.¶
Finally, qlog operators should consider the interplay between their use case needs and end user rights or preferences. While active user participation (as indicated by [RFC6973]) on a per-qlog basis is difficult, as logs are often captured out-of-band to the main user interaction and intent, general user expectations should be taken into account. qlog deployments that provide mechanisms to integrate the capture, storage and removal of qlogs with more general, often pre-existing, user preference and privacy control systems, improve the ability to protect data sensitive or confidential to the end user. In qlog, these data are typically (but not exclusively) contained in fields of the RawInfo type (see Section 7). qlog users should thus be particularly hesitant to include these fields for all but the most stringent use cases.¶
There are no IANA considerations.¶
Much of the initial work by Robin Marx was done at the Hasselt and KU Leuven Universities.¶
Thanks to Jana Iyengar, Brian Trammell, Dmitri Tikhonov, Stephen Petrides, Jari Arkko, Marcus Ihlar, Victor Vasiliev, Mirja Kuehlewind, and Jeremy Laine for their feedback and suggestions.¶
This section is to be removed before publishing as an RFC.¶
Decoupled qlog from the JSON format and described a mapping instead (#89)¶