Quick Links: |
Provide thread-safe logging toolkit suitable for all platforms. More...
Components | |
Component ball_administration | |
Provide a suite of utility functions for logging administration. | |
Component ball_asyncfileobserver | |
Provide an asynchronous observer that logs to a file and | |
Component ball_attribute | |
Provide a representation of (literal) name/value pairs. | |
Component ball_attributecollectorregistry | |
Provide a registry for attribute collector functors. | |
Component ball_attributecontainer | |
Provide a protocol for containers holding logging attributes. | |
Component ball_attributecontainerlist | |
Provide a list of attribute container addresses. | |
Component ball_attributecontext | |
Provide a container for storing attributes and caching results. | |
Component ball_broadcastobserver | |
Provide a broadcast observer that forwards to other observers. | |
Component ball_category | |
Provide a container for a name and associated thresholds. | |
Component ball_categorymanager | |
Provide a manager of named categories each having "thresholds". | |
Component ball_context | |
Provide a container for the context of a transmitted log record. | |
Component ball_countingallocator | |
Provide a concrete allocator that keeps count of allocated bytes. | |
Component ball_defaultattributecontainer | |
Provide a default container for storing attribute name/value pairs. | |
Component ball_fileobserver | |
Provide a thread-safe observer that logs to a file and to | |
Component ball_fileobserver2 | |
Provide a thread-safe observer that emits log records to a file. | |
Component ball_filteringobserver | |
Provide an observer that filters log records. | |
Component ball_fixedsizerecordbuffer | |
Provide a thread-safe fixed-size buffer of record handles. | |
Component ball_log | |
Provide macros and utility functions to facilitate logging. | |
Component ball_logfilecleanerutil | |
Provide a utility class for removing log files. | |
Component ball_loggercategoryutil | |
Provide a suite of utility functions for category management. | |
Component ball_loggerfunctorpayloads | |
Provide a suite of logger manager singleton functor payloads. | |
Component ball_loggermanager | |
Provide a manager of core logging functionality. | |
Component ball_loggermanagerconfiguration | |
Provide a constrained-attribute class for the logger manager. | |
Component ball_loggermanagerdefaults | |
Provide constrained default attributes for the logger manager. | |
Component ball_logthrottle | |
Provide throttling equivalents of some of the | |
Component ball_managedattribute | |
Provide a wrapper for | |
Component ball_managedattributeset | |
Provide a container for managed attributes. | |
Component ball_multiplexobserver: DEPRECATED | |
Provide a multiplexing observer that forwards to other observers. | |
Component ball_observer | |
Define a protocol for receiving and processing log records. | |
Component ball_observeradapter | |
Provide a helper for implementing the | |
Component ball_patternutil | |
Provide a utility class for string pattern matching. | |
Component ball_predicate: DEPRECATED | |
Provide a predicate object that consists of a name/value pair. | |
Component ball_predicateset: DEPRECATED | |
Provide a container for managed attributes. | |
Component ball_record | |
Provide a container for the fields and attributes of a log record. | |
Component ball_recordattributes | |
Provide a container for a fixed set of fields suitable for logging. | |
Component ball_recordbuffer | |
Provide a protocol for managing log record handles. | |
Component ball_recordjsonformatter | |
Provide a formatter for log records that renders output in JSON. | |
Component ball_recordstringformatter | |
Provide a record formatter that uses a | |
Component ball_rule | |
Provide an object having a pattern, thresholds, and attributes. | |
Component ball_ruleset | |
Provide a set of unique rules. | |
Component ball_scopedattribute | |
Provide a scoped guard for a single BALL attribute. | |
Component ball_scopedattributes | |
Provide a class to add and remove attributes automatically. | |
Component ball_severity | |
Enumerate a set of logging severity levels. | |
Component ball_severityutil | |
Provide a suite of utility functions on | |
Component ball_streamobserver | |
Provide an observer that emits log records to a stream. | |
Component ball_testobserver | |
Provide an instrumented observer for testing. | |
Component ball_thresholdaggregate | |
Provide an aggregate of the four logging threshold levels. | |
Component ball_transmission | |
Enumerate the set of states for log record transmission. | |
Component ball_userfields | |
Provide a container of user supplied field values. | |
Component ball_userfieldtype | |
Enumerate the set of data types for a user supplied attribute. | |
Component ball_userfieldvalue | |
Provide a type for the value of a user supplied field. |
ball
Terminology
ball
Logging Toolkit
ball::LoggerManager
Singleton publish
ball
package provides a toolkit for logging messages in applications and library code. The logger toolkit has an administration layer that allows configuration both at start-up and during program execution, a basic logger API for the most general possible use, and two sets of macros for somewhat less flexible but simpler, more convenient use. In particular, messages may be logged via these macros in either a C++ stream-style syntax (i.e., with the <<
operator) or a printf
-style syntax. Users are encouraged to use the macros exclusively, because they provide uniformity, and because they are less error-prone. See the Appendix: Macro Reference section below. ball_log.h
, then create an example function where we initialize the log category within the context of this function. The logging macros such as BALL_LOG_ERROR
will not compile unless a category has been specified in the current lexical scope: #include <ball_log.h> int processData() { BALL_LOG_SET_CATEGORY("MYLIBRARY.MYSUBSYSTEM");
ball::LoggerManager
singleton): BALL_LOG_FATAL << "Write this message to the log if the log threshold " << "is above 'ball::Severity::e_FATAL' (i.e., 32)."; BALL_LOG_TRACE << "Write this message to the log if the log threshold " << "is above 'ball::Severity::e_TRACE' (i.e., 192).";
bsl::vector<int> myVector(4, 328); BALL_LOG_TRACE_BLOCK { BALL_LOG_OUTPUT_STREAM << "myVector = [ "; unsigned int position = 0; for (bsl::vector<int>::const_iterator it = myVector.begin(), end = myVector.end(); it != end; ++it, ++position) { BALL_LOG_OUTPUT_STREAM << position << ':' << *it << ' '; } BALL_LOG_OUTPUT_STREAM << ']'; } }
BALL_LOG_OUTPUT_STREAM
provides access to the log stream within the block. .cpp
file): class AccountInformation { BALL_LOG_SET_CLASS_CATEGORY("MYLIBRARY.AccountInformation"); void privateRetrieveData(); public: void addSecurity(const bsl::string_view& security); void removeSecurity(const bsl::string_view& security); }; void AccountInformation::addSecurity(const bsl::string_view& security) { BALL_LOG_INFO << "addSecurity"; }
ball::ScopedAttribute
to associate an attribute with the current thread's logging context. void AccountInformation::privateRetrieveData() { BALL_LOG_INFO << "retrieveData"; } void AccountInformation::removeSecurity(const bsl::string_view& security) { ball::ScopedAttribute securityAttribute("mylibrary.security", security); BALL_LOG_INFO << "removeSecurity"; privateRetrieveData(); }
securityAttribute
object (including the log message created by privateRetrieveData
). To publish the attribute to the log the ball::Observer
must be configured correctly (e.g., using the "%a" format specification with ball::FileObserver
or ball::RecordStringFormatter
), as we do in the subsequent example. ball::LoggerManagerScopedGuard
class. This example shows how to create a logger manager with basic "default behavior". Subsequent examples will show more customized behavior. main
). ball::LoggerManagerConfiguration
object, configuration
, and set the logging "pass-through" level -- the level at which log records are published to registered observers -- to WARN
(see Categories, Severities, and Threshold Levels
): // myApp.cpp int main() { ball::LoggerManagerConfiguration configuration; configuration.setDefaultThresholdLevelsIfValid(ball::Severity::e_WARN);
ball::LoggerManagerScopedGuard
object whose constructor takes the configuration object just created. The guard will initialize the logger manager singleton on creation and destroy the singleton upon destruction. This guarantees that any resources used by the logger manager will be properly released when they are not needed: ball::LoggerManagerScopedGuard guard(configuration);
ball
logging subsystem, but until the application registers an observer, all log messages will be discarded. ball::FileObserver
object observer
that will publish records to a file, and exceptional records to stdout
. We configure the log format to publish log attributes (see {Key Example 1: Write to a Log}, enable the logger to write to a log file, and then register observer
with the logger manager. Note that observers must be registered by name; this example simply uses "default" for a name: bslma::Allocator *alloc = bslma::Default::globalAllocator(0); bsl::shared_ptr<ball::FileObserver> observer = bsl::allocate_shared<ball::FileObserver>(alloc); observer->setLogFormat( ball::RecordStringFormatter::k_BASIC_ATTRIBUTE_FORMAT, ball::RecordStringFormatter::k_BASIC_ATTRIBUTE_FORMAT); if (0 != observer->enableFileLogging("myapplication.log.%T")) { bsl::cout << "Failed to enable logging" << bsl::endl; return -1; } ball::LoggerManager::singleton().registerObserver(observer, "default");
ball
logging subsystem: // ... BALL_LOG_SET_CATEGORY("MYLIBRARY.MYSUBSYSTEM"); BALL_LOG_ERROR << "Exiting the application (0)"; return 0; }
ball
package currently has 51 components having 17 levels of physical dependency. The list below shows the hierarchical ordering of the components. The order of components within each level is not architecturally significant, just alphabetical. 17. ball_asyncfileobserver 16. ball_fileobserver ball_logfilecleanerutil 15. ball_fileobserver2 ball_logthrottle 14. ball_log 13. ball_administration 12. ball_loggercategoryutil ball_loggerfunctorpayloads 11. ball_loggermanager ball_scopedattribute ball_scopedattributes 10. ball_attributecontext 9. ball_categorymanager 8. ball_category 7. ball_broadcastobserver ball_filteringobserver ball_multiplexobserver !DEPRECATED! ball_ruleset 6. ball_observeradapter ball_rule ball_streamobserver ball_testobserver 5. ball_fixedsizerecordbuffer ball_observer ball_predicateset !DEPRECATED! ball_recordjsonformatter ball_recordstringformatter 4. ball_managedattributeset ball_record 3. ball_attributecontainerlist ball_defaultattributecontainer ball_predicate !DEPRECATED! ball_userfields 2. ball_attributecollectorregistry ball_attributecontainer ball_context ball_loggermanagerconfiguration ball_managedattribute ball_recordbuffer ball_severityutil ball_userfieldvalue 1. ball_attribute ball_countingallocator ball_loggermanagerdefaults ball_patternutil ball_recordattributes ball_severity ball_thresholdaggregate ball_transmission ball_userfieldtype
ball_administration
: ball_asyncfileobserver
: stdout
.ball_attribute
: ball_attributecollectorregistry
: ball_attributecontainer
: ball_attributecontainerlist
: ball_attributecontext
: ball_broadcastobserver
: ball_category
: ball_categorymanager
: ball_context
: ball_countingallocator
: ball_defaultattributecontainer
: ball_fileobserver
: stdout
.ball_fileobserver2
: ball_filteringobserver
: ball_fixedsizerecordbuffer
: ball_log
: ball_logfilecleanerutil
: ball_loggercategoryutil
: ball_loggerfunctorpayloads
: ball_loggermanager
: ball_loggermanagerconfiguration
: ball_loggermanagerdefaults
: ball_logthrottle
: ball_log
macros.ball_managedattributeset
: ball_multiplexobserver
: DEPRECATED ball_observer
: ball_patternutil
: ball_predicate
: DEPRECATED ball_predicateset
: DEPRECATED ball_record
: ball_recordattributes
: ball_recordbuffer
: ball_recordjsonformatter
: ball_recordstringformatter
: printf
-style format spec.ball_rule
: ball_ruleset
: ball_scopedattribute
: ball_scopedattributes
: ball_severity
: ball_streamobserver
: ball_testobserver
: ball_thresholdaggregate
: ball_transmission
: ball_userfields
: ball_userfieldtype
: ball_userfieldvalue
: ball
logging toolkit is thread-enabled and suitable for multi-threaded applications. At the user's option, the multi-threaded toolkit permits each thread to install a distinct instance of ball::Logger
; the process-wide "default" logger is available to any thread that does not install its own logger. ball
logging toolkit, and introduces (without formal definition) some of the terminology used in ball
. Refer to the hierarchical and alphabetical Synopsis sections above to associate these features with the overall ball
design, and see the various sections below for more detailed descriptions. Severity Level plus (administrable) per-category Threshold Levels allow:
if
statement) Rule-Based Logging
below. ball
package provides a flexible logging toolkit that supports several standard and non-standard features. Perhaps most notable among the non-standard features is the ability to write messages to an in-memory "circular" (finite) buffer with the expectation that those messages will be overwritten and never actually "logged" to any permanent medium. With this feature, during normal production operation a large quantity of "trace-back" information can be "logged" to memory, and typically be discarded, without having clogged production resources. If, however, some "error" occurs, the information just prior to that error will be available for fast diagnosis and debugging. It is easy to re-configure logger operation so that every message is archived, if that is the preferred behavior, but the efficient trace-back feature is an explicit design goal. See the "Hello world!" examples under Usage below for illustrations of how to configure the logger behavior. ball
defines the ball::Observer
protocol (abstract interface), and provides a few concrete observers. It is expected that most users will find the concrete observers that are provided in ball
to be sufficient for their needs. However, users are free to define their own observers tailored to meet their specific requirements. In the current release, exactly one observer is registered with the ball
logger on initialization; we anticipate that multiple observers, e.g., one per thread or perhaps one per logger, may be available in future releases. An observer makes its publish
method available to the logger; since the publish
method is free to do almost anything that the user wants, the limitation of one observer per process is not very restrictive. The observer may: write the message to a simple file, write the message to a set of managed files, write the message to the console (perhaps with some information removed and/or added), process the message (complete with Category and Severity information) and take specific responsive actions, or any combination of these or other behaviors. In particular, ball
provides a "broadcast observer", ball::BroadcastObserver
, that forwards log records to any number of observers registered with the broadcast observer. Note that ball::LoggerManager
contains an integrated broadcast observer and all observers registered with the logger manager will receive log records. ball_loggermanager
component for more details about categories and category administration. [0 .. 255]
. Much more commonly, however, (and necessarily when using the ball
convenience macros defined in the ball_log
component), the user will choose to use the fixed set of enumerated severity levels as defined in the ball_severity
component. The enumerator names suggest a meaning that is consistent with common practice, but no policy is enforced by the basic logger API or the macros. ball::Severity::Level
enum
implementation is presented below. Note that this implementation and the specific numerical values may not be relied upon. Also note that the enumerator NONE
is deprecated, since its name has proven to be confusing. As an argument to logMessage
, it would satisfy "none" of the thresholds, but when provided as a threshold value itself, it would enable "all" of the enumerated logMessage
requests (and all of the macros). Use e_OFF
as a threshold level to disable all logging events for a particular logging behavior (see the "Log Record Storage and Publication" section below). enum Level { e_OFF = 0, // disable generation of corresponding message e_FATAL = 32, // a condition that will (likely) cause a *crash* e_ERROR = 64, // a condition that *will* cause incorrect behavior e_WARN = 96, // a *potentially* problematic condition e_INFO = 128, // data about the running process e_DEBUG = 160, // information useful while debugging e_TRACE = 192, // execution trace data };
Level
values correspond to conditions having greater severity. ball
. A log record consists of a number of fixed and user-defined fields; one of the fixed fields is called a "log message" (or "message" for short). In casual usage, we use "record" and "message" interchangeably, but note that they are distinct concepts. The set of fixed fields that constitute a log record are defined in the ball_recordattributes
component. A brief description of the fixed fields (or "record attributes") is given in the following table. Attribute Type Description Default ---------- -------------- ------------------------------ ------- timestamp bdlt::Datetime creation date and time (*Note*) processID int process id of creator 0 threadID Uint64 thread id of creator 0 fileName bsl::string file where created (__FILE__) "" lineNumber int line number in file (__LINE__) 0 category bsl::string category of logged record "" severity int severity of logged record 0 message bsl::string log message text "" Note: The default value given to the timestamp attribute is implementation defined. (See the 'bdlt_datetime' component-level documentation for more information.)
ball::UserFields
. A ball::LoggerManagerConfiguration::UserFieldsPopulatorCallback
functor may be provided to the logger manager on construction (via a ball::LoggerManagerConfiguration
object) that, when invoked, "populates" (i.e., provides the values for) all elements of the ball::UserFields
object containing the user-defined fields. See the ball_record
component for more information about the use of ball::UserFields
in logging, and see the ball_loggermanager
component for more information about installing a user populator callback. ball
infrastructure has no provisions for specifying or enforcing constraints on the encoding used for logged messages (in general, logged messages are published to registered observers in the encoding in which they were originally supplied). However, particular applications, frameworks, and consumers of log records (e.g., readers of log files) may place their own constraints on log records with respect to encoding. Clients generating log records should be aware of the encoding requirements for logged messages generated by their application (e.g., many applications at Bloomberg require UTF-8 encoded messages). ball_severity
for more information on typical severity levels). A message's text, its associated severity, and the name of its associated category are each among the fixed fields in the logged record (see the "Messages, Records, and other <code>ball</code> Terminology" section above). ball::LoggerManager
is a singleton that must be constructed with a configuration object (see below) and an optional bslma
allocator before any true logging operations can be performed. The logger manager's main tasks are to administer categories and to "allocate" loggers. If the user has not allocated and installed any logger instances, then the getLogger
method will return the "default logger" that is a specific instance of ball::Logger
owned and managed by the logger manager. This default logger is suitable for single-threaded and multi-threaded use, and is used "transparently" by users willing to accept default-logger behavior. See Usage below. registerObserver
, deregisterObserver
, and findObserver
methods that register, deregister, and find observers, respectively. The internal broadcast observer forwards all log records that it receives to all registered observers. allocateLogger
and setLogger
methods that allocate arbitrarily many loggers and install (at most) one logger per thread, respectively. Any thread in which the setLogger
method was not called will use the default logger. See {Example 6} and {Example 7} below for a discussion of how and why to allocate multiple loggers. ball::Logger
provides the most central logging functionality, namely the logMessage
method. However, most users will never call logMessage
directly, but rather will use the sets of macros defined in the ball_log
component. See the Appendix: Macro Reference section below. ball::Logger
instance managed by the logger manager is suitable for many purposes, so typical users need never interact with logger instances explicitly at all. However, in multi-threaded applications (and perhaps also in certain special-purpose single-threaded programs), the user may want to instantiate one or more logger instances and install at most one logger instance per thread. See {Example 6} and {Example 7} below. ball::LoggerManagerConfiguration
is a "configuration" object that must be supplied to the logger manager at construction. The default object configures the "default" logging behavior; the user can change the logger behavior by changing attributes in the configuration object. The name, type, and description of the configuration attributes are presented in the two tables below; the awkward repetition of NAME
is due to the long type names of the functor types. NAME TYPE ------------------- ----------------------------------------------------- defaults ball::LoggerManagerDefaults userFieldsPopulatorCallback bsl::function<void(ball::UserFields *)> categoryNameFilterCallback bsl::function<void(bsl::string *, const char *)> defaultThresholdLevelsCallback bsl::function<void(int *, int *, int *, int *, const char *)> logOrder ball::LoggerManagerConfiguration::LogOrder
NAME DESCRIPTION ------------------- ----------------------------------------------------- defaults constrained defaults for buffer size and thresholds userFieldsPopulatorCallback populates optional user fields in a log record [!DEPRECATED!] categoryNameFilterCallback invoked on category names, e.g., to re-map characters defaultThresholdLevelsCallback sets category severity threshold levels (by default) logOrder log message publication order on "trigger" events
ball::LoggerManagerDefaults
is a value-semantic simply-constrained attribute type that is itself an attribute of the above "configuration" object. The "defaults" object contains the following six constrained attributes. TYPE NAME DESCRIPTION ----------- ---------------- ------------------------------------- int recordBufferSize size in bytes of *default* logger's record buffer int loggerBufferSize default size in bytes of *each* logger's "scratch" buffer (for macros) char recordLevel default record severity level char passLevel default pass-through severity level char triggerLevel default trigger severity level char triggerAllLevel default trigger-all severity level
NAME CONSTRAINT +--------------------+---------------------------------------------+ | recordBufferSize | 1 <= recordBufferSize | +--------------------+---------------------------------------------+ | loggerBufferSize | 1 <= loggerBufferSize | +--------------------+---------------------------------------------+ | recordLevel | 0 <= recordLevel <= 255 | | passLevel | 0 <= passLevel <= 255 | | triggerLevel | 0 <= triggerLevel <= 255 | | triggerAllLevel | 0 <= triggerAllLevel <= 255 | +--------------------+---------------------------------------------+
ball::LoggerManagerDefaults
was factored out of the configuration object because the former is purely value-semantic, and can be generated from, e.g., a configuration file. From this perspective, it is very convenient to set this one attribute in the configuration object. It is quite possible to ignore the independent existence of ball::LoggerManagerDefaults
and set the above constrained attributes directly in the ball::LoggerManagerConfiguration
object. The latter is more convenient in "simple" usage, and is illustrated in Usage below. ball::Observer
is the object that receives "published" log messages. Specifically, ball::Observer
is a protocol (abstract interface), and the user must supply a concrete implementation (separately written or chosen from existing ball
observers). The observer provides a publish
method that defines the behavior of the logical concept of "publication" of a message used throughout this document. Note that multiple observers may be registered with the logger manager and each registered observer will receive "published" log messages. ball
provides a suite of logging macros (defined in the ball_log
component). All but the most demanding and customized applications will use the logging macros for basic logging operations (e.g., setting categories and logging messages). The logger manager interface is used primarily to administer global and category-specific severity threshold levels, and to allocate loggers. See the Usage and Appendix: Macro Reference sections below for examples and reference documentation, respectively. BALL_LOG_SET_CATEGORY
macro is not only convenient, it is most often required for macro use, since it defines symbols that the other macros expect to see. (Alternatively, either the BALL_LOG_SET_DYNAMIC_CATEGORY
or BALL_LOG_SET_CLASS_CATEGORY
macro may be used instead; see the ball_log
component for details.) printf
style. The printf
-style logging macros write to an intermediate fixed-size buffer managed by the active logger instance. For any one macro invocation, data larger than the buffer size is (silently) truncated. The buffer size is 8k bytes by default; the buffer size can be queried by the logger messageBufferSize
instance method, and can be set at logger manager construction via the ball::LoggerManagerConfiguration
object. (This is not the case with the C++ stream-style macros, which are more efficient.) [0 .. 255]
, are not published and are subject to change over time. Explicit settings for these factory values may be specified when the singleton is constructed. This is done by constructing a ball::LoggerManagerConfiguration
object (needed for logger manager construction in any event) and then calling the setDefaultThresholdLevelsIfValid
method with the desired values for its four arguments before supplying the configuration object to the logger manager configuration. setCategory(const char *categoryName)
method is called. That method returns the address of the category having categoryName
, if it exists; if no such category exists, and a category having categoryName
cannot be created due to a capacity limitation on the category registry maintained by the logger manager singleton, then the Default Category is returned. setCategory(const char *)
method are given threshold levels by one of two means. One alternative is to use the ball::LoggerManagerConfiguration::DefaultThresholdLevelsCallback
functor that is optionally supplied by the client when the logger manager singleton is initialized. If such a functor is provided by the client, then it is used to supply threshold levels to categories added by setCategory(const char *)
. Otherwise, default threshold levels maintained by the logger manager for that purpose are used. At initialization, these default threshold levels are given the same "factory-supplied" settings as those for the Default Category that, again, may be explicitly overridden at construction. setDefaultThresholdLevels
) and reset to their original values (resetDefaultThresholdLevels
). Note that if factory values are overridden at initialization, a reset will restore thresholds to the user-specified default values. In addition, there is a method to set the threshold levels of a given category to the current default threshold levels (setCategoryThresholdsToCurrentDefaults
) or to the factory-supplied (or client-overridden) default values (setCategoryThresholdsToFactoryDefaults
). ball::LoggerManagerConfiguration::CategoryNameFilterCallback
functor to translate category names from an external to an internal representation. For example, a project may allow programmers to refer to categories using mixed-case within an application, but provide a toLower
CategoryNameFilterCallback
to map all external upper-case letters to lower-case internally. Such a name-filtering functor is set in the ball::LoggerManagerConfiguration
object before that configuration object is passed to the logger manager scoped guard constructor. In this scenario, the (hypothetical) external category names "EQUITY.MARKET.NYSE" and "equity.market.nyse" would be mapped to the same category internally by the presumed toLower
functor. ball
logging framework provides the ability to associatiate "attributes" (name-value pairs) with the current logging context, which can be both be written to the log as part of a log record, as well as used in logging rules (see Rule-Based Logging below). This is typically done using the ball_scopedattribute
component. int processData(const bsl::string& security, const bsl::vector<char>& data) { ball::ScopedAttribute securityAttribute("mylibrary.security", security); // ... int rc = reticulateSplines(data); return rc; } int reticulateSplines(data) { // ... if (0 != rc) { BALL_LOG_ERROR << "Error computing splines (" << rc << ")"; } return rc; }
securityAttribute
object. As a result, if attributes have been enabled in the log format, the error log message generated by this example might look like: ERROR example.cpp:105 EXAMPLE.CATEGORY mylibrary.security="IBM US Equity" Error computing splines (-1)
ball_recordstringformatter
component, which supports the following (new) format specifiers to render log attributes: +------------------+--------------------------------------------------------------------------+ | Format Specifier | Description | +==================+==========================================================================+ | %A | Log all the attributes of the record | +------------------+--------------------------------------------------------------------------+ | %a | Log only those attributes not already logged by the "%a[name]" or | | | "%av[name]" specifiers | +------------------+--------------------------------------------------------------------------+ | %a[name] | Log an attribute with the specified 'name' as "name=value", | | | log nothing if the attribute with the specified 'name' is not found | +------------------+--------------------------------------------------------------------------+ | %av[name] | Log only the *value* of the attribute with the specified 'name', | | | log nothing if the attribute with the specified 'name' is not found | +------------------+--------------------------------------------------------------------------+
int initFileObserver() { // 'ball::FileObserver' uses an internal record string formatter and // is configured by supplying a format string via 'setLogFormat(). bslma::Allocator *alloc = bslma::Default::globalAllocator(0); bsl::shared_ptr<ball::FileObserver> observer = bsl::allocate_shared<ball::FileObserver>(alloc); // Set the log format for file and console logs to "\n%d %p:%t %s %f:%l %c %a %m\n" observer->setLogFormat(ball::RecordStringFormatter::k_BASIC_ATTRIBUTE_FORMAT, ball::RecordStringFormatter::k_BASIC_ATTRIBUTE_FORMAT); if (0 != observer->enableFileLogging("myapplication.log.%T")) { bsl::cout << "Failed to enable logging" << bsl::endl; return -1; } ball::LoggerManager::singleton().registerObserver(observer, "default"); return 0; }
ball::RecordStringFormatter::k_BASIC_ATTRIBUTE_FORMAT
, which is the recommended log format specification for most users (see the example above). The default log format specification remains the same to avoid changing behavior for existing applications. Note that the default format specification might be changed in a subsequent release of BDE. ball
library provides the following classes that implement ready-to-use application solutions for log attributes: ball_attribute
: This class represents an attribute that consists of a (literal) name (held but not owned), and an associated value (owned). The value might be a number or string (see ball_attribute
for more detail). ball_scopedattribute
: Scoped guard that creates an attribute container with a single attribute supplied at construction and adds this container to the current thread's context. The container is removed from the thread context when the guard leaves the scope. ball_recordformatter
: Implements a log record formatting functor. Formats log records into human-readable log messages according to the specified format specs. ball::ScopedAttribute
is recommended for most applications. However, some low-level performance-critical systems (e.g., BAS) may implement custom attribute-collection mechanisms. This adds considerable complexity, but can deliver a small benefit in performance by taking advantage of compile time knowledge of the attributes being collected. See ball_attributecollectorregistry
for more details on attribute collection mechanism. _
), and dots(.
). Do not use any other special characters. +--------------------------+----------------------------+ | "context id" | BAD (contains whitespace) | +--------------------------+----------------------------+ | "contextid" | BAD (no namespace) | +--------------------------+----------------------------+ | "bde.contextid" | GOOD | +--------------------------+----------------------------+ | "bde.logger.context_id" | GOOD | +--------------------------+----------------------------+
ball
library cannot guarantee any deterministic behavior in this case. It is the responsibility of the users to guarantee uniqueness of attribute names. name="value"
pairs (for example: mylibrary.requestType="POST"
). Note that attribute names are not quoted, whereas attribute values, if they are strings, are always quoted. ball
logging toolkit provides a set of components that collectively allow clients to define rules to modify logging severity levels depending on attributes associated with the current thread. The canonical example of rule-based logging is to enable verbose logging when processing requests for a particular user in a service. Advanced Features Example 1: Rule-Based Logging
. ball::Attribute
values. ball::Attribute
values with the current thread. ball_rule
) that set a logging category's thresholds if a set of attributes associated with the current thread matches those of the rule (see ball_managedattribute
). For example, a ball::Rule
might express: set the logging threshold for any logging category starting with the prefix "DATABASE" to e_TRACE
if the "myLib.userid" is 123456 and the "myLib.requesttype" is "trade" (where "myLib.userid" and "myLib.requesttype" are names of attributes that will be associated with a processing thread). Clients can then call ball::LoggerManager::addRule
to add this rule to the logger manager. ball::AttributeContainer
holding the attribute to the ball::AttributeContext
(note that an attribute context is a thread-local object). The simplest means for doing this is to create a ball::ScopedAttribute
object as a local variable, which adds an attribute container (holding a single attribute) that exists for the lifetime of the scoped-attribute object. A ball::ScopedAttribute
appends a container having a single attribute to a list of containers, which is easy and efficient if there are only a small number of attributes. Clients with a large number of attributes could use a ball::DefaultAttributeContainer
, which provides an efficient container for a large number of attributes. However, clients must take care to remove the container from the ball::AttributeContext
before the object goes out of scope and is destroyed. See ball_scopedattributes
(plural) for an example. Finally, clients may also provide their own implementations of ball::AttributeContainer
customized for their needs (e.g., by creating a simple, efficient ball::AttributeContainer
specifically for the "myLib.userid" and "myLib.requesttype" attributes); see ball_attributecontainer
for an example. Advanced Features Example 1: Rule-Based Logging
below demonstrates creating a simple logging rule, and associating attributes with a processing thread. ball
logging toolkit may be used in single-threaded and multi-threaded library code and applications with equal ease, and with virtually no difference in coding. In particular, the same call to the logger manager scoped guard constructor is required in main
in both cases, and individual calls to the ball::Logger
instance method logMessage
(and logging calls via the logging macros -- see ball_log
and the Appendix: Macro Reference below) are identical from the user's perspective. Category threshold administration is also identical in both cases, since that is done by the singleton logger manager. allocateLogger
) and install that logger for a given thread (via the manager instance method setLogger
), then all records from all threads in a program will be logged to the one default logger. However, since each thread of execution may have its own logger instance, multi-threaded users may choose to allocate and install multiple loggers. Note that each thread may have at most one logger, but a single logger may be used by any number of threads. ball::LoggerManagerScopedGuard
object in main
(before creating any threads). The logger manager scoped guard constructor takes a configuration object (an instance of ball::LoggerManagerConfiguration
) and an optional allocator. The logger manager singleton is created as a side-effect of creating the scoped guard object. When the guard object goes out of scope (i.e., on program exit), the logger manager singleton is automatically destroyed. static
ball::LoggerManager::initSingleton
method that takes the same arguments as the scoped guard constructor may be used to initialize the singleton. However, in this case the ball::LoggerManager::shutDownSingleton
method must be explicitly called to destroy the logger manager singleton on program exit. Unless shutDownSingleton
is called, the singleton will not be destroyed and resources used by the singleton will leak. public
logger manager constructor to initialize the logger manager singleton is deprecated. The constructor will be declared private
in a future release. ball::LoggerManager::initSingleton
methods that do not take an instance of ball::LoggerManagerConfiguration
to initialize the logger manager singleton is deprecated. These methods will be eliminated in a future release. ball
logger in a fair number of production applications. In particular, all of these examples use the macros from the ball_log
component to perform the actual logging. Methods from ball_loggermanager
are used for initialization and administration, but for most applications, anything that can be done using the logging macros should be done with the macros. main
function and essentially nothing else. Among the points of usage that we illustrate are: ball::LoggerManagerConfiguration
and passing it to the logger manager scoped guard constructor. ball::StreamObserver
and registering it with the logger manager singleton. BALL_LOG_SET_CATEGORY
macro to set a category (named "main
category" in this example); records logged from a scope where the effects of the macro are visible (and not superseded by a macro invocation in a nested scope) will be logged to this category. BALL_LOG_INFO
macro to log a "Hello world!" message at e_INFO
severity; messages at e_INFO
severity are ignored by default. e_ERROR
severity that will be published as a "pass-through". If the command-line argument is absent, the program will succeed with no messages published. This is the intended behavior. BALL_LOG_SET_CATEGORY
macro is not only extremely convenient, it is typically required, since it in turn defines symbols that the logging macros expect to see. Also note that BALL_LOG_SET_CATEGORY
must be used at function scope, and can be used only once within a scope, although it can be nested in inner scopes. // logging.m.cpp -*-C++-*- #include <ball_log.h> #include <ball_loggermanager.h> #include <ball_loggermanagerconfiguration.h> #include <ball_streamobserver.h> #include <bslma_allocator.h> #include <bslma_default.h> #include <bsl_iostream.h> #include <bsl_memory.h> using namespace BloombergLP; int main(int argc, char *argv[]) { int verbose = argc > 1; // Enable command-line control of program behavior. bslma::Allocator *alloc_p = bslma::Default::globalAllocator(); // Get global allocator. ball::LoggerManagerConfiguration configuration; configuration.setDefaultThresholdLevelsIfValid(ball::Severity::e_WARN); // Configure the minimum threshold at which records are published to // the observer to 'e_WARN'. ball::LoggerManagerScopedGuard scopedGuard(configuration); // Instantiate the logger manager singleton. ball::LoggerManager& manager = ball::LoggerManager::singleton(); bsl::shared_ptr<ball::StreamObserver> observer( new(*alloc_p) ball::StreamObserver(&bsl::cout), alloc_p); // Create simple observer; writes to 'stdout'. manager.registerObserver(observer, "default"); // Register the observer under (arbitrary) name "default". BALL_LOG_SET_CATEGORY("main category"); // Set a category -- an arbitrary name. BALL_LOG_INFO << "Hello world!"; // With default settings, this line has no effect. if (verbose) { // 'if' to allow command-line activation BALL_LOG_ERROR << "Good-bye world!"; // Log 'message' at 'ball::Severity::e_ERROR' severity level. } else { bsl::cout << "This program should produce no other output.\n"; // By default, 'e_INFO' is ignored; only 'e_ERROR' and above are // published. } return 0; }
stdout
as: This program should produce no other output.
e_ERROR
and e_FATAL
) are published immediately, but other events (such as e_INFO
, e_DEBUG
, and e_TRACE
) are ignored. The user can easily modify this default behavior; subsequent examples will address these alternate usage modes. stdout
will appear something like the following. (The spacing is changed to aid readability.) 27SEP2007_13:26:46.322 6152 1 ERROR logging.m.cpp 33 main category Good-bye world!
ball_asyncfileobserver
. ball::LoggerManagerConfiguration
are changed from their defaults (in a single line, in this case). setDefaultThresholdLevelsIfValid
method of ball::LoggerManagerConfiguration
that we will use to change the default configuration takes four arguments, each in the range [0 .. 255]
. The method returns a status, and will fail (with no effect) if any argument is not a valid threshold level. Note that any ball::Severity::Level
enumerator value is valid, and it is good practice to limit arguments to one of those enumerators. The one line that effects our change is: configuration.setDefaultThresholdLevelsIfValid( ball::Severity::e_TRACE, // record level ball::Severity::e_WARN, // pass-through level ball::Severity::e_ERROR, // trigger level ball::Severity::e_FATAL); // trigger-all level
e_TRACE
is written to the record buffer, but not necessarily published. e_WARN
is published immediately as a pass-through record and is recorded in the record buffer. Note that, should the record buffer be published before such a record is overwritten, the record will be published a second time, avoiding "holes" in the trace-back if and when the buffer is published. e_ERROR
is published immediately as a pass-through record and is recorded in the record buffer, and then triggers the publication of the record buffer. This behavior, among other things, guarantees that the e_ERROR
record will be published twice. Although this might seem like a bug, it is the intended behavior. Each published record is sent to the observer with a publication cause (a ball::Transmission::Cause
value) that the observer is free to use in filtering and re-routing published records. Future examples will illustrate various filtering techniques. e_FATAL
will behave as for e_ERROR
above, except that the record buffer from every active logger registered with the logger manager will be published. See the multi-threaded examples below for more information about multiple loggers. #include
directive of ball_severity.h
(since we use symbols from that header explicitly), the code is otherwise exactly as in {Example 1} above. // logging.m.cpp -*-C++-*- #include <ball_log.h> #include <ball_loggermanager.h> #include <ball_loggermanagerconfiguration.h> #include <ball_severity.h> #include <ball_streamobserver.h> #include <bslma_allocator.h> #include <bslma_default.h> #include <bsl_iostream.h> #include <bsl_memory.h> using namespace BloombergLP; int main(int argc, char *argv[]) { int verbose = argc > 1; // Enable command-line control of program behavior. bslma::Allocator *alloc_p = bslma::Default::globalAllocator(); // Get global allocator. ball::LoggerManagerConfiguration configuration; // Instantiate the default configuration. configuration.setDefaultThresholdLevelsIfValid( ball::Severity::e_TRACE, // record level ball::Severity::e_WARN, // pass-through level ball::Severity::e_ERROR, // trigger level ball::Severity::e_FATAL); // trigger-all level // Set the four severity threshold levels; note that this method can // fail, and therefore returns a status. ball::LoggerManagerScopedGuard scopedGuard(configuration); // Instantiate the logger manager singleton. ball::LoggerManager& manager = ball::LoggerManager::singleton(); bsl::shared_ptr<ball::StreamObserver> observer( new(*alloc_p) ball::StreamObserver(&bsl::cout), alloc_p); // Create simple observer; writes to 'stdout'. manager.registerObserver(observer, "default"); // Register the observer under (arbitrary) name "default". BALL_LOG_SET_CATEGORY("main category"); // Set a category -- an arbitrary name. BALL_LOG_INFO << "Hello world!"; // With default settings, this line has no effect. if (verbose) { // 'if' to allow command-line activation BALL_LOG_ERROR << "Good-bye world!"; // Log 'message' at 'ball::Severity::e_ERROR' severity level // *and* trigger the publication of the record buffer // "trace-back". } else { bsl::cout << "This program should produce no other output.\n"; // 'e_INFO' messages are recorded but *not* automatically // published; an 'e_ERROR' or 'e_FATAL' message is needed to // trigger the publication of the record buffer. } return 0; }
stdout
as: This program should produce no other output.
27SEP2007_13:29:18.147 105 1 ERROR logging.m.cpp 42 main category Good-bye world! 27SEP2007_13:29:18.147 105 1 ERROR logging.m.cpp 42 main category Good-bye world! 27SEP2007_13:29:18.147 105 1 INFO logging.m.cpp 38 main category Hello world!
e_ERROR
message appears twice. This is not a bug but, as mentioned above, an intended consequence of the settings. As described above, the sequence of events is as follows: (1) The e_INFO
message is written to an in-memory buffer but not published (because e_INFO
is above the "Record" threshold but below the "Pass-Through", "Trigger", and "Trigger-All" thresholds). (2) The (one) e_ERROR
message is first published (a relatively quick operation insuring that the error indication is seen) because e_ERROR
is above the "Pass-Through" threshold, and then is written to the in-memory buffer, because e_ERROR
is above the "Record" threshold. Then, the entire contents of the in-memory buffer is published, in "Last In, First
Out" (LIFO) order, because e_ERROR
is at the "Trigger" threshold. e_ERROR
message is removed, as is the use of command-line arguments, since they are not needed to illustrate our point. Instead, the user calls the publish
method of the logger instance "manually" before exiting, to force the publication of all unpublished messages in the logger's buffer. Note that this is not "default" behavior, but is accomplished simply enough. The line: manager.getLogger().publish();
publish
method. Note that the logger's "manual" publish
method should not be confused with the ball::Observer
publish
method that is invoked by all "manual" and "automatic" logger "publishing" operations. Also note that should the manager
instance for some reason not be visible, the class method ball::LoggerManager::singleton()
can be used to obtain a reference to that instance. The full program file appears as follows. // logging.m.cpp -*-C++-*- #include <ball_log.h> #include <ball_loggermanager.h> #include <ball_loggermanagerconfiguration.h> #include <ball_severity.h> #include <ball_streamobserver.h> #include <bslma_allocator.h> #include <bslma_default.h> #include <bsl_iostream.h> #include <bsl_memory.h> using namespace BloombergLP; int main() { bslma::Allocator *alloc_p = bslma::Default::globalAllocator(); // Get global allocator. ball::LoggerManagerConfiguration configuration; // Create default configuration. configuration.setDefaultThresholdLevelsIfValid( ball::Severity::e_TRACE, // record level ball::Severity::e_WARN, // pass-through level ball::Severity::e_ERROR, // trigger level ball::Severity::e_FATAL); // trigger-all level // Set the four severity threshold levels; note that this method can // fail, and therefore returns a status. ball::LoggerManagerScopedGuard scopedGuard(configuration); // Instantiate the logger manager singleton. ball::LoggerManager& manager = ball::LoggerManager::singleton(); bsl::shared_ptr<ball::StreamObserver> observer( new(*alloc_p) ball::StreamObserver(&bsl::cout), alloc_p); // Create simple observer; writes to 'stdout'. manager.registerObserver(observer, "default"); // Register the observer under (arbitrary) name "default". BALL_LOG_SET_CATEGORY("main category"); // Set a category -- an arbitrary name. BALL_LOG_INFO << "Hello world!"; BALL_LOG_INFO << "Hello again, world!"; bsl::cout << "We're almost ready to exit." " " "Let's publish the buffer first:" "\n"; manager.getLogger().publish(); // This chain of calls insures that all messages that are still held // in the logger's in-memory buffer are published before the program // terminates. return 0; }
stdout
, altered here again in spacing format and timestamp value for ease of reading. We're almost ready to exit. Let's publish the buffer first:
27SEP2007_13:32:17.778 13702 1 INFO logging.m.cpp 37 main category
Hello again, world!
27SEP2007_13:32:17.778 13702 1 INFO logging.m.cpp 35 main category
Hello world!
main
, we introduce a separate translation unit f_log.cpp
that defines a function logThisInfoMsg
. The f_log.cpp
file defines its own category, "function
category", that is distinct from the "main category" defined in main
. When logThisInfoMsg
is invoked, the resulting message is logged to "function
category". Note, however, that when the now-familiar logging macros are used from main
directly, those messages are logged to "main category". e_INFO
are passed through immediately to the observer (in this case, to stdout
). In this example, we make the change by calling the setDefaultThresholdLevels
method of the logger manager: manager.setDefaultThresholdLevels( ball::Severity::e_TRACE, // sets "Record" threshold ball::Severity::e_INFO, // sets "Pass-Through" threshold ball::Severity::e_ERROR, // sets "Trigger" threshold ball::Severity::e_FATAL); // sets "Trigger-All" threshold
setDefaultThresholdLevelsIfValid
method. The latter sets the permanent "factory defaults", as well as the "instantaneous defaults" that may be altered as often as desired using the above call. If, after the above call is made, the setCategoryThresholdsToFactoryDefaults
method is called, the effects of setDefaultThresholdLevels
are erased. This distinction is important for more advanced administration, but is not central to this example; we offer the alternative to illustrate logger functionality. f_log
(or any library code) to instantiate a logger manager. The logger manager is a singleton, and should be constructed using the logger manager scoped guard (ball::LoggerManagerScopedGuard
). In any sound design, this should be done in main
before any other threads have been created. logging.m.cpp
file, plus f_log.cpp
and its associated f_log.h
file, are shown below. First the f_log.h
header file: // f_log.h -*-C++-*- #ifndef INCLUDED_F_LOG #define INCLUDED_F_LOG namespace BloombergLP { void logThisInfoMsg(const char *message); // Log the specified 'message' at the 'ball::Severity::e_INFO' severity // level. The category to which 'message' is logged is defined in // the implementation. Note that this function may affect the // operation of other logging operations that do not explicitly set // their own categories. } // close enterprise namespace #endif
f_log.cpp
implementation file: // f_log.cpp -*-C++-*- #include <ball_log.h> namespace BloombergLP { void logThisInfoMsg(const char *message) { // Note that the uppercase symbols are macros defined in 'ball_log'. BALL_LOG_SET_CATEGORY("function category"); // Set a category -- arbitrary and may be changed. BALL_LOG_INFO << message; // Log 'message' at 'ball::Severity::e_INFO' severity level. } } // close enterprise namespace
logging.m.cpp
file that defines main
: // logging.m.cpp -*-C++-*- #include <ball_log.h> #include <ball_loggermanager.h> #include <ball_loggermanagerconfiguration.h> #include <ball_severity.h> #include <ball_streamobserver.h> #include <bslma_allocator.h> #include <bslma_default.h> #include <bsl_iostream.h> #include <bsl_memory.h> #include <f_log.h> using namespace BloombergLP; int main(int argc, char *argv[]) { int verbose = argc > 1; // Enable command-line control of program behavior. bslma::Allocator *alloc_p = bslma::Default::globalAllocator(); // Get global allocator. ball::LoggerManagerConfiguration configuration; // Instantiate the default configuration. ball::LoggerManagerScopedGuard scopedGuard(configuration); // Instantiate the logger manager singleton. ball::LoggerManager& manager = ball::LoggerManager::singleton(); manager.setDefaultThresholdLevels( ball::Severity::e_TRACE, // sets "Record" threshold ball::Severity::e_INFO, // sets "Pass-Through" threshold ball::Severity::e_ERROR, // sets "Trigger" threshold ball::Severity::e_FATAL); // sets "Trigger-All" threshold bsl::shared_ptr<ball::StreamObserver> observer( new(*alloc_p) ball::StreamObserver(&bsl::cout), alloc_p); // Create simple observer; writes to 'stdout'. manager.registerObserver(observer, "default"); // Register the observer under (arbitrary) name "default". BALL_LOG_SET_CATEGORY("main category"); // Set a category -- note that this will *not* affect the category // set in 'logThisInfoMsg'. BALL_LOG_INFO << "Called directly from 'main'"; // Logged to "main category". if (verbose) { // 'if' to allow command-line activation for (int i = 0; i < 3; ++i) { logThisInfoMsg("Hello world!"); bsl::cout << "Watch the loop execute: i = " << i << bsl::endl; // proves Msg is published as "pass-through" } } BALL_LOG_INFO << "Called again directly from 'main'"; // Logged to "main category". return 0; }
27SEP2007_13:35:33.356 9427 1 INFO logging.m.cpp 40 main category Called directly from 'main' 27SEP2007_13:35:33.357 9427 1 INFO logging.m.cpp 51 main category Called again directly from 'main'
for
loop to execute, we then get the following output (with formats and timestamps altered as is now familiar in these examples). 27SEP2007_13:39:07.150 8249 1 INFO logging.m.cpp 40 main category Called directly from 'main' 27SEP2007_13:39:07.151 8249 1 INFO f_log.cpp 16 function category Hello world! Watch the loop execute: i = 0 27SEP2007_13:39:07.151 8249 1 INFO f_log.cpp 16 function category Hello world! Watch the loop execute: i = 1 27SEP2007_13:39:07.152 8249 1 INFO f_log.cpp 16 function category Hello world! Watch the loop execute: i = 2 27SEP2007_13:39:07.152 8249 1 INFO logging.m.cpp 51 main category Called again directly from 'main'
mtlogging1.cpp
. f1
and f2
, that log their message
text argument to function-defined categories, at e_INFO
and e_WARN
severities, respectively. We also define two extern
"C" thread functions, threadFunction1
and threadFunction2
, that call f1
and f2
, respectively, in a loop of three iterations. The thread functions also differ slightly in their calls to bslmt::ThreadUtil::sleep
within the loop. These sleep
calls, in conjunction with the logged timestamps, show the interleaved operation of the threads. Note that although the thread functions define their own categories, the logging macros in f1
and f2
use only the category defined within the scope of f1
and f2
, respectively. main
creates an observer that will write to stdout
, initializes the logger manager singleton, sets a category, and sets the default threshold values, just as in the single-threaded examples above. Then main
uses bslmt
to spawn two threads running the above two thread functions. Each thread logs its messages, but only the second thread's log messages having e_WARN
severity will be published immediately as "Pass-Through" records. Finally, using the same verbose
mechanism (set by the presence or absence of a command-line argument) as the above examples, we conditionally log a message in main
at e_ERROR
severity. If this block of code executes, then the entire record buffer for the example (i.e., for both threads and main
) will be published, in reverse chronological order. // logging.m.cpp -*-C++-*- #include <ball_log.h> #include <ball_loggermanager.h> #include <ball_loggermanagerconfiguration.h> #include <ball_severity.h> #include <ball_streamobserver.h> #include <bslma_allocator.h> #include <bslma_default.h> #include <bslmt_threadutil.h> #include <bsls_timeinterval.h> #include <bsl_iostream.h> #include <bsl_memory.h> using namespace BloombergLP; void f1(const char *message) // Log the specified 'message' to the "Function 1" category at 'e_INFO' // severity. { BALL_LOG_SET_CATEGORY("Function 1"); BALL_LOG_INFO << message; } void f2(const char *message) // Log the specified 'message' to the "Function 2" category at 'e_WARN' // severity. { BALL_LOG_SET_CATEGORY("Function 2"); BALL_LOG_WARN << message; } extern "C" void *threadFunction1(void *) // Log to the default logger a sequence of messages to the "Function 1" // category at 'e_INFO' severity. { char buf[10] = "Message n"; bsls::TimeInterval waitTime(4.0); for (int i = 0; i < 3; ++i) { buf[8] = '0' + i; f1(buf); bslmt::ThreadUtil::sleep(waitTime); } return 0; } extern "C" void *threadFunction2(void *) // Log to the default logger a sequence of messages to the "Function 2" // category at 'e_WARN' severity. { char buf[10] = "Message n"; bsls::TimeInterval waitTime(2.0); for (int i = 0; i < 3; ++i) { buf[8] = '0' + i; bslmt::ThreadUtil::sleep(waitTime); f2(buf); } return 0; } int main(int argc, char *argv[]) { int verbose = argc > 1; // allows user to control output from command // line bslma::Allocator *alloc_p = bslma::Default::globalAllocator(); // Get global allocator. ball::LoggerManagerConfiguration configuration; configuration.setDefaultThresholdLevelsIfValid( ball::Severity::e_TRACE, // "Record" ball::Severity::e_WARN, // "Pass-Through" ball::Severity::e_ERROR, // "Trigger" ball::Severity::e_FATAL); // "Trigger-All" ball::LoggerManagerScopedGuard scopedGuard(configuration); // Instantiate the logger manager singleton. ball::LoggerManager& manager = ball::LoggerManager::singleton(); bsl::shared_ptr<ball::StreamObserver> observer( new(*alloc_p) ball::StreamObserver(&bsl::cout), alloc_p); // Create simple observer; writes to 'stdout'. manager.registerObserver(observer, "default"); // Register the observer under (arbitrary) name "default". BALL_LOG_SET_CATEGORY("main"); bslmt::ThreadAttributes attributes; bslmt::ThreadUtil::Handle handle1; bslmt::ThreadUtil::Handle handle2; bslmt::ThreadUtil::create(&handle1, attributes, threadFunction1, 0); bslmt::ThreadUtil::create(&handle2, attributes, threadFunction2, 0); bslmt::ThreadUtil::join(handle1); bslmt::ThreadUtil::join(handle2); if (verbose) { // 'if' to allow command-line activation BALL_LOG_ERROR << "Force publication."; } return 0; }
e_WARN
records (from thread 3) are published, and that they are in chronological order. 27SEP2007_13:50:20.023 6625 3 WARN logging.m.cpp 28 Function 2 Message 0 27SEP2007_13:50:22.033 6625 3 WARN logging.m.cpp 28 Function 2 Message 1 27SEP2007_13:50:24.042 6625 3 WARN logging.m.cpp 28 Function 2 Message 2
if
statement containing the BALL_LOG_ERROR
macro will execute. This causes the e_ERROR
record itself to be published (from thread 1, the main
thread, as a "pass-through"), and it also triggers the publication of the entire record buffer. Note how the records from threads 1, 2, and 3 are published in inverse chronological order. Note in particular how the e_INFO
and 'e_WARN records interleave irregularly because of the different sleep
statements in the above code. 27SEP2007_13:52:20.435 13809 3 WARN logging.m.cpp 28 Function 2 Message 0 27SEP2007_13:52:22.445 13809 3 WARN logging.m.cpp 28 Function 2 Message 1 27SEP2007_13:52:24.455 13809 3 WARN logging.m.cpp 28 Function 2 Message 2 27SEP2007_13:52:30.456 13809 1 ERROR logging.m.cpp 89 main Force publication. 27SEP2007_13:52:30.456 13809 1 ERROR logging.m.cpp 89 main Force publication. 27SEP2007_13:52:26.446 13809 2 INFO logging.m.cpp 20 Function 1 Message 2 27SEP2007_13:52:24.455 13809 3 WARN logging.m.cpp 28 Function 2 Message 2 27SEP2007_13:52:22.445 13809 3 WARN logging.m.cpp 28 Function 2 Message 1 27SEP2007_13:52:22.435 13809 2 INFO logging.m.cpp 20 Function 1 Message 1 27SEP2007_13:52:20.435 13809 3 WARN logging.m.cpp 28 Function 2 Message 0 27SEP2007_13:52:18.433 13809 2 INFO logging.m.cpp 20 Function 1 Message 0
main
, to make clear that the "one-logger-per-thread" usage pattern applies on a thread-by-thread basis, and is not "all-or-nothing".) By choosing this more sophisticated usage, however, we need to address the question of logger resource management and lifetime -- a question that is completely avoided by using the default logger. As is often the case in programming, there is no one "right" answer to this resource question, and so we will discuss our choices and the alternatives in this example. threadFunction1
is simple enough to do, but a question arises immediately: who should own the logger resources? Before we discuss this question, let's look at the specific solution that we have chosen to clarify the question of what resources a logger needs. threadFunction1
to take a logger as an argument (passed as a void *
) and to use the logger manager's setLogger
function to install the logger as the active logger for that thread. The code to do this in threadFunction1
is straightforward: extern "C" void *threadFunction1(void *logger) // ... { // ... ball::LoggerManager::singleton().setLogger((ball::Logger*)logger); // Install a local 'logger' for this thread. // ... }
threadFunction1
does not own logger
or any of its resources. This must be the case, because the function has no way of knowing in how many other threads the passed-in logger
might be installed. Recall that any one logger may be installed in any number of threads. setLogger
method is, from the thread function's perspective, the only change needed if the thread is not to manage its own logger resources. But if threadFunction1
does not manage the logger resources, then main
must do so. Let's look at what main
needs to do so that we can be concrete about what resources are needed in the first place. ball::Logger
instance. The logger needs a (concrete) ball::RecordBuffer
, and the preferred record buffer, ball::FixedSizeRecordBuffer
, in turn needs a size for the record buffer (LOGBUF_SIZE
). We also provide an explicit bslma::Allocator
to supply memory; the global allocator returned by bslma::Default::globalAllocator()
is sufficient here. Assuming that 32k bytes is a good size for the buffer, the following eight lines will appear in main
. enum { LOGBUF_SIZE = 32 * 1024 }; bslma::Allocator *alloc_p = bslma::Default::globalAllocator(); ball::FixedSizeRecordBuffer rb(LOGBUF_SIZE, alloc_p); ball::LoggerManager& manager = ball::LoggerManager::singleton(); ball::Logger *logger = manager.allocateLogger(&rb); // ... manager.deallocateLogger(logger); // free resources
main
returns. main
clearly owns logger
s resources. We now have enough information to address the question of what the consequences of this decision are, and whether or not main
actually should own these resources. threadFunction1
would look like in that case, but there are two separate reasons why the owner of main
might prefer not to delegate those responsibilities to the thread function. main
may wish to install the same logger in several threads. In this case, no one thread can manage "its own" logger, because the logger is (or at least might be) shared. Sharing loggers among groups of threads is at least a reasonable design choice, and so it is useful to see how main
takes ownership of logger resources. main
and not the thread function to own the logger's resources is illustrated -- albeit trivially -- in this example: main
can potentially log a e_FATAL
record (here the "Trigger-All" threshold) after all other threads have terminated. If the user wants the (now-terminated) thread's trace-back log to be published by the "Trigger-All", then main
must preserve the logger resources. Otherwise, if a thread were to own its own logger resources, then any "trace-back" records generated by that thread would necessarily be discarded (or, at best, unconditionally published) when the thread function returned. ball::StreamObserver
, namely that an instance can write to a file as easily as to stdout
. The three lines in main
: ball::LoggerManagerScopedGuard scopedGuard(configuration); // Instantiate the logger manager singleton. bsl::ofstream outFile("outFile"); bsl::shared_ptr<ball::StreamObserver> observer( new(*alloc_p) ball::StreamObserver(&outFile), alloc_p); // Create simple observer; writes to 'outFile". ball::LoggerManager& manager = ball::LoggerManager::singleton(); manager.registerObserver(observer, "default"); // Register the observer under (arbitrary) name "default".
ofstream
class, but it is useful all the same. // logging.m.cpp -*-C++-*- #include <ball_fixedsizerecordbuffer.h> #include <ball_log.h> #include <ball_loggermanager.h> #include <ball_loggermanagerconfiguration.h> #include <ball_severity.h> #include <ball_streamobserver.h> #include <bslma_allocator.h> #include <bslma_default.h> #include <bslmt_threadutil.h> #include <bsls_timeinterval.h> #include <bsl_fstream.h> #include <bsl_memory.h> using namespace BloombergLP; void f1(const char *message) // Log the specified 'message' to the "Function 1" category at 'e_INFO' // severity. { BALL_LOG_SET_CATEGORY("Function 1"); BALL_LOG_INFO << message; } void f2(const char *message) // Log the specified 'message' to the "Function 2" category at 'e_WARN' // severity. { BALL_LOG_SET_CATEGORY("Function 2"); BALL_LOG_WARN << message; } extern "C" void *threadFunction1(void *logger) // Log to the specified 'logger' a sequence of messages to the "Function // 1" category at 'e_INFO' severity. { ball::LoggerManager::singleton().setLogger((ball::Logger *)logger); // Install a local logger for this thread. char buf[10] = "Message n"; bsls::TimeInterval waitTime(4.0); for (int i = 0; i < 3; ++i) { buf[8] = '0' + i; f1(buf); bslmt::ThreadUtil::sleep(waitTime); } return 0; } extern "C" void *threadFunction2(void *) // Log to the default logger a sequence of messages to the "Function 2" // category at 'e_WARN' severity. { char buf[10] = "Message n"; bsls::TimeInterval waitTime(2.0); for (int i = 0; i < 3; ++i) { buf[8] = '0' + i; bslmt::ThreadUtil::sleep(waitTime); f2(buf); } return 0; } int main(int argc, char *argv[]) { int verbose = argc > 1; // allows user to control output from command // line bslma::Allocator *alloc_p = bslma::Default::globalAllocator(); // Get global allocator. ball::LoggerManagerConfiguration configuration; configuration.setDefaultThresholdLevelsIfValid( ball::Severity::e_TRACE, // "Record" ball::Severity::e_WARN, // "Pass-Through" ball::Severity::e_ERROR, // "Trigger" ball::Severity::e_FATAL); // "Trigger-All" ball::LoggerManagerScopedGuard scopedGuard(configuration); // Instantiate the logger manager singleton. ball::LoggerManager& manager = ball::LoggerManager::singleton(); bsl::ofstream outFile("outFile"); bsl::shared_ptr<ball::StreamObserver> observer( new(*alloc_p) ball::StreamObserver(&outFile), alloc_p); // Create simple observer; writes to 'outFile". manager.registerObserver(observer, "default"); // Register the observer under (arbitrary) name "default". BALL_LOG_SET_CATEGORY("main"); // The following lines prepare resources to allocate a 'logger' from the // logger manager. 'main' is responsible for managing resource // lifetimes. enum { LOGBUF_SIZE = 32 * 1024 }; ball::FixedSizeRecordBuffer rb(LOGBUF_SIZE, alloc_p); ball::Logger *logger = manager.allocateLogger(&rb); bslmt::ThreadAttributes attributes; bslmt::ThreadUtil::Handle handle1; bslmt::ThreadUtil::Handle handle2; // first thread gets 'logger'; second thread uses default logger bslmt::ThreadUtil::create(&handle1, attributes, threadFunction1, logger); bslmt::ThreadUtil::create(&handle2, attributes, threadFunction2, 0); bslmt::ThreadUtil::join(handle1); bslmt::ThreadUtil::join(handle2); if (verbose) { // 'if' to allow command-line activation BALL_LOG_FATAL << "Force publication."; } manager.deallocateLogger(logger); // free resources return 0; }
outFile
. Note that, except for the timestamps, process id, and filename, the output is the same as for {Example 5} above: only the e_WARN
records (from thread 3) are published and they are in chronological order. 27SEP2007_13:57:52.012 3648 3 WARN logging.m.cpp 29 Function 2 Message 0 27SEP2007_13:57:54.022 3648 3 WARN logging.m.cpp 29 Function 2 Message 1 27SEP2007_13:57:56.032 3648 3 WARN logging.m.cpp 29 Function 2 Message 2
main
(now BALL_LOG_FATAL
) to execute, the initial output is similar to {Example 5}. Specifically, the three e_WARN
records are published in chronological order as "Pass-Through" records, as is the e_FATAL
record (that now replaces the e_ERROR
record). From here on, however, the sequence of events is a bit different. e_FATAL
is the "Trigger-All" threshold level. For a "Trigger-All", each record buffer is published in turn. Within a record buffer, records are published in reverse chronological order, but, as can clearly be seen from the timestamps, the sequence of records as a whole is not well ordered in time at all. Note, rather, that the e_FATAL
and e_WARN
records from the default logger are published in reverse chronological order, and then the e_INFO
records from the installed logger
are published in reverse chronological order. 27SEP2007_13:59:23.504 8707 3 WARN logging.m.cpp 29 Function 2 Message 0 27SEP2007_13:59:25.514 8707 3 WARN logging.m.cpp 29 Function 2 Message 1 27SEP2007_13:59:27.524 8707 3 WARN logging.m.cpp 29 Function 2 Message 2 27SEP2007_13:59:33.524 8707 1 FATAL logging.m.cpp 103 main Force publication. 27SEP2007_13:59:33.524 8707 1 FATAL logging.m.cpp 103 main Force publication. 27SEP2007_13:59:27.524 8707 3 WARN logging.m.cpp 29 Function 2 Message 2 27SEP2007_13:59:25.514 8707 3 WARN logging.m.cpp 29 Function 2 Message 1 27SEP2007_13:59:23.504 8707 3 WARN logging.m.cpp 29 Function 2 Message 0 27SEP2007_13:59:29.514 8707 2 INFO logging.m.cpp 21 Function 1 Message 2 27SEP2007_13:59:25.504 8707 2 INFO logging.m.cpp 21 Function 1 Message 1 27SEP2007_13:59:21.498 8707 2 INFO logging.m.cpp 21 Function 1 Message 0
main
to threadFunction1
, so that the latter function no longer needs an argument. Therefore, we show only threadFunction1
and main
explicitly in this example. Refer to the discussion of {Example 6} for details. // ... extern "C" void *threadFunction1(void *) // Log to an internally managed logger a sequence of messages to the // "Function 1" category at 'e_INFO' severity. { // The following lines prepare resources to allocate a 'logger' from the // logger manager. This thread is responsible for managing resource // lifetimes. enum { LOGBUF_SIZE = 32 * 1024 }; bslma::Allocator *alloc_p = bslma::Default::globalAllocator(); ball::FixedSizeRecordBuffer rb(LOGBUF_SIZE, alloc_p); ball::LoggerManager& manager = ball::LoggerManager::singleton(); ball::Logger *logger = manager.allocateLogger(&rb); manager.setLogger((ball::Logger*)logger); // Install a local logger for this thread. char buf[10] = "Message n"; bsls::TimeInterval waitTime(4.0); for (int i = 0; i < 3; ++i) { buf[8] = '0' + i; f1(buf); bslmt::ThreadUtil::sleep(waitTime); } manager.deallocateLogger(logger); // free resources return 0; } // ... int main(int argc, char *argv[]) { int verbose = argc > 1; // allows user to control output from command // line bslma::Allocator *alloc_p = bslma::Default::globalAllocator(); // Get global allocator. ball::LoggerManagerConfiguration configuration; // default configuration configuration.setDefaultThresholdLevelsIfValid( ball::Severity::e_TRACE, // "Record" ball::Severity::e_WARN, // "Pass-Through" ball::Severity::e_ERROR, // "Trigger" ball::Severity::e_FATAL); // "Trigger-All" ball::LoggerManagerScopedGuard scopedGuard(configuration); // Instantiate the logger manager singleton. ball::LoggerManager& manager = ball::LoggerManager::singleton(); bsl::ofstream outFile("outFile"); bsl::shared_ptr<ball::StreamObserver> observer( new(*alloc_p) ball::StreamObserver(&outFile), alloc_p); // Create simple observer; writes to 'outFile". manager.registerObserver(observer, "default"); // Register the observer under (arbitrary) name "default". BALL_LOG_SET_CATEGORY("main"); bslmt::ThreadAttributes attributes; bslmt::ThreadUtil::Handle handle1; bslmt::ThreadUtil::Handle handle2; // first thread manages its own logger; second thread uses default logger bslmt::ThreadUtil::create(&handle1, attributes, threadFunction1, 0); bslmt::ThreadUtil::create(&handle2, attributes, threadFunction2, 0); bslmt::ThreadUtil::join(handle1); bslmt::ThreadUtil::join(handle2); if (verbose) { // 'if' to allow command-line activation BALL_LOG_FATAL << "Force publication."; } return 0; }
e_FATAL
record to be logged and thus a "Trigger-All", the records from thread 2 are not available to be published. This is of course not an error but desired behavior chosen by the programmer. The output is as follows. 27SEP2007_14:09:31.197 1615 3 WARN logging.m.cpp 29 Function 2 Message 0 27SEP2007_14:09:33.207 1615 3 WARN logging.m.cpp 29 Function 2 Message 1 27SEP2007_14:09:35.217 1615 3 WARN logging.m.cpp 29 Function 2 Message 2 27SEP2007_14:09:41.217 1615 1 FATAL logging.m.cpp 107 main Force publication. 27SEP2007_14:09:41.217 1615 1 FATAL logging.m.cpp 107 main Force publication. 27SEP2007_14:09:35.217 1615 3 WARN logging.m.cpp 29 Function 2 Message 2 27SEP2007_14:09:33.207 1615 3 WARN logging.m.cpp 29 Function 2 Message 1 27SEP2007_14:09:31.197 1615 3 WARN logging.m.cpp 29 Function 2 Message 0
ball
logging system. They might be used, for example, by a low-level infrastructure library where the trade-off of greater complexity would be justified. processData
, that is passed data in a vector<char>
and information about the user who sent the data. This example function performs no actual processing, but does log a single message at the ball::Severity::e_DEBUG
threshold level. The processData
function also adds the user information passed to this function to the thread's attribute context. We will use these attributes later, to create a logging rule that enables verbose logging only for a particular user. void processData(int uuid, int luw, int terminalNumber, const bsl::vector<char>& data) // Process the specified 'data' associated with the specified Bloomberg // 'uuid', 'luw', and 'terminalNumber'. { (void)data; // suppress "unused" warning
ball::ScopedAttribute
, which adds an attribute container with one attribute to a list of containers. This is easy and efficient if the number of attributes is small, but should not be used if there are a large number of attributes. If motivated, we could use ball::DefaultAttributeContainer
, which provides an efficient container for a large number of attributes, or even create a more efficient attribute container implementation specifically for these three attributes (uuid, luw, and terminalNumber). See ball_scopedattributes
(plural) for an example of using a different attribute container, and ball_attributecontainer
for an example of creating a custom attribute container. // We use 'ball::ScopedAttribute' here because the number of // attributes is relatively small. ball::ScopedAttribute uuidAttribute("mylibrary.uuid", uuid); ball::ScopedAttribute luwAttribute("mylibrary.luw", luw); ball::ScopedAttribute termNumAttribute("mylibrary.terminalNumber", terminalNumber);
ball::Severity::e_DEBUG
level. BALL_LOG_SET_CATEGORY("EXAMPLE.CATEGORY"); BALL_LOG_DEBUG << "An example message";
ball::ScopedAttribute
(e.g., if we were using a local ball::DefaultAttributeContainer
instead), then the container **must** be removed from the ball::AttributeContext
before it is destroyed! See ball_scopedattributes
(plural) for an example. }
ball::Severity::e_TRACE
(i.e., enables verbose logging) for a particular user when calling the processData
function defined above. processData
function: This first call to processData
will not result in any logged messages because processData
logs its message at the ball::Severity::e_DEBUG
level, which is below the default configured logging threshold. int main(int argc, const char *argv[]) { ball::LoggerManagerConfiguration configuration; ball::LoggerManagerScopedGuard lmg(configuration); ball::LoggerManager& manager = ball::LoggerManager::singleton(); bsl::shared_ptr<ball::StreamObserver> observer = bsl::make_shared<ball::StreamObserver>(&bsl::cout); manager.registerObserver(observer, "default"); BALL_LOG_SET_CATEGORY("EXAMPLE.CATEGORY"); bsl::vector<char> message; BALL_LOG_ERROR << "Processing the first message."; processData(3938908, 2, 9001, message);
ball::Severity::e_TRACE
(i.e., enabling verbose logging) if the thread's context contains a "uuid" of 3938908. Note that we use the wild-card value *
for the category so that the ball::Rule
rule will apply to all categories. ball::Rule rule("*", 0, ball::Severity::e_TRACE, 0, 0); rule.addAttribute(ball::ManagedAttribute("mylibrary.uuid", 3938908)); ball::LoggerManager::singleton().addRule(rule); BALL_LOG_ERROR << "Processing the second message."; processData(3938908, 2, 9001, message);
processData
function below, passes a "uuid" of 2171395 (not 3938908) so the logging rule we defined will not apply and no message will be logged. BALL_LOG_ERROR << "Processing the third message."; processData(2171395, 2, 9001, message); }
ERROR example.cpp:105 EXAMPLE.CATEGORY Processing the first message. ERROR example.cpp:117 EXAMPLE.CATEGORY Processing the second message. DEBUG example.cpp:35 EXAMPLE.CATEGORY An example message ERROR example.cpp:129 EXAMPLE.CATEGORY Processing the third message.
ball::ScopedAttribute
object (see {Key Example 1: Write to a Log} and Key Example 2: Initialization). However, advanced users can customize the collection of attributes, either by registering an attribute-collector callback with the ball::LoggerManager
, or by creating their own ball::AttributeCollector
implementation, or both. These customizations can be used to implement alternative behavior, or provide faster implementation (e.g., by taking advantage of compile-time knowledge of the attributes being recorded). ball::AttributeContainer (rather than using 'ballScopedAttribute
). ball::AttributeContainer
implementation. Although the performance gains from doing so are typically insignificant, the use of a context-specific attribute container object allows us to take advantage of compile-time knowledge of the attributes being collected and make small improvements in the overhead required, which may be important for performance critical systems. // serviceattributes.h class ServiceAttributes : public ball::AttributeContainer { // Provide a concrete implementation of the 'ball::AttributeContainer' // protocol that holds the 'uuid', 'luw', and 'firmId' associated with a // request to the example service. int d_uuid; int d_luw; int d_firmId; // ... public: // CREATORS ServiceAttributes(int uuid, int luw, int firmId); // Create a service-attributes object with the specified 'uuid', // 'luw', and 'firmId'. virtual ~ServiceAttributes(); // ACCESSORS virtual bool hasValue(const ball::Attribute& value) const; virtual void visitAttributes( const bsl::function<void(const ball::Attribute&)>& visitor) const; virtual bsl::ostream& print(bsl::ostream& stream, int level = 0, int spacesPerLevel = 4) const; // Format this object to the specified output 'stream'. }; // CREATORS inline ServiceAttributes::ServiceAttributes(int uuid, int luw, int firmId) : d_uuid(uuid) , d_luw(luw) , d_firmId(firmId) { } // serviceattributes.cpp // CREATORS ServiceAttributes::~ServiceAttributes() { } // ACCESSORS bool ServiceAttributes::hasValue(const ball::Attribute& value) const { return ball::Attribute("mylibrary.uuid", d_uuid) == value || ball::Attribute("mylibrary.luw", d_luw) == value || ball::Attribute("mylibrary.firmId", d_firmId) == value; } void ServiceAttributes::visitAttributes( const bsl::function<void(const ball::Attribute&)>& visitor) const { visitor(ball::Attribute("mylibrary.uuid", d_uuid)); visitor(ball::Attribute("mylibrary.luw", d_luw)); visitor(ball::Attribute("mylibrary.firmId", d_firmId)); } bsl::ostream& ServiceAttributes::print(bsl::ostream& stream, int level, int spacesPerLevel) const { bslim::Printer printer(&stream, level, spacesPerLevel); printer.start(); printer.printAttribute("uuid", d_uuid); printer.printAttribute("luw", d_luw); printer.printAttribute("firmId", d_firmId); printer.end(); return stream; }
ServiceAttributes
container from the current logging attribute context: class ServiceAttributesGuard { // DATA ServiceAttributes d_attributes; // attributes const ball::AttributeContext::iterator d_it; // reference to attribute container public: ServiceAttributesGuard(int uuid, int luw, int firmId) : d_attributes(uuid, luw, firmId) , d_it( ball::AttributeContext::getContext()->addAttributes(&d_attributes)) { } ~ServiceAttributesGuard() { ball::AttributeContext::getContext()->removeAttributes(d_it); } };
ServiceAttributesGuard
in a critical infrastructure function: int processData(int uuid, int luw, int firmId, const char *data) { BALL_LOG_SET_CATEGORY("MYLIBRARY.MYSUBSYSTEM"); ServiceAttributesGuard attributes(uuid, luw, firmId); BALL_LOG_TRACE << "Processing data: " << data; int rc = 0; // ... if (0 != rc) { BALL_LOG_WARN << "Error processing data: " << data; } return rc; }
processData
is called, attributes for uuid
, luw
, and firmId
will be associated with each log message emitted during that function call. void loadHostnameAttribute( const ball::LoggerManager::AttributeVisitor& visitor) { char hostname[256]; if (0!= gethostname(hostname, 256)) { bsl::strcpy(hostname, "failed.to.get.hostname"); } visitor(ball::Attribute("mylibrary.hostname", hostname)); }
loadHostnameAttribute
with the logger manager: int configureLibrary() { ball::LoggerManager::singleton().registerAttributeCollector( &loadHostnameAttribute, "mylibrary.hostnamecollector"); // ... }
ball_log
that are most commonly used. printf
-style macros are used: BALL_LOG_SET_CATEGORY(CATEGORY)
: CATEGORY
(assumed to be of type convertible to const char *
). Note that this macro must be used at block scope and can be used at most once in any given block (or else a compiler diagnostic will result). Also note that this macro invokes the ball::Log::setCategory
method to retrieve the address of an appropriate category structure for its scope. (See the function- level documentation of ball::Log::setCategory
for more information.) Also note that the category is set only on the first invocation of this macro in a code block; subsequent invocations will use a cached address of the category.BALL_LOG_SET_DYNAMIC_CATEGORY(CATEGORY)
: CATEGORY
. This macro is identical to BALL_LOG_SET_CATEGORY
in scoping, parameter, and use of the ball::Log::setCategory
method. However, the address returned from ball::Log::setCategory
is not cached for subsequent calls. Use this macro to create categories that depend on run-time values (e.g., UUID).BALL_LOG_TRACE
, BALL_LOG_DEBUG
, BALL_LOG_INFO
, BALL_LOG_WARN
, BALL_LOG_ERROR
, and BALL_LOG_FATAL
, have the following usage pattern: BALL_LOG_TRACE << X << Y ...; BALL_LOG_DEBUG << X << Y ...; BALL_LOG_INFO << X << Y ...; BALL_LOG_WARN << X << Y ...; BALL_LOG_ERROR << X << Y ...; BALL_LOG_FATAL << X << Y ...; where X, Y, ... represents any sequence of values for which 'operator<<' is defined. The resulting formatted message string is logged with the severity indicated by the name of the initial macro (e.g., 'BALL_LOG_TRACE' logs with severity 'ball::Severity::e_TRACE'). Note that the formatted string includes the category and filename as established by the 'BALL_LOG_SET_CATEGORY' (or 'BALL_LOG_SET_DYNAMIC_CATEGORY') and '__FILE__' macros, respectively.
printf
-style format specifications: BALL_LOGVA_TRACE(MSG, ...); BALL_LOGVA_DEBUG(MSG, ...); BALL_LOGVA_INFO( MSG, ...); BALL_LOGVA_WARN( MSG, ...); BALL_LOGVA_ERROR(MSG, ...); BALL_LOGVA_FATAL(MSG, ...); Format the specified '...' optional arguments, if any, according to the 'printf'-style format specification in the specified 'MSG' (assumed to be of type convertible to 'const char *') and log the resulting formatted message string with the severity indicated by the name of the macro (e.g., 'BALL_LOGVA_INFO' logs with severity 'ball::Severity::e_INFO'). The behavior is undefined unless the number and types of optional arguments are compatible with the format specification in 'MSG'. Note that each use of these macros must be terminated by a ';'.