Allows information to be output to a log.
You.i Engine's logging system provides a method for outputting, at run-time, information relating to the application.
By default logging is enabled in You.i Engine and all messages are output using the default logging sink for the target platform.
Macros are provided to simplify writing log messages to the logging system. These are the macros for each of the log severities supported by the logger:
Log Macro | Description |
---|---|
YI_LOGF | Logs a fatal error and immediately aborts the application. |
YI_LOGE | Logs an error. |
YI_LOGW | Logs a warning. |
YI_LOGI | Logs information. |
YI_LOGD | Logs debug information. |
The log macros listed in the table above are listed in order of severity, from highest to lowest. All log macros have the same syntax and support variadic arguments for the message.
The following code snippet displays the use of a log macro in its simplest form:
All of the logging macros support dynamic arguments injected into a specified format string, for instance:
While the spdlog library supports fmt::print-style argument format specifiers by default, for compatibility with existing code the macros above use legacy printf-style format API as implemented by the fmt library. See fmt::printf API for precise format details.
The appearance of log messages can be customized. Use CYILogger::SetPattern to specify a different format string to use.
The pattern flags are in the form of "%flag" and are modeled after the strftime()
function:
flag | meaning |
---|---|
"%v" | The actual message text to log |
"%t" | Thread id |
"%P" | Process id |
"%n" | Logger's name |
"%l" | The full log level of the message |
"%L" | Short log level of the message |
"%a" | Abbreviated weekday name |
"%A" | Full weekday name |
"%b" | Abbreviated month name |
"%B" | Full month name |
"%c" | Date and time representation |
"%C" | Year in 2 last digits |
"%Y" | Year in 4 full digits |
"%D" or "%x" | Short MM/DD/YY date |
"%m" | Month (01-12) |
"%d" | Day of month (01-31) |
"%H" | Hours in 24-hour format (00-23) |
"%I" | Hours in 12-hour format (01-12) |
"%M" | Minutes (00-59) |
"%S" | Seconds (00-59) |
"%e" | Millisecond part of the current second (000-999) |
"%f" | Microsecond part of the current second (000000-999999) |
"%F" | Nanosecond part of the current second (000000000-999999999) |
"%p" | AM/PM string |
"%r" | Time using 12-hour clock |
"%R" | Time using 24-hour clock (HH:MM), equivalent to "%H:%M" |
"%T" or "%X" | ISO 8601 time format (HH:MM:SS), equivalent to "%H:%M:%S" |
"%z" | ISO 8601 offset from UTC in timezone ([+/-]HH:MM) |
"%E" | Seconds since the epoch |
"%%" | The % sign |
"%o" | Elapsed time in milliseconds since previous message |
"%i" | Elapsed time in microseconds since previous message |
"%u" | Elapsed time in nanoseconds since previous message |
"%O" | Elapsed time in seconds since previous message |
For more format specifiers, see the spdlog formatting documentation
The default format string used by the logger depends on whether the code is compiled in release or debug mode. The debug mode format string presents log messages more verbosely, providing more context for easier debugging. On the other hand, the release mode log messages are slimmer, to reduce the amount of logging traffic and to prevent a performance impact.
The best way to pass a log tag to a YI_LOGx
macro is to pass a string literal like so:
To avoid repeating the same string in multiple log calls, a macro constant named LOG_TAG
could be used:
String instances (CYIString and std::string) can also be passed in:
By default messages of all severities are output, including logs generated by You.i Engine. However, the application developer may not want to output all log messages, and to accommodate this the logging system has a built-in mechanism for log filtering.
The EYILogLevel is an enumeration representing the severity of a log message. Here is the full list sorted by severity (highest to lowest):
Severity | Meaning |
---|---|
EYILogLevel::critical | A critical failure message (application execution is aborted) |
EYILogLevel::err | An error message |
EYILogLevel::warn | A warning message |
EYILogLevel::info | An information message |
EYILogLevel::debug | A debugging message |
To configure the minimum level of messages to be emitted, call CYILogger::SetLevel with the desired value. All messages below the specified threshold will be ignored and not added to the log. Passing the special level value of EYILogLevel::off
suppresses all emitted messages.
In case multiple logging sinks are used (see below), each sink can have its own logging level specified. Thus, it's possible, for instance, to do verbose logging to a file but to only send warnings and errors to the console.
In addition to the global level-based filtering, there's a way to provide custom log message filtering based on arbitrary criteria. The logging subsystem supports a stack of log message filters chained together in a way that selectively removes only certain messages from the logging feed and leaves others intact. Each such filter in the stack reduces the logging traffic in a way similar to how photo camera filters filter out light that reaches the camera sensor/film. The pruned log messages are then sent to all of the installed logging sinks (where each sink can have its own minimum pass-through level, as mentioned above)
To add a log message filter, use the CYILogger::PushFilter method with an std::function
predicate of your choice to perform per-message based filtering. The predicate accepts the message to be logged and decides whether to keep it or to discard it. If the predicate function returns true, the message is kept, otherwise it gets rejected. For example:
As is the case with any std::function
, the predicate can be a lambda, a regular function or method, or any other callable object.
To remove filters from the stack (the last one added is removed first), use CYILogger::PopFilter. To remove all installed filters, invoke CYILogger::RemoveAllFilters.
A common use case for log filtering is reducing logs based on tags, where certain log tags get selectively suppressed or made to stand out more than the rest. To facilitate this scenario, CYILogger::CreateFilter method takes a map of log tags as keys and minimum levels as values. As a result, if a log message with one of the given tags is encountered, the matching log level will be used as a cut-off threshold. If the tag is not found in the map, by default the message is allowed through and normal sink levels (if configured) are applied as the next step. The optional elseMinLevel argument provides a way to specify the default minimum level in the fallback case. This can be used to perform an "opt-in" style of logging, where most/all of the log messages are ignored, except those that are specified in the map with their respective baseline levels. One useful application of this is in automated tests, where code is only interested in specific messages to be emitted to the log. It usually makes sense to use this mode for the last filter of the filter stack.
Example 1:
Example 2:
The logging system supports simultaneous output to multiple logging destinations like console, log file or other custom targets called "logging sinks". The default output sink depends on the target platform and can use platform-specific logging facilities. For instance, Android logs are emitted in a way that allows them to be analyzed using the stock Android logcat tool, and Apple logs use the Unified Logging subsystem.
In order to provide additional logging targets like special log files or to implement logging over the network, custom sinks can be implemented by extending the CYILogSink class or the spdlog::sinks::sink class template directly. Third-party custom spdlog sinks are supported as well.
An example of a simple custom sink implementation:
To add a custom sink, call CYILogger::AddSink, and to remove it, use CYILogger::RemoveSink. If you want to completely avoid using the stock logger sinks and reconfigure the logger to redirect your logs elsewhere, it's possible to remove all installed sinks by calling CYILogger::RemoveAllSinks and add the desired ones from scratch.
To send log output to a file, use CYILogger::CreateFileSink to create the logging sink to do so. You can have multiple file sinks at the same time, perhaps with different log levels applied to them. (Remember that the log filtering stack is applied to all of the installed sinks.)
Example:
Note that unlike the stock spdlog engine, the You.i logging engine currently only supports a single logger (but with multiple output sinks).
The You.i engine has built-in support for sending logs from a device on which the app runs to a machine acting as a log server over network (typically, to the app developer's workstation). In order to enable network logging, a few options need to be specified during application generation phase.
Here is the list of variables that are employed for network logger configuration:
Variable | Value | Description |
---|---|---|
YI_NETWORK_LOGGER_ENABLED | ON or OFF | Use ON to enable and OFF to disable network logging. |
YI_NETWORK_LOGGER_IP | a.b.c.d | The IP address of the network log receiver. If omitted, the preferred local IP address will be used by default. |
YI_NETWORK_LOGGER_PORT | nnnnn | The port number (greater than 1023) of the network log receiver. If omitted, the default value of 10000 will be used. |
Thus, to enable network logging, it's necessary to specify the YI_NETWORK_LOGGER_ENABLED
option with the value of ON
:
As you can see in the example above, we didn't specify the IP address of the log receiver host. If the address is omitted, the generation script will try to determine the address automatically. However, if you want to build the app on one workstation and receive its logs on another, then the IP address can be specified explicitly:
The requested IP address and port number (if omitted, a value of 10000 is used by default) are placed into a generated file that is linked against the app and the engine during the final app linking step. In order for the linking not to fail with an error about unresolved symbols, your application main file should include the generated file somewhere at the top of the include block:
The logger configuration file is generated even if network logging is not enabled, although with default values indicating that network logging should not be activated on app startup.
Conversely, for some code that should never use network logging but which still links to the You.i engine (for example, your app unit/integration tests), it's possible to suppress network logging by inserting the following line prior to including the generated logger configuration file:
If you have generated and built your application without errors, it will try talking to the log receiver during startup in order to send all logs to it. If the log receiver is not available (not running or configured differently), the app will resume normal startup without network logging after a timeout.
The network log receiver is a script located at tools/networkLogReceiver.rb
in the You.i engine tree. If you launch it without specifying any parameters, it will start listening on the default port 10000.
You can specify a different port value than the default with a -p
option:
Make sure that the app and the log receiver use the same port number to be able to transmit logs correctly.
Classes | |
class | CYILogger |
class | CYILogSink |
A parent class for all logging sinks, supported by the spdlog logging engine. More... | |
Macros | |
#define | YI_LOG_PURE(tag, message, ...) _YI_LOG_AT_LEVEL(EYILogLevel::critical, (tag), message, ##__VA_ARGS__) |
#define | YI_LOGF(tag, message, ...) |
#define | YI_LOGE(tag, message, ...) _YI_LOG_AT_LEVEL(EYILogLevel::err, (tag), message, ##__VA_ARGS__) |
#define | YI_LOGW(tag, message, ...) _YI_LOG_AT_LEVEL(EYILogLevel::warn, (tag), message, ##__VA_ARGS__) |
#define | YI_LOGI(tag, message, ...) _YI_LOG_AT_LEVEL(EYILogLevel::info, (tag), message, ##__VA_ARGS__) |
#define | YI_LOGD(tag, message, ...) _YI_LOG_AT_LEVEL(EYILogLevel::debug, (tag), message, ##__VA_ARGS__) |
Typedefs | |
using | EYILogLevel = spdlog::level::level_enum |
using | CYILogMessage = spdlog::details::log_msg |
#define YI_LOG_PURE | ( | tag, | |
message, | |||
... | |||
) | _YI_LOG_AT_LEVEL(EYILogLevel::critical, (tag), message, ##__VA_ARGS__) |
Macro to log without a severity tag. This will always print even in a release build.
#define YI_LOGD | ( | tag, | |
message, | |||
... | |||
) | _YI_LOG_AT_LEVEL(EYILogLevel::debug, (tag), message, ##__VA_ARGS__) |
Use this macro to log a debug message.
#define YI_LOGE | ( | tag, | |
message, | |||
... | |||
) | _YI_LOG_AT_LEVEL(EYILogLevel::err, (tag), message, ##__VA_ARGS__) |
Use this macro to log an error.
#define YI_LOGF | ( | tag, | |
message, | |||
... | |||
) |
Use this macro to log a fatal error.
#define YI_LOGI | ( | tag, | |
message, | |||
... | |||
) | _YI_LOG_AT_LEVEL(EYILogLevel::info, (tag), message, ##__VA_ARGS__) |
Use this macro to log some information.
#define YI_LOGW | ( | tag, | |
message, | |||
... | |||
) | _YI_LOG_AT_LEVEL(EYILogLevel::warn, (tag), message, ##__VA_ARGS__) |
Use this macro to log a warning.
using CYILogMessage = spdlog::details::log_msg |
A structure containing a log message with additional attributes like source file and line.
using EYILogLevel = spdlog::level::level_enum |
An enum representing a logging level/message severity.