// ball_loggermanager.h                                               -*-C++-*-
#ifndef INCLUDED_BALL_LOGGERMANAGER
#define INCLUDED_BALL_LOGGERMANAGER

#include <bsls_ident.h>
BSLS_IDENT("$Id: $")

//@PURPOSE: Provide a manager of core logging functionality.
//
//@CLASSES:
//  ball::Logger: log record store and publication manager
//  ball::LoggerManager: logger factory and category administrator
//  ball::LoggerManagerScopedGuard: scoped guard for `LoggerManager` singleton
//
//@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:
//
// * **Record**
//  > If the severity level of the record is at least as severe as the Record
//  > threshold level of the associated category, then the record will be
//  > stored by the logger in its log record buffer (i.e., it will be
//  > recorded).
//
// * **Pass**
//  > If the severity of the record is at least as severe as the Pass
//  > threshold level of the associated category, then the record will be
//  > immediately published by the logger (i.e., it will be transmitted to
//  > the logger's downstream recipient -- the observer).
//
// * **Trigger**
//  > If the severity of the record is at least as severe as the Trigger
//  > threshold level of the associated category, then the record will cause
//  > immediate publication of that record and any records in the logger's
//  > log record buffer (i.e., this record will trigger a general log record
//  > dump).
//
// * **Trigger-All**
//  > If the severity of the record is at least as severe as the Trigger-All
//  > threshold level of the associated category, then the record will cause
//  > immediate publication of that record and all other log records stored
//  > by *all* active loggers.
//
// Note that more than one of the above actions can apply to a given log
// record, since the four threshold levels are independent of one another.
// Note also that *all* of these actions are governed by the threshold levels
// of the record being logged, and not by the threshold levels of any stored
// records that are published as a result of a Trigger or Trigger-All event.
//
///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
// ```
//
// 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
// ```
//
// [**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()
// {
//     ball::LoggerManagerConfiguration configuration;
//     configuration.setDefaultThresholdLevelsIfValid(ball::Severity::e_WARN);
// ```
// 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);
// ```
// 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:
// ```
//     bslma::Allocator *alloc = bslma::Default::globalAllocator(0);
//
//     bsl::shared_ptr<ball::StreamObserver> observer(
//                               new(*alloc) ball::StreamObserver(&bsl::cout),
//                               alloc);
//     ball::LoggerManager::singleton().registerObserver(observer, "default");
// ```
// 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);
// }
// ```
// 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);
//
//     const ball::LoggerManager& manager = ball::LoggerManager::singleton();
//     if (0 != getDefaultThresholdLevels(recordLevel,
//                                        passLevel,
//                                        triggerLevel,
//                                        triggerAllLevel,
//                                        '.',
//                                        manager,
//                                        categoryName)) {
//         *recordLevel     = manager.defaultRecordThresholdLevel();
//         *passLevel       = manager.defaultPassThresholdLevel();
//         *triggerLevel    = manager.defaultTriggerThresholdLevel();
//         *triggerAllLevel = manager.defaultTriggerAllThresholdLevel();
//     }
// }
// ```
// 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() {
//     // ...
//
//     ball::LoggerManager::CategoryNameFilterCallback nameFilter(&toLower);
//
//     ball::LoggerManager::DefaultThresholdLevelsCallback
//                                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):
// ```
//     ball::LoggerManagerDefaults defaults;
//     defaults.setDefaultThresholdLevelsIfValid(recordLevel,
//                                               passLevel,
//                                               triggerLevel,
//                                               triggerAllLevel);
// ```
// Next, we create and set the `ball::LoggerManagerConfiguration` object,
// `configuration`, that will describe our desired configuration:
// ```
//     ball::LoggerManagerConfiguration configuration;
//     configuration.setDefaultValues(defaults);
//     configuration.setCategoryNameFilterCallback(nameFilter);
//     configuration.setDefaultThresholdLevelsCallback(thresholdsCallback);
// ```
// 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:
// ```
//     ball::LoggerManager& manager = ball::LoggerManager::singleton();
// ```
// 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());
// ```
// 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());
// ```
// 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());
// ```
// 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:
// ```
//     bslma::Allocator *alloc =  bslma::Default::globalAllocator(0);
//
//     bsl::shared_ptr<ball::StreamObserver> observer(
//                               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;
// }
// ```
// The following function logs an `Information` object to the specified
// `logger`:
// ```
// void logInformation(ball::Logger          *logger,
//                     const Information&     information,
//                     ball::Severity::Level  severity,
//                     const ball::Category&  category,
//                     const char            *fileName,
//                     int                    lineNumber)
// {
// ```
// First, obtain a record that has its `fileName` and `lineNumber` attributes
// set:
// ```
//     ball::Record *record = logger->getRecord(fileName, lineNumber);
// ```
// Then, we get a non-`const` reference to the fixed fields of `record`:
// ```
//     ball::RecordAttributes& attributes = record->fixedFields();
// ```
// 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());
// ```
// 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 {
//     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
// };
// ```
// 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 =
//         ball::LoggerManager::singleton().setCategory(
//                                         "equities.graphics.math.factorial",
//                                         ball::Severity::e_INFO,
//                                         ball::Severity::e_TRACE,
//                                         ball::Severity::e_ERROR,
//                                         ball::Severity::e_FATAL);
// ```
// 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:
// ```
//     ball::Logger& logger = ball::LoggerManager::singleton().getLogger();
// ```
// 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,
//                           ball::Severity::e_WARN,
//                           __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,
//                           ball::Severity::e_WARN,
//                           __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,
//                       ball::Severity::e_TRACE,
//                       __FILE__,
//                       __LINE__,
//                       "Exiting 'factorial' successfully.");
//
//     return product;
// }
// ```

#include <balscm_version.h>

#include <ball_attribute.h>
#include <ball_attributecollectorregistry.h>
#include <ball_broadcastobserver.h>
#include <ball_categorymanager.h>
#include <ball_loggermanagerconfiguration.h>
#include <ball_record.h>
#include <ball_recordbuffer.h>
#include <ball_thresholdaggregate.h>
#include <ball_transmission.h>

#include <bdlcc_objectpool.h>
#include <bdlcc_sharedobjectpool.h>

#include <bdlma_concurrentpool.h>

#include <bslma_allocator.h>
#include <bslma_managedptr.h>

#include <bslmf_util.h>    // 'forward(V)'

#include <bslmt_mutex.h>
#include <bslmt_readerwritermutex.h>

#include <bsls_atomic.h>
#include <bsls_compilerfeatures.h>
#include <bsls_performancehint.h>
#include <bsls_util.h>     // 'forward<T>(V)'

#include <bsl_functional.h>
#include <bsl_map.h>
#include <bsl_memory.h>
#include <bsl_set.h>
#include <bsl_string_view.h>

namespace BloombergLP {
namespace ball {

class Context;
class LoggerManager;
class Observer;
class RecordBuffer;

                           // ============
                           // class Logger
                           // ============

/// This class provides log record management services.  Each instance of
/// `Logger` receives log records from one or more clients, manages the
/// storage of those records, and transmits them to a registered recipient
/// (i.e., an observer) when appropriate.
class Logger {

  public:
    // TYPES

    /// `UserFieldsPopulatorCallback` is the type of a user-supplied
    /// callback functor used to populate the user-defined fields in each
    /// log record.
    typedef LoggerManagerConfiguration::UserFieldsPopulatorCallback
                                                   UserFieldsPopulatorCallback;

    /// `PublishAllTriggerCallback` is the type of the functor that is
    /// invoked with the publication cause to publish all record buffers of
    /// all loggers that are allocated by the logger manager.
    typedef bsl::function<void(Transmission::Cause)> PublishAllTriggerCallback;

  private:
    // DATA
    bdlcc::SharedObjectPool<Record,
                            bdlcc::ObjectPoolFunctors::DefaultCreator,
                            bdlcc::ObjectPoolFunctors::Clear<Record> >
                  d_recordPool;                 // pool of records with a
                                                // custom 'RESETTER'

    const bsl::shared_ptr<Observer>
                  d_observer;                   // holds observer

    RecordBuffer *d_recordBuffer_p;             // holds log record buffer
                                                // (not owned)

    UserFieldsPopulatorCallback
                  d_userFieldsPopulator;        // user fields populator
                                                // functor

    const AttributeCollectorRegistry
                 *d_attributeCollectors_p;      // pointer to the registry of
                                                // attribute collector
                                                // callbacks (not owned)

    PublishAllTriggerCallback
                  d_publishAll;                 // publishAll callback functor

    bdlma::ConcurrentPool
                  d_bufferPool;                 // pool of buffers for
                                                // formatting log messages
                                                // allowing recursive access

    char         *d_scratchBuffer_p;            // buffer for formatting log
                                                // messages (owned)

    bslmt::Mutex  d_scratchBufferMutex;         // ensure thread-safety of
                                                // message buffer

    int           d_scratchBufferSize;          // message buffer size (bytes)

    LoggerManagerConfiguration::LogOrder
                  d_logOrder;                   // logging order

    LoggerManagerConfiguration::TriggerMarkers
                  d_triggerMarkers;             // trigger markers

    bslma::Allocator
                 *d_allocator_p;                // memory allocator (held, not
                                                // owned)

    // FRIENDS
    friend class LoggerManager;

  private:
    // NOT IMPLEMENTED
    Logger(const Logger&);
    Logger& operator=(const Logger&);

    // PRIVATE CREATORS

    /// Create a logger having the specified `observer` that receives
    /// published log records, the specified `recordBuffer` that stores log
    /// records, the specified `userFieldsPopulator` that populates the
    /// user-defined fields of log records, the specified
    /// `attributeCollectors` registry of user-installed attribute
    /// collectors, the specified `publishAllCallback` that is invoked when
    /// a Trigger-All event occurs, the specified `scratchBufferSize` for
    /// the internal message buffer accessible via `obtainMessageBuffer`,
    /// and the specified `globalAllocator` used to supply memory.  On a
    /// Trigger or Trigger-All event, the messages are published in the
    /// specified `logOrder`.  Note that this constructor is `private` since
    /// the creation of instances of `Logger` is managed by its `friend`
    /// `LoggerManager`.
    Logger(const bsl::shared_ptr<Observer>&            observer,
           RecordBuffer                               *recordBuffer,
           const UserFieldsPopulatorCallback&          userFieldsPopulator,
           const AttributeCollectorRegistry           *attributeCollectors,
           const PublishAllTriggerCallback&            publishAllCallback,
           int                                         scratchBufferSize,
           LoggerManagerConfiguration::LogOrder        logOrder,
           LoggerManagerConfiguration::TriggerMarkers  triggerMarkers,
           bslma::Allocator                           *globalAllocator);

    /// Destroy this logger.
    ~Logger();

    // PRIVATE MANIPULATORS

    /// Return a shared pointer to a modifiable record having the specified
    /// `fileName` and `lineNumber` attributes, and retrieved from the
    /// shared object pool managed by this logger.
    bsl::shared_ptr<Record> getRecordPtr(const bsl::string_view& fileName,
                                         int                     lineNumber);

    /// Log the specified `record` after setting its category field to the
    /// specified `category`, severity field to the specified `severity`,
    /// and the rest of the fixed fields (except `fileName`, `lineNumber`,
    /// and `message`, all of which are assumed to be already set in
    /// `record`) based on the threshold levels of the specified `levels`.
    /// (See the component-level documentation of `ball_record` for more
    /// information on the fields that are logged.)  Store the record in the
    /// buffer held by this logger if `severity` is at least as severe as
    /// the "Record" threshold level of `levels`.  Pass the record directly
    /// to the observers registered with this logger if `severity` is at
    /// least as severe as the "Pass" threshold level of `levels`.  Publish
    /// the entire contents of the buffer of this logger if `severity` is at
    /// least as severe as the "Trigger" threshold level of `levels`.
    /// Publish the entire contents of all buffers of all active loggers if
    /// `severity` is at least as severe as the "Trigger-All" threshold
    /// level of `levels` (i.e., via the callback supplied at construction).
    /// Finally, dispose of `record`.  This method has no effect (other than
    /// disposing of `record`) if `severity` is less severe than each of the
    /// threshold levels of `levels`.  The behavior is undefined unless
    /// `severity` is in the range `[1 .. 255]` and `record` was previously
    /// obtained via a call to `getRecord`.  Note that `record` will be
    /// invalid after this method returns.
    void logMessage(const Category&                category,
                    int                            severity,
                    const bsl::shared_ptr<Record>& record,
                    const ThresholdAggregate&      levels);

    /// Publish to the observer held by this logger all records stored in
    /// the record buffer of this logger and indicate to the observer the
    /// specified publication `cause`.
    void publish(Transmission::Cause cause);

    /// Publish to the observer held by this logger the specified `record` with
    /// the specified `context`.
    void publish(const bsl::shared_ptr<Record>& record,
                 const Context&                 context);

  public:
    // MANIPULATORS

    /// Return the address of a modifiable record having the specified
    /// `fileName` and `lineNumber` attributes, and retrieved from the
    /// object pool managed by this logger.  Note that the returned `Record`
    /// must subsequently be supplied to a call to the 3-argument
    /// `logMessage` method on this logger.
    Record *getRecord(const bsl::string_view& fileName, int lineNumber);

    /// Log a record containing the specified `message` text, `fileName`,
    /// `lineNumber`, `severity`, and the name of the specified `category`.
    /// (See the component-level documentation of `ball_record` for more
    /// information on the additional fields that are logged.)  Store the
    /// record in the buffer held by this logger if `severity` is at least
    /// as severe as the current "Record" threshold level of `category`.
    /// Pass the record directly to the observer held by this logger if
    /// `severity` is at least as severe as the current "Pass" threshold
    /// level of `category`.  Publish the entire contents of the buffer of
    /// this logger if `severity` is at least as severe as the current
    /// "Trigger" threshold level of `category`.  Publish the entire
    /// contents of all buffers of all active loggers of this logger factory
    /// if `severity` is at least as severe as the current "Trigger-All"
    /// threshold level of `category` (i.e., via the callback supplied at
    /// construction).  Note that this method will have no effect if
    /// `severity` is less severe than all of the threshold levels of
    /// `category`.  The behavior is undefined unless `severity` is in the
    /// range `[1 .. 255]`.
    void logMessage(const Category&         category,
                    int                     severity,
                    const bsl::string_view& fileName,
                    int                     lineNumber,
                    const bsl::string_view& message);

    /// Log the specified `*record` after setting its category attribute to
    /// the name of the specified `category` and severity attribute to the
    /// specified `severity`.  (See the component-level documentation of
    /// `ball_record` for more information on the fields that are logged.)
    /// Store the record in the buffer held by this logger if `severity` is
    /// at least as severe as the current "Record" threshold level of
    /// `category`.  Pass the record directly to the observer held by this
    /// logger if `severity` is at least as severe as the current "Pass"
    /// threshold level of `category`.  Publish the entire contents of the
    /// buffer of this logger if `severity` is at least as severe as the
    /// current "Trigger" threshold level of `category`.  Publish the entire
    /// contents of all buffers of all active loggers if `severity` is at
    /// least as severe as the current "Trigger-All" threshold level of
    /// `category` (i.e., via the callback supplied at construction).
    /// Finally, dispose of `record`.  This method has no effect (other than
    /// disposing of `record`) if `severity` is less severe than each of the
    /// threshold levels of `category`.  The behavior is undefined unless
    /// `severity` is in the range `[1 .. 255]`, both `fileName` and
    /// `message` are null-terminated, and `record` was previously obtained
    /// by a call to `getRecord` on this logger.  Note that `record` will be
    /// invalid after this method returns.
    void logMessage(const Category&  category,
                    int              severity,
                    Record          *record);

#ifndef BDE_OMIT_INTERNAL_DEPRECATED
    /// Return the address of the modifiable message buffer managed by this
    /// logger.  Note that the returned buffer is intended to be used *only*
    /// for formatting log messages immediately before calling `logMessage`.
    ///
    /// @DEPRECATED: Use `obtainMessageBuffer` instead.  Do *not* use this
    /// method in multi-threaded code.
    char *messageBuffer();
#endif // BDE_OMIT_INTERNAL_DEPRECATED

    /// Block until access to the buffer of this logger used for formatting
    /// messages is available.  Return the address of the modifiable buffer
    /// to which this thread of execution has exclusive access, load the
    /// address of the mutex that protects the buffer into the specified
    /// `*mutex` address, and load the size (in bytes) of the buffer into
    /// the specified `bufferSize` address.  The address remains valid, and
    /// the buffer remains locked by this thread of execution, until this
    /// thread calls `mutex->unlock()`.  The behavior is undefined if this
    /// thread of execution currently holds a lock on the buffer.  Note that
    /// the buffer is intended to be used *only* for formatting log messages
    /// immediately before calling `logMessage`; other use may adversely
    /// affect performance for the entire program.
    char *obtainMessageBuffer(bslmt::Mutex **mutex, int *bufferSize);

    /// Return a managed pointer that refers to the memory block to which
    /// this thread of execution has exclusive access and load the size (in
    /// bytes) of this buffer into the specified `bufferSize` address.  Note
    /// that this method is intended for *internal* *use* only.
    bslma::ManagedPtr<char> obtainMessageBuffer(int *bufferSize);

    /// Publish to the observer held by this logger all records stored in
    /// the record buffer of this logger and indicate to the observer that
    /// the cause is `MANUAL_PUBLISH`.
    void publish();

    /// Remove all log records from the record buffer of this logger.
    void removeAll();

    // ACCESSORS

    /// Return the size, in bytes, of the message buffer managed by this
    /// logger.
    int messageBufferSize() const;

    /// Return a *snapshot* of number of records that have been dispensed by
    /// `getRecord` but have not yet been supplied (returned) using
    /// `logRecord`.
    int numRecordsInUse() const;
};

#ifndef BDE_OMIT_INTERNAL_DEPRECATED
class LoggerManagerCategoryIter;
class LoggerManagerCategoryManip;
#endif  // BDE_OMIT_INTERNAL_DEPRECATED

                           // ===================
                           // class LoggerManager
                           // ===================

/// This class is *usually* a singleton.  It provides a factory for `Logger`
/// objects and is also a wrapper for category administration services.
/// Note that some services provided by this class are available only after
/// the singleton has been initialized.
class LoggerManager {

  public:
    // TYPES

    /// `CategoryNameFilterCallback` is the type of the user-supplied
    /// functor that translates external category names to internal names.
    typedef LoggerManagerConfiguration::CategoryNameFilterCallback
                                                    CategoryNameFilterCallback;

    /// `DefaultThresholdLevelsCallback` is the type of the functor that
    /// determines default threshold levels for categories added to the
    /// registry by the `setCategory(const char *)` method.
    typedef LoggerManagerConfiguration::DefaultThresholdLevelsCallback
                                                DefaultThresholdLevelsCallback;

    /// `PublishAllTriggerCallback` is the type of the functor that is
    /// invoked to publish all record buffers of all active loggers (i.e.,
    /// loggers allocated by the logger manager that have not yet been
    /// deallocated).
    typedef Logger::PublishAllTriggerCallback PublishAllTriggerCallback;

    /// `UserFieldsPopulatorCallback` is the type of a user-supplied
    /// callback functor used to populate the user-defined fields in each
    /// log record.
    typedef LoggerManagerConfiguration::UserFieldsPopulatorCallback
                                                   UserFieldsPopulatorCallback;

    /// `AttributeCollector` is the type of a user-supplied functor used to
    /// visit a collection of attributes.
    typedef AttributeCollectorRegistry::Collector AttributeCollector;

    /// `AttributeVisitor` is the type of a user-supplied functor invoked
    /// by an attribute collector for every attribute.
    typedef AttributeCollectorRegistry::Visitor AttributeVisitor;

    /// This `typedef` is an alias for the type of the internal broadcast
    /// observer registry.
    typedef BroadcastObserver::ObserverRegistry ObserverRegistry;

  private:
    // NOT IMPLEMENTED
    LoggerManager(const LoggerManager&);
    LoggerManager& operator=(const LoggerManager&);

#ifndef BDE_OMIT_INTERNAL_DEPRECATED
    // FRIENDS
    friend class LoggerManagerCategoryIter;
    friend class LoggerManagerCategoryManip;
#endif  // BDE_OMIT_INTERNAL_DEPRECATED

    // CLASS DATA
    static LoggerManager  *s_singleton_p;        // address of singleton if
                                                 // initialized; 0 otherwise

    static bool            s_isSingletonOwned;   // `true` by default whereby
                                                 // `ball` owns the singleton
                                                 // and destroys it in
                                                 // `shutDownSingleton`; can be
                                                 // set to `false` by the
                                                 // Windows-specific
                                                 // `initSingleton` taking an
                                                 // `adoptSingleton` flag

    // DATA
    const bsl::shared_ptr<BroadcastObserver>
                           d_observer;           // internal broadcast
                                                 // observer

    CategoryNameFilterCallback
                           d_nameFilter;         // category name filter
                                                 // functor

    DefaultThresholdLevelsCallback
                           d_defaultThresholds;  // functor for obtaining
                                                 // default threshold levels of
                                                 // "set" categories

    mutable bslmt::ReaderWriterMutex
                           d_defaultThresholdsLock;
                                                 // `d_defaultThresholdsLock`
                                                 // protector

    ThresholdAggregate     d_defaultThresholdLevels;
                                                 // default threshold levels

    const ThresholdAggregate
                           d_factoryThresholdLevels;
                                                 // factory default threshold
                                                 // levels

    UserFieldsPopulatorCallback
                           d_userFieldsPopulator;
                                                 // user fields populator
                                                 // functor

    ball::AttributeCollectorRegistry
                           d_attributeCollectors;// Registered attribute
                                                 // collector callbacks

    Logger                *d_logger_p;           // holds default logger
                                                 // (owned)

    bsls::AtomicUint       d_defaultLoggerCount; // number of thread-specific
                                                 // default loggers

    CategoryManager        d_categoryManager;    // category manager

    unsigned int           d_maxNumCategoriesMinusOne;
                                                 // one less than the current
                                                 // capacity of the registry

    bsl::set<Logger *>     d_loggers;            // set of *allocated* loggers

    bslmt::ReaderWriterMutex
                           d_loggersLock;        // `d_loggers` protector

    RecordBuffer          *d_recordBuffer_p;     // holds record buffer (owned)

    PublishAllTriggerCallback
                           d_publishAllCallback; // self-installed callback
                                                 // functor to publish all
                                                 // records within process
                                                 // (always valid)

    Category              *d_defaultCategory_p;  // holds *Default* *Category*
                                                 // (owned)

    int                    d_scratchBufferSize;  // logger default message
                                                 // buffer size (bytes)

    bsl::map<void *, Logger *>
                           d_defaultLoggers;     // *registered* loggers

    bslmt::ReaderWriterMutex
                           d_defaultLoggersLock; // registry lock

    LoggerManagerConfiguration::LogOrder
                           d_logOrder;           // logging order

    LoggerManagerConfiguration::TriggerMarkers
                           d_triggerMarkers;     // trigger markers

    bslma::Allocator      *d_allocator_p;        // memory allocator (held,
                                                 // not owned)

    // PRIVATE CLASS METHODS

    /// Initialize the logger manager singleton having the specified
    /// `configuration` of defaults and attributes, and the specified
    /// `globalAllocator` used to supply memory.  If `globalAllocator` is 0,
    /// the currently installed global allocator is used.  This method has
    /// no effect (aside from logging a warning) if the logger manager
    /// singleton already exists.  The behavior is undefined unless the lock
    /// guarding the singleton is acquired before calling this method.  Note
    /// that `singletonQLock`, an instance of `bslmt::QLock` local to the
    /// implementation file, enforces exclusive access to `s_singleton_p`
    /// during initialization and shutdown of the logger manager singleton.
    static void initSingletonImpl(
                           const LoggerManagerConfiguration&  configuration,
                           bslma::Allocator                  *globalAllocator);

    // PRIVATE CREATORS

#ifndef BDE_OMIT_INTERNAL_DEPRECATED
    /// Create a logger manager having the specified `observer` that
    /// receives published log records and the specified `configuration` of
    /// defaults and attributes.  Optionally specify a `globalAllocator`
    /// used to supply memory.  If `globalAllocator` is 0, the currently
    /// installed global allocator is used.  The behavior is undefined if
    /// `observer` is 0, goes out of scope, or is otherwise destroyed.  Note
    /// that the new logger manager is *not* the singleton logger manager
    /// used by macros of the BALL logging framework.
    LoggerManager(const LoggerManagerConfiguration&  configuration,
                  Observer                          *observer,
                  bslma::Allocator                  *globalAllocator = 0);
#endif  // BDE_OMIT_INTERNAL_DEPRECATED

    // PRIVATE MANIPULATORS

    /// Construct the default category, default logger members, and record
    /// buffer members of this logger manager based on the specified
    /// `configuration`.  The behavior is undefined if this method is
    /// invoked more than once on this logger manager.
    void constructObject(const LoggerManagerConfiguration& configuration);

    /// Transmit to the observers registered with this logger manager all
    /// log records accumulated in the record buffers of all loggers managed
    /// by this logger manager and indicate to the observers the specified
    /// publication `cause`.
    void publishAllImp(Transmission::Cause cause);

    /// Return a non-`const` reference to a logger managed by this logger
    /// manager suitable for performing logging operations for this thread
    /// of execution.
    Logger& getLoggerSlow();

  public:
    // CLASS METHODS
#ifndef BDE_OMIT_INTERNAL_DEPRECATED
    /// Create a logger manager that is *not* the singleton logger manager
    /// having the specified `observer` that receives published log records
    /// and the specified `configuration` of defaults and attributes; load
    /// the newly-created logger manager into the specified `manager`
    /// managed pointer.  Optionally specify a `basicAllocator` used to
    /// supply memory.  If `basicAllocator` is 0, the currently installed
    /// default allocator is used.  The behavior is undefined if `observer`
    /// is 0, goes out of scope, or is otherwise destroyed.  Note that this
    /// method does *not* create the singleton logger manager used by the
    /// macros of the BALL logging framework.
    ///
    /// @DEPRECATED: Use the `createLoggerManager` method that does not
    /// take a *raw* pointer to an `observer`, together with the
    /// `registerObserver` method (which takes a *shared* pointer to an
    /// `observer`), instead.
    static void createLoggerManager(
                        bslma::ManagedPtr<LoggerManager>  *manager,
                        Observer                          *observer,
                        const LoggerManagerConfiguration&  configuration,
                        bslma::Allocator                  *basicAllocator = 0);
#endif  // BDE_OMIT_INTERNAL_DEPRECATED

    /// Create a logger manager that is *not* the singleton logger manager
    /// having the specified `configuration` of defaults and attributes;
    /// load the newly-created logger manager into the specified `manager`
    /// managed pointer.  Optionally specify a `basicAllocator` used to
    /// supply memory.  If `basicAllocator` is 0, the currently installed
    /// default allocator is used.  Note that this method does *not* create
    /// the singleton logger manager used by the macros of the BALL logging
    /// framework.
    static void createLoggerManager(
                        bslma::ManagedPtr<LoggerManager>  *manager,
                        const LoggerManagerConfiguration&  configuration,
                        bslma::Allocator                  *basicAllocator = 0);

    /// Return the address of a modifiable record with the specified
    /// `fileName` and `lineNumber` attributes, and whose memory is supplied
    /// by the currently installed default allocator.  Note that the
    /// returned `Record` must subsequently be supplied to a call to the
    /// `LoggerManager::logMessage` method.
    static Record *getRecord(const bsl::string_view& fileName, int lineNumber);

#ifndef BDE_OMIT_INTERNAL_DEPRECATED
    /// Initialize the logger manager singleton having the specified
    /// `observer` that receives published log records.  Optionally specify
    /// a `configuration` describing how the singleton should be configured.
    /// If `configuration` is not specified, a default constructed
    /// `LoggerManagerConfiguration` object is used.  Optionally specify a
    /// `globalAllocator` used to supply memory.  If `globalAllocator` is 0,
    /// the currently installed global allocator is used.  Return a
    /// non-`const` reference to the logger manager singleton.  This method
    /// has no effect (aside from logging a warning) if the logger manager
    /// singleton already exists.  The behavior is undefined if `observer`
    /// is 0, goes out of scope, or is otherwise destroyed.
    ///
    /// @DEPRECATED: Use the `initSingleton` method that does not take a
    /// *raw* pointer to an `observer`, together with the `registerObserver`
    /// method (which takes a *shared* pointer to an `observer`), instead.
    static LoggerManager& initSingleton(
                       Observer                          *observer,
                       bslma::Allocator                  *globalAllocator = 0);
    static LoggerManager& initSingleton(
                       Observer                          *observer,
                       const LoggerManagerConfiguration&  configuration,
                       bslma::Allocator                  *globalAllocator = 0);
#endif  // BDE_OMIT_INTERNAL_DEPRECATED

    /// Initialize the logger manager singleton.  Optionally specify a
    /// `configuration` describing how the singleton should be configured.
    /// If `configuration` is not specified, a default constructed
    /// `LoggerManagerConfiguration` object is used.  Optionally specify a
    /// `globalAllocator` used to supply memory.  If `globalAllocator` is 0,
    /// the currently installed global allocator is used.  Return a
    /// non-`const` reference to the logger manager singleton.  This method
    /// has no effect (aside from logging a warning) if the logger manager
    /// singleton already exists.
    static LoggerManager& initSingleton(bslma::Allocator *globalAllocator = 0);
    static LoggerManager& initSingleton(
                       const LoggerManagerConfiguration&  configuration,
                       bslma::Allocator                  *globalAllocator = 0);

    /// Initialize the logger manager singleton with the specified
    /// `singleton`.  Optionally specify an `adoptSingleton` flag indicating
    /// whether this method takes ownership of `singleton`, in which case
    /// `shutDownSingleton` will destroy `singleton`.  If `adoptSingleton`
    /// is not specified, this method does *not* take ownership of
    /// `singleton` (and `shutDownSingleton` will not destroy it).  Return 0
    /// if the logger manager singleton was successfully initialized, and a
    /// non-zero value otherwise.  This method has no effect (aside from
    /// logging a warning) if the logger manager singleton already exists,
    /// in which case this method does *not* take ownership of `singleton`
    /// regardless of the value of `adoptSingleton`.  Note that this version
    /// of `initSingleton` is meant for use *only* on Windows to initialize
    /// another dynamically loaded copy of the `LoggerManager` system.  Also
    /// note that a suitable singleton may be obtained by calling
    /// `createLoggerManager`, or from the `singleton` class method of an
    /// already-initialized `LoggerManager` system.
    static int initSingleton(LoggerManager *singleton,
                             bool           adoptSingleton = false);

    /// Return `true` if the logger manager singleton exists, and `false`
    /// otherwise.
    static bool isInitialized();

    /// Publish the specified `record` using
    /// `bsls::Log::platformDefaultMessageHandler` after setting its
    /// severity attribute to the specified `severity`, and dispose of
    /// `record`.  The behavior is undefined unless `record` was obtained by
    /// a call to the `LoggerManager::getRecord` method.  Note that `record`
    /// will be invalid after this method returns.
    static void logMessage(int severity, Record *record);

    /// Block until access to the static buffer used for formatting messages
    /// is available.  Return the address of the modifiable buffer to which
    /// this thread of execution has exclusive access, load the address of
    /// the mutex that protects the buffer into the specified `*mutex`
    /// address, and load the size (in bytes) of the buffer into the
    /// specified `bufferSize` address.  The address remains valid, and the
    /// buffer remains locked by this thread of execution, this thread calls
    /// `mutex->unlock()`.  The behavior is undefined if this thread of
    /// execution currently holds a lock on the buffer.  Note that the
    /// buffer is intended to be used *only* for formatting log messages
    /// immediately before calling `logMessage`; other use may adversely
    /// affect performance for the entire program.
    static char *obtainMessageBuffer(bslmt::Mutex **mutex, int *bufferSize);

    /// Return a managed pointer that refers to the memory block to which
    /// this thread of execution has exclusive access and load the size (in
    /// bytes) of this buffer into the specified `bufferSize` address.  Note
    /// that this method is intended for *internal* *use* only.
    static bslma::ManagedPtr<char> obtainMessageBuffer(int *bufferSize);

    /// Destroy the logger manager singleton and release all resources used
    /// by it.  This method has no effect if the logger manager singleton
    /// does not exist (i.e., it has not been initialized or has already
    /// been destroyed).  The behavior is undefined if this method is called
    /// from one thread while another thread is accessing the logger manager
    /// singleton (i.e., this method is *not* thread-safe).
    static void shutDownSingleton();

    /// Return a non-`const` reference to the logger manager singleton.  The
    /// behavior is undefined unless the logger manager singleton exists.
    static LoggerManager& singleton();

    // CREATORS

    /// Create a logger manager having the specified `configuration` of
    /// defaults and attributes.  Optionally specify a `globalAllocator`
    /// used to supply memory.  If `globalAllocator` is 0, the currently
    /// installed global allocator is used.  Note that the new logger
    /// manager is *not* the singleton logger manager used by macros of the
    /// BALL logging framework.
    explicit LoggerManager(
                       const LoggerManagerConfiguration&  configuration,
                       bslma::Allocator                  *globalAllocator = 0);

    /// Destroy this logger manager.
    ~LoggerManager();

    // MANIPULATORS
                             // Logger Management

    /// Return the address of a modifiable logger managed by this logger
    /// manager configured with the specified record `buffer`.  Optionally
    /// specify a `scratchBufferSize` for the logger's user-accessible
    /// message buffer.  If `scratchBufferSize` is not specified, the value
    /// configured at construction is used.  Note that this method is
    /// primarily intended for use in multi-threaded applications, but can
    /// be used to partition logging streams even within a single thread.
    /// Also note that ownership of `buffer` is *not* transferred, and
    /// hence, will *not* be destroyed (or otherwise affected) after the
    /// logger is deallocated.
    Logger *allocateLogger(RecordBuffer *buffer);
    Logger *allocateLogger(RecordBuffer *buffer, int scratchBufferSize);

#ifndef BDE_OMIT_INTERNAL_DEPRECATED
    /// Return the address of a modifiable logger managed by this logger
    /// manager having the specified `observer` that receives published log
    /// records and configured with the specified record `buffer`.
    /// Optionally specify a `scratchBufferSize` for the logger's
    /// user-accessible message buffer.  If `scratchBufferSize` is not
    /// specified, the value configured at construction is used.  Note that
    /// this method is primarily intended for use in multi-threaded
    /// applications, but can be used to partition logging streams even
    /// within a single thread.  Also note that ownership of `buffer` and
    /// `observer` is *not* transferred, and hence, will *not* be destroyed
    /// (or otherwise affected) after the logger is deallocated.
    ///
    /// @DEPRECATED: Use the `allocateLogger` method that does not take a
    /// *raw* pointer to an `observer`, together with the `registerObserver`
    /// method (which takes a *shared* pointer to an `observer`), instead.
    Logger *allocateLogger(RecordBuffer *buffer,
                           Observer     *observer);
    Logger *allocateLogger(RecordBuffer *buffer,
                           int           scratchBufferSize,
                           Observer     *observer);
#endif // BDE_OMIT_INTERNAL_DEPRECATED

    /// Return the address of a modifiable logger managed by this logger
    /// manager having the specified `observer` that receives published log
    /// records and configured with the specified record `buffer`.
    /// Optionally specify a `scratchBufferSize` for the logger's
    /// user-accessible message buffer.  If `scratchBufferSize` is not
    /// specified, the value configured at construction is used.  Note that
    /// this method is primarily intended for use in multi-threaded
    /// applications, but can be used to partition logging streams even
    /// within a single thread.  Also note that ownership of `buffer` and
    /// `observer` is *not* transferred, and hence, will *not* be destroyed
    /// (or otherwise affected) after the logger is deallocated.
    Logger *allocateLogger(RecordBuffer                     *buffer,
                           const bsl::shared_ptr<Observer>&  observer);
    Logger *allocateLogger(RecordBuffer                     *buffer,
                           int                               scratchBufferSize,
                           const bsl::shared_ptr<Observer>&  observer);

    /// Deallocate the specified `logger` and release `logger` from
    /// management by this logger manager.  The behavior is undefined unless
    /// `logger` was obtained by invoking the `allocateLogger` method of
    /// this logger manager and `logger` has not yet been deallocated.  Note
    /// that this method is intended primarily for multi-threaded
    /// applications.
    void deallocateLogger(Logger *logger);

    /// Return a non-`const` reference to a logger managed by this logger
    /// manager suitable for performing logging operations for this thread
    /// of execution.
    Logger& getLogger();

    /// Set the default logger used by this thread of execution to the
    /// specified `logger`, or to the global default logger if `logger` is
    /// 0.  The behavior is undefined unless `logger` was obtained from this
    /// logger manager, and this thread of execution does not hold exclusive
    /// access to the record buffer of its current default logger.
    void setLogger(Logger *logger);

                             // Category Management

    /// Add to the category registry of this logger manager a new category
    /// having the specified `categoryName` and the specified `recordLevel`,
    /// `passLevel`, `triggerLevel`, and `triggerAllLevel` threshold levels,
    /// respectively, if (1) `categoryName` is not present in the registry,
    /// (2) the number of categories in the registry is less than the
    /// registry capacity, and (3) each threshold level is in the range
    /// `[0 .. 255]`.  Return the address of the new modifiable category on
    /// success, and 0 otherwise.  The behavior is undefined unless
    /// `categoryName` is null-terminated.
    Category *addCategory(const char *categoryName,
                          int         recordLevel,
                          int         passLevel,
                          int         triggerLevel,
                          int         triggerAllLevel);

    /// Return a non-`const` reference to the *Default* *Category* in the
    /// category registry of this logger manager.
    Category& defaultCategory();

    /// Return the address of the modifiable category in the category
    /// registry of this logger manager having the specified `categoryName`,
    /// or 0 if no such category exists.  The behavior is undefined unless
    /// `categoryName` is null-terminated.
    Category *lookupCategory(const char *categoryName);

    /// Add to the category registry of this logger manager a new category
    /// having the specified `categoryName` and default threshold levels if
    /// `categoryName` is not present in the registry and the number of
    /// categories in the registry is less than the registry capacity.
    /// Return the address of the (possibly newly-created) non-modifiable
    /// category having `categoryName`, if such a category exists, and the
    /// address of the non-modifiable *Default* *Category* otherwise.  The
    /// behavior is undefined unless `categoryName` is null-terminated.
    /// Note that a valid category address is *always* returned.
    const Category *setCategory(const char *categoryName);

    /// Add to the category registry of this logger manager a new category
    /// having the specified `categoryName` and default threshold levels if
    /// `categoryName` is not present in the registry and the number of
    /// categories in the registry is less than the registry capacity.
    /// Return the address of the (possibly newly-created) non-modifiable
    /// category having `categoryName`, if such a category exists, and the
    /// address of the non-modifiable *Default* *Category* otherwise.  If
    /// the specified `categoryHolder` is non-null, then also load into
    /// `categoryHolder` the returned category and its maximum level and
    /// link `categoryHolder` to the category if it has not yet been linked.
    /// The behavior is undefined unless `categoryName` is null-terminated.
    /// Note that a valid category address is *always* returned.
    const Category *setCategory(CategoryHolder *categoryHolder,
                                const char     *categoryName);

    /// Add to the category registry of this logger manager a new category
    /// having the specified `categoryName` and `recordLevel`, `passLevel`,
    /// `triggerLevel`, and `triggerAllLevel` threshold levels,
    /// respectively, if (1) `categoryName` is not present in the registry,
    /// (2) the number of categories in the registry is less than the
    /// registry capacity, and (3) `recordLevel`, `passLevel`,
    /// `triggerLevel`, and `triggerAllLevel` are all within the range
    /// `[0 .. 255]`.  If `categoryName` is already present and each
    /// threshold level is within the valid range then reset the threshold
    /// levels of `categoryName` to the specified values.  Return the
    /// address of the (possibly newly-created) modifiable category having
    /// `categoryName` if `categoryName` was either created or its
    /// thresholds reset, and 0 otherwise.  The behavior is undefined unless
    /// `categoryName` is null-terminated.  Note that 0, and *not* the
    /// *Default* *Category*, is returned on failure.
    Category *setCategory(const char *categoryName,
                          int         recordLevel,
                          int         passLevel,
                          int         triggerLevel,
                          int         triggerAllLevel);

    /// Set the capacity of the category registry of this logger manager to
    /// the specified `length`.  If `length` is 0, no limit will be imposed.
    /// No categories are removed from the registry if the current number of
    /// categories exceeds `length`.  However, subsequent attempts to add
    /// categories to the registry will fail.  The behavior is undefined
    /// unless `0 <= length`.
    void setMaxNumCategories(int length);

                             // Observer Management

    /// Remove all observers from the registry of observers maintained by
    /// this logger manager.
    void deregisterAllObservers();

    /// Remove the observer having the specified `observerName` from the
    /// registry of observers maintained by this logger manager.  Return 0
    /// if the observer having `observerName` was successfully deregistered
    /// from this logger manager, and a non-zero value (with no effect)
    /// otherwise.  Henceforth, the observer that had `observerName` will no
    /// longer receive log records published by this logger manager.
    int deregisterObserver(const bsl::string_view& observerName);

    /// Remove the attribute collector having the specified `collectorName`
    /// from the registry of collectors maintained by this logger manager.
    /// Return 0 if the collector having `collectorName` was successfully
    /// deregistered from this logger manager, and a non-zero value (with no
    /// effect) otherwise.
    int deregisterAttributeCollector(const bsl::string_view& collectorName);

    /// Return a shared pointer to the observer having the specified
    /// `observerName` in the registry of this logger manager, and an empty
    /// shared pointer if there is no such observer otherwise.
    bsl::shared_ptr<Observer> findObserver(
                                         const bsl::string_view& observerName);

    /// Load into the specified `result` a shared pointer to the observer of
    /// (template parameter) `t_OBSERVER` type having the specified
    /// `observerName` in the registry of this logger manager, and an empty
    /// shared pointer if there is no such observer otherwise.  Return 0 if
    /// a non-empty shared pointer was loaded, and a non-zero value
    /// otherwise.  Note that an empty shared pointer will be loaded if
    /// either no observer having `observerName` is in the registry or the
    /// observer registered with that name is not of `t_OBSERVER` type.
    template <class t_OBSERVER>
    int findObserver(bsl::shared_ptr<t_OBSERVER> *result,
                     const bsl::string_view&      observerName);

#ifndef BDE_OMIT_INTERNAL_DEPRECATED
    /// Return the address of the modifiable legacy observer registered with
    /// this logger manager.
    ///
    /// @DEPRECATED: Use `findObserver` instead.
    Observer *observer();
#endif  // BDE_OMIT_INTERNAL_DEPRECATED

    /// Add the specified `observer` with the specified `observerName` to
    /// the registry of observers maintained by this logger manager.  Return
    /// 0 if `observer` was successfully registered with this logger
    /// manager, and a non-zero value (with no effect) otherwise.
    /// Henceforth, all log records published by this logger manager are
    /// published to this observer, until `observer` is deregistered.  The
    /// behavior is undefined if a cyclic reference is created among
    /// registered observers.  Note that this method will fail if an
    /// observer having `observerName` is already registered.
    int registerObserver(const bsl::shared_ptr<Observer>& observer,
                         const bsl::string_view&          observerName);

    /// Add the specified `collector` with the specified `collectorName` to
    /// the registry of attribute collectors maintained by this logger
    /// manager.  Return 0 if `collector` was successfully registered with
    /// this logger manager, and a non-zero value (with no effect)
    /// otherwise.  Note that this method will fail if a collector having
    /// `collectorName` is already registered.
    int registerAttributeCollector(const AttributeCollector& collector,
                                   const bsl::string_view&   collectorName);

                     // Threshold Level Management Manipulators

    /// Reset the default threshold levels of this logger manager to the
    /// original "factory-supplied" default values or the factory overrides
    /// supplied at construction.
    void resetDefaultThresholdLevels();

    /// Set the threshold levels of the specified `category` in the category
    /// registry of this logger manager to the current default threshold
    /// values.  The behavior is undefined unless `category` is non-null.
    void setCategoryThresholdsToCurrentDefaults(Category *category);

    /// Set the threshold levels of the specified `category` in the category
    /// registry of this logger manager to the original "factory-supplied"
    /// default values or the factory overrides supplied at construction.
    /// The behavior is undefined unless `category` is non-null.
    void setCategoryThresholdsToFactoryDefaults(Category *category);

    /// Set the default threshold levels of this logger manager to the
    /// specified `recordLevel`, `passLevel`, `triggerLevel`, and
    /// `triggerAllLevel` values, respectively, if each threshold level is
    /// in the range `[0 .. 255]`.  Return 0 on success, and a non-zero
    /// value otherwise (with no effect on any default threshold level).
    int setDefaultThresholdLevels(int recordLevel,
                                  int passLevel,
                                  int triggerLevel,
                                  int triggerAllLevel);

    /// Set the default-thresholds callback of this logger manager to the
    /// specified `callback`.  The default-thresholds callback is used to
    /// determine default threshold levels for categories added to the
    /// registry by the `setCategory(const char *)` method.
    void setDefaultThresholdLevelsCallback(
                                     DefaultThresholdLevelsCallback *callback);

                             // Rule Management

    /// Add a rule having the specified `value` to the set of (unique)
    /// rules maintained by this object.  Return the number of rules added
    /// (i.e., 1 on success and 0 if a rule with the same value is already
    /// present).
    int addRule(const Rule& value);

    /// Add each rule in the specified `ruleSet` to the set of (unique)
    /// rules maintained by this object.  Return the number of rules added.
    /// Note that each rule having the same value as an existing rule will
    /// be ignored.
    int addRules(const RuleSet& ruleSet);

    /// Remove every rule from the set of rules maintained by this object.
    void removeAllRules();

    /// Remove the rule having the specified `value` from this set of rules
    /// maintained by this object.  Return the number of rules removed
    /// (i.e., 1 on success and 0 if no rule having the same value is
    /// found.)
    int removeRule(const Rule& value);

    /// Remove each rule in the specified `ruleSet` from this set of rules
    /// maintained by this object.  Return the number of rules removed.
    int removeRules(const RuleSet& ruleSet);

                             // Miscellaneous

    /// Transmit to the observers registered with this logger manager all
    /// log records accumulated in the record buffers of all loggers managed
    /// by this logger manager, and indicate the publication cause to be
    /// `MANUAL_PUBLISH_ALL`.
    void publishAll();

    /// Invoke the specified `visitor` functor on each category managed by
    /// this object, providing that functor modifiable access to each
    /// category.  `visitor` must be a functor that can be called as if it
    /// had the following signature:
    /// ```
    /// void operator()(Category *);
    /// ```
    template <class t_CATEGORY_VISITOR>
    void visitCategories(const t_CATEGORY_VISITOR& visitor);

    /// Invoke the specified `visitor` functor of (template parameter)
    /// `t_OBSERVER_VISITOR` type on each element in the registry of this
    /// logger manager, supplying that functor modifiable access to each
    /// observer.  `visitor` must be a functor that can be called as if it
    /// had the following signature:
    /// ```
    /// void operator()(const bsl::shared_ptr<Observer>& observer,
    ///                 const bsl::string_view&          observerName);
    /// ```
    template <class t_OBSERVER_VISITOR>
    void visitObservers(
                BSLS_COMPILERFEATURES_FORWARD_REF(t_OBSERVER_VISITOR) visitor);

    // ACCESSORS

    /// Return the address of the modifiable allocator held by this logger
    /// manager.
    bslma::Allocator *allocator() const;

    /// Return a `const` reference to the *Default* *Category* in the
    /// category registry of this logger manager.
    const Category& defaultCategory() const;

    /// Return the default pass threshold level of this logger manager.
    int defaultPassThresholdLevel() const;

    /// Return the default record threshold level of this logger manager.
    int defaultRecordThresholdLevel() const;

    /// Return the default trigger-all threshold level of this logger
    /// manager.
    int defaultTriggerAllThresholdLevel() const;

    /// Return the default trigger threshold level of this logger manager.
    int defaultTriggerThresholdLevel() const;

    /// Return a shared pointer to the observer having the specified
    /// `observerName` in the registry of this logger manager, and an empty
    /// shared pointer if there is no such observer otherwise.
    bsl::shared_ptr<const Observer> findObserver(
                                   const bsl::string_view& observerName) const;

    /// Load into the specified `result` a shared pointer to the observer of
    /// (template parameter) `t_OBSERVER` type having the specified
    /// `observerName` in the registry of this logger manager, and an empty
    /// shared pointer if there is no such observer otherwise.  Return 0 if
    /// a non-empty shared pointer was loaded, and a non-zero value
    /// otherwise.  Note that an empty shared pointer will be loaded if
    /// either no observer having `observerName` is in the registry or the
    /// observer registered with that name is not of `t_OBSERVER` type.
    template <class t_OBSERVER>
    int findObserver(bsl::shared_ptr<const t_OBSERVER> *result,
                     const bsl::string_view&            observerName) const;

    /// Return `true` if the specified `severity` is more severe (i.e., is
    /// numerically less than) at least one of the threshold levels of the
    /// specified `category`, and `false` otherwise.  If the returned
    /// `value` is `false`, then a subsequent call to
    /// `getLogger().logMessage()` (with `category` and `severity`) will
    /// have no effect.  This method compares `severity` with the threshold
    /// levels determined by combining `category->thresholdLevels()` with
    /// the thresholds provided by any relevant and active logging rules (in
    /// `ruleSet()`) that apply to `category`.  Note that a rule applies to
    /// `category` if the rule's pattern matches `category->categoryName()`,
    /// and a rule is active if all the predicates defined for that rule are
    /// satisfied by the current thread's attributes (i.e.,
    /// `Rule::evaluate()` returns `true` for the collection of attributes
    /// maintained by the current thread's `AttributeContext` object).
    bool isCategoryEnabled(const Category *category, int severity) const;

    /// Return the address of the non-modifiable category in the category
    /// registry of this logger manager having the specified `categoryName`,
    /// or 0 if no such category exists.  The behavior is undefined unless
    /// `categoryName` is null-terminated.
    const Category *lookupCategory(const char *categoryName) const;

    /// Return the current capacity of the category registry of this logger
    /// manager.  A capacity of 0 implies that no limit will be imposed;
    /// otherwise, new categories may be added only if
    /// `numCategories() < maxNumCategories()`.  Note that
    /// `0 < maxNumCategories() < numCategories()` *is* a valid state,
    /// implying no new categories may be added.
    int maxNumCategories() const;

    /// Return the number of categories in the category registry of this
    /// logger manager.
    int numCategories() const;

#ifndef BDE_OMIT_INTERNAL_DEPRECATED
    /// Return the address of the non-modifiable observer registered with
    /// this logger manager.
    ///
    /// @DEPRECATED: Use `findObserver` instead.
    const Observer *observer() const;
#endif  // BDE_OMIT_INTERNAL_DEPRECATED

    /// Return a `const` reference to the rule set maintained by this
    /// object.
    const RuleSet& ruleSet() const;

    /// Return the address of the non-modifiable user populator functor
    /// registered with this logger manager, or 0 if there is no registered
    /// user populator functor.
    const UserFieldsPopulatorCallback *userFieldsPopulatorCallback() const;

    /// Invoke the specified `visitor` functor on each category managed by
    /// this object, providing that functor non-modifiable access to each
    /// category.  `visitor` must be a functor that can be called as if it
    /// had the following signature:
    /// ```
    /// void operator()(const Category *);
    /// ```
    template <class t_CATEGORY_VISITOR>
    void visitCategories(const t_CATEGORY_VISITOR& visitor) const;

    /// Invoke the specified `visitor` functor of (template parameter)
    /// `t_OBSERVER_VISITOR` type on each element in the registry of this
    /// logger manager, supplying that functor modifiable access to each
    /// observer.  `visitor` must be a functor that can be called as if it
    /// had the following signature:
    /// ```
    /// void operator()(const bsl::shared_ptr<Observer>& observer,
    ///                 const bsl::string_view&          observerName);
    /// ```
    template <class t_OBSERVER_VISITOR>
    void visitObservers(
          BSLS_COMPILERFEATURES_FORWARD_REF(t_OBSERVER_VISITOR) visitor) const;

    // Threshold Level Management Accessors

    /// Return the default threshold levels associated with this logger
    /// manager object.
    const ThresholdAggregate& defaultThresholdLevels() const;

    /// Load into the specified `*levels` the threshold levels that would be
    /// set for a newly created category, irrespective of whether a category
    /// with the specified `categoryName` is already in the registry.
    /// Return 0 on success and a non-zero value otherwise.  If the client
    /// has configured a default threshold levels callback (see
    /// `ball::LoggerManager::DefaultThresholdLevelsCallback` in the
    /// component doc), the `categoryName` will be supplied to that callback
    /// which will set `*levels`.  Otherwise, if no default threshold levels
    /// callback has been provided, the default threshold levels are used.
    /// Note that this function will report an error if the callback returns
    /// invalid levels.  Also note that if a category named `categoryName`
    /// is already in the registry, the levels returned by this method may
    /// differ from the levels of that category.
    int thresholdLevelsForNewCategory(ThresholdAggregate *levels,
                                      const char         *categoryName) const;
};

                        // ==============================
                        // class LoggerManagerScopedGuard
                        // ==============================

/// This class implements a scoped guard that, on construction, creates the
/// logger manager singleton, and, on destruction, destroys the singleton.
class LoggerManagerScopedGuard {

  private:
    // NOT IMPLEMENTED
    LoggerManagerScopedGuard(const LoggerManagerScopedGuard&);
    LoggerManagerScopedGuard& operator=(const LoggerManagerScopedGuard&);

  public:
    // CREATORS
#ifndef BDE_OMIT_INTERNAL_DEPRECATED
    /// Create a scoped guard that will create the logger manager singleton
    /// having the specified `observer` that receives published log records
    /// and the specified `configuration` of defaults and attributes.
    /// Optionally specify a `globalAllocator` used to supply memory.  If
    /// `globalAllocator` is 0, the currently installed global allocator is
    /// used.  This method has no effect (aside from logging a warning) if
    /// the logger manager singleton already exists.  The behavior is
    /// undefined if `observer` is 0, goes out of scope, or is otherwise
    /// destroyed.  Note that on destruction, this scoped guard will destroy
    /// the logger manager singleton, if the singleton exists at that time.
    ///
    /// @DEPRECATED: Use the `LoggerManagerScopedGuard` constructor that
    /// does not take a *raw* pointer to an `observer`, together with the
    /// `registerObserver` method (which takes a *shared* pointer to an
    /// `observer`), instead.
    LoggerManagerScopedGuard(
                       Observer                          *observer,
                       const LoggerManagerConfiguration&  configuration,
                       bslma::Allocator                  *globalAllocator = 0);
#endif  // BDE_OMIT_INTERNAL_DEPRECATED

    /// Create a scoped guard that will create the logger manager singleton
    /// having the specified `configuration` of defaults and attributes.
    /// Optionally specify a `globalAllocator` used to supply memory.  If
    /// `globalAllocator` is 0, the currently installed global allocator is
    /// used.  This method has no effect (aside from logging a warning) if
    /// the logger manager singleton already exists.  Note that on
    /// destruction, this scoped guard will destroy the logger manager
    /// singleton, if the singleton exists at that time.
    explicit LoggerManagerScopedGuard(
                       const LoggerManagerConfiguration&  configuration,
                       bslma::Allocator                  *globalAllocator = 0);

    /// Destroy the logger manager singleton, if the singleton exists, and
    /// destroy this scoped guard.
    ~LoggerManagerScopedGuard();
};

#ifndef BDE_OMIT_INTERNAL_DEPRECATED

                        // ===============================
                        // class LoggerManagerCategoryIter
                        // ===============================

/// This class defines an iterator providing sequential, read-only access to
/// the categories in the registry of a logger manager's category manager.
/// The order of the iteration is undefined.
///
/// @DEPRECATED: Use the `LoggerManager::visitCategories` accessor instead.
class LoggerManagerCategoryIter {

    // DATA
    CategoryManagerIter d_iter;  // contained category manager iterator

  private:
    // NOT IMPLEMENTED
    LoggerManagerCategoryIter(const LoggerManagerCategoryIter&);
    LoggerManagerCategoryIter& operator=(const LoggerManagerCategoryIter&);

  public:
    // CREATORS

    /// Create an iterator providing non-modifiable access to the categories
    /// maintained by the specified `loggerManager` that is initialized to
    /// refer to the first category in the sequence of categories in the
    /// registry of `loggerManager`, if one exists, and is initialized to be
    /// invalid otherwise.  The order of iteration is undefined.  The
    /// behavior is undefined unless the lifetime of `loggerManager` is at
    /// least as long as the lifetime of this iterator.
    explicit LoggerManagerCategoryIter(const LoggerManager& loggerManager);

    //! ~LoggerManagerCategoryIter() = default;
        // Destroy this iterator.

    // MANIPULATORS

    /// Advance this iterator to refer to the next unvisited category.  If
    /// no such category exists, this iterator becomes invalid.  The
    /// behavior is undefined unless this iterator is initially valid.  Note
    /// that the order of iteration is undefined.
    void operator++();

    // ACCESSORS

    /// Return a non-zero value if this iterator is valid, and 0 otherwise.
    operator const void *() const;

    /// Return a `const` reference to the category currently referred to by
    /// this iterator.  The behavior is undefined unless this iterator is
    /// valid.
    const Category& operator()() const;
};

                        // ================================
                        // class LoggerManagerCategoryManip
                        // ================================

/// This class defines an iterator providing sequential, modifiable access
/// to the categories in the registry of a logger manager's category
/// manager.  The order of the iteration is undefined.
///
/// @DEPRECATED: Use the `LoggerManager::visitCategories` manipulator instead.
class LoggerManagerCategoryManip {

    // DATA
    CategoryManagerManip d_manip;  // contained category manager iterator

  private:
    // NOT IMPLEMENTED
    LoggerManagerCategoryManip(const LoggerManagerCategoryManip&);
    LoggerManagerCategoryManip& operator=(const LoggerManagerCategoryManip&);

  public:
    // CREATORS

    /// Create an iterator providing modifiable access to the categories
    /// maintained by the specified `loggerManager` that is initialized to
    /// refer to the first category in the sequence of categories in the
    /// registry of `loggerManager`, if one exists, and is initialized to be
    /// invalid otherwise.  The order of iteration is undefined.  The
    /// behavior is undefined unless the lifetime of `loggerManager` is at
    /// least as long as the lifetime of this iterator.
    explicit LoggerManagerCategoryManip(LoggerManager *loggerManager);

    // Destroy this iterator.
    //! ~LoggerManagerCategoryManip() = default;

    // MANIPULATORS

    /// Advance this iterator to refer to the next unvisited category.  If
    /// no such category exists, this iterator becomes invalid.  The
    /// behavior is undefined unless this iterator is initially valid.  Note
    /// that the order of iteration is undefined.
    void advance();

    /// Return a non-`const` reference to the category currently referred to
    /// by this iterator.  The behavior is undefined unless this iterator is
    /// valid.
    Category& operator()();

    // ACCESSORS

    /// Return a non-zero value if this iterator is valid, and 0 otherwise.
    operator const void *() const;
};

#endif  // BDE_OMIT_INTERNAL_DEPRECATED

// ============================================================================
//                              INLINE DEFINITIONS
// ============================================================================

                        // ------------
                        // class Logger
                        // ------------

// MANIPULATORS
#ifndef BDE_OMIT_INTERNAL_DEPRECATED
inline
char *Logger::messageBuffer()
{
    return d_scratchBuffer_p;
}
#endif // BDE_OMIT_INTERNAL_DEPRECATED

inline
void Logger::publish()
{
    publish(Transmission::e_MANUAL_PUBLISH);
}

inline
void Logger::removeAll()
{
    d_recordBuffer_p->removeAll();
}

// ACCESSORS
inline
int Logger::messageBufferSize() const
{
    return d_scratchBufferSize;
}

inline
int Logger::numRecordsInUse() const
{
    return d_recordPool.numObjects() - d_recordPool.numAvailableObjects();
}

                        // -------------------
                        // class LoggerManager
                        // -------------------

// CLASS METHODS
inline
bool LoggerManager::isInitialized()
{
    return (LoggerManager *)0 != s_singleton_p;
}

inline
LoggerManager& LoggerManager::singleton()
{
    return *s_singleton_p;
}

// MANIPULATORS
                             // Category Management

inline
Category& LoggerManager::defaultCategory()
{
    return *d_defaultCategory_p;
}

inline
const Category *LoggerManager::setCategory(const char *categoryName)
{
    return setCategory(0, categoryName);
}

                             // Observer Management

inline
void LoggerManager::deregisterAllObservers()
{
    d_observer->deregisterAllObservers();
}

inline
int LoggerManager::deregisterObserver(const bsl::string_view& observerName)
{
    return d_observer->deregisterObserver(observerName);
}

inline
int LoggerManager::deregisterAttributeCollector(
                                         const bsl::string_view& collectorName)
{
    return d_attributeCollectors.removeCollector(collectorName);
}

inline
bsl::shared_ptr<Observer>
LoggerManager::findObserver(const bsl::string_view& observerName)
{
    return d_observer->findObserver(observerName);
}

template <class t_OBSERVER>
inline
int LoggerManager::findObserver(bsl::shared_ptr<t_OBSERVER> *result,
                                const bsl::string_view&      observerName)
{
    return d_observer->findObserver(result, observerName);
}

inline
int
LoggerManager::registerObserver(const bsl::shared_ptr<Observer>& observer,
                                const bsl::string_view&          observerName)
{
    return d_observer->registerObserver(observer, observerName);
}

inline
int
LoggerManager::registerAttributeCollector(
                                       const AttributeCollector& collector,
                                       const bsl::string_view&   collectorName)
{
    return d_attributeCollectors.addCollector(collector, collectorName);
}
                             // Threshold Level Management

inline
void LoggerManager::resetDefaultThresholdLevels()
{
    d_defaultThresholdLevels = d_factoryThresholdLevels;
}

                             // Rule Management

inline
int LoggerManager::addRule(const Rule& value)
{
    return d_categoryManager.addRule(value);
}

inline
int LoggerManager::addRules(const RuleSet& ruleSet)
{
    return d_categoryManager.addRules(ruleSet);
}

inline
void LoggerManager::removeAllRules()
{
    d_categoryManager.removeAllRules();
}

inline
int LoggerManager::removeRule(const Rule& value)
{
    return d_categoryManager.removeRule(value);
}

inline
int LoggerManager::removeRules(const RuleSet& ruleSet)
{
    return d_categoryManager.removeRules(ruleSet);
}

                             // Miscellaneous

inline
void LoggerManager::publishAll()
{
    publishAllImp(Transmission::e_MANUAL_PUBLISH_ALL);
}

template <class t_CATEGORY_VISITOR>
inline
void LoggerManager::visitCategories(const t_CATEGORY_VISITOR& visitor)
{
    d_categoryManager.visitCategories(visitor);
}

template <class t_OBSERVER_VISITOR>
inline
void LoggerManager::visitObservers(
                 BSLS_COMPILERFEATURES_FORWARD_REF(t_OBSERVER_VISITOR) visitor)
{
    d_observer->visitObservers(
                   BSLS_COMPILERFEATURES_FORWARD(t_OBSERVER_VISITOR, visitor));
}

inline
Logger& LoggerManager::getLogger()
{
    if (BSLS_PERFORMANCEHINT_PREDICT_UNLIKELY(
                                         d_defaultLoggerCount.loadAcquire())) {
        return getLoggerSlow();
    }
    return *d_logger_p;
}

// ACCESSORS
inline
bslma::Allocator *LoggerManager::allocator() const
{
    return d_allocator_p;
}

inline
const Category& LoggerManager::defaultCategory() const
{
    return *d_defaultCategory_p;
}

inline
int LoggerManager::defaultPassThresholdLevel() const
{
    return d_defaultThresholdLevels.passLevel();
}

inline
int LoggerManager::defaultRecordThresholdLevel() const
{
    return d_defaultThresholdLevels.recordLevel();
}

inline
int LoggerManager::defaultTriggerAllThresholdLevel() const
{
    return d_defaultThresholdLevels.triggerAllLevel();
}

inline
int LoggerManager::defaultTriggerThresholdLevel() const
{
    return d_defaultThresholdLevels.triggerLevel();
}

inline
bsl::shared_ptr<const Observer>
LoggerManager::findObserver(const bsl::string_view& observerName) const
{
    const BroadcastObserver *observerPtr = d_observer.get();

    return observerPtr->findObserver(observerName);
}

template <class t_OBSERVER>
inline
int LoggerManager::findObserver(
                         bsl::shared_ptr<const t_OBSERVER> *result,
                         const bsl::string_view&            observerName) const
{
    const BroadcastObserver *observerPtr = d_observer.get();

    return observerPtr->findObserver(result, observerName);
}

inline
int LoggerManager::maxNumCategories() const
{
    return static_cast<int>(d_maxNumCategoriesMinusOne) + 1;
}

inline
int LoggerManager::numCategories() const
{
    return d_categoryManager.length();
}

inline
const RuleSet& LoggerManager::ruleSet() const
{
    return d_categoryManager.ruleSet();
}

template <class t_CATEGORY_VISITOR>
inline
void LoggerManager::visitCategories(const t_CATEGORY_VISITOR& visitor) const
{
    d_categoryManager.visitCategories(visitor);
}

template <class t_OBSERVER_VISITOR>
inline
void LoggerManager::visitObservers(
           BSLS_COMPILERFEATURES_FORWARD_REF(t_OBSERVER_VISITOR) visitor) const
{
    d_observer->visitObservers(
                   BSLS_COMPILERFEATURES_FORWARD(t_OBSERVER_VISITOR, visitor));
}

                        // ------------------------------
                        // class LoggerManagerScopedGuard
                        // ------------------------------

// CREATORS
#ifndef BDE_OMIT_INTERNAL_DEPRECATED
inline
LoggerManagerScopedGuard::LoggerManagerScopedGuard(
                            Observer                          *observer,
                            const LoggerManagerConfiguration&  configuration,
                            bslma::Allocator                  *globalAllocator)
{
    LoggerManager::initSingleton(observer, configuration, globalAllocator);
}
#endif  // BDE_OMIT_INTERNAL_DEPRECATED

inline
LoggerManagerScopedGuard::LoggerManagerScopedGuard(
                            const LoggerManagerConfiguration&  configuration,
                            bslma::Allocator                  *globalAllocator)
{
    LoggerManager::initSingleton(configuration, globalAllocator);
}

inline
LoggerManagerScopedGuard::~LoggerManagerScopedGuard()
{
    LoggerManager::shutDownSingleton();
}

#ifndef BDE_OMIT_INTERNAL_DEPRECATED

                        // -------------------------------
                        // class LoggerManagerCategoryIter
                        // -------------------------------

// CREATORS
inline
LoggerManagerCategoryIter::LoggerManagerCategoryIter(
                                            const LoggerManager& loggerManager)
: d_iter(loggerManager.d_categoryManager)
{
}

// MANIPULATORS
inline
void LoggerManagerCategoryIter::operator++()
{
    ++d_iter;
}

// ACCESSORS
inline
LoggerManagerCategoryIter::operator const void *() const
{
    return d_iter;
}

inline
const Category& LoggerManagerCategoryIter::operator()() const
{
    return d_iter();
}

                        // --------------------------------
                        // class LoggerManagerCategoryManip
                        // --------------------------------

// CREATORS
inline
LoggerManagerCategoryManip::LoggerManagerCategoryManip(
                                                  LoggerManager *loggerManager)
: d_manip(&loggerManager->d_categoryManager)
{
}

// MANIPULATORS
inline
void LoggerManagerCategoryManip::advance()
{
    d_manip.advance();
}

inline
Category& LoggerManagerCategoryManip::operator()()
{
    return d_manip();
}

// ACCESSORS
inline
LoggerManagerCategoryManip::operator const void *() const
{
    return d_manip;
}

#endif  // BDE_OMIT_INTERNAL_DEPRECATED

}  // close package namespace
}  // close enterprise namespace

#endif

// ----------------------------------------------------------------------------
// Copyright 2017 Bloomberg Finance L.P.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ----------------------------- END-OF-FILE ----------------------------------
