BDE 4.14.0 Production release
Loading...
Searching...
No Matches
ball_loggermanager

Detailed Description

Outline

Purpose

Provide a manager of core logging functionality.

Classes

See also
ball_record, ball_recordattributes, ball_observer, ball_context, ball_loggermanagerdefaults, ball_loggermanagerconfiguration, ball_severity, ball_transmission, ball_log

Description

This component provides the core of the ball logging toolkit: the logger class itself, ball::Logger, that manages log record storage and publication control; the logger manager class, ball::LoggerManager, typically instantiated as a singleton, that is both a factory for loggers and a category manager; and the logger manager scoped guard, ball::LoggerManagerScopedGuard, that provides a convenient way to initialize and manage lifetime of the logger manager singleton object.

General Features and Behavior

The ball logging toolkit is very flexible. A user can log messages with very little effort, and with only a superficial understanding of logger operation, in which case the logger will exhibit its "default behavior". The user can also elect to customize many aspects of logging, such as storage and publication behavior, both at start-up and dynamically during program execution. Naturally, to exercise such control, the user must become more familiar with ball logger operation; the user can choose more convenience or more versatility, with a reasonably fine granularity.

Log records incorporate both fixed (logger-defined) and optional (user-defined) fields, affording yet more flexibility (see "Log Record Contents" below). The logger directly populates certain of the required fields, and indirectly manages population of any optional fields by invoking a client-supplied callback function that sets the optional values.

Clients obtain one or more loggers from the logger manager, although at most one logger may be "active" in any one thread; a request to log a message is directed to the active logger in that thread. Each logger both stores and publishes appropriate log records.

All loggers share a single internal broadcast observer to which log records are transmitted when they are published (see the component-level documentation of ball_observer for more information on observers).

A logger can achieve high performance through the use of an in-memory record buffer for storing the records logged by a program. Each logger is constructed with a record manager that is an instance of a concrete class derived from ball::RecordBuffer. The singleton logger manager supplies a "default" record manager to the default logger; loggers allocated by the logger manager's allocateLogger method use a record manager supplied by the client. The default log record buffer is of user-configurable static size and is circular (see the ball_circularrecordbuffer component for details), whereby continuous logging (without publication of logged records) can result in older records being overwritten by newer ones. A circular buffer provides an efficient "trace-back" strategy, wherein only log records proximate to a user-specified logging event (see below) are published. Such a circular buffer may not be appropriate for all situations; the user can change the behavior of the default logger by adjusting the logging threshold levels (see below) or can install a logger that uses a different kind of record buffer.

Logger Manager Singleton Initialization

The recommended way to initialize the logger manager singleton is to create a 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.

The ball::LoggerManagerConfiguration object is used to supply a set of user-defined "default" values and other options. However, to obtain the "default" logging behavior, it is sufficient to instantiate a default ball::LoggerManagerConfiguration object and pass that to the constructor of the scoped guard along with an observer. (See {Usage} below.)

As an alternative to using the scoped guard, the initSingleton method that takes the same arguments as the scoped guard may be used to initialize the singleton. However, in this case the 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.

Note that the logger manager singleton can be reinitialized after it has been destroyed. However, such practice should generally be restricted to test drivers and very specialized use cases. Clients should generally avoid initializing and destroying the singleton more than once in a program unless they know what they are doing.

Deprecation Notice

Direct use of any of the ball::LoggerManager or ball::LoggerManagerScopedGuard methods that take raw pointers to observers is deprecated. These methods will be eliminated in a future release.

The ball::LoggerManagerCategoryIter and ball::LoggerManagerCategoryManip classes are deprecated. Clients of ball::LoggerManager should use the visitCategories accessor (the replacement for LoggerManagerCategoryIter) or visitCategories manipulator (replacing LoggerManagerCategoryManip) instead.

Categories, Severities, and Threshold Levels

The logger supports the notions of "severity level" and "category"; every record is logged at some severity level and to some category. Categories are user-defined (except for the "default category"), and have unique names. Severity levels are integers in the range [0 .. 255], and are most typically chosen from among the enumeration in the ball_severity component, although use of the enum is optional. The severity level and the category name are each among the fixed fields of the record being logged (see "Log Record Contents" below).

From the logger's perspective, all categories are peers; there is no special significance to any sequence of characters in a category name. The user may impose a hierarchical meaning to category names, and the logger manager facilitates a certain degree of hierarchical behavior via several callback functors provided within this component (see below, and also the ball_loggerfunctorpayloads component). However, such hierarchy is not fundamental to categories, nor to the behavior described in this section. Similarly, there is no a priori significance to severity levels except that they are ordered and may be compared for inequality, although the enumerator names in the ball::Severity::Level enumeration (e.g., DEBUG, WARN, ERROR, etc.) suggest the intended "standard" meanings.

Every category has associated with it four "severity threshold levels" that may be set explicitly by the user on category creation/registration (via the addCategory method) or else will default to specific values via one of several mechanisms described below (invoked by the one-argument setCategory method). Category threshold levels may also be changed during program execution via the five-argument setCategory method.

When the user logs a record to a given category and at a given severity (via the ball::Logger logMessage method or via the logging macros – see the ball_log component), the logger manager uses the specified severity and the category's registered severity threshold levels to govern the logger's behavior; depending on the thresholds, the message may be recorded to an in-memory buffer, published to an external observer, or ignored. In addition, if thresholds are set appropriately, the entire contents of the in-memory buffer of one or more loggers may be published to external observers. Clients of the logger can use, and dynamically administer, the category threshold levels to enhance run-time performance and/or to reduce message volume while still capturing all critical log messages.

The names and exact meanings of the four severity threshold levels are as follows:

Terminology: "Factory Default" Thresholds

The logger manager supplies "default values" for category threshold levels whenever a category is created without client-supplied values. These default values can come from any one of several possible sources, depending upon options that the user has elected; the system is flexible, but leads to a bit of confusion in terminology. This section explains the meaning of "factory default" values and introduces the various "default" threshold mechanisms.

The logger manager is a "factory" for loggers; we therefore define "factory defaults" to be the default values that the ball::LoggerManager singleton is aware of at construction. Depending on the values and options in the ball::LoggerManagerConfiguration object provided to the logger manager on construction, the factory defaults may be either implementation-defined or user-defined.

In either case, the user can change the default values during logger operation via the setDefaultThresholdLevels method. These threshold levels become the "default" values for new categories, but they do not affect the "factory defaults" that subsequently can be restored via the resetDefaultThresholdLevels method.

A third mechanism, the ball::LoggerManager::DefaultThresholdLevelsCallback functor, adds even more flexibility. If this callback is installed by the user at construction, or subsequently via the setDefaultThresholdLevelsCallback method, the callback is the source of all default thresholds, and the above mechanisms are not used. The next section covers category thresholds in more detail.

Category Creation, Management, and Threshold Levels

When the logger manager singleton is created, a unique category known as the Default Category is created, and is given "factory-supplied" default threshold levels. The default values for the default category are each in the range [0 .. 255], but are otherwise unspecified. The user can also specify default values explicitly when the logger manager singleton is constructed. This is accomplished by constructing a ball::LoggerManagerDefaults object, setting the desired values, and then setting that object as an attribute of the ball::LoggerManagerConfiguration argument to the ball::LoggerManagerScopedGuard constructor.

The default category is issued to the user via the return value of the setCategory(const char *categoryName) method whenever a new category cannot be created due to a capacity limitation on the category registry maintained by the logger manager. The method's normal behavior is to return the category having categoryName.

Categories that are added to the registry during logging through calls to the setCategory(const char *) method are given threshold levels by one of two means. The "default" mechanism (a slightly overloaded term in ball) is to use the same default thresholds as described above for the default category. The alternative is to specify a ball::LoggerManager::DefaultThresholdLevelsCallback functor, either when the logger manager singleton is initialized or else afterwards via the setDefaultThresholdLevelsCallback method. This functor, if provided, is used by the logger manager to supply the four int threshold values; the functor may generate these values by any means that the user sees fit. See the ball_loggerfunctorpayloads component for an example payload function for the functor.

The default threshold levels can be adjusted (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).

As a final note regarding categories, a client can optionally supply to the logger manager on construction a ball::LoggerManager::CategoryNameFilterCallback functor (via the ball::LoggerManagerConfiguration object) 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. 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.

Log Record Contents

Each log record contains a set of fixed fields and a set of optional, user-definable fields and attributes. The following table lists the fixed fields in each log record (see the component-level documentation of ball_recordattributes for more information on the fixed fields of a log record):

Field Name Type Description
----------- -------------- --------------------------------------
timestamp bdlt::Datetime creation date and time
process ID int process ID of creator
thread ID int thread ID of creator
filename string file where created (i.e., '__FILE__')
line number int line number in file (i.e., '__LINE__')
category string category name
severity int severity of logged record
message string log message text
Definition bdlt_datetime.h:331

The following table lists optional fields and attributes in each log record (see the component-level documentation of ball_userfields and ball_managedattribute for more information):

Field Name Type Description
----------- -------------- -------------------------
userFields ball::UserFields [!DEPRECATED!]
attributes bsl::vector<ball::ManagedAttribute> user-managed log
attributes
Definition ball_userfields.h:136
Definition bslstl_vector.h:1025

[DEPRECATED] If a ball::LoggerManager::UserFieldsPopulatorCallback functor is supplied by the client (see ball_loggermanagerconfiguration ), thereafter, every logged record has its user-defined fields (indirectly) populated by an invocation of the UserFieldsPopulatorCallback functor.

The log record's attributes are populated by attribute collector functor(s) registered by the user.

Multi-Threaded Usage

The 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 use of the ball::LoggerManagerScopedGuard class to initialize the logger manager singleton 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) are identical, from the user's perspective. Category threshold administration is also identical in both cases.

Differences in logger usage, or, more precisely, additional options for the multi-threaded user, arise when the user wishes to allocate one or more loggers beyond the default logger that is owned by the singleton logger manager. If a user does not explicitly allocate a logger (via the logger manager instance method 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.

Multi-threaded users of logging may prefer to allocate and install one logger per thread in order to take advantage of the "trace-back" feature described above on a per-thread basis. In the event of an error condition as defined by the programmer, such a logging configuration provides a trace-back through the record buffer of the thread that caused the error, without any dilution from records from other threads. Conversely, if several threads are known to interact closely, it may be advantageous to have them share a common logger so that the trace-back log does include all relevant records.

bsls::Log Logging Redirection

The ball::LoggerManager singleton, on construction, redirects bsls::Log messages to ball. Such messages use the logging category "BSLS.LOG". Upon its destruction, the logger manager singleton redirects bsls::Log messages back to the bsls::Log message handler that was in effect prior to the creation of the singleton (see bsls_log).

Usage

This section illustrates instantiation of the logger manager singleton that is required in main, and also shows direct use of the logger and logger manager interfaces, much of which is actually not recommended. The most basic logger functionality has been wrapped in macros defined in the ball_log component. See the ball package-level documentation and the ball_log component documentation for recommended real-world usage examples.

Example 1: Initialization Case 1

Clients that perform logging must first instantiate the singleton logger manager using the ball::LoggerManagerScopedGuard class. This example shows how to create a logger manager with the most basic "default behavior". Subsequent examples will show more customized behavior.

The following snippets of code illustrate the initialization sequence (typically performed near the top of main).

First, we create a 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()
{
Definition ball_loggermanagerconfiguration.h:281
int setDefaultThresholdLevelsIfValid(int passLevel)
@ e_WARN
Definition ball_severity.h:171

Next, create a 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);
Definition ball_loggermanager.h:2073

Note that the application is now prepared to log messages using the ball logging subsystem, but until the application registers an observer, all log messages will be discarded.

Finally, we create a ball::StreamObserver object observer that will publish records to stdout and register it with the logger manager singleton. Note that observers must be registered by name; this example simply uses "default" for a name:

new(*alloc) ball::StreamObserver(&bsl::cout),
alloc);
static LoggerManager & singleton()
Definition ball_loggermanager.h:2290
int registerObserver(const bsl::shared_ptr< Observer > &observer, const bsl::string_view &observerName)
Definition ball_loggermanager.h:2348
Definition ball_streamobserver.h:192
Definition bslstl_sharedptr.h:1830
Definition bslma_allocator.h:457
static Allocator * globalAllocator(Allocator *basicAllocator=0)
Definition bslma_default.h:905

The application is now prepared to log messages using the ball logging subsystem:

// ...
return 0;
}

Note that concrete observers that can be configured after their creation (e.g., as to whether log records are published in UTC or local time) generally can have their configuration adjusted at any time, either before or after being registered with a logger manager. For an example of such an observer, see ball_asyncfileobserver .

Example 2: Initialization Case 2

In this example, we demonstrate a more elaborate initial configuration for the logger manager. In particular, we create the singleton logger manager with a configuration that has a category name filter functor, a DefaultThresholdLevelsCallback functor, and user-chosen values for the "factory default" threshold levels.

First, we define three static functions that are employed by the two functors. The toLower function implements our category name filter. It is wrapped within a functor object and maps category names to lower-case:

static
void toLower(bsl::string *buffer, const char *s)
{
assert(buffer);
assert(s);
buffer->clear();
while (*s) {
buffer->push_back(static_cast<char>(bsl::tolower(*s)));
++s;
}
buffer->push_back(0);
}
Definition bslstl_string.h:1281
void push_back(CHAR_TYPE character)
Append the specified character to this string.
Definition bslstl_string.h:5699
void clear() BSLS_KEYWORD_NOEXCEPT
Definition bslstl_string.h:5430

The following two functions provide the implementation for our DefaultThresholdLevelsCallback functor. The inheritThresholdLevels function is wrapped within a functor object; the getDefaultThresholdLevels function is a helper that does the hard work. We assume a hierarchical category naming scheme that uses . to delimit the constituents of names. For example, the three categories named "x", "x.y", and "x.y.z" are related in the sense that "x" is an ancestor of both "x.y" and "x.y.z", and "x.y" is an ancestor "x.y.z". Suppose that "x" is added to the registry first. If "x.y" is then added to the registry by calling setCategory(const char *), it would "inherit" threshold level values from "x". Similarly, when "x.y.z" is added to the registry by calling the 1-argument setCategory method, it inherits threshold level values from "x.y" (i.e., a category inherits from its nearest ancestor that exists in the registry when it is added). Note that a category named "xx.y" (for example) is not related to either of "x", "x.y", or "x.y.z":

/// Obtain appropriate threshold levels for the category having the
/// specified `categoryName` by searching the registry of the specified
/// `loggerManager`, and store the resulting values at the specified
/// `recordLevel`, `passLevel`, `triggerLevel`, and `triggerAllLevel`
/// addresses. A hierarchical category naming scheme is assumed that
/// employs the specified `delimiter` to separate the components of
/// category names. Return 0 on success, and a non-zero value
/// otherwise. The behavior is undefined unless `recordLevel`,
/// `passLevel`, `triggerLevel`, and `triggerAllLevel` are non-null, and
/// `categoryName` is null-terminated.
static
int getDefaultThresholdLevels(int *recordLevel,
int *passLevel,
int *triggerLevel,
int *triggerAllLevel,
char delimiter,
const ball::LoggerManager& loggerManager,
const char *categoryName)
{
assert(recordLevel);
assert(passLevel);
assert(triggerLevel);
assert(triggerAllLevel);
assert(categoryName);
enum { SUCCESS = 0, FAILURE = -1 };
bsl::string buffer(categoryName);
while (1) {
const ball::Category *category =
loggerManager.lookupCategory(buffer.c_str());
if (0 != category) {
*recordLevel = category->recordLevel();
*passLevel = category->passLevel();
*triggerLevel = category->triggerLevel();
*triggerAllLevel = category->triggerAllLevel();
return SUCCESS; // RETURN
}
const char *newEnd = bsl::strrchr(buffer.c_str(), delimiter);
if (0 == newEnd) {
return FAILURE; // RETURN
}
buffer.resize(newEnd - buffer.data());
}
}
/// Obtain appropriate threshold levels for the category having the
/// specified `categoryName`, and store the resulting values at the
/// specified `recordLevel`, `passLevel`, `triggerLevel`, and
/// `triggerAllLevel` addresses. The behavior is undefined unless
/// `recordLevel`, `passLevel`, `triggerLevel`, and `triggerAllLevel`
/// are non-null, and `categoryName` is null-terminated.
static
void inheritThresholdLevels(int *recordLevel,
int *passLevel,
int *triggerLevel,
int *triggerAllLevel,
const char *categoryName)
{
assert(recordLevel);
assert(passLevel);
assert(triggerLevel);
assert(triggerAllLevel);
assert(categoryName);
if (0 != getDefaultThresholdLevels(recordLevel,
passLevel,
triggerLevel,
triggerAllLevel,
'.',
manager,
categoryName)) {
*recordLevel = manager.defaultRecordThresholdLevel();
*passLevel = manager.defaultPassThresholdLevel();
*triggerLevel = manager.defaultTriggerThresholdLevel();
*triggerAllLevel = manager.defaultTriggerAllThresholdLevel();
}
}
Definition ball_category.h:184
int triggerLevel() const
Return the trigger level of this category.
Definition ball_category.h:545
int triggerAllLevel() const
Return the trigger-all level of this category.
Definition ball_category.h:552
int recordLevel() const
Return the record level of this category.
Definition ball_category.h:531
int passLevel() const
Return the pass level of this category.
Definition ball_category.h:538
Definition ball_loggermanager.h:1257
int defaultPassThresholdLevel() const
Return the default pass threshold level of this logger manager.
Definition ball_loggermanager.h:2440
int defaultTriggerThresholdLevel() const
Return the default trigger threshold level of this logger manager.
Definition ball_loggermanager.h:2458
int defaultTriggerAllThresholdLevel() const
Definition ball_loggermanager.h:2452
int defaultRecordThresholdLevel() const
Return the default record threshold level of this logger manager.
Definition ball_loggermanager.h:2446
Category * lookupCategory(const char *categoryName)
const CHAR_TYPE * c_str() const BSLS_KEYWORD_NOEXCEPT
Definition bslstl_string.h:6705
CHAR_TYPE * data() BSLS_KEYWORD_NOEXCEPT
Definition bslstl_string.h:6477
void resize(size_type newLength, CHAR_TYPE character)
Definition bslstl_string.h:5364

Then, we create the callback functors that will be supplied to the logger manager singleton initialization (as in "Example 1" above, we assume that the initialization sequence occurs somewhere near the top of main):

// myApp2.cpp
int main() {
// ...
thresholdsCallback(&inheritThresholdLevels);

Next, we define four values for our custom "factory default" thresholds. These values will be stored within the logger manager and will be available to all users whenever the "factory defaults" are needed, for the life of the logger manager. In this example, however, we will also be installing the thresholdsCallback defined above, so unless that functor is un-installed (by a call to setDefaultThresholdLevelsCallback), these four "factory defaults" will have no practical effect, since the callback mechanism "steps in front of" the default values:

int recordLevel = 125;
int passLevel = 100;
int triggerLevel = 75;
int triggerAllLevel = 50;

Then, we can configure a ball::LoggerManagerDefaults object, defaults, with these four threshold values. defaults can then be used to configure the ball::LoggerManagerConfiguration object that will be passed to the ball::LoggerManagerScopedGuard constructor (below):

defaults.setDefaultThresholdLevelsIfValid(recordLevel,
passLevel,
triggerLevel,
triggerAllLevel);
Definition ball_loggermanagerdefaults.h:204
int setDefaultThresholdLevelsIfValid(int passLevel)

Next, we create and set the ball::LoggerManagerConfiguration object, configuration, that will describe our desired configuration:

configuration.setDefaultValues(defaults);
configuration.setCategoryNameFilterCallback(nameFilter);
configuration.setDefaultThresholdLevelsCallback(thresholdsCallback);
void setDefaultThresholdLevelsCallback(const DefaultThresholdLevelsCallback &thresholdsCb)
void setCategoryNameFilterCallback(const CategoryNameFilterCallback &nameFilter)
void setDefaultValues(const LoggerManagerDefaults &defaults)

Then, we instantiate the singleton logger manager, passing in the configuration that we have just created:

ball::LoggerManagerScopedGuard guard(configuration);

Note that the application is now prepared to log messages using the ball logging subsystem, but until the application registers an observer, all log messages will be discarded.

Now, we will demonstrate the functors and client-supplied default threshold overrides.

First, we obtain a reference to the singleton logger manager:

Then, we obtain a reference to the Default Category and assert that its threshold levels match the client-supplied values that override the "factory-supplied" default values:

const ball::Category& defaultCategory = manager.defaultCategory();
assert(125 == defaultCategory.recordLevel());
assert(100 == defaultCategory.passLevel());
assert( 75 == defaultCategory.triggerLevel());
assert( 50 == defaultCategory.triggerAllLevel());
Category & defaultCategory()
Definition ball_loggermanager.h:2299

Next, we add a category named "BloombergLP" (by calling addCategory). Note that threshold levels supplied with the category override all defaults (including thresholds set by the supplied callback). Also note that the logger manager invokes the supplied category name filter to map the category name to lower-case before the new category is added to the category registry. The name filter is also invoked by lookupCategory whenever a category is searched for (i.e., by name) in the registry:

const ball::Category *blpCategory =
manager.addCategory("BloombergLP", 128, 96, 64, 32);
assert(blpCategory == manager.lookupCategory("BLOOMBERGLP"));
assert( 0 == bsl::strcmp("bloomberglp", blpCategory->categoryName()));
assert(128 == blpCategory->recordLevel());
assert( 96 == blpCategory->passLevel());
assert( 64 == blpCategory->triggerLevel());
assert( 32 == blpCategory->triggerAllLevel());
const char * categoryName() const
Return the name of this category.
Definition ball_category.h:513
Category * addCategory(const char *categoryName, int recordLevel, int passLevel, int triggerLevel, int triggerAllLevel)

Then, we add a second category named "BloombergLP.bal.ball" (by calling setCategory) and assert that the threshold levels are "inherited" from category "BloombergLP":

const ball::Category *ballCategory =
manager.setCategory("BLOOMbergLP.bal.ball");
assert(ballCategory == manager.lookupCategory("bloomberglp.bal.ball"));
assert( 0 == bsl::strcmp("bloomberglp.bal.ball",
ballCategory->categoryName()));
assert(128 == ballCategory->recordLevel());
assert( 96 == ballCategory->passLevel());
assert( 64 == ballCategory->triggerLevel());
assert( 32 == ballCategory->triggerAllLevel());
const Category * setCategory(const char *categoryName)
Definition ball_loggermanager.h:2305

Now, we add a third category named "Other.equities", again by calling setCategory. This category has no ancestor currently in the registry, so its threshold levels match those of the Default Category:

const ball::Category *equitiesCategory =
manager.setCategory("Other.equities");
assert(equitiesCategory == manager.lookupCategory("OTHER.EQUITIES"));
assert( 0 == bsl::strcmp("other.equities",
equitiesCategory->categoryName()));
assert(125 == equitiesCategory->recordLevel());
assert(100 == equitiesCategory->passLevel());
assert( 75 == equitiesCategory->triggerLevel());
assert( 50 == equitiesCategory->triggerAllLevel());

Finally, we create a ball::StreamObserver object observer that will publish records to stdout and register it with the logger manager singleton. Note that observers must be registered by name; this example simply uses "default" for a name:

new(*alloc) ball::StreamObserver(&bsl::cout),
alloc);
manager.registerObserver(observer, "default");
// ...
return 0;
}

Example 3: Efficient Logging of ostream-able Objects

The following example demonstrates how instances of a class supporting streaming to bsl::ostream (via overloaded operator<<) can be logged. It also demonstrates how to use the logMessage method to log messages to a logger. Suppose we want to efficiently log instances of the following class:

/// This (incomplete) class is a simple aggregate of a "heading" and
/// "contents" pertaining to that heading. It serves to illustrate how
/// to log the string representation of an object.
class Information {
bsl::string d_heading;
bsl::string d_contents;
public:
Information(const char *heading, const char *contents);
~Information();
const bsl::string& heading() const;
const bsl::string& contents() const;
};

In addition, we define the following free operator for streaming instances of Information to an bsl::ostream:

bsl::ostream& operator<<(bsl::ostream& stream,
const Information& information)
{
stream << information.heading();
stream << ": ";
stream << information.contents() << bsl::endl;
return stream;
}
bsl::ostream & operator<<(bsl::ostream &stream, const bdlat_AttributeInfo &attributeInfo)

The following function logs an Information object to the specified logger:

void logInformation(ball::Logger *logger,
const Information& information,
const ball::Category& category,
const char *fileName,
int lineNumber)
{
Definition ball_loggermanager.h:993
Level
Definition ball_severity.h:167

First, obtain a record that has its fileName and lineNumber attributes set:

ball::Record *record = logger->getRecord(fileName, lineNumber);
Record * getRecord(const char *fileName, int lineNumber)
Definition ball_record.h:178

Then, we get a non-const reference to the fixed fields of record:

ball::RecordAttributes& attributes = record->fixedFields();
Definition ball_recordattributes.h:274
RecordAttributes & fixedFields()
Return the modifiable fixed fields of this log record.
Definition ball_record.h:396

Next, we create a bsl::ostream to which the string representation information can be output. Note that stream is supplied with the stream buffer of attributes:

bsl::ostream stream(&attributes.messageStreamBuf());
bdlsb::MemOutStreamBuf & messageStreamBuf()
Definition ball_recordattributes.h:532

Then, we stream information into our output stream. This will set the message attribute of record to the streamed data:

stream << information;

Finally, we log record using logger:

logger->logMessage(category, severity, record);
}

Notice that we did not need to allocate a scratch buffer to stream the object contents into. That would have required an extra copy and the cost of allocation and deallocation, and thus would have been more inefficient.

Example 4: Logging using a ball::Logger

This example demonstrates using a ball::Logger directly to log messages. In practice, clients are encouraged to use the logging macros (see {ball_log}. The following example assumes logging has been correctly initialized (see prior examples).

The following simple factorial function takes and returns values of type int. Note that this function has a very limited range of input, namely integers in the range [0 .. 13]. This limited range serves to illustrate a usage pattern of the logger, namely to log "warnings" whenever a key function is given bad input.

For this example, it is sufficient to use the severity levels defined in the ball::Severity::Level enumeration:

enum Level {
OFF = 0, // disable generation of corresponding message
FATAL = 32, // a condition that will (likely) cause a *crash*
ERROR = 64, // a condition that *will* cause incorrect behavior
WARN = 96, // a *potentially* problematic condition
INFO = 128, // data about the running process
DEBUG = 160, // information useful while debugging
TRACE = 192 // execution trace data
};

Note that the intervals left between enumerator values allow applications to define additional values in case there is a desire to log with more finely-graduated levels of severity. We will not need that granularity here; ball::Severity::e_WARN is appropriate to log a warning message if the input argument to our factorial function is not in this range of values.

We will register a unique category for this function, so that logged messages from our function will be identified in the published output. Also, with a unique category name, the logging behavior of this function can be administered by resetting the various threshold levels for the category. In this example, we will accept the default thresholds.

The setCategory method accepts a name and returns the address of a ball::Category with that name or, in some circumstances, the address of the Default Category (see the function-level documentation of setCategory for details). The address returned by setCategory is stored in a function-static pointer variable (i.e., it is fetched only once upon first use). In this example, we assume that we are writing a function for Equities Graphics that will live in that group's Math library. The dot "delimiters" (.) have no particular significance to the logger, but may be used by the administration methods to "induce" a hierarchical behavior on our category, should that be useful. See, e.g., the callback functor ball::LoggerManager::DefaultThresholdLevelsCallback and its documentation, and Usage Example 2 above for information on how to use category names to customize logger behavior:

/// Return the factorial of the specified value `n` if the factorial
/// can be represented as an `int`, and a negative value otherwise.
int factorial(int n)
{
static const ball::Category *factorialCategory =
"equities.graphics.math.factorial",
@ e_TRACE
Definition ball_severity.h:174
@ e_ERROR
Definition ball_severity.h:170
@ e_FATAL
Definition ball_severity.h:169
@ e_INFO
Definition ball_severity.h:172

We must also obtain a reference to a logger by calling the logger manager getLogger method. Note that this logger may not safely be cached as a function static variable since our function may be called in different threads having different loggers. Even in a single-threaded program, the owner of main is free to install new loggers at any point, so a statically-cached logger would be a problem:

Now we validate the input value n. If n is either negative or too large, we will log a warning message (at severity level ball::Severity::e_WARN) and return a negative value. Note that calls to logMessage have no run-time overhead (beyond the execution of a simple if test) unless ball::Severity::e_WARN is at least as severe as one of the threshold levels of factorialCategory:

if (0 > n) {
logger.logMessage(*factorialCategory,
__FILE__,
__LINE__,
"Attempt to take factorial of negative value.");
return n; // RETURN
}
enum { MAX_ARGUMENT = 13 }; // maximum value accepted by 'factorial'
if (MAX_ARGUMENT < n) {
logger.logMessage(*factorialCategory,
__FILE__,
__LINE__,
"Result too large for 'int'.");
return -n; // RETURN
}

The remaining code proceeds mostly as expected, but adds one last message that tracks control flow when ball::Severity::e_TRACE is at least as severe as one of the threshold levels of factorialCategory (e.g., as might be the case during debugging):

int product = 1;
while (1 < n) {
product *= n;
--n;
}
logger.logMessage(*factorialCategory,
__FILE__,
__LINE__,
"Exiting 'factorial' successfully.");
return product;
}