234 lines
9.7 KiB
Markdown
234 lines
9.7 KiB
Markdown
# Trace Logging Library Usage
|
||
|
||
The idea behind Trace Logging is that we can choose to trace a function and
|
||
then, with minimal additional code, determine how long function execution ran
|
||
for, whether the function was successful, and connect all of this information to
|
||
any existing informational logs. The below provides information about how this
|
||
can be achieved with OSP's TraceLogging Infrastructure.
|
||
|
||
## Compilation
|
||
|
||
By default, TraceLogging is enabled as part of the build.
|
||
To disable TraceLogging, include flag `--args="enable_trace_logging=false"`
|
||
when calling `gn gen` as part of building this library.
|
||
|
||
## Imports
|
||
|
||
To use TraceLogging, import the following header file:
|
||
* *util/trace_logging.h*
|
||
This file will import all other Trace Logging dependencies.
|
||
|
||
## Trace IDs
|
||
|
||
When a Trace Log is created, a new unsigned integer is associated with this
|
||
specific trace, which will going forward be referred to as a Trace ID.
|
||
Associating a Trace ID has the following benefits:
|
||
|
||
* The same Trace ID can be associated with all Informational Logs generated
|
||
during this traced method’s execution. This will allow for all informational
|
||
logs to easily be associated with a method execution, so that if an unexpected
|
||
failure occurs or edge case pops up we will have additional information useful
|
||
in debugging this issue and can more easily determine the state of the system
|
||
leading to this issue.
|
||
|
||
* If another Trace Log is created during the execution of this first traced
|
||
method, we will have a hierarchy of Trace IDs. This will allow us to easily
|
||
create an execution tree (through trivial scripting, preexisting tool, or
|
||
database operations) to get a better view of how execution is proceeding.
|
||
Together, the IDs associated with these calls form a Trace ID Hierarchy. The
|
||
Hierarchy has the following format:
|
||
* *Current Trace ID*: The ID of the function currently being traced.
|
||
* *Parent Trace ID*: The ID of the function which was being traced when this
|
||
Trace was initiated.
|
||
* *Root Trace ID*: The ID of the first traced function under which this the
|
||
current Trace was called.
|
||
|
||
As an example:
|
||
``` cpp
|
||
public void DoThings() {
|
||
TRACE_SCOPED(category, "outside");
|
||
{
|
||
TRACE_SCOPED(category, "middle");
|
||
{
|
||
TRACE_SCOPED(category, "inside");
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
This could result in the following Trace ID Information:
|
||
|
||
| NAME | ROOT ID | PARENT ID | TRACE ID | RESULT |
|
||
|---------|----------|------------|-----------|---------|
|
||
| outside | 100 | 0 | 100 | success |
|
||
| middle | 100 | 100 | 101 | success |
|
||
| inside | 100 | 101 | 102 | success |
|
||
|
||
Note that, prior to any trace calls, the Trace ID is considered to be 0x0 by
|
||
convention.
|
||
|
||
## Trace Results
|
||
|
||
The "result" of a trace is meant to indicate whether the traced function
|
||
completed successfully or not. Results are handled differently for synchronous
|
||
and asynchronous traces.
|
||
|
||
For scoped traces, the trace is by default assumed to be successful. If an error
|
||
state is desired, this should be set using `TRACE_SET_RESULT(result)` where
|
||
result is some Error::Code enum value.
|
||
|
||
For asynchronous calls, the result must be set as part of the `TRACE_ASYNC_END`
|
||
call. As with scoped traces, the result must be some Error::Code enum value.
|
||
|
||
## Tracing Functions
|
||
All of the below functions rely on the Platform Layer's IsTraceLoggingEnabled()
|
||
function. When logging is disabled, either for the specific category of trace
|
||
logging which the Macro specifies or for TraceCategory::Any in all other cases,
|
||
the below functions will be treated as a NoOp.
|
||
|
||
### Synchronous Tracing
|
||
```c++
|
||
TRACE_SCOPED(category, name)
|
||
TRACE_SCOPED(category, name, traceId, parentId, rootId)
|
||
TRACE_SCOPED(category, name, traceIdHierarchy)
|
||
```
|
||
If logging is enabled for the provided |category|, trace the current scope. The scope
|
||
should encompass the operation described by |name|. The latter two uses of this macro are
|
||
for manually providing the trace ID hierarchy; the first auto-generates a new trace ID for
|
||
this scope and sets its parent trace ID to that of the encompassing scope (if any).
|
||
|
||
```c++
|
||
TRACE_DEFAULT_SCOPED(category)
|
||
TRACE_DEFAULT_SCOPED(category, traceId, parentId, rootId)
|
||
TRACE_DEFAULT_SCOPED(category, traceIdHierarchy)
|
||
```
|
||
Same as TRACE_SCOPED(), but use the current function/method signature as the operation
|
||
name.
|
||
|
||
### Asynchronous Tracing
|
||
`TRACE_ASYNC_START(category, name)`
|
||
If logging is enabled for the provided category, this function will initiate
|
||
a new asynchronous function trace with name as provided. It will not end
|
||
until TRACE_ASYNC_END is called with the same Trace ID generated for this
|
||
async trace. When this call is used, the Trace ID Hierarchy will be
|
||
determined automatically and the caller does not need to worry about it and,
|
||
as such, **this call should be used in the majority of asynchronous tracing
|
||
cases**.
|
||
|
||
`TRACE_ASYNC_START(category, name, traceId, parentId, rootId)`
|
||
If logging is enabled for the provided category, this function will initiate
|
||
a new asynchronous function trace with name and full Trac ID Hierarchy as
|
||
provided. It will not end until TRACE_ASYNC_END is called with the same
|
||
Trace ID provided for this async trace. Each of trace ID, parent ID, and
|
||
root ID is optional, so providing only a subset of these values is also
|
||
valid if the caller only desires to set specific ones.
|
||
|
||
`TRACE_ASYNC_START(category, name, traceIdHierarchy)`
|
||
This call is intended for use in conjunction with the TRACE_HIERARCHY macro
|
||
(as described below). this function will initiate a new asynchronous
|
||
function trace with name and full Trace ID Hierarchy as provided. It will
|
||
not end until TRACE_ASYNC_END is called with the same Trace ID provided for
|
||
this async trace.
|
||
|
||
`TRACE_ASYNC_END(category, id, result)`
|
||
This call will end a trace initiated by TRACE_ASYNC_START (as described
|
||
above) if logging is enabled for the associated category. The id is expected
|
||
to match that used by an TRACE_ASYNC_START call, and result is the same as
|
||
TRACE_SET_RESULT's argument.
|
||
|
||
### Other Tracing Macros
|
||
`TRACE_CURRENT_ID`
|
||
This macro returns the current Trace ID at this point in time.
|
||
|
||
`TRACE_ROOT_ID`
|
||
This macro returns the root Trace ID at this point in time.
|
||
|
||
`TRACE_HIERARCHY`
|
||
This macro returns an instance of struct Trace ID Hierarchy containing the
|
||
current Trace ID Hierarchy. This is intended to be used with
|
||
`TRACE_SET_HIERARCHY` (described below) so that Trace ID Hierarchy can be
|
||
maintained when crossing thread or machine boundaries.
|
||
|
||
`TRACE_SET_HIERARCHY(ids)`
|
||
This macro sets the current Trace Id Hierarchy without initiating a scoped
|
||
trace. The set ids will cease to apply when the current scope ends. This is
|
||
intended to be used with `TRACE_HIERARCHY` (described above) so that Trace
|
||
ID Hierarchy can be maintained when crossing thread or machine boundaries.
|
||
|
||
`TRACE_SET_RESULT(result)`
|
||
Sets the current scoped trace's result to be the same as the Error::Code
|
||
argument provided.
|
||
|
||
### Example Code
|
||
Synchronous Tracing:
|
||
``` cpp
|
||
public void DoThings() {
|
||
TRACE_SCOPED(category, "DoThings");
|
||
|
||
// Do Things.
|
||
// A trace log is generated when the scope containing the above call ends.
|
||
|
||
TRACE_SET_RESULT(Error::Code::kNone);
|
||
}
|
||
```
|
||
|
||
Asynchronous tracing with known Trace ID (recommended):
|
||
This approach allows for asynchronous tracing when the function being traced can
|
||
be associated with a known Trace ID. For instance, a packet ID, a request ID, or
|
||
another ID which will live for the duration of the trace but will not need to be
|
||
passed around separately.
|
||
``` cpp
|
||
public void DoThingsStart() {
|
||
TRACE_ASYNC_START(category, "DoThings", kKnownId);
|
||
}
|
||
|
||
public void DoThingsEnd() {
|
||
TRACE_ASYNC_END(category, kKnownId, Error::Code::kNone);
|
||
}
|
||
```
|
||
|
||
Asynchronous tracing with unknown Trace ID (not recommended):
|
||
This approach allows for asynchronous tracing even when no existing ID can be
|
||
associated with the trace.
|
||
``` cpp
|
||
public TraceId DoThingsStart() {
|
||
TRACE_ASYNC_START(category, "DoThings");
|
||
return TRACE_CURRENT_ID;
|
||
}
|
||
|
||
public void DoThingsEnd(TraceId trace_id) {
|
||
TRACE_ASYNC_END(category, trace_id, Error::Code::kNone);
|
||
}
|
||
```
|
||
|
||
## File Division
|
||
The code for Trace Logging is divided up as follows:
|
||
* *util/trace_logging.h*: the macros Open Screen library code is expected to
|
||
use to log trace events.
|
||
* *platform/base/trace_logging_types.h*: the types/enums used in the platform
|
||
API as well as internal library code.
|
||
* *util/trace_logging/*: the internal infrastructure backing the macros in
|
||
*trace_logging.h*, and connecting it to the platform API.
|
||
* *platform/api/trace_logging_platform.h*: the platform implementation that is
|
||
used as the trace logging destination while tracing is active.
|
||
This information is intended to be only eplanatory for embedders - only the one
|
||
file mentioned above in Imports must be imported.
|
||
|
||
## Embedder-Specific Tracing Implementations
|
||
|
||
For an embedder to create a custom TraceLogging implementation:
|
||
|
||
1. *Create a TraceLoggingPlatform*
|
||
In platform/api/trace_logging_platform.h, the interface TraceLoggingPlatform
|
||
is defined. An embedder must define a class implementing this interface. The
|
||
methods should be as performance-optimal as possible, since they might be
|
||
called frequently (especially `IsLoggingEnabled(TraceCategory)`) and are often
|
||
in the critical execution path of the library's code.
|
||
|
||
2. *Call `openscreen::StartTracing()` and `StopTracing()`*
|
||
These activate/deactivate tracing by providing the TraceLoggingPlatform
|
||
instance and later clearing references to it.
|
||
|
||
**The default implementation of this layer can be seen in
|
||
platform/impl/trace_logging_platform.cc.**
|