- Babeltrace 2 library
- Logging
- Logging API
- Guides
- Instrument a C/C++ source file (generic)
- Instrument a C/C++ header file (generic)
- Instrument a library C/C++ source file
- Instrument a library C/C++ header file
- Instrument a component class C/C++ source file
- Instrument a component class C/C++ header file
- Choose a logging tag
- Choose a log level
- Write an appropriate message
- Output
- Valgrind
- Testing
- C++ usage
- Python Usage
- Git
This is a partial contributor’s guide for the Babeltrace 2 project. If you have any questions that are not answered by this guide, please post them on Babeltrace’s mailing list.
This section covers the rationale behind the design of Babeltrace 2’s object lifetime management. This applies to the Babeltrace 2 library, as well as to the CTF writer library (although the public reference counting functions are not named the same way).
Starting from Babeltrace 2.0, all publicly exposed objects inherit
a common base: bt_object
. This base provides a number of facilities to
all objects, chief amongst which are lifetime management functions.
The lifetime of some public objects is managed by reference counting. In
this case, the API offers the bt_*_get_ref()
and bt_*_put_ref()
functions which respectively increment and decrement an object’s
reference count.
As far as lifetime management in concerned, Babeltrace 2 makes a clear distinction between regular objects, which have a single parent, and root objects, which don’t.
Let us consider a problematic case to illustrate the need for this distinction.
A user of the Babeltrace 2 library creates a trace class, which has a stream class (the class of a stream) and that stream class, in turn, has an event class (the class of an event).
Nothing prevents this user from releasing his reference on any one of these objects in any order. However, all objects in the trace—stream class—event class hierarchy can be retrieved from any other.
For instance, the user could discard his reference on both the event class and the stream class, only keeping a reference on the trace class. From this trace class reference, stream classes can be enumerated, providing the user with a new reference to the stream class he discarded earlier. Event classes can also be enumerated from stream classes, providing the user with references to the individual event classes.
Conversely, the user could also hold a reference to an event class and retrieve its parent stream class. The trace class, in turn, can then be retrieved from the stream class.
This example illustrates what could be interpreted as a circular reference dependency existing between these objects. Of course, if the objects in such a scenario were to hold references to each other (in both directions), we would be in presence of a circular ownership resulting in a leak of both objects as their reference counts would never reach zero.
Nonetheless, the API must offer the guarantee that holding a node to any node of the graph keeps all other reachable nodes alive.
The scheme employed in Babeltrace 2 to break this cycle consists in the "children" holding reverse component references to their parents. That is, in the context of the trace IR, that event classes hold a reference to their parent stream class and stream classes hold a reference to their parent trace class.
On the other hand, parents hold claiming aggregation references to their children. A claiming aggregation reference means that the object being referenced should not be deleted as long as the reference still exists. In this respect, it can be said that parents truly hold the ownership of their children, since they control their lifetime. Conversely, the reference counting mechanism is leveraged by children to notify parents that no other child indirectly exposes the parent.
When a parented object’s reference count reaches zero, it invokes
bt_*_put_ref()
on its parent and does not free itself. However,
from that point, the object depends on its parent to signal the moment
when it can be safely reclaimed.
The invocation of bt_*_put_ref()
by the last children holding a
reference to its parent might trigger a cascade of bt_*_put_ref()
from child to parent. Eventually, a root object is reached. At that
point, if this orphaned object’s reference count reaches zero, the
object invokes the destructor method defined by everyone of its children
as part of their base struct bt_object
. The key point here is that the
cascade of destructor will necessarily originate from the root and
propagate in preorder to the children. These children will propagate the
destruction to their own children before reclaiming their own memory.
This ensures that a node’s pointer to its parent is always valid since
the parent has the responsibility of tearing-down their children before
cleaning themselves up.
Assuming a reference to an object is acquired by calling
bt_*_get_ref()
while its reference count is zero, the object
acquires, in turn, a reference on its parent using bt_*_get_ref()
.
At that point, the child can be thought of as having converted its weak
reference to its parent into a regular reference. That is why this
reference is referred to as a claiming aggregation reference.
This scheme imposes a number of strict rules defining the relation between objects:
-
Objects may only have one parent.
-
Objects, beside the root, are only retrievable from their direct parent or children.
The initial situation is rather simple: User A is holding a reference to a trace class, TC1. As per the rules previously enounced, stream classes SC1 and SC2 don’t hold a reference to TC1 since their own reference counts are zero. The same holds true for EC1, EC2 and EC3 with respect to SC1 and SC2.
In this second step, we can see that User A has acquired a reference on SC2 through the trace class, TC1.
The stream class’s reference count transitions from zero to one, triggering the acquisition of a strong reference on TC1 from SC2.
Hence, at this point, the trace class’s ownership is shared by User A and SC2.
Next, User A acquires a reference on the EC3 event class through its parent stream class, SC2. Again, the transition of an object’s reference count from 0 to 1 triggers the acquisition of a reference on its parent.
Note that SC2’s reference count was incremented to 2. The trace class’s reference count remains unchanged.
User A decides to drop its reference on SC2. SC2's reference count returns back to 1, everything else remaining unchanged.
User A can then decide to drop its reference on the trace class. This results in a reversal of the initial situation: User A now owns an event, EC3, which is keeping everything else alive and reachable.
If another object, User B, enters the picture and acquires a reference on the SC1 stream class, we see that SC1's reference count transitioned from 0 to 1, triggering the acquisition of a reference on TC1.
User B hands off a reference to EC1, acquired through SC1, to another object, User C. The acquisition of a reference on EC1, which transitions from 0 to 1, triggers the acquisition of a reference on its parent, SC1.
At some point, User A releases its reference on EC3. Since EC3's reference count transitions to zero, it releases its reference on SC2. SC2's reference count, in turn, reaches zero and it releases its reference to TC1.
TC1's reference count is now 1 and no further action is taken.
User B releases its reference on SC1. User C becomes the sole owner of the whole hierarchy through his ownership of EC1.
Finally, User C releases his ownership of EC1, triggering the release of the whole hierarchy. Let’s walk through the reclamation of the whole graph.
Mirroring what happened when User A released its last reference on EC3, the release of EC1 by User C causes its reference count to fall to zero.
This transition to zero causes EC1 to release its reference on SC1. SC1's reference count reaching zero causes it to release its reference on TC1.
Since the reference count of TC1, a root object, has reached zero, it invokes the destructor method on its children. This method is recursive and causes the stream classes to call the destructor method on their event classes.
The event classes are reached and, having no children of their own, are reclaimed.
The stream classes having destroyed their children, are then reclaimed by the trace class.
Finally, the stream classes having been reclaimed, TC1 is reclaimed.
Logging is a great instrument for a developer to be able to collect information about a running software.
Babeltrace 2 is a complex software with many layers. When a Babeltrace 2
graph fails to run, what caused the failure? It could be caused by any
component, any message iterator, and any deeply nested validation of a
CTF IR object (within the ctf
plugin), for example. With the
appropriate logging statements manually placed in the source code, we
can find the cause of a bug faster.
While care must be taken when placing DEBUG to FATAL logging statements, you should liberally instrument your Babeltrace 2 module with TRACE logging statements to help future you and other developers understand what’s happening at run time.
The Babeltrace 2 logging API is internal: it is not exposed to the users
of the library; only to their developers. The only thing that a library
user can control is the current log level of the library itself with
bt_logging_set_global_level()
and the initial library’s log level with
the LIBBABELTRACE2_INIT_LOG_LEVEL
environment variable.
This API is based on zf_log, a
lightweight, yet featureful, MIT-licensed core logging library for C and
C++. The zf_log source files were modified to have the BT_
and
bt_
prefixes, and other small changes, like color support and using
the project’s BT_DEBUG_MODE
definition instead of the standard
NDEBUG
.
The logging functions are implemented in the logging convenience
library (src/logging
directory).
The logging API headers are:
<babeltrace2/logging.h>
-
Public header which a library user can use to set and get libbabeltrace2’s current log level.
"logging/log.h"
-
Internal, generic logging API which you can use in any Babeltrace 2 module. This is the translation of
zf_log.h
.This header offers the generic logging statement macros.
"lib/logging.h"
-
Specific internal header to use within the library.
This header defines
BT_LOG_OUTPUT_LEVEL
to a custom, library-wide hidden symbol which is the library’s current log level before including"logging/log.h"
.This header offers the library-specific logging statement macros.
"logging/comp-logging.h"
-
Specific internal header to use within a component class.
This header offers the component-specific logging statement macros.
The internal logging API offers the following log levels, in ascending order of severity:
Log level name | Log level short name | Internal API enumerator | Public API enumerator |
---|---|---|---|
TRACE |
|
|
|
DEBUG |
|
|
|
INFO |
|
|
|
WARNING |
|
|
|
ERROR |
|
|
|
FATAL |
|
|
|
NONE |
|
|
|
The short name is accepted by the log level environment variables and by
the CLI’s --log-level
options.
See how to decide which one to use below.
There are two important log level expressions:
- Build-time, minimal log level
-
The minimal log level, or build-time log level, is set at build time and determines the minimal log level of the logging statements which can be executed. This applies to all the modules (CLI, library, plugins, bindings, etc.).
All the logging statements with a level below this level are not built at all. All the logging statements with a level equal to or greater than this level can be executed, depending on the run-time log level.
You can set this level at configuration time with the
BABELTRACE_MINIMAL_LOG_LEVEL
environment variable, for example:$ BABELTRACE_MINIMAL_LOG_LEVEL=INFO ./configure
The default build-time log level is
DEBUG
. For optimal performance, set it toINFO
, which effectively disables all fast path logging in all the Babeltrace 2 modules. You can’t set it toWARNING
,ERROR
,FATAL
, orNONE
because the impact on performance is minuscule starting from the INFO log level anyway and we want any Babeltrace 2 build to always be able to print INFO-level logs.The library’s public API provides
bt_logging_get_minimal_level()
to get the configured minimal log level. - Run-time, dynamic log level
-
The dynamic log level is set at run time and determines the current, active log level. All the logging statements with a level below this level are not executed, but they still evaluate the condition. All the logging statements with a level equal to or greater than this level are executed, provided that their level is also enabled at build time.
zf_log
has a concept of a global run-time log level which uses the_bt_log_global_output_lvl
symbol. In practice, we never use this symbol, and always make sure thatBT_LOG_OUTPUT_LEVEL
is defined to a module-wise expression before including"logging/log.h"
.In the library,
"lib/logging.h"
defines its ownBT_LOG_OUTPUT_LEVEL
to the library’s log level symbol before it includes"logging/log.h"
itself.In libbabeltrace2, the user can set the current run-time log level with the
bt_logging_set_global_level()
function, for example:bt_logging_set_global_level(BT_LOGGING_LEVEL_INFO);
The library’s initial run-time log level is defined by the
LIBBABELTRACE2_INIT_LOG_LEVEL
environment variable, or set to NONE if this environment variable is undefined.Other modules have their own way of setting their run-time log level.
For example, the CLI uses the
BABELTRACE_CLI_LOG_LEVEL
environment variable, as well as its global--log-level
option:$ babeltrace2 --log-level=I ...
The components use their own log level (as returned by
bt_component_get_logging_level()
). With the CLI, you can set a specific component’s log level with its own, position-dependent--log-level
option:$ babeltrace2 /path/to/trace -c sink.ctf.fs --log-level=D
Code which is common to the whole project, for example
src/common
andsrc/compat
, use function parameters to get its run-time log level, for example:char *bt_common_get_home_plugin_path(int log_level);
Typically, when a logging-enabled module calls such a function, it passes its own log level expression directly (
BT_LOG_OUTPUT_LEVEL
):path = bt_common_get_home_plugin_path(BT_LOG_OUTPUT_LEVEL);
Otherwise, just pass
BT_LOG_NONE
:path = bt_common_get_home_plugin_path(BT_LOG_NONE);
The Babeltrace 2 logging statement macros work just like printf()
(except the BT_LOG*_STR()
ones) and contain their log
level (short name) in their name.
Each of the following macros evaluate the
build-time log level definition and
run-time log level expression (as defined by
BT_LOG_OUTPUT_LEVEL
) to log conditionally.
See Instrument a C/C++ source file (generic) and Instrument a C/C++ header file (generic) to learn how to be able to use the following macros.
BT_LOGT("format string", ...)
-
Generic trace logging statement.
BT_LOGD("format string", ...)
-
Generic debug logging statement.
BT_LOGI("format string", ...)
-
Generic info logging statement.
BT_LOGW("format string", ...)
-
Generic warning logging statement.
BT_LOGE("format string", ...)
-
Generic error logging statement.
BT_LOGF("format string", ...)
-
Generic fatal logging statement.
BT_LOGT_STR("preformatted string")
-
Generic preformatted string trace logging statement.
BT_LOGD_STR("preformatted string")
-
Generic preformatted string debug logging statement.
BT_LOGI_STR("preformatted string")
-
Generic preformatted string info logging statement.
BT_LOGW_STR("preformatted string")
-
Generic preformatted string warning logging statement.
BT_LOGE_STR("preformatted string")
-
Generic preformatted string error logging statement.
BT_LOGF_STR("preformatted string")
-
Generic preformatted string fatal logging statement.
BT_LOGT_MEM(data_ptr, data_size, "format string", ...)
-
Generic memory trace logging statement.
BT_LOGD_MEM(data_ptr, data_size, "format string", ...)
-
Generic memory debug logging statement.
BT_LOGI_MEM(data_ptr, data_size, "format string", ...)
-
Generic memory info logging statement.
BT_LOGW_MEM(data_ptr, data_size, "format string", ...)
-
Generic memory warning logging statement.
BT_LOGE_MEM(data_ptr, data_size, "format string", ...)
-
Generic memory error logging statement.
BT_LOGF_MEM(data_ptr, data_size, "format string", ...)
-
Generic memory fatal logging statement.
BT_LOGT_ERRNO("initial message", "format string", ...)
-
Generic
errno
string trace logging statement. BT_LOGD_ERRNO("initial message", "format string", ...)
-
Generic
errno
string debug logging statement. BT_LOGI_ERRNO("initial message", "format string", ...)
-
Generic
errno
string info logging statement. BT_LOGW_ERRNO("initial message", "format string", ...)
-
Generic
errno
string warning logging statement. BT_LOGE_ERRNO("initial message", "format string", ...)
-
Generic
errno
string error logging statement. BT_LOGF_ERRNO("initial message", "format string", ...)
-
Generic
errno
string fatal logging statement.
The Babeltrace 2 library contains an internal logging API based on the generic logging framework. You can use it to log known Babeltrace 2 objects without having to manually log each member.
See Instrument a library C/C++ source file and Instrument a library C/C++ header file to learn how to be able to use the following macros.
The library logging statement macros are named BT_LIB_LOG*()
instead
of BT_LOG*()
:
BT_LIB_LOGT("format string", ...)
-
Library trace logging statement.
BT_LIB_LOGD("format string", ...)
-
Library debug logging statement.
BT_LIB_LOGI("format string", ...)
-
Library info logging statement.
BT_LIB_LOGW("format string", ...)
-
Library warning logging statement.
BT_LIB_LOGE("format string", ...)
-
Library error logging statement.
BT_LIB_LOGF("format string", ...)
-
Library fatal logging statement.
BT_LIB_LOGW_APPEND_CAUSE("format string", ...)
-
Library warning logging statement, and unconditional error cause appending.
BT_LIB_LOGE_APPEND_CAUSE("format string", ...)
-
Library error logging statement, and unconditional error cause appending.
The macros above accept the typical printf()
conversion specifiers
with the following limitations:
-
The
*
width specifier is not accepted. -
The
*
precision specifier is not accepted. -
The
j
andt
length modifiers are not accepted. -
The
n
format specifier is not accepted. -
The format specifiers defined in
<inttypes.h>
are not accepted, except forPRId64
,PRIu64
,PRIx64
,PRIX64
,PRIo64
, andPRIi64
.
The Babeltrace 2 library custom conversion specifier is accepted. Its
syntax is either %!u
to format a UUID (bt_uuid
type), or:
-
Introductory
%!
sequence. -
Optional:
[
followed by a custom prefix for the printed fields of this specifier, followed by]
. The standard form is to end this prefix with-
so that, for example, with the prefixtc-
, the complete field name becomestc-addr
. -
Optional:
+
to print extended object members. This depends on the provided format specifier. -
Format specifier (see below).
The available format specifiers are:
Specifier | Object | Expected C type |
---|---|---|
|
Trace IR field class |
|
|
Trace IR field |
|
|
Trace IR field path |
|
|
Trace IR field location |
|
|
Trace IR event class |
|
|
Trace IR event |
|
|
Trace IR stream class. |
|
|
Trace IR stream |
|
|
Trace IR packet |
|
|
Trace IR trace class |
|
|
Trace IR trace |
|
|
Trace IR clock class |
|
|
Trace IR clock snapshot |
|
|
Value object |
|
|
Integer range set |
|
|
Message |
|
|
Message iterator class |
|
|
Message iterator |
|
|
Component class |
|
|
Component |
|
|
Port |
|
|
Connection |
|
|
Graph |
|
|
Interrupter |
|
|
Plugin |
|
|
Error cause |
|
|
Object pool |
|
|
Object |
|
Conversion specifier examples:
-
%!f
-
%![my-event-]+e
-
%!t
-
%!+F
The ,
string (comma and space) is printed between individual
fields, but not after the last one. Therefore, you must put this
separator in the format string between two conversion specifiers, for
example:
BT_LIB_LOGW("Message: count=%u, %!E, %!+K", count, event_class, clock_class);
Example with a custom prefix:
BT_LIB_LOGI("Some message: %![ec-a-]e, %![ec-b-]+e", ec_a, ec_b);
It is safe to pass NULL
as any Babeltrace 2 object parameter: the macros
only print its null address.
Warning
|
Build-time printf() format checks are disabled for the
BT_LIB_LOG*() macros because there are custom conversion specifiers,
so make sure to test your logging statements.
|
There are available logging macros for components. They prepend a prefix including the component’s name to the logging message.
See Instrument a component class C/C++ source file and Instrument a component class C/C++ header file to learn how to be able to use the following macros.
The component logging statement macros are named BT_COMP_LOG*()
instead of BT_LOG*()
:
BT_COMP_LOGT("format string", ...)
-
Component trace logging statement.
BT_COMP_LOGD("format string", ...)
-
Component debug logging statement.
BT_COMP_LOGI("format string", ...)
-
Component info logging statement.
BT_COMP_LOGW("format string", ...)
-
Component warning logging statement.
BT_COMP_LOGE("format string", ...)
-
Component error logging statement.
BT_COMP_LOGF("format string", ...)
-
Component fatal logging statement.
BT_COMP_LOGT_STR("preformatted string")
-
Component preformatted string trace logging statement.
BT_COMP_LOGD_STR("preformatted string")
-
Component preformatted string debug logging statement.
BT_COMP_LOGI_STR("preformatted string")
-
Component preformatted string info logging statement.
BT_COMP_LOGW_STR("preformatted string")
-
Component preformatted string warning logging statement.
BT_COMP_LOGE_STR("preformatted string")
-
Component preformatted string error logging statement.
BT_COMP_LOGF_STR("preformatted string")
-
Component preformatted string fatal logging statement.
BT_COMP_LOGT_ERRNO("initial message", "format string", ...)
-
Component
errno
string trace logging statement. BT_COMP_LOGD_ERRNO("initial message", "format string", ...)
-
Component
errno
string debug logging statement. BT_COMP_LOGI_ERRNO("initial message", "format string", ...)
-
Component
errno
string info logging statement. BT_COMP_LOGW_ERRNO("initial message", "format string", ...)
-
Component
errno
string warning logging statement. BT_COMP_LOGE_ERRNO("initial message", "format string", ...)
-
Component
errno
string error logging statement. BT_COMP_LOGF_ERRNO("initial message", "format string", ...)
-
Component
errno
string fatal logging statement. BT_COMP_LOGT_MEM(data_ptr, data_size, "format string", ...)
-
Component memory trace logging statement.
BT_COMP_LOGD_MEM(data_ptr, data_size, "format string", ...)
-
Component memory debug logging statement.
BT_COMP_LOGI_MEM(data_ptr, data_size, "format string", ...)
-
Component memory info logging statement.
BT_COMP_LOGW_MEM(data_ptr, data_size, "format string", ...)
-
Component memory warning logging statement.
BT_COMP_LOGE_MEM(data_ptr, data_size, "format string", ...)
-
Component memory error logging statement.
BT_COMP_LOGF_MEM(data_ptr, data_size, "format string", ...)
-
Component memory fatal logging statement.
BT_LOG_IF(cond, statement)
-
Execute
statement
only ifcond
is true.Example:
BT_LOG_IF(i < count / 2, BT_LOGD("Log this: i=%d", i));
To check the build-time log level:
#if BT_LOG_ENABLED_DEBUG
...
#endif
This tests if the DEBUG level was enabled at build time. This means
that the current, run-time log level could be
DEBUG, but it could also be higher. The rule of thumb is to use only
logging statements at the same level in a BT_LOG_ENABLED_*
conditional
block.
The available definitions for build-time conditions are:
-
BT_LOG_ENABLED_TRACE
-
BT_LOG_ENABLED_DEBUG
-
BT_LOG_ENABLED_INFO
-
BT_LOG_ENABLED_WARNING
-
BT_LOG_ENABLED_ERROR
-
BT_LOG_ENABLED_FATAL
To check the current, run-time log level:
if (BT_LOG_ON_DEBUG) {
...
}
This tests if the DEBUG log level is dynamically turned on (implies that it’s also enabled at build time). This check could have a noticeable impact on performance.
The available definitions for run-time conditions are:
-
BT_LOG_ON_TRACE
-
BT_LOG_ON_DEBUG
-
BT_LOG_ON_INFO
-
BT_LOG_ON_WARNING
-
BT_LOG_ON_ERROR
-
BT_LOG_ON_FATAL
Those macros check the module-specific log level symbol (defined by
BT_LOG_OUTPUT_LEVEL
).
Never, ever write code which would be executed only to compute the fields of a logging statement outside a conditional logging scope, for example:
int number = get_number_of_event_classes_with_property_x(...);
BT_LOGD("Bla bla: number=%d", number);
Do this instead:
if (BT_LOG_ON_DEBUG) {
int number = get_number_of_event_classes_with_property_x(...);
BT_LOGD("Bla bla: number=%d", number);
}
Or even this:
BT_LOGD("Bla bla: number=%d", get_number_of_event_classes_with_property_x(...));
To instrument a C/C++ source file (.c
/.cpp
):
-
At the top of the file, before the first
#include
line (if any), define your file’s logging tag name:#define BT_LOG_TAG "SUBSYS/MY-MODULE/MY-FILE"
-
Below the line above, define the source file’s log level expression,
BT_LOG_OUTPUT_LEVEL
. This expression is evaluated for each logging statement to know the current run-time log level.Examples:
/* Global log level variable */ #define BT_LOG_OUTPUT_LEVEL module_global_log_level
/* Local log level variable; must exist where you use BT_LOG*() */ #define BT_LOG_OUTPUT_LEVEL log_level
/* Object's log level; `obj` must exist where you use BT_LOG*() */ #define BT_LOG_OUTPUT_LEVEL (obj->log_level)
-
Include
"logging/log.h"
:#include "logging/log.h"
-
In the file, instrument your code with the generic logging statement macros.
To instrument a C/C++ header file (.h
/.hpp
), if you have
static inline
functions in it:
-
Do not include
"logging/log.h"
! -
Do one of:
-
In the file, instrument your code with the generic logging statement macros, making each of them conditional to the existence of the macro you’re using:
static inline int some_function(int x) { /* ... */ #ifdef BT_LOGT BT_LOGT(...); #endif /* ... */ #ifdef BT_LOGW_STR BT_LOGW_STR(...); #endif /* ... */ }
The C/C++ source files which include this header file determine if logging is enabled or not for them, and if so, what is their logging tag and run-time log level expression.
-
Require that logging be enabled:
/* Protection: this file uses BT_LOG*() macros directly */ #ifndef BT_LOG_SUPPORTED # error Please include "logging/log.h" before including this file. #endif
Then, in the file, instrument your code with the generic logging statement macros.
-
To instrument a library C/C++ source file (.c
/.cpp
):
-
At the top of the file, before the first
#include
line (if any), define your file’s logging tag name (this tag must start withLIB/
):#define BT_LOG_TAG "LIB/THE-FILE"
-
Include
"lib/logging.h"
:#include "lib/logging.h"
-
In the file, instrument your code with the library logging statement macros or with the generic logging statement macros.
To instrument a library C/C++ header file (.h
/.hpp
), if you have
static inline
functions in it:
-
Do not include
"lib/logging.h"
! -
Require that library logging be enabled:
/* Protection: this file uses BT_LIB_LOG*() macros directly */ #ifndef BT_LIB_LOG_SUPPORTED # error Please include "lib/logging.h" before including this file. #endif
-
In the file, instrument your code with the library logging statement macros or with the generic logging statement macros.
To instrument a component class C/C++ source file (.c
/.cpp
):
-
At the top of the file, before the first
#include
line (if any), define your file’s logging tag name (this tag must start withPLUGIN/
followed by the component class identifier):#define BT_LOG_TAG "PLUGIN/SRC.MY-PLUGIN.MY-SRC"
-
Below the line above, define the source file’s log level expression,
BT_LOG_OUTPUT_LEVEL
. This expression is evaluated for each logging statement to know the current run-time log level.For a component class file, it is usually a member of a local component private structure variable:
#define BT_LOG_OUTPUT_LEVEL (my_comp->log_level)
-
Below the line above, define
BT_COMP_LOG_SELF_COMP
to an expression which, evaluated in the context of the logging statements, evaluates to the self component address (bt_self_component *
) of the component.This is usually a member of a local component private structure variable:
#define BT_COMP_LOG_SELF_COMP (my_comp->self_comp)
-
Include
"logging/comp-logging.h"
:#include "logging/comp-logging.h"
-
In the component initialization method, make sure to set the component private structure’s log level member to the initial component’s log level:
struct my_comp { bt_logging_level log_level; /* ... */ }; bt_self_component_status my_comp_init( bt_self_component_source *self_comp_src, bt_value *params, void *init_method_data) { struct my_comp *my_comp = g_new0(struct my_comp, 1); bt_self_component *self_comp = bt_self_component_source_as_self_component(self_comp_src); const bt_component *comp = bt_self_component_as_component(self_comp); BT_ASSERT(my_comp); my_comp->log_level = bt_component_get_logging_level(comp); /* ... */ }
-
In the file, instrument your code with the component logging statement macros.
To instrument a component class C/C++ header file (.h
/.hpp
), if
you have static inline
functions in it:
-
Do not include
"logging/comp-logging.h"
! -
Require that component logging be enabled:
/* Protection: this file uses BT_COMP_LOG*() macros directly */ #ifndef BT_COMP_LOG_SUPPORTED # error Please include "logging/comp-logging.h" before including this file. #endif
-
In the file, instrument your code with the component logging statement macros.
Each logging-enabled C/C++ source file must define BT_LOG_TAG
to a
logging tag. A logging tag is a namespace to identify the logging
messages of this specific source file.
In general, a logging tag name must be only uppercase letters, digits,
and the -
, .
, and /
characters.
Use /
to show the subsystem to source file hierarchy.
For the Babeltrace 2 library, start with LIB/
.
For the CTF writer library, start with CTF-WRITER/
.
For component classes, use:
PLUGIN/CCTYPE.PNAME.CCNAME[/FILE]
With:
CCTYPE
-
Component class’s type (
SRC
,FLT
, orSINK
). PNAME
-
Plugin’s name.
CCNAME
-
Component class’s name.
FILE
-
Additional information to specify the source file name or module.
For plugins (files common to many component classes), use:
PLUGIN/PNAME[/FILE]
With:
PNAME
-
Plugin’s name.
FILE
-
Additional information to specify the source file name or module.
Choosing the appropriate level for your logging statement is very important.
Log level | Description | Use cases | Expected impact on performance |
---|---|---|---|
FATAL |
The program, library, or plugin cannot continue to work in this condition: it must be terminated immediately. A FATAL-level logging statement should always be followed by
|
|
Almost none: always enabled. |
ERROR |
An important error which is somewhat not fatal, that is, the program, library, or plugin can continue to work after this, but you judge that it should be reported to the user. Usually, the program cannot recover from such an error, but it can at least exit cleanly. |
|
Almost none: always enabled. |
WARNING |
An error which still allows the execution to continue, but you judge that it should be reported to the user. WARNING-level logging statements are for any error or weird action that is directly or indirectly caused by the user, often through some bad input data. For example, not having enough memory is considered beyond the user’s control, so we always log memory errors with an ERROR level (not FATAL because we usually don’t abort in this condition). |
|
Almost none: always enabled. |
INFO |
Any useful information which a non-developer user would possibly understand. Anything logged with this level must not happen repetitively on the fast path, that is, nothing related to each message, for example. This level is used for sporadic and one-shot events. |
|
Very little: always enabled. |
DEBUG |
Something that only Babeltrace 2 developers would be interested into, which can occur on the fast path, but not more often than once per message. The DEBUG level is the default build-time log level as, since it’s not too verbose, the performance is similar to an INFO build. |
|
Noticeable, but not as much as the TRACE level: could be executed in production if you’re going to need a thorough log for support tickets without having to rebuild the project. |
TRACE |
Low-level debugging context information (anything that does not fit the other log levels). More appropriate for tracing in general. |
|
Huge: not executed in production. |
Important
|
Make sure not to use a WARNING (or higher) log level when the condition leading to the logging statement can occur under normal circumstances. For example, a public function to get some object or property from an object by name or key that fails to find the value is not a warning scenario: the user could legitimately use this function to check if the name/key exists in the object. In this case, use the TRACE level (or do not log at all). |
Follow those rules when you write a logging statement’s message:
-
Use an English sentence which starts with a capital letter.
-
Start the sentence with the appropriate verb tense depending on the context. For example:
-
Beginning of operation (present continuous): Creating …, Copying …, Serializing …, Freezing …, Destroying …
-
End of operation (simple past): Created …, Successfully created …, Failed to create …, Set … (simple past of to set which is also set)
For warning and error messages, you can start the message with Cannot or Failed to followed by a verb if it’s appropriate.
-
-
Do not include the log level in the message itself. For example, do not start the message with Error while or Warning:.
-
Do not put newlines, tabs, or other special characters in the message, unless you want to log a string with such characters. Note that multiline logging messages can be hard to parse, analyze, and filter, however, so prefer multiple logging statements over a single statement with newlines.
-
If there are fields that your logging statement must record, follow the message with
:
followed by a space, then with the list of fields (more about this below). If there are no fields, end the sentence with a period.
The statement’s fields must be a comma-separated list of
name=value
tokens. Keep name
as simple as possible; use
kebab case if possible. If value
is a non-alphanumeric string, put
it between double quotes ("%s"
specifier). Always use the PRId64
and
PRIu64
specifiers to log an int64_t
or an uint64_t
value. Use %d
to log a boolean value.
Example:
"Cannot read stream data for indexing: path=\"%s\", name=\"%s\", " "stream-id=%" PRIu64 ", stream-fd=%d, " "index=%" PRIu64 ", status=%s, is-mapped=%d"
By following a standard format for the statement fields, it is easier to use tools like Logstash or even Splunk to split fields and analyze logs.
Prefer the following suffixes in field names:
Field name suffix | Description | Format specifier |
---|---|---|
|
Memory address |
|
|
File descriptor |
|
|
File stream ( |
|
|
Object’s ID |
|
|
Index |
|
|
Object’s name |
|
The log is printed to the standard error stream. A log line contains the time, the process and thread IDs, the log level, the logging tag, the source’s function name, file name and line number, and the message.
When Babeltrace 2 supports terminal color codes (depends on the
BABELTRACE_TERM_COLOR
environment variable’s value and what the
standard output and error streams are plugged into), INFO-level lines
are blue, WARNING-level lines are yellow, and ERROR-level and
FATAL-level lines are red.
Log line example:
05-11 00:58:03.691 23402 23402 D VALUES bt_value_destroy@values.c:498 Destroying value: addr=0xb9c3eb0
You can easily filter the log with grep
or ag
. For example, to keep
only the DEBUG-level logging messages that the FIELD-CLASS
module
generates:
$ babeltrace2 --log-level=D /path/to/trace |& ag 'D FIELD-CLASS'
To use Valgrind on an application (for example, the CLI or a test) which loads libbabeltrace2, use:
$ G_SLICE=always-malloc G_DEBUG=gc-friendly PYTHONMALLOC=malloc \ LIBBABELTRACE2_NO_DLCLOSE=1 valgrind --leak-check=full app
G_SLICE=always-malloc
and G_DEBUG=gc-friendly
is for GLib and
PYTHONMALLOC=malloc
is for the Python interpreter, if it is used by
the Python plugin provider (Valgrind will probably show a lot of errors
which originate from the Python interpreter anyway).
LIBBABELTRACE2_NO_DLCLOSE=1
makes libbabeltrace2 not close the shared
libraries (plugins) which it loads. You need this to see the appropriate
backtrace when Valgrind shows errors.
Running make check
in the build directory (regardless of whether the build is
in-tree or out-of-tree) automatically sets up the appropriate environment for
tests to run in, so nothing more is needed.
If building in-tree, you can run single tests from the tree directly:
$ ./tests/plugins/sink.text.pretty/test-enum.sh
If building out-of-tree, you can get the appropriate environment by sourcing
the tests/utils/env.sh
file residing in the build directory against which you
want to run tests.
$ source /path/to/my/build/tests/utils/env.sh $ ./tests/plugins/sink.text.pretty/test-enum.sh
You can use the tests/utils/run-in-py-env.sh
script to run any command
within an environment making the build’s bt2
Python package available,
as well as the testing utility Python modules.
run-in-py-env.sh
uses utils.sh
which needs to know the
build directory, so make sure you set the BT_TESTS_BUILDDIR
environment variable correctly if you build out of tree, for example:
$ export BT_TESTS_BUILDDIR=/path/to/build/babeltrace/tests
You can run any command which needs the bt2
Python package through
run-in-py-env.sh
, for example:
$ ./tests/utils/run-in-py-env.sh ipython3
All test scripts output the test results following the Test Anything Protocol (TAP) format.
The TAP format has two mechanisms to print additional information about a test:
-
Print a line starting with
#
to the standard output.This is usually done with the
diag()
C function or thediag
shell function. -
Print to the standard error.
The bt2
Python package tests are located in
tests/bindings/python/bt2
.
tests/utils/python/testrunner.py
is Babeltrace 2’s Python test runner
which loads Python files containing unit tests, finds all the test
cases, and runs the tests, producing a TAP report.
You can see the test runner command’s help with:
$ python3 ./tests/utils/python/testrunner.py --help
By default, the test runner reports failing tests (TAP’s not ok
line), but continues to run other tests. You can use the --failfast
option to make the test runner fail as soon as a test fails.
To run all the bt2
Python package tests:
-
Run:
$ ./tests/utils/run-in-py-env.sh ./tests/bindings/python/bt2/test-python-bt2.sh
or:
$ ./tests/utils/run-in-py-env.sh python3 ./tests/utils/python/testrunner.py \ ./tests/bindings/python/bt2/ -p '*.py'
To run all the tests in a test module (for example,
test_value.py
):
-
Run:
$ ./tests/utils/run-in-py-env.sh python3 ./tests/utils/python/testrunner.py \ ./tests/bindings/python/bt2 -t test_value
To run a specific test case (for example, RealValueTestCase
within
test_value.py
):
-
Run:
$ ./tests/utils/run-in-py-env.sh python3 ./tests/utils/python/testrunner.py \ ./tests/bindings/python/bt2/ -t test_value.RealValueTestCase
To run a specific test (for example,
RealValueTestCase.test_assign_pos_int
within test_value.py
):
-
Run:
$ ./tests/utils/run-in-py-env.sh python3 ./tests/utils/python/testrunner.py \ ./tests/bindings/python/bt2/ -t test_value.RealValueTestCase.test_assign_pos_int
A significant part and, in general, all the new code of Babeltrace 2 is written in C++.
This section shows what’s important to know about C++ to contribute to Babeltrace 2.
Important
|
Babeltrace 2 only has C++ sources for internal code. In other words, libbabeltrace2 must expose a pure C99 API to preserve ABI compatibility over time. |
The Babeltrace 2 project is configured to use the C++11 standard.
C++11 makes it possible to build Babeltrace 2 with a broad range of compilers, from GCC 4.8 and Clang 3.3.
Many parts of the project need common C++ code. You’ll find all of it
under src/cpp-common
.
In general, anything under a namespace named internal
is internal to
the API containing it. For example, everything under the bt2::internal
namespace is internal to the bt2
namespace and therefore isn’t meant
to be used outside the src/cpp-common/bt2
directory.
src/cpp-common/bt2
contains our internal C++ bindings of
the libbabeltrace2 C API, under the bt2
namespace.
Those bindings are designed to have, as much as possible, no performance
impact. Anything which inherits bt2::BorrowedObject
contains a single
libbabeltrace2 object pointer.
Pass and return borrowed objects by value (copy).
In general, the following holds:
C++ expression | Equivalent C expression |
---|---|
|
|
|
|
|
|
|
|
Similar to the role of src/common
for C code.
In general, everything in here is under the bt2c
namespace.
Notable files are:
align.hpp
-
The
bt2c::align()
function template: a wrapper ofsrc/common/align.h
. c-string-view.hpp
-
The
bt2c::CStringView
class: a view on a constant null-terminated C string.We have this because
bt2s::string_view
doesn’t imply null termination, only a beginning and a length.A
bt2c::CStringView
instance is convertible toconst char *
and may be empty (the underlying pointer is null). call.hpp
-
The
bt2c::call()
function template: a partial implementation of INVOKE.We use this mostly to assign the result of calling an immediately invoked function expression (lambda) to an
auto
variable without risking to assign the lambda itself, should we forget the calling parentheses:const auto res = bt2c::call([&] { /* Complex initialization */ });
endian.hpp
-
Typed wrappers of
src/compat/endian.h
. exc.hpp
-
Common exception classes.
fmt.hpp
-
Common {fmt} formatters.
logging.hpp
-
The
bt2c::Logger
class and helperBT_CPPLOG*()
macros for any C++ logging.When possible, prefer using this over the C logging API.
One important benefit is that this API uses {fmt} to format the logging message instead of
vsnprintf()
. make-span.hpp
-
The function template
bt2c::makeSpan()
which is an alternative to CTAD (a C++17 feature). prio-heap.hpp
-
The
bt2c::PrioHeap
class template: an efficient heap data structure. read-fixed-len-int.hpp
-
The function templates
bt2c::readFixedLenInt()
,bt2c::readFixedLenIntLe()
, andbt2c::readFixedLenIntBe()
: read a fixed-length integer from a byte buffer. safe-ops.hpp
-
The
bt2c::safe*()
function templates: arithmetic operations which assert that there’s no possible overflow. std-int.hpp
-
The
bt2c::StdIntT
type alias template: becomes one of thestd::*int*_t
types depending on its parameters.For example,
bt2c::StdIntT<32, true>
isstd::int32_t
. type-traits.hpp
-
Common type traits.
uuid.hpp
-
The following classes:
bt2c::Uuid
-
Container of a 16-byte UUID.
Provides the static
generate()
method as well as conversion tobt2c::UuidView
. bt2c::UuidView
-
View on a UUID (not a container).
Provides byte access, comparison, as well as string conversion methods.
Also provides conversion to
bt2c::Uuid
.
vector.hpp
-
The
bt2c::vectorFastRemove()
function template: remove an element from anstd::vector
instance quickly when the order isn’t important.
Everything under the bt2s
namespace has its equivalent under the std
namespace, but in C++ versions we don’t yet have access to, namely:
make-unique.hpp
-
bt2s::make_unique()
, a drop-in replacement ofstd::make_unique()
(C++14). optional.hpp
-
Drop-in replacement of the
std::optional
API (C++17). span.hpp
-
Drop-in replacement of the
std::span
API (C++20). string-view.hpp
-
Drop-in replacement of the
std::string_view
API (C++17).
This directory contains copies of the source code of C++ dependencies to avoid packaging issues.
They are:
fmt
nlohmann
optional-lite
-
Important
Use the symbols of src/cpp-common/bt2s/optional.hpp
, under thebt2s
namespace, instead of using this directly. span-lite
-
Important
Use the symbols of src/cpp-common/bt2s/span.hpp
, under thebt2s
namespace, instead of using this directly.Tipsrc/cpp-common/bt2c/make-span.hpp
offersbt2c::makeSpan()
which is an alternative to CTAD (a C++17 feature). string-view-lite
-
Important
Use the symbols of src/cpp-common/bt2s/string-view.hpp
, under thebt2s
namespace, instead of using this directly. wise_enum
To add a C++ source file to a part of the project, use the .cpp
extension and add it to the list of source files in Makefile.am
as
usual.
If a program or a shared library has a direct C++ source file, then Libtool uses the C++ linker to create the result, dynamically linking important runtime libraries such as libstdc++ and libgcc_s.
Because a Libtool convenience library is just an archive (.a
), it’s
not dynamically linked to runtime libraries, even if one of its direct
sources is a C++ file. This means that for each program or shared
library named my_target
in Makefile.am
which is linked to a
convenience library having C++ sources (recursively), you must do
one of:
-
Have at least one direct C++ source file in the
*_my_target_SOURCES
list. -
Add:
nodist_EXTRA_my_target_SOURCES = dummy.cpp
See Libtool Convenience Libraries to learn more.
For a given program or library, you cannot have a C file and a
C++ file having the same name, for example list.c
and
list.cpp
.
All the project’s C++ files follow the
clang-format
style of the
.clang-format
file for whitespaces, indentation, and line breaks.
You must format modified and new C++ files with clang-format before you create a contribution patch.
You need clang-format 15 to use the project’s .clang-format
file.
To automatically format all the project’s C++ files, run:
$ ./tools/format-cpp.sh
To only format the C++ files of a given directory:
$ ./tools/format-cpp.sh ./src/cli
Use the FORMATTER
environment variable to override the default
formatter (clang-format -i
):
$ FORMATTER='my-clang-format-15 -i' ./tools/format-cpp.sh
-
Use camel case with a lowercase first letter for:
-
Variable names:
size
,objSize
. -
Function/method names:
size()
,formatAndPrint()
.
-
-
Use camel case with an uppercase first letter for:
-
Types:
Pistachio
,NutManager
. -
Template parameters:
PlanetT
,TotalSize
. -
Enumerators:
Type::SignedInt
,Scope::Function
.
-
-
Use snake case with uppercase letters for:
-
Definition/macro names:
MARK_AS_UNUSED()
,SOME_FEATURE_EXISTS
.
-
-
Use only lowercase letters and digits for namespaces:
mylib
,bt2
. -
Use the
T
suffix for type template parameters and theV
suffix for non-type template parameters:template <typename NameT, typename ItemT, unsigned int SizeV = 0>
-
Name a template parameter pack
ArgTs
.template <typename NameT, typename... ArgTs>
-
Use an underscore prefix for private and protected methods and member type names:
_tryConnect()
,_NodeType
. -
Use the prefix
_m
for private and protected member variable names:_mLogger
,_mSize
,_mFieldClass
.This is to avoid name clashes with private/protected getters/setters.
-
Name setters and getters like the property name, without the
set
andget
prefixes. -
Use the
is
orhas
prefix, if possible, to name the functions which returnbool
.However, try to keep the name readable. For example, prefer
colorIsBlue()
overisColorBlue()
.
In general, the project’s contributors make an effort to follow, for C++11 code:
-
The C++ Core Guidelines.
-
Scott Meyers’s “Effective Modern C++”.
Here are a few important reminders:
-
Namespace your code.
-
Create one header/source file pair per class when possible.
For a class named
MyClass
, name the corresponding filesmy-class.hpp
andmy-class.cpp
. -
Use the
inline
keyword, notstatic inline
, for header-only functions that are not templates. -
When defining a class, use this order:
-
Friends (without any preceding access specifier).
-
Public types and type aliases.
Private/protected types may be here too if they can’t be lower.
-
Constructors, whatever their access.
-
Destructor (always public).
-
Copy-assignment and move-assignment operators.
-
Public methods.
-
Protected types and type aliases.
-
Protected methods.
-
Private types and type aliases.
-
Private methods.
-
Protected data members.
-
Private data members.
-
-
Declare variables as close to where they’re used as possible.
-
In general, try to avoid variables if it doesn’t lead to more lines.
For example, given:
const auto size = getSize(myObj, 23); auto& obj = this->_objForSize(size); abc::sendObj(obj, SendOpts::WAIT);
Prefer this:
abc::sendObj(this->_objForSize(getSize(myObj, 23)), SendOpts::WAIT);
-
If you really need variables, then scope them to avoid “leaking” them:
doSomeStuff(123, &obj); { const auto status = tryChange(obj); BT_CPPLOGD("New status: {}.", status); BT_ASSERT(status == Status::CONTINUE); } doMoreStuff(&obj);
This also means to either use a dedicated, named method/function or to use
bt2c::call()
with an immediately invoked function expression (lambda) to perform complex initialization of an ideallyconst
variable:const auto size = bt2c::call([this] { auto& sender = this->_curSender(); if (sender.strategy() == Strategy::ACK) { return sender.strategySize(); } else if (sender.strategy() == Strategy::NACK) { return 0; } return _mDefSize; });
-
Always use
bt2c::call()
to call an immediately invoked function expression (see the previous point). -
If possible, initialize object members without a default value with the initializer list of a constructor, not in the constructor body.
If the initialization is complex, either use a dedicated, named method/function or
bt2c::call()
with an immediately invoked function expression (lambda):MyObj::MyObj(const std::size_t size) : _mSize {size}, _mOtherObj {bt2c::call([size] { /* Complex initialization here */ })} { }
-
Use
auto
when possible.Use
auto&
instead ofconst auto&
when you know that the type isconst
anyway.Don’t use
auto *
. -
Use
const
as much as possible, even for pointers (const char * const
) and numeric values (const unsigned int
) which never need to change. -
Prefer the
MyObj myObj {...}
initialization form overauto myObj = MyObj {...}
. -
Implement simple setters, getters, and one-liners in header files and everything else that’s not a template in source files, including constructors.
-
Make methods
const noexcept
orconst
as much as possible. -
Make constructors
explicit
unless you really need an implicit constructor (which is rare), including default constructors:explicit Meow();
-
Use
std::unique_ptr
to manage memory when possible.However, use references (
*my_unique_ptr
) and raw pointers (my_unique_ptr.get()
) when not transferring ownership. -
Use
nullptr
, notNULL
nor 0. -
Return by value (rvalue) instead of by output parameter (non-const lvalue reference), even complex objects, unless you can prove that the performance is improved when returning by parameter.
-
For a function parameter or a return value of which the type needs to be a reference or pointer, use:
- If the value is mandatory
-
A reference.
- If the value is optional
-
A raw pointer.
-
Don’t use
std::move()
when you already have an rvalue, which means:-
Don’t write
return std::move(...);
as this can interfere with RVO. -
Don’t use
std::move()
with a function call (std::move(func())
).
-
-
For each possible move/copy constructor or assignment operator, do one of:
-
Write a custom one.
-
Mark it as defaulted (
default
) -
Mark it as deleted (
delete
).
-
-
Use scoped enumerations (
enum class
). -
Mark classes known to be final with the
final
keyword. -
Use type aliases (
using
), not type definitions (typedef
). -
In a
.cpp
file, use anonymous namespaces for local symbols instead ofstatic
orinline
. -
Prefer a function in an anonymous namespace in a
.cpp
file over a private static method if it doesn’t need private access to an object. -
Don’t pollute the global namespace:
-
Don’t use
using namespace xyz
anywhere. -
Use only namespace aliases in source files (
.cpp
), trying to use them in the smallest possible scope (function, or even smaller).
-
-
Return a structure with named members instead of a generic container such as
std::pair
orstd::tuple
. -
When a class inherits a base class with virtual methods, use the
override
keyword to mark overridden virtual methods, and do not use thevirtual
keyword again. -
Define overloaded operators only if their meaning is obvious, unsurprising, and consistent with the corresponding built-in operators.
For example, use
|
as a bitwise- or logical-or, not as a shell-style pipe. -
Use RAII wrappers when managing system resources or interacting with C libraries.
In other words, don’t rely on
goto
s and error labels to clean up as you would do in C.Use the RAII, Luke.
-
Throw an exception when there’s an unexpected, exceptional condition, including from a constructor, instead of returning a status code.
-
Accept a by-value parameter and move it (when it’s moveable) when you intend to copy it anyway.
You can do this with most STL containers.
Example:
void Obj::doSomething(std::string str) { _mName = std::move(str); /* ... */ }
baby.hpp
This example shows a C++ header which follows the Babeltrace 2 C++ coding convention.
/*
* SPDX-FileCopyrightText: 2020 Harry Burnett <hburnett@reese.choco>
* SPDX-License-Identifier: MIT
*/
#ifndef BABELTRACE_BABY_HPP
#define BABELTRACE_BABY_HPP
#include <string>
#include <unordered_set>
#include <utility>
namespace life {
class Toy;
/*
* A baby is a little human.
*/
class Baby : public Human
{
friend class Parent;
public:
using Toys = std::unordered_set<Toy>;
enum class Gender
{
Male,
Female,
Other,
};
explicit Baby() = default;
explicit Baby(const Toys& toys);
Baby(const Baby&) = delete;
Baby(Baby&&) = delete;
protected:
explicit Baby(Gender initialGender = Gender::OTHER);
public:
~Baby();
Baby& operator=(const Baby&) = delete;
Baby& operator=(Baby&&) = delete;
/*
* Eats `weight` grams of food.
*/
void eat(unsigned long weight);
/*
* Sleeps for `duration` seconds.
*/
void sleep(double duration);
/*
* Sets this baby's name to `name`.
*/
void name(std::string name)
{
_mName = std::move(name);
}
/*
* This baby's name.
*/
const std::string& name() const noexcept
{
return _mName;
}
protected:
void _addTeeth(unsigned long index);
void _grow(double size) override;
private:
std::string _mName {"Paul"};
Toys _mToys;
};
} /* namespace life */
#endif /* BABELTRACE_BABY_HPP */
When referring to a specific commit (either in free text or in a
trailer line), use the 12-DIGIT HASH ("SUBJECT")
format,
for example:
602446e40e7a ("ctf: add explicit cast to CStringView in ternary")
Generate such a string from the HEAD
commit of a repository with:
$ git log -1 --abbrev=12 --pretty='%h ("%s")'