diff options
Diffstat (limited to 'src/lib/log/log_formatter.h')
-rw-r--r-- | src/lib/log/log_formatter.h | 263 |
1 files changed, 263 insertions, 0 deletions
diff --git a/src/lib/log/log_formatter.h b/src/lib/log/log_formatter.h new file mode 100644 index 0000000..7fc67f1 --- /dev/null +++ b/src/lib/log/log_formatter.h @@ -0,0 +1,263 @@ +// Copyright (C) 2011-2020 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef LOG_FORMATTER_H +#define LOG_FORMATTER_H + +#include <cstddef> +#include <string> +#include <iostream> + +#include <exceptions/exceptions.h> +#include <log/logger_level.h> + +#include <boost/make_shared.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/lexical_cast.hpp> + +namespace isc { +namespace log { + +/// \brief Format Failure +/// +/// This exception is used to wrap a bad_lexical_cast exception thrown during +/// formatting an argument. + +class FormatFailure : public isc::Exception { +public: + FormatFailure(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) + {} +}; + + +/// \brief Mismatched Placeholders +/// +/// This exception is used when the number of placeholders do not match +/// the number of arguments passed to the formatter. + +class MismatchedPlaceholders : public isc::Exception { +public: + MismatchedPlaceholders(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) + {} +}; + + +/// +/// \brief Internal excess placeholder checker +/// +/// This is used internally by the Formatter to check for excess +/// placeholders (and fewer arguments). +void +checkExcessPlaceholders(std::string& message, unsigned int placeholder); + +/// +/// \brief The internal replacement routine +/// +/// This is used internally by the Formatter. Replaces a placeholder +/// in the message by replacement. If the placeholder is not found, +/// it adds a complain at the end. +void +replacePlaceholder(std::string& message, const std::string& replacement, + const unsigned placeholder); + +/// +/// \brief The log message formatter +/// +/// This class allows us to format logging messages conveniently. We +/// call something like logger.warn(WARN_MSG).arg(15).arg(dnsMsg). This +/// outputs some text with placeholders replaced by the arguments, if +/// the logging verbosity is at WARN level or more. +/// +/// To make this work, we use the Formatter. The warn (or whatever logging +/// function) returns a Formatter object. That one holds the string to be +/// output with the placeholders. It also remembers if there should be any +/// output at all (eg. if the logging is enabled for this level). When there's +/// no .arg call on the object, it is destroyed right away and we use the +/// destructor to output the text (but only in case we should output anything). +/// +/// If there's an .arg call, we return reference to the same object, so another +/// .arg can be called on it. After the last .arg call is done, the object is +/// destroyed and, again, we can produce the output. +/// +/// Of course, if the logging is turned off, we don't bother with any replacing +/// and just return. +/// +/// User of logging code should not really care much about this class, only +/// call the .arg method to generate the correct output. +/// +/// The class is a template to allow easy testing. Also, we want everything +/// here in the header anyway and it doesn't depend on the details of what +/// Logger really is, so it doesn't hurt anything. +/// +/// Also, if you are interested in the internals, you might find the copy +/// constructor a bit strange. It deactivates the original formatter. We don't +/// really want to support copying of the Formatter by user, but C++ needs a +/// copy constructor when returning from the logging functions, so we need one. +/// And if we did not deactivate the original Formatter, that one would get +/// destroyed before any call to .arg, producing an output, and then the one +/// the .arg calls are called on would get destroyed as well, producing output +/// again. So, think of this behavior as soul moving from one to another. +template<class Logger> class Formatter { +private: + /// \brief The logger we will use to output the final message. + /// + /// If NULL, we are not active and should not produce anything. + mutable Logger* logger_; + + /// \brief Message severity + Severity severity_; + + /// \brief The messages with %1, %2... placeholders + boost::shared_ptr<std::string> message_; + + /// \brief Which will be the next placeholder to replace + unsigned nextPlaceholder_; + + +public: + /// \brief Constructor of "active" formatter + /// + /// This will create a formatter. If the arguments are set, it + /// will be active (will produce output). If you leave them all as NULL, + /// it will create an inactive Formatter -- one that'll produce no output. + /// + /// It is not expected to be called by user of logging system directly. + /// + /// \param severity The severity of the message (DEBUG, ERROR etc.) + /// \param message The message with placeholders. We take ownership of + /// it and we will modify the string. Must not be NULL unless + /// logger is also NULL, but it's not checked. + /// \param logger The logger where the final output will go, or NULL + /// if no output is wanted. + Formatter(const Severity& severity = NONE, + boost::shared_ptr<std::string> message = boost::make_shared<std::string>(), + Logger* logger = NULL) : + logger_(logger), severity_(severity), message_(message), + nextPlaceholder_(0) { + } + + /// \brief Copy constructor + /// + /// "Control" is passed to the created object in that it is the created object + /// that will have responsibility for outputting the formatted message - the + /// object being copied relinquishes that responsibility. + Formatter(const Formatter& other) : + logger_(other.logger_), severity_(other.severity_), + message_(other.message_), nextPlaceholder_(other.nextPlaceholder_) { + other.logger_ = NULL; + } + + /// \brief Destructor. + // + /// This is the place where output happens if the formatter is active. + ~Formatter() { + if (logger_) { + try { + checkExcessPlaceholders(*message_, ++nextPlaceholder_); + logger_->output(severity_, *message_); + } catch (...) { + // Catch and ignore all exceptions here. + } + } + } + + /// \brief Assignment operator + /// + /// Essentially the same function as the assignment operator - the object being + /// assigned to takes responsibility for outputting the message. + Formatter& operator =(const Formatter& other) { + if (&other != this) { + logger_ = other.logger_; + severity_ = other.severity_; + message_ = other.message_; + nextPlaceholder_ = other.nextPlaceholder_; + other.logger_ = NULL; + } + + return *this; + } + + /// \brief Replaces another placeholder + /// + /// Replaces another placeholder and returns a new formatter with it. + /// Deactivates the current formatter. In case the formatter is not active, + /// only produces another inactive formatter. + /// + /// \param value The argument to place into the placeholder. + template<class Arg> Formatter& arg(const Arg& value) { + if (logger_) { + try { + return (arg(boost::lexical_cast<std::string>(value))); + } catch (const boost::bad_lexical_cast& ex) { + // The formatting of the log message got wrong, we don't want + // to output it. + deactivate(); + // A bad_lexical_cast during a conversion to a string is + // *extremely* unlikely to fail. However, there is nothing + // in the documentation that rules it out, so we need to handle + // it. As it is a potentially very serious problem, throw the + // exception detailing the problem with as much information as + // we can. (Note that this does not include 'value' - + // boost::lexical_cast failed to convert it to a string, so an + // attempt to do so here would probably fail as well.) + isc_throw(FormatFailure, "bad_lexical_cast in call to " + "Formatter::arg(): " << ex.what()); + } + } else { + return (*this); + } + } + + /// \brief String version of arg. + /// + /// \param arg The text to place into the placeholder. + Formatter& arg(const std::string& arg) { + if (logger_) { + // Note that this method does a replacement and returns the + // modified string. If there are multiple invocations of arg() (e.g. + // logger.info(msgid).arg(xxx).arg(yyy)...), each invocation + // operates on the string returned by the previous one. This + // sequential operation means that if we had a message like "%1 %2", + // and called .arg("%2").arg(42), we would get "42 42"; the first + // call replaces the %1" with "%2" and the second replaces all + // occurrences of "%2" with 42. (Conversely, the sequence + // .arg(42).arg("%1") would return "42 %1" - there are no recursive + // replacements). + try { + replacePlaceholder(*message_, arg, ++nextPlaceholder_); + } catch (...) { + // Something went wrong here, the log message is broken, so + // we don't want to output it, nor we want to check all the + // placeholders were used (because they won't be). + deactivate(); + throw; + } + } + return (*this); + } + + /// \brief Turn off the output of this logger. + /// + /// If the logger would output anything at the end, now it won't. + /// Also, this turns off the strict checking of placeholders, if + /// it is compiled in. + /// + /// The expected use is when there was an exception processing + /// the arguments for the message. + void deactivate() { + if (logger_) { + message_.reset(); + logger_ = NULL; + } + } +}; + +} // namespace log +} // namespace isc + +#endif |