Quick Links:

bal | bbl | bdl | bsl

Namespaces

Component ball_log
[Package ball]

Provide macros and utility functions to facilitate logging. More...

Namespaces

namespace  ball

Detailed Description

Outline
Purpose:
Provide macros and utility functions to facilitate logging.
Classes:
ball::Log namespace for logging utilities (for internal use only)
Macros:
BALL_LOG_SET_CATEGORY(C) set a category for logging to the specified C
BALL_LOG_SET_DYNAMIC_CATEGORY(C) set a run-time-dependent category
BALL_LOG_SET_CLASS_CATEGORY(C) set a category in the scope of a class
BALL_LOG_SET_NAMESPACE_CATEGORY(C) set a category in a namespace
BALL_LOG_SET_CATEGORY_HIERARCHICALLY(C) set a category hierarchically
BALL_LOG_SET_DYNAMIC_CATEGORY_HIERARCHICALLY(C) set a run-time category
BALL_LOG_SET_CLASS_CATEGORY_HIERARCHICALLY(C) set a class category
BALL_LOG_SET_NAMESPACE_CATEGORY_HIERARCHICALLY(C) set a namespace category
BALL_LOG_TRACE produce a log record with the e_TRACE severity level
BALL_LOG_DEBUG produce a log record with the e_DEBUG severity level
BALL_LOG_INFO produce a log record with the e_INFO severity level
BALL_LOG_WARN produce a log record with the e_WARN severity level
BALL_LOG_ERROR produce a log record with the e_ERROR severity level
BALL_LOG_FATAL produce a log record with the e_FATAL severity level
BALL_LOG_STREAM(SEV) produce a log record with the specified SEV level
BALL_LOGCB_TRACE(CB) produce a e_TRACE log record using callback CB
BALL_LOGCB_DEBUG(CB) produce a e_DEBUG log record using callback CB
BALL_LOGCB_INFO(CB) produce an e_INFO log record using callback CB
BALL_LOGCB_WARN(CB) produce a e_WARN log record using callback CB
BALL_LOGCB_ERROR(CB) produce an e_ERROR log record using callback CB
BALL_LOGCB_FATAL(CB) produce a e_FATAL log record using callback CB
BALL_LOGCB_STREAM(SEV, CB) produce a SEV log record using callback
BALL_LOGVA_TRACE(MSG, ...) produce e_TRACE record using printf format
BALL_LOGVA_DEBUG(MSG, ...) produce e_DEBUG record using printf format
BALL_LOGVA_INFO( MSG, ...) produce e_INFO record using printf format
BALL_LOGVA_WARN( MSG, ...) produce e_WARN record using printf format
BALL_LOGVA_ERROR(MSG, ...) produce e_ERROR record using printf format
BALL_LOGVA_FATAL(MSG, ...) produce e_FATAL record using printf format
BALL_LOGVA(SEV, MSG, ...) produce a SEV log record using printf format
BALL_LOG_TRACE_BLOCK set code block with e_TRACE condition of execution
BALL_LOG_DEBUG_BLOCK set code block with e_DEBUG condition of execution
BALL_LOG_INFO_BLOCK set a code block with e_INFO condition of execution
BALL_LOG_WARN_BLOCK set a code block with e_WARN condition of execution
BALL_LOG_ERROR_BLOCK set code block with e_ERROR condition of execution
BALL_LOG_FATAL_BLOCK set code block with e_FATAL condition of execution
BALL_LOG_STREAM_BLOCK(SEV) set a code block with SEV condition
BALL_LOGCB_TRACE_BLOCK(CB) set e_TRACE block with the specified callback
BALL_LOGCB_DEBUG_BLOCK(CB) set e_DEBUG block with the specified callback
BALL_LOGCB_INFO_BLOCK(CB) set e_INFO block with the specified callback
BALL_LOGCB_WARN_BLOCK(CB) set e_WARN block with the specified callback
BALL_LOGCB_ERROR_BLOCK(CB) set an e_ERROR block with the specified CB
BALL_LOGCB_FATAL_BLOCK(CB) set e_FATAL block with the specified callback
BALL_LOGCB_STREAM_BLOCK(SEV, CB) set a SEV block with the specified CB
BALL_LOG_IS_ENABLED(SEV) indicate if SEV is severe enough for logging
See also:
Component ball_loggermanager, Component ball_category, Component ball_severity, Component ball_record
Description:
This component provides preprocessor macros and utility functions to facilitate use of the ball_loggermanager component. In particular, the macros defined herein greatly simplify the mechanics of generating log records. The utility functions provided in ball::Log are intended only for use by the macros and should not be called directly.
The macros defined herein pertain to the logger manager singleton only, and not to any non-singleton instances of ball::LoggerManager. In particular, the macros do not have any effect unless the logger manager singleton is initialized. Note that the flow of control may pass through a use of any of the macros before the logger manager singleton has been initialized or after it has been destroyed; however, control should not pass through any macro use during logger manager singleton initialization or destruction. See ball_loggermanager|Logger Manager Singleton Initialization for details on the recommended procedure for initializing the singleton.
Thread Safety:
All macros defined in this component are thread-safe, and can be invoked concurrently by multiple threads.
Macro Reference:
This section documents the preprocessor macros defined in this component.
The first three macros described below are used to define categories, at either block scope or class scope, to which records are logged by the C++ stream-based and printf-style logging macros (described further below).
Macros for Defining Categories at Block Scope:
The following two macros are used to establish logging categories that have block scope:
BALL_LOG_SET_CATEGORY(CATEGORY):
Set a category for logging to the specified CATEGORY (assumed to be of type convertible to const char *). On the first invocation of this macro in a code block, the ball::Log::setCategory method is invoked to retrieve the address of an appropriate category structure for its scope; subsequent invocations will use a cached address of the category. (See the function-level documentation of ball::Log::setCategory for more information.) 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).
BALL_LOG_SET_DYNAMIC_CATEGORY(CATEGORY):
Set, on EACH invocation, a category for logging to the specified CATEGORY (assumed to be of type convertible to const char *). On EVERY invocation of this macro in a code block, the ball::Log::setCategory method is invoked to retrieve the address of an appropriate category structure for its scope; the address returned from ball::Log::setCategory is NOT cached for subsequent calls. (See the function-level documentation of ball::Log::setCategory for more information.) 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). Note that this macro should be used to create categories that depend on RUN-TIME values only (e.g., LUW or UUID).
There can be at most one use of either BALL_LOG_SET_CATEGORY or BALL_LOG_SET_DYNAMIC_CATEGORY in any given block (or else a compiler diagnostic will result). Note that categories that are set using these macros, including dynamic categories, are not destroyed until the logger manager singleton is destroyed.
Macro for Defining Categories at Class Scope:
The following macro is used to establish logging categories that have class scope:
BALL_LOG_SET_CLASS_CATEGORY(CATEGORY):
Set a category for logging to the specified CATEGORY (assumed to be of type convertible to const char *) in the scope of the class within which this macro is used. Similar to BALL_LOG_SET_CATEGORY, the category is set once only, the first time that it is accessed (i.e., it is not a dynamic category). This macro must be used, at most once, within the definition of a class or class template (or else a compiler diagnostic will result). Note that use of this macro may occur in either a public, private, or protected section of a class's interface, although private should be preferred.
Note that similar to block-scope categories (see BALL_LOG_SET_CATEGORY and BALL_LOG_SET_DYNAMIC_CATEGORY), class-scope categories are not destroyed until the logger manager singleton is destroyed.
Macro for Defining Categories at Namespace or Global Scope:
The following macro is used to establish logging categories that have namespace or global scope:
BALL_LOG_SET_NAMESPACE_CATEGORY(CATEGORY):
Set a category for logging to the specified CATEGORY (assumed to be of type convertible to const char *) in the namespace (or global) scope within which this macro is used. Similar to BALL_LOG_SET_CATEGORY, the category is set once only, the first time that it is accessed (i.e., it is not a dynamic category). This macro may be used, in .cpp files only, at most once in any given namespace and at most once at global scope (or else a compiler diagnostic will result). Do NOT use this macro in .h files.
Note that similar to block-scope categories (see BALL_LOG_SET_CATEGORY and BALL_LOG_SET_DYNAMIC_CATEGORY), namespace-scope categories are not destroyed until the logger manager singleton is destroyed.
Macros for Defining Hierarchical Categories:
The following macros are used to establish logging categories having thresholds that are given by the existing non-default category whose name matches the longest prefix of the new category's name (i.e., the threshold levels of new categories are determined "hierarchically" from existing categories rather than from the default threshold levels of the logger manager singleton).
BALL_LOG_SET_CATEGORY_HIERARCHICALLY(CATEGORY) Set a category for logging to the specified CATEGORY (assumed to be of type convertible to const char *). On the first invocation of this macro in a code block, the ball::Log::setCategoryHierarchically method is invoked to retrieve the address of an appropriate category structure for its scope; subsequent invocations will use a cached address of the category. (See the function-level documentation of ball::Log::setCategoryHierarchically for more information.) 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).
BALL_LOG_SET_DYNAMIC_CATEGORY_HIERARCHICALLY(CATEGORY):
Set, on EACH invocation, a category for logging to the specified CATEGORY (assumed to be of type convertible to const char *). On EVERY invocation of this macro in a code block, the ball::Log::setCategoryHierarchically method is invoked to retrieve the address of an appropriate category structure for its scope; the address returned from ball::Log::setCategoryHierarchically is NOT cached for subsequent calls. (See the function-level documentation of ball::Log::setCategoryHierarchically for more information.) 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). Note that this macro should be used to create categories that depend on RUN-TIME values only (e.g., LUW or UUID).
BALL_LOG_SET_CLASS_CATEGORY_HIERARCHICALLY(CATEGORY) Set a category for logging to the specified CATEGORY (assumed to be of type convertible to const char *) in the scope of the class within which this macro is used. Similar to BALL_LOG_SET_CATEGORY_HIERARCHICALLY, the category is set once only, using the ball::Log::setCategoryHierarchically function, the first time that it is accessed (i.e., it is not a dynamic category). (See the function-level documentation for ball::Log::setCategoryHierarchically for more information.) This macro must be used, at most once, within the definition of a class or class template (or else a compiler diagnostic will result). Note that use of this macro may occur in either a public, private, or protected section of a class's interface, although private should be preferred.
BALL_LOG_SET_NAMESPACE_CATEGORY_HIERARCHICALLY(CATEGORY) Set a category for logging to the specified CATEGORY (assumed to be of type convertible to const char *) in the namespace (or global) scope within which this macro is used. Similar to BALL_LOG_SET_CATEGORY_HIERARCHICALLY, the category is set once only, using the ball::Log::setCategoryHierarchically function, the first time that it is accessed (i.e., it is not a dynamic category). (See the function-level documentation for ball::Log::setCategoryHierarchically for more information.) This macro may be used, in .cpp files only, at most once in any given namespace and at most once at global scope (or else a compiler diagnostic will result). Do NOT use this macro in .h files.
Macros for Logging Records:
The macros defined in this subsection are the ones that are actually used to produce log records. A use of any one of the logging macros requires that a logging category (as established by the macros defined above) be in scope at the point where the macro is used. Note that the formatted string that is generated for the message attribute of each log record includes the category that is in scope and the filename as established by the standard __FILE__ macro.
The code within any logging statement/code block must not produce any side effects because it may or may not be executed based on run-time configuration of the ball logging subsystem:
  BALL_LOG_INFO << ++i;    // (!) May or may not be incremented

  BALL_LOG_TRACE_BLOCK {
     processRequest(...);  // (!) May or may not be called
  }
A set of macros based on C++ streams, BALL_LOG_TRACE, BALL_LOG_DEBUG, BALL_LOG_INFO, BALL_LOG_WARN, BALL_LOG_ERROR, and BALL_LOG_FATAL, are the ones most commonly used for logging. They 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 macro
      (e.g., 'BALL_LOG_TRACE' logs with severity 'ball::Severity::e_TRACE').
A closely-related macro also based on C++ streams, BALL_LOG_STREAM, requires that the severity be explicitly supplied as an argument:
  BALL_LOG_STREAM(SEVERITY) << X << Y ... ;
      where X, Y, ... represents any sequence of values for which
      'operator<<' is defined.  The resulting formatted message string is
      logged with the specified 'SEVERITY'.
Another set of macros based on C++ streams, similar to BALL_LOG_TRACE, etc., allow the caller to specify a "callback" function that is passed the ball::UserFields * used to represent the user fields of a log record. BALL_LOGCB_TRACE, BALL_LOGCB_DEBUG, BALL_LOGCB_INFO, BALL_LOGCB_WARN, BALL_LOGCB_ERROR, and BALL_LOGCB_FATAL have the following usage pattern:
  BALL_LOGCB_TRACE(CALLBACK) << X << Y ... ;
  BALL_LOGCB_DEBUG(CALLBACK) << X << Y ... ;
  BALL_LOGCB_INFO(CALLBACK)  << X << Y ... ;
  BALL_LOGCB_WARN(CALLBACK)  << X << Y ... ;
  BALL_LOGCB_ERROR(CALLBACK) << X << Y ... ;
  BALL_LOGCB_FATAL(CALLBACK) << X << Y ... ;
      where X, Y, ... represents any sequence of values for which
      'operator<<' is defined and 'CALLBACK' is a callback taking a
      'ball::UserFields *' as an argument.  The resulting formatted message
      string is logged with the severity indicated by the name of the macro
      (e.g., 'BALL_LOGCB_ERROR' logs with severity
      'ball::Severity::e_ERROR').  The generated log record will contain the
      'ball::UserFields' representing user fields as populated by 'CALLBACK'.
      Note that the callback supplied to the logging macro must match the
      prototype 'void (*)(ball::UserFields *)'.
A closely-related macro also based on C++ streams, BALL_LOGCB_STREAM, requires that the severity be explicitly supplied as an argument:
  BALL_LOGCB_STREAM(SEVERITY, CALLBACK) << X << Y ... ;
      where X, Y, ... represents any sequence of values for which
      'operator<<' is defined.  The resulting formatted message string is
      logged with the specified 'SEVERITY'.  The generated log record will
      contain the 'ball::UserFields' representing user fields as populated by
      'CALLBACK'.  Note that the callback supplied to the logging macro must
      match the prototype 'void (*)(ball::UserFields *)'.
The remaining macros are based on 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 ';'.
A closely-related printf-style macro, BALL_LOGVA, requires that the severity be explicitly supplied as an argument:
  BALL_LOGVA(SEVERITY, 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 specified 'SEVERITY'.  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 this macro must be terminated by a ';'.
Macros for Logging Code Blocks:
The following macros allow the caller to start a code block that will be conditionally executed depending on the current logging threshold of the category that is in scope of those macros: A closely-related macro, BALL_LOG_STREAM_BLOCK, requires that the severity be explicitly supplied as an argument:
  BALL_LOG_STREAM_BLOCK(SEVERITY) { ... }
Another set of macros, similar to BALL_LOG_*_BLOCK, allow the caller to specify a "callback" function that is passed the ball::UserFields * used to represent the user fields of a log record:
  BALL_LOGCB_TRACE_BLOCK(CALLBACK) { ... }
  BALL_LOGCB_DEBUG_BLOCK(CALLBACK) { ... }
  BALL_LOGCB_INFO_BLOCK(CALLBACK)  { ... }
  BALL_LOGCB_WARN_BLOCK(CALLBACK)  { ... }
  BALL_LOGCB_ERROR_BLOCK(CALLBACK) { ... }
  BALL_LOGCB_FATAL_BLOCK(CALLBACK) { ... }
A closely-related macro, BALL_LOGCB_STREAM_BLOCK, requires that the severity be explicitly supplied as an argument:
  BALL_LOGCB_STREAM_BLOCK(SEVERITY, CALLBACK) { ... }
Within the logging code block a special macro, BALL_LOG_OUTPUT_STREAM, provides access to the log stream.
Utility Macros:
The following utility macro is intended for special-purpose use for fine-tuning logging behavior. A use of this macro requires that a logging category (as established by the macros defined above) be in scope at the point where the macro is used.
  BALL_LOG_IS_ENABLED(SEVERITY)
      Return 'true' if the specified 'SEVERITY' is at least as severe as any
      of the threshold levels of the logging category that is in scope, and
      'false' otherwise.
Usage:
The following code fragments illustrate the standard pattern of macro usage.
Example 1: A Basic Logging Example:
The following trivial example shows how to use the logging macros to log messages at various levels of severity.
First, 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:
  BALL_LOG_SET_CATEGORY("EXAMPLE.CATEGORY");
Then, we record messages at various levels of severity. These messages will be conditionally written to the log depending on the current logging threshold of the category (configured using the 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).";
Next, we demonstrate how to use proprietary code within logging macros. Suppose you want to add the content of a vector to the log trace:
  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 << ']';
  }
Note that the code block will be conditionally executed depending on the current logging threshold of the category. The code within the block must not produce any side effects, because its execution depends on the current logging configuration. The special macro BALL_LOG_OUTPUT_STREAM provides access to the log stream within the block.
Example 2: Setting the Current Log Category:
This example provides more detail on setting the log category in the current lexical scope. The following macro instantiation sets the category for logging to be "EQUITY.NASD" in the enclosing lexical scope:
  BALL_LOG_SET_CATEGORY("EQUITY.NASD")
Note that this macro must not be used at file scope and it can be used at most once in any given block (or else a compiler diagnostic will result). A different category may be established to override one that is in effect, but it must occur in a nested scope. In any case, a use of this macro (or of BALL_LOG_SET_DYNAMIC_CATEGORY) must be visible from within the lexical scope of every use of the log-generating macros. The following fragment of code shows how to set a different category in a nested inner block that hides a category set in an enclosing block:
  void logIt()
  {
      BALL_LOG_SET_CATEGORY("EQUITY.NASD")

      // Logging to category "EQUITY.NASD" unless overridden in a nested
      // block.
      // [*] ...

      {
          // [*] ...
          // Still logging to category "EQUITY.NASD".

          BALL_LOG_SET_CATEGORY("EQUITY.NASD.SUNW")

          // Now logging to category "EQUITY.NASD.SUNW".
          // [*] ...
      }
      // Again logging to category "EQUITY.NASD".
      // [*] ...
  }
Within logIt, a requisite logging category is visible at each of the locations marked by [*].
Example 3: C++ I/O Streams-Style Logging Macros:
The preferred logging method we use, the iostream-style macros such as BALL_LOG_INFO, allow streaming via the bsl::ostream class and the C++ stream operator <<. An advantage the C++ streaming style has over the printf style output (shown below in example 4) is that complex types often have the operator<<(ostream&, const TYPE&) function overloaded so that they are able to be easily streamed to output. We demonstrate this here using C++ streaming to stream a bdlt::Date to output:
  int         lotSize = 400;
  const char *ticker  = "SUNW";
  double      price   = 5.65;

  // Trading on a market that settles 3 days in the future.

  bdlt::Date settle = bdlt::CurrentTime::local().date() + 3;

  BALL_LOG_SET_CATEGORY("EQUITY.NASD")
We are logging with category "EQUITY.NASD", which is configured for a pass-through level of e_INFO, from here on. We output a line using the BALL_LOG_INFO macro:
  BALL_LOG_INFO << "[1] " << lotSize
                << " shares of " << ticker
                << " sold at " << price
                << " settlement date " << settle;
The above results in the following single-line message being output:
  <ts> <pid> <tid> INFO x.cpp 1161 EQUITY.NASD [1] 400 shares of SUNW sold
  at 5.65 settlement date 17FEB2017
<ts> is the timestamp, <pid> is the process id, <tid> is the thread id, x.cpp is the expansion of the __FILE__ macro that is the name of the source file containing the call, 1161 is the line number of the call, and the trailing date following "settlement date" is the value of settle.
Next, we set the category to "EQUITY.NASD.SUNW", which has been defined with ball::Administration::addCategory with its pass-through level set to e_INFO and the trigger levels set at or above e_ERROR, so a level of e_WARN also passes through:
  {
      BALL_LOG_SET_CATEGORY("EQUITY.NASD.SUNW")

      // Now logging with category "EQUITY.NASD.SUNW".

      BALL_LOG_WARN << "[2] " << lotSize
                    << " shares of " << ticker
                    << " sold at " << price
                    << " settlement date " << settle;
  }
The above results in the following message to category "EQUITY.NASD.SUNW":
  <ts> <pid> <tid> WARN x.cpp 1185 EQUITY.NASD.SUNW [2] 400 shares of SUNW
  sold at 5.65 settlement date 17FEB2017
Now, the category "EQUITY.NASD.SUNW" just went out of scope and category "EQUITY.NASD" is visible again, so it applies to the following:
  BALL_LOG_INFO << "[3] " << lotSize
                << " shares of " << ticker
                << " sold at " << price
                << " settlement date " << settle;
Finally, the above results in the following single-line message being output:
  <ts> <pid> <tid> INFO x.cpp 1198 EQUITY.NASD [3] 400 shares of SUNW sold
  at 5.65 settlement date 17FEB2017
The settlement date was appended to the message as a simple illustration of the added flexibility provided by the C++ stream-based macros. This last message was logged to category "EQUITY.NASD" at severity level ball::Severity::e_INFO.
The C++ stream-based macros, as opposed to the printf-style macros, ensure at compile-time that no run-time format mismatches will occur. Use of the stream-based logging style exclusively will likely lead to clearer, more maintainable code with fewer initial defects.
Note that all uses of the log-generating macros, both printf-style and C++ stream-based, must occur within function scope (i.e., not at file scope).
Example 4: printf-Style Output:
In the following example, we expand the logIt function (defined above) to log two messages using the BALL_LOGVA_INFO logging macro provided by this component. This variadic macro takes a format string and a variable-length series of arguments, similar to printf.
  int         lotSize = 400;
  const char *ticker  = "SUNW";
  double      price   = 5.65;

  // Trading on a market that settles 3 days in the future.

  bdlt::Date settleDate = bdlt::CurrentTime::local().date() + 3;
Because we can't easily printf complex types like bdlt::Date or bsl::string, we have to convert settleDate to a const char * ourselves. Note that all this additional work was unnecessary in Example 3 when we used the C++ iostream-style, rather than the printf-style, macros.
  bsl::ostringstream  settleOss;
  settleOss << settleDate;
  const bsl::string&  settleStr = settleOss.str();
  const char         *settle    = settleStr.c_str();
We set logging with category "EQUITY.NASD", which was configured for a pass-through severity level of e_INFO, and call BALL_LOGVA_INFO to print our trade:
  BALL_LOG_SET_CATEGORY("EQUITY.NASD")

  BALL_LOGVA_INFO("[4] %d shares of %s sold at %f settlement date %s\n",
                  lotSize, ticker, price, settle);
The above results in the following single-line message being output to category "EQUITY.NASD.SUNW" at severity level ball::Severity::e_INFO:
  <ts> <pid> <tid> INFO x.cpp 1256 EQUITY.NASD [4] 400 shares of SUNW sold
  at 5.650000 settlement date 17FEB2017
In the above, <ts> is the timestamp, <pid> is the process id, <tid> is the thread id, x.cpp is the expansion of the __FILE__ macro that is the name of the source file containing the call, and 1256 is the line number of the call.
Note that the first argument supplied to the BALL_LOGVA_INFO macro is a printf-style format specification.
Next, we set the category to "EQUITY.NASD.SUNW", which is configured for a pass-through severity level of e_INFO:
  {
      BALL_LOG_SET_CATEGORY("EQUITY.NASD.SUNW")

      // Now logging with category "EQUITY.NASD.SUNW".

      BALL_LOGVA_WARN("[5] %d shares of %s sold at %f settlement date %s\n",
                      lotSize, ticker, price, settle);
  }
The above results in the following single-line message to category "EQUITY.NASD.SUNW":
  <ts> <pid> <tid> WARN x.cpp 1281 EQUITY.NASD.SUNW [5] 400 shares of SUNW
  sold at 5.650000 settlement date 17FEB2017
Now, the category "EQUITY.NASD.SUNW" just went out of scope and category "EQUITY.NASD" is visible again, so it applies to the following:
  BALL_LOGVA_INFO("[6] %d shares of %s sold at %f settlement date %s\n",
                  lotSize, ticker, price, settle);
Finally, the above results in the following single-line message being output:
  <ts> <pid> <tid> INFO x.cpp 1294 EQUITY.NASD [6] 400 shares of SUNW sold
  at 5.650000 settlement date 17FEB2017
Example 5: Dynamic Categories:
Logging must sometimes be controlled by parameters that are not available until run-time. The BALL_LOG_SET_DYNAMIC_CATEGORY macro sets a category each time it is invoked (unlike BALL_LOG_SET_CATEGORY, which sets a category only on the first invocation and uses the cached address of the category on subsequent invocations). The category name in the following processSecurity function is a combination of a static prefix and the (dynamic) exchange argument:
  void processSecurity(const char *security, const char *exchange)
  {
      bsl::string categoryName("EXCHANGE:");
      categoryName.append(exchange);

      BALL_LOG_SET_DYNAMIC_CATEGORY(categoryName.c_str());

      BALL_LOG_TRACE << "processing: " << security;

      // ...
  }
Now logging can be controlled independently for each exchange.
WARNING: Along with the added flexibility provided by dynamic categories comes the additional overhead of computing and setting a category on each invocation. Consequently, dynamic categories should be used SPARINGLY in most applications.
Example 6: Rule-Based Logging:
The following example demonstrates the use of attributes and rules to conditionally enable logging.
We start by defining a function, 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
We add our attributes using 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);
In this simplified example we perform no actual processing, and simply log a message at the ball::Severity::e_DEBUG level.
      BALL_LOG_SET_CATEGORY("EXAMPLE.CATEGORY");

      BALL_LOG_DEBUG << "An example message";
Notice that if we were not using a "scoped" attribute container like that provided automatically by 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.
  }
Next we demonstrate how to create a logging rule that sets the pass-through logging threshold to ball::Severity::e_TRACE (i.e., enables verbose logging) for a particular user when calling the processData function defined above.
We start by creating the singleton logger manager that we configure with the stream observer and a default configuration. We then call the 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.
  ball::LoggerManagerConfiguration lmConfig;
  ball::LoggerManagerScopedGuard   lmGuard(lmConfig);

  bsl::shared_ptr<ball::StreamObserver> observer =
                          bsl::make_shared<ball::StreamObserver>(&bsl::cout);

  ball::LoggerManager::singleton().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);
Now we add a logging rule, setting the pass-through threshold to be ball::Severity::e_TRACE (i.e., enabling verbose logging) if the thread's context contains an attribute with name "mylibrary.uuid" with value 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);
The final call to the 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);
The resulting logged output for this example looks like the following:
  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.
Example 7: Logging Using a Callback:
The following example demonstrates how to register a logging callback. The C++ stream-based macros that take a callback are particularly useful to seamlessly populate the user fields of a record, thus simplifying the logging line.
We define a callback function populateUsingPoint that appends to the specified fields the attributes of the point to log:
  void populateUsingPoint(ball::UserFields *fields, const Point& point)
      // Append to the specified 'list' the name, x value, and y value of
      // the specified 'point'.
  {
      fields->appendString(point.name());
      fields->appendInt64(point.x());
      fields->appendInt64(point.y());
  }

  int validatePoint(const Point& point)
  {
      BALL_LOG_SET_CATEGORY("EXAMPLE.CATEGORY");
We now bind our callback function populateUsingPoint and the supplied point to a functor object we will pass to the logging callback. Note that the callback supplied to the logging macro must match the prototype void (*)(ball::UserFields *).
      bsl::function <void(ball::UserFields *)> callback;
      callback = bdlf::BindUtil::bind(&populateUsingPoint,
                                      bdlf::PlaceHolders::_1,
                                      point);

      int numErrors = 0;
      if (point.x() > 255) {
          BALL_LOGCB_ERROR(callback) << "X > 255";
          ++numErrors;
      }
      if (point.x() < -255) {
          BALL_LOGCB_ERROR(callback) << "X < -255";
          ++numErrors;
      }
      if (point.y() > 255) {
          BALL_LOGCB_ERROR(callback) << "Y > 255";
          ++numErrors;
      }
      if (point.y() < -255) {
          BALL_LOGCB_ERROR(callback) << "Y < -255";
          ++numErrors;
      }
      return numErrors;
  }
Example 8: Class-Scope Logging:
The following example demonstrates how to define and use logging categories that have class scope.
First, we define a class, Thing, for which we want to do class-scope logging. The use of the BALL_LOG_SET_CLASS_CATEGORY macro generates the requisite declarations within the definition of the class. We have used the macro in a private section of the interface, which should be preferred, but public (or protected) is fine, too:
  // pckg_thing.h
  namespace pckg {

  class Thing {
      // ...

    private:
      BALL_LOG_SET_CLASS_CATEGORY("PCKG.THING");

    public:
      // ...

      // MANIPULATORS
      void outOfLineMethodThatLogs(bool useClassCategory);
          // Log to the class-scope category "PCKG.THING" if the specified
          // 'useClassCategory' flag is 'true', and to the block-scope
          // category "X.Y.Z" otherwise.

      // ...

      // ACCESSORS
      void inlineMethodThatLogs() const;
          // Log a record to the class-scope category "PCKG.THING".
  };
Next, we define the inlineMethodThatLogs method inline within the header file and log to the class-scope category using BALL_LOG_TRACE. Since there is no other category in scope, the record is necessarily logged to the "PCKG.THING" category that is within the scope of the Thing class:
  // ...

  // ACCESSORS
  inline
  void Thing::inlineMethodThatLogs() const
  {
      BALL_LOG_TRACE << "log to PCKG.THING";
  }

  }  // close namespace pckg
Now, we define the outOfLineMethodThatLogs method within the .cpp file. On each invocation, this method logs one record using BALL_LOG_TRACE. It logs to the "PCKG.THING" class-scope category if useClassCategory is true, and logs to the "X.Y.Z" block-scope category otherwise:
  // pckg_thing.cpp
  namespace pckg {

  // ...

  // MANIPULATORS
  void Thing::outOfLineMethodThatLogs(bool useClassCategory)
  {
      if (useClassCategory) {
          BALL_LOG_TRACE << "log to PCKG.THING";
      }
      else {
          BALL_LOG_SET_CATEGORY("X.Y.Z");
          BALL_LOG_TRACE << "log to X.Y.Z";
      }
  }

  }  // close namespace pckg
Finally, note that both block-scope and class-scope categories can be logged to within the same block. For example, the following block within a Thing method would first log to "PCKG.THING" then log to "X.Y.Z":
      {
          BALL_LOG_TRACE << "log to PCKG.THING";

          BALL_LOG_SET_CATEGORY("X.Y.Z");

          BALL_LOG_TRACE << "log to X.Y.Z";
      }