Logging
Purpose
YDB uses structured logging for events that occur in the system. Structured logging means that in the log (operation journal), the textual description of events is separated from the parameters of those events, and the parameter values themselves are stored and processed independently of each other. For example, if a file read error occurs, its description is text like "Unable to read file", while the parameters are the file path, system error code, textual description of the system error, and so on.
Separating event descriptions from their parameters can subsequently be used for efficient message search, statistics collection, optimized log storage, and other tasks.
Basic tools for writing messages to the log
In most cases, the following conditions are met when writing messages to the log:
- The source code is contained in some
.cppfile (logging from header files is supported but discussed further). - In one
.cppfile, all messages are written on behalf of the same component. Therefore, at the beginning of the file (but after all#includedirectives), theYDB_LOG_THIS_FILE_COMPONENTmacro must be defined, which sets the component code for the entire file.
Note
Logging in YDB assumes that the system consists of a large number of internal components, each of which has its own unique code assigned. You can find the full list of components and their codes on GitHub.
When writing a message to the log, the component code on whose behalf the message is written must be explicitly or implicitly specified. Subsequently, the component name is always output to the log and can be used to search or filter log contents.
In addition, each component can have its own logging settings.
Example:
#define YDB_LOG_THIS_FILE_COMPONENT NKikimrServices::STATESTORAGE
Warning
The build system may combine multiple .cpp files into a single compilation unit using JOIN_SRCS, which includes them via #include directives. Therefore, it is strongly recommended to undefine the macro at the end of each source file:
#undef YDB_LOG_THIS_FILE_COMPONENT
Otherwise, the definition of the YDB_LOG_THIS_FILE_COMPONENT macro may propagate to multiple source files.
- Logging occurs during the operation of an actor, and the
NActors::TlsActivationContextvariable is available for working with the context (in particular, for sending messages to the logging actor).
Warning
If at least one of the three listed conditions is not met, you should use extended logging tools.
Further in this .cpp file, the following macros can be used for logging messages:
| Macro | Message level |
|---|---|
YDB_LOG_EMERG(message, ...values...) |
Possible system failure (for example, cluster failure). |
YDB_LOG_ALERT(message, ...values...) |
Possible system degradation, system components may fail. |
YDB_LOG_CRIT(message, ...values...) |
Critical state. |
YDB_LOG_ERROR(message, ...values...) |
Non-critical error. |
YDB_LOG_WARN(message, ...values...) |
Warning that should be responded to and fixed if it is not temporary. |
YDB_LOG_NOTICE(message, ...values...) |
An event significant for the system or user has occurred. |
YDB_LOG_INFO(message, ...values...) |
Debug information for statistics collection. |
YDB_LOG_DEBUG(message, ...values...) |
Debug information for developers. |
YDB_LOG_TRACE(message, ...values...) |
Very detailed debug information. |
The following parameters are specified in the arguments of the listed macros:
message— text message;...values...— optional parameters. Each parameter is specified as a{name, value}pair, wherenameis a text string with the parameter name, andvalueis the parameter value.
Examples of writing messages to the log:
- Message without parameters:
YDB_LOG_INFO("Module started");
- Message with parameters:
YDB_LOG_ERROR("Unable to open file",
{"sourceFilePath", filename},
{"errorCode", err});
Dynamic determination of message level
In some cases, the message level is determined dynamically at runtime. Then you should use a macro that accepts the message level as an argument. The macro syntax is:
YDB_LOG(prio, message, ...values...);
Usage example:
auto prio = NActors::NLog::PRI_ERROR;
...
YDB_LOG(prio, "Unable to open file",
{"sourceFilePath", filename},
{"errorCode", err});
Extended tools for writing messages to the log
Logging core
The lowest-level tool for writing messages to the log is the YDB_LOG_CTX_COMP macro. It checks the need for logging (according to settings), forms the text message, attaches optional parameters to it, and then sends all this information to the logging actor. Next, the logging actor processes the received message according to its current settings (for example, writes it to a file or syslog).
Note
All other logging tools (including basic logging tools) are built on the basis of the YDB_LOG_CTX_COMP macro.
The full syntax of the logging macro is as follows:
YDB_LOG_CTX_COMP(ctx, prio, comp, message, ...values...)
The following parameters are specified in the macro call arguments:
ctx— actor execution context (needed to send the message to the logging actor);prio— message logging level (corresponds to logging levels);comp— component identifier;message— text message;...values...— optional parameters. Each parameter is specified as a{name, value}pair, wherenameis a text string with the parameter name, andvalueis the parameter value.
In the examples below, EXAMPLE_COMP_CODE denotes the logging component code. In real code, specify an existing component instead, for example NKikimrServices::STATESTORAGE.
Examples of writing a message to the log
- Message without parameters:
YDB_LOG_CTX_COMP(ctx, PRI_INFO, EXAMPLE_COMP_CODE, "Module started");
- Message with parameters:
YDB_LOG_CTX_COMP(ctx, PRI_ERROR, EXAMPLE_COMP_CODE, "Unable to open file",
{"sourceFilePath", filename},
{"errorCode", err});
In this example, a message with the text Unable to open file and two parameters is written: sourceFilePath (the value is taken from the filename variable) and errorCode (the value is taken from the err variable). The execution context ctx is used to send the message to the logging actor, and EXAMPLE_COMP_CODE is passed as the component code.
The YDB_LOG_CTX_COMP macro implies dynamic determination of the message level and passing it as an argument. By analogy with basic logging tools, there are a number of macros that do not require passing the message level as an argument.
Macros without specifying the message level as an argument
| Macro | Message level |
|---|---|
YDB_LOG_EMERG_CTX_COMP |
Possible system failure (for example, cluster failure). |
YDB_LOG_ALERT_CTX_COMP |
Possible system degradation, system components may fail. |
YDB_LOG_CRIT_CTX_COMP |
Critical state. |
YDB_LOG_ERROR_CTX_COMP |
Non-critical error. |
YDB_LOG_WARN_CTX_COMP |
Warning that should be responded to and fixed if it is not temporary. |
YDB_LOG_NOTICE_CTX_COMP |
An event significant for the system or user has occurred. |
YDB_LOG_INFO_CTX_COMP |
Debug information for statistics collection. |
YDB_LOG_DEBUG_CTX_COMP |
Debug information for developers. |
YDB_LOG_TRACE_CTX_COMP |
Very detailed debug information. |
The macros listed in the table do not require specifying the prio argument.
Example:
YDB_LOG_ERROR_CTX_COMP(ctx, EXAMPLE_COMP_CODE, "Unable to open file",
{"sourceFilePath", filename},
{"errorCode", err});
Many other macros discussed below are based on the YDB_LOG_CTX_COMP macro. Their names are constructed according to the following scheme:
- The macro name always starts with
YDB_LOG. - If the macro does not require specifying the message level, then the message level name is added to the macro name with an underscore character.
- If the macro requires specifying the execution context, then
_CTXis added to the macro name. - If the macro requires specifying the component, then
_COMPis added to the macro name.
Using the standard execution context
In most cases, the execution context available through the global variable NActors::TlsActivationContext is used to send messages to the logging actor. To avoid visually overloading the source code with references to this global variable, an additional YDB_LOG_COMP macro has been introduced, which always uses NActors::TlsActivationContext and does not require specifying the CTX parameter.
Example:
YDB_LOG_COMP(PRI_ERROR, EXAMPLE_COMP_CODE, "Unable to open file",
{"sourceFilePath", filename},
{"errorCode", err});
Note
The construction of the YDB_LOG_COMP macro name fully fits the basic macro naming principle discussed in the logging core.
In this example, the standard execution context is used, and logging occurs on behalf of the component with code EXAMPLE_COMP_CODE.
Macros without specifying the message level as an argument
| Macro | Message level |
|---|---|
YDB_LOG_EMERG_COMP |
Possible system failure (for example, cluster failure). |
YDB_LOG_ALERT_COMP |
Possible system degradation, system components may fail. |
YDB_LOG_CRIT_COMP |
Critical state. |
YDB_LOG_ERROR_COMP |
Non-critical error. |
YDB_LOG_WARN_COMP |
Warning that should be responded to and fixed if it is not temporary. |
YDB_LOG_NOTICE_COMP |
An event significant for the system or user has occurred. |
YDB_LOG_INFO_COMP |
Debug information for statistics collection. |
YDB_LOG_DEBUG_COMP |
Debug information for developers. |
YDB_LOG_TRACE_COMP |
Very detailed debug information. |
Example:
YDB_LOG_ERROR_COMP(EXAMPLE_COMP_CODE, "Unable to open file",
{"sourceFilePath", filename},
{"errorCode", err});
Using the default component
The same mechanism is used as in basic logging tools:
- At the beginning of the file (but after all
#includedirectives), theYDB_LOG_THIS_FILE_COMPONENTmacro must be defined. It sets the component code for the entire file.
Warning
The build system may combine multiple .cpp files into a single compilation unit using JOIN_SRCS, which includes them via #include directives. Therefore, it is strongly recommended to undefine the macro at the end of each source file:
#undef YDB_LOG_THIS_FILE_COMPONENT
Otherwise, the definition of the YDB_LOG_THIS_FILE_COMPONENT macro may propagate to multiple source files.
- Further in the file, logging macros that do not require specifying the component code should be used. They are similar to those already discussed, but their name does not contain the
_COMPstring.
Example:
#define YDB_LOG_THIS_FILE_COMPONENT EXAMPLE_COMP_CODE
...
YDB_LOG_CTX(ctx, PRI_ERROR, "Unable to open file",
{"sourceFilePath", filename},
{"errorCode", err});
Macros without specifying the message level as an argument
| Macro | Message level |
|---|---|
YDB_LOG_EMERG_CTX |
Possible system failure (for example, cluster failure). |
YDB_LOG_ALERT_CTX |
Possible system degradation, system components may fail. |
YDB_LOG_CRIT_CTX |
Critical state. |
YDB_LOG_ERROR_CTX |
Non-critical error. |
YDB_LOG_WARN_CTX |
Warning that should be responded to and fixed if it is not temporary. |
YDB_LOG_NOTICE_CTX |
An event significant for the system or user has occurred. |
YDB_LOG_INFO_CTX |
Debug information for statistics collection. |
YDB_LOG_DEBUG_CTX |
Debug information for developers. |
YDB_LOG_TRACE_CTX |
Very detailed debug information. |
Example:
#define YDB_LOG_THIS_FILE_COMPONENT EXAMPLE_COMP_CODE
...
YDB_LOG_ERROR_CTX(ctx, "Unable to open file",
{"sourceFilePath", filename},
{"errorCode", err});
Logging in header files
Warning
It is strongly discouraged to define the YDB_LOG_THIS_FILE_COMPONENT macro in header files, as this can lead to the following consequences:
- Complex logic for determining the component code (it is not obvious which header file the
YDB_LOG_THIS_FILE_COMPONENTdefinition comes from). - Compilation errors (in different files, the
YDB_LOG_THIS_FILE_COMPONENTmacro may be defined differently).
If logging is needed in a header file, macros that require explicit specification of the component code should be used:
YDB_LOG_CTX_COMP— requires explicit specification of the message level, execution context, and component code.YDB_LOG_XXXX_CTX_COMP— does not require specifying the message level.YDB_LOG_COMP— uses the standard execution context.YDB_LOG_XXXX_COMP— uses the standard execution context and does not require specifying the message level.
Constructing messages
Reusable sets of attached parameters
If the same set of parameters is attached to different messages, then to avoid code duplication, you can create this parameter set in advance and then reuse it when logging various events. The YDB_LOG_CREATE_MESSAGE macro is intended for this purpose. It accepts a set of parameters (as {name, value} pairs) as arguments and returns an object that can subsequently be used when sending messages to the log.
Example
#define YDB_LOG_THIS_FILE_COMPONENT EXAMPLE_COMP_CODE
...
void MyFunction(const std::string& filename) {
...
// Create message parameters
auto context = YDB_LOG_CREATE_MESSAGE(
{"sourceFilePath", filename});
...
if (err != 0) {
YDB_LOG_ERROR("MyFunction failed",
context, // Use message parameters
{"errorCode", err});
return;
}
...
YDB_LOG_NOTICE("MyFunction succeeded",
context); // Use message parameters
}
The result of calling the YDB_LOG_CREATE_MESSAGE macro is an instance of the TStructuredMessage class. This instance stores the set of parameters included in it, their names, types, and values. Instances of the TStructuredMessage class can be stored in local and global variables, copied, passed, and so on.
Note
The TStructuredMessage class can be considered a specialized container for storing {name, value} pairs attached to log messages.
Operations for modifying a previously created parameter set are supported:
- Adding and updating.
Example
YDB_LOG_UPDATE_MESSAGE(context,
{"socket", socketNum});
- Removing parameters.
Example
context.RemoveValue("socket");
Nested parameter sets
It is possible to create nested sets of attached parameters. To do this, when logging a message (or creating another parameter set), a previously created value set (i.e., an instance of the TStructuredMessage class) must be specified as the value in the {name, value} pair. Then all parameters from the value set will be automatically added to the resulting message (or the created value set), but with the prefix specified in name added to the parameter names, with . as the separator.
Example
#define YDB_LOG_THIS_FILE_COMPONENT EXAMPLE_COMP_CODE
...
void MyFunction() {
// Create message parameters
auto context = YDB_LOG_CREATE_MESSAGE(
{"operationName", "read"},
{"sourceFilePath", filename});
...
if (err != 0) {
YDB_LOG_ERROR("MyFunction failed",
{"details", context}, // Use message parameters
{"errorCode", err});
return;
}
...
}
In this example, the parameters details.operationName, details.sourceFilePath, and errorCode will be attached to the message with the text MyFunction failed.
Logging contexts
Forming and reusing sets of attached parameters is inconvenient in the following cases:
- These sets need to be specified when logging a large number of messages.
- These sets need to be passed to all called functions so that they can be specified there when logging messages.
You can simplify the source code by using a logging context — a set of parameters that will be automatically attached to all messages sent to the log in the given execution thread. To do this, call the YDB_LOG_CREATE_CONTEXT macro and pass it {name, value} pairs (the same way as when sending messages to the log or creating reusable sets), as well as previously created TStructuredMessage objects. The parameters listed in YDB_LOG_CREATE_CONTEXT are added during logging in the code block where this macro is called, as well as in nested code blocks and all called functions.
Example of using a logging context
#define YDB_LOG_THIS_FILE_COMPONENT EXAMPLE_COMP_CODE
...
void MyFunction() {
YDB_LOG_CREATE_CONTEXT(
{"sourceFilePath", filename});
...
if (errorCode != 0) {
YDB_LOG_ERROR("MyFunction failed", // The sourceFilePath and errorCode parameters will be attached to the message
{"errorCode", errorCode});
return;
}
...
YDB_LOG_NOTICE("MyFunction succeeded"); // The sourceFilePath parameter will be attached to the message
}
The sourceFilePath parameter will be attached to all messages logged in this execution thread in the MyFunction function and the functions it calls, up to the point of exiting the MyFunction function.
Logging contexts are organized as a stack. If a logging context is configured, it is stored at the top of the stack. Each execution thread has its own stack of logging contexts.
When YDB_LOG_CREATE_CONTEXT is called, the following actions occur:
- A new parameter set is created (as when calling
YDB_LOG_CREATE_MESSAGE), containing all parameters from the existing context, as well as parameters directly specified in theYDB_LOG_CREATE_CONTEXTmacro. - The new parameter set is placed on the top of the stack and thus becomes the current logging context in the given execution thread.
- When writing a message to the log (calling the
YDB_LOG_CTX_COMPmacro or similar), parameters from the top of the logging context stack are automatically attached to the message, and the parameters specified in the logging macro are written on top of this set. - The top of the stack will be automatically removed when exiting the current block, thus returning to the previously configured context.
To add parameters to the current context or update their values, use the YDB_LOG_UPDATE_CONTEXT macro:
YDB_LOG_UPDATE_CONTEXT({"requestId", requestId});
Multiple parameters and their values can be specified in the YDB_LOG_UPDATE_CONTEXT macro. If the specified parameters already exist in the context, their values will be updated. If the specified parameters are absent from the context, their values will be added to the context.
To remove parameters from the current context, pass their names to YDB_LOG_REMOVE_CONTEXT:
YDB_LOG_REMOVE_CONTEXT("requestId");
The YDB_LOG_UPDATE_CONTEXT and YDB_LOG_REMOVE_CONTEXT macros can be called any number of times. They modify only the current context (that is, the top of the context stack), but do not create new contexts and do not affect other contexts located lower in the context stack.
Recommendations for log content
Security
It is forbidden to write passwords, access tokens, encryption keys, connection strings, and personal data to the log. If a value is needed for diagnostics, the sensitive part must be removed, masked, or replaced with an irreversible hash before passing it to the macro.
Coding style
The following coding style is recommended when using logging macros:
- All mandatory information (execution context, component code, message level, and message text) is placed on one line.
- Each
{name, value}pair is placed on a separate line.
Example of writing a message with multiple parameters:
YDB_LOG_DEBUG("Handle TEvNodeWardenNotifyConfigMismatch",
{"clusterStateGeneration", ClusterStateGeneration},
{"msgGeneration", msgGeneration},
{"clusterStateGuid", ClusterStateGuid},
{"msgGuid", msgGuid});
Text message style
The following style for writing message texts is recommended:
-
The text is a meaningful (and preferably correct) sentence in English that speaks about some event inside the system, with the following requirements:
- the text starts with a capital letter;
- if the text is a separate sentence, no period is placed at the end of the message;
- if the text consists of several sentences, they are separated by periods, but no period is placed after the last sentence.
-
The text may contain class and function names. If an actor logs the fact of receiving some message, a good practice is to log with the text
Handle <event class name>. -
Messages are fixed text without dynamically formed fragments.
-
All dynamic information known only at runtime should be placed as attached parameters.
To align message texts and parameter names, there is a simple empirical rule:
If you take the message text and add the parameter values to it in the form
(name=value, name=value, ... ), you should get a meaningful, complete, unambiguous, and human-understandable text message.
Example of aligned message text and parameter names
YDB_LOG_ERROR_CTX(ctx, "Unable to open file",
{"sourceFilePath", filename},
{"errorCode", err});
The expected event description looks like this (message parameters are listed in alphabetical order):
Unable to open file (errorCode = ..., sourceFilePath = ...)
General recommendations for naming individual parameters
The style of naming individual parameters is based on two basic principles:
- In the context of a specific message, the parameter value should be interpreted simply and unambiguously.
YDB_LOG_ERROR("Response timeout elapsed",
{"nodeHostName", hostName},
{"requestNum", requestNum},
{"waitAtPosixTime", startTime},
{"timeoutMs", timeout});
YDB_LOG_ERROR("Response timeout elapsed",
{"node", hostName},
{"request", requestNum},
{"wait", startTime},
{"timeout", timeout});
If you use the previously mentioned rule for aligning message texts and parameter names, you get the sentence Response timeout elapsed (node=<string>, request=<number>, wait=<number>, timeout=<number>), where it will be unclear: is the node value a host name or some internal node identifier? Is the request value a sequence number or a numeric identifier? What is wait and how should this value be interpreted? In what units is the wait value specified?
- Semantically similar parameters should have the same names, since different naming of the same concepts makes it difficult to search for log entries. For example, it is undesirable to denote a transaction identifier as
txIdin some messages and astransactionIdin others.
YDB_LOG_INFO("Started transaction",
{"txId", transactionId});
...
YDB_LOG_INFO("Transaction modifies table",
{"queryId", queryId},
{"txId", transactionId});
...
YDB_LOG_INFO("Query commits transaction",
{"queryId", queryId},
{"txId", transactionId});
...
YDB_LOG_INFO("Started transaction",
{"id", transactionId});
...
YDB_LOG_INFO("Transaction modifies table",
{"id", queryId},
{"transactionId", transactionId});
...
YDB_LOG_INFO("Query commits transaction",
{"id", queryId},
{"txId", transactionId});
...
In this example, the transaction identifier is written to parameters with different names (id, transactionId, and txId), and at the same time, the parameter named id stores identifiers of completely different entities each time.
- Parameter names are written in
camelCase.
Specific recommendations for naming individual parameters
The following recommendations are aimed at fulfilling the two principles mentioned above:
- It is undesirable to use individual common words (
id,item,value,path,child,parent,min,max, etc.) as parameter names — this complicates searching log entries. For example, the wordiddenotes an identifier, but it is completely unclear which entity it refers to, although in the context of a specific message, the meaning of this parameter may be perfectly clear.
YDB_LOG_INFO("Started transaction",
{"txId", ...});
...
YDB_LOG_INFO("Received query",
{"queryId", ...});
...
YDB_LOG_INFO("Sent request to actor",
{"actorId", ...});
YDB_LOG_INFO("Started transaction",
{"id", ...});
...
YDB_LOG_INFO("Received query",
{"id", ...});
...
YDB_LOG_INFO("Sent request to actor",
{"id", ...});
- Parameter names should consist of several words, with the last word being the main one, and each preceding word clarifying the meaning of the following one. For example:
actorId— actor identifier;operationId— operation identifier;operationName— operation name;operationSessionId— identifier of the session within which the operation is performed.
YDB_LOG_INFO("Copy data to another shard",
{"srcShardId", ...},
{"dstShardId", ...});
YDB_LOG_INFO("Copy data to another shard",
{"srcId", ...},
{"dstShard", ...});
Here it is unclear both what entity the srcId parameter is an identifier of, and how to interpret the dstShard value (is it an identifier, a name, or something else).
- Rules for denoting individual entities and collections:
- a separate mention of an entity name in the singular implies that the parameter value contains a description of that entity. For example,
tableis a description of a table (however, in this case, it is better to add a suffix, that is,tableIdclearly indicates that the parameter contains a table identifier,tableName— a table name,tableDesc— a table description, etc.); - a separate mention of an entity name in the plural implies that the parameter value contains a description of a collection of entities. For example,
tablesis a description of several tables (but not their count); - a separate mention of an entity name in the plural with the addition of
Countimplies that the parameter value contains the number of elements in the collection. For example,tablesCount— the number of tables; - it is undesirable to add
Sizeinstead ofCountto the parameter name to denote the number of elements in a collection. For example, the parameter nametotalFilesSizemakes it unclear whether we are talking about the number of files or their total size.
YDB_LOG_INFO("Backup volumes progress",
{"currentVolumeNum", ...},
{"volumesCount", ...},
{"volumesSizeBytes", ...});
YDB_LOG_INFO("Backup volumes progress",
{"currentVolume", ...},
{"volumes", ...});
- If a commonly accepted abbreviation or term in YDB is used in a parameter name, it should be written in the case in which it is conventionally used. Examples of such parameter names:
VDiskId— VDisk identifier (written not asvdiskId);PDiskId— PDisk identifier (written not aspdiskId);- etc.
-
Popular abbreviations are allowed (
idxinstead ofindex,msginstead ofmessage,txinstead oftransaction, etc.). -
If a parameter contains a numeric value, it is desirable to add a suffix to the parameter name:
Id, if the number is an identifier (but not asID);- the name of the units of measurement (for example, for memory volumes —
Byte,KByte,MByte, for time —Ms,Sec,Min, for indicating percentage fractions of something —Percent, and so on). If a non-standard unit of measurement is used, it is desirable to use the prepositionIn(for example,bufferSizeInBlocks);
YDB_LOG_INFO("Table dump progress",
{"tableId", ...},
{"dumpedRecordsCount", ...},
{"dumpedSizeBytes", ...},
{"totalSizeBytes", ...});
YDB_LOG_INFO("Table dump progress",
{"table", ...},
{"dumpedRecords", ...},
{"dumpedSize", ...},
{"totalSize", ...});
- If a numeric parameter value is more convenient for a person to analyze not in decimal form, it is better to specify this value as a character string and supplement it with commonly accepted characters (for example, a hexadecimal representation should have the prefix
0x, and a binary one should have the prefix0b, etc.). A good practice is to supplement the parameter name with the suffixHexor similar.
unsigned flags;
...
YDB_LOG_INFO("Invalid access rights to file",
{"filename", ...},
{"aclFlags", flags},
{"aclFlagsOct", ToOct(flags)}); // Make flags as "0XXXXX" string
unsigned flags;
...
YDB_LOG_INFO("Invalid access rights to file",
{"filename", ...},
{"aclFlags", flags});
- If a parameter contains textual information, it is desirable to add a suffix to the parameter name that makes it easier to interpret the parameter value (for example,
Name,Desc,Json,Base64, etc.). - Local variable naming rules can be applied to parameter names (for example, for boolean flags, add
is,has, or similar to the beginning of the parameter name).
Commonly accepted parameter names
There are commonly accepted parameter names that are desirable to use.
| Name | Parameter content |
|---|---|
actorClassName |
Name of the class in which the actor is implemented. |
actorStateName |
Actor state. |
backtrace |
Exception occurrence stack (result of the TBackTrace::FromCurrentException().PrintToString() function). |
ev |
Description of the message being processed by the actor. |
evType |
Type of the message being processed by the actor. |
exception |
Description of the occurred C++ exception. |
selfId |
Identifier of the actor processing the message. |
Methodology for enriching logs with contextual information
The following methodology is proposed for enriching events that a specific actor writes to the log.
Step 1. The actor should implement a function that forms the current logging context as a set of parameters attached to logged messages.
Implementation example
TStructuredMessage GetLogContext() const {
return YDB_LOG_CREATE_MESSAGE(
{"actorClassName", "TFileReader"},
{"selfId", SelfId()},
{"sourceFilePath", filename});
}
Instead of the name GetLogContext, GetLogPrefix or similar can be used. In principle, this may not even be a function, but a class field created in the constructor and modified during the actor's operation.
Step 2. Setting up the context at "entry points". Each actor has a small set of functions that can be called by the actor system core. These include:
- The
Bootstrapfunction. - Message processing functions (found by the
STATEFNmacro or similar). - Other virtual functions that the actor overrides (they are easily found by the
overridekeyword).
Then, in the first line of each such function, the logging context should be configured using the YDB_LOG_CREATE_CONTEXT macro.
Example of context configuration
STATEFN(StateWork) {
YDB_LOG_CREATE_CONTEXT(GetLogContext());
switch (ev->GetTypeRewrite()) {
...
}
}