diff options
Diffstat (limited to 'xpcom/base/Logging.h')
-rw-r--r-- | xpcom/base/Logging.h | 316 |
1 files changed, 316 insertions, 0 deletions
diff --git a/xpcom/base/Logging.h b/xpcom/base/Logging.h new file mode 100644 index 0000000000..c417bdbf77 --- /dev/null +++ b/xpcom/base/Logging.h @@ -0,0 +1,316 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 mozilla_logging_h +#define mozilla_logging_h + +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <stdarg.h> + +#include "mozilla/Assertions.h" +#include "mozilla/Atomics.h" +#include "mozilla/Attributes.h" +#include "mozilla/Likely.h" + +// We normally have logging enabled everywhere, but measurements showed that +// having logging enabled on Android is quite expensive (hundreds of kilobytes +// for both the format strings for logging and the code to perform all the +// logging calls). Because retrieving logs from a mobile device is +// comparatively more difficult for Android than it is for desktop and because +// desktop machines tend to be less space/bandwidth-constrained than Android +// devices, we've chosen to leave logging enabled on desktop, but disabled on +// Android. Given that logging can still be useful for development purposes, +// however, we leave logging enabled on Android developer builds. +#if !defined(ANDROID) || !defined(RELEASE_OR_BETA) +# define MOZ_LOGGING_ENABLED 1 +#else +# define MOZ_LOGGING_ENABLED 0 +#endif + +// The mandatory extension we add to log files. Note that rotate will append +// the file piece number still at the end. +#define MOZ_LOG_FILE_EXTENSION ".moz_log" + +// Token for Process ID substitution. +#define MOZ_LOG_PID_TOKEN "%PID" + +namespace mozilla { + +class TimeStamp; + +// While not a 100% mapping to PR_LOG's numeric values, mozilla::LogLevel does +// maintain a direct mapping for the Disabled, Debug and Verbose levels. +// +// Mappings of LogLevel to PR_LOG's numeric values: +// +// +---------+------------------+-----------------+ +// | Numeric | NSPR Logging | Mozilla Logging | +// +---------+------------------+-----------------+ +// | 0 | PR_LOG_NONE | Disabled | +// | 1 | PR_LOG_ALWAYS | Error | +// | 2 | PR_LOG_ERROR | Warning | +// | 3 | PR_LOG_WARNING | Info | +// | 4 | PR_LOG_DEBUG | Debug | +// | 5 | PR_LOG_DEBUG + 1 | Verbose | +// +---------+------------------+-----------------+ +// +enum class LogLevel { + Disabled = 0, + Error, + Warning, + Info, + Debug, + Verbose, +}; + +/** + * Safely converts an integer into a valid LogLevel. + */ +LogLevel ToLogLevel(int32_t aLevel); + +class LogModule { + public: + ~LogModule() { ::free(mName); } + + /** + * Retrieves the module with the given name. If it does not already exist + * it will be created. + * + * @param aName The name of the module. + * @return A log module for the given name. This may be shared. + */ + static LogModule* Get(const char* aName); + + /** + * Logging processes -MOZ_LOG and -MOZ_LOG_FILE command line arguments + * to override or set modules and the file as if passed through MOZ_LOG + * and MOZ_LOG_FILE env vars. It's fine to pass (0, nullptr) if args + * are not accessible in the caller's context, it will just do nothing. + * Note that the args take effect (are processed) only when this function + * is called the first time. + */ + static void Init(int argc, char* argv[]); + + /** + * Sets the log file to the given filename. + */ + static void SetLogFile(const char* aFilename); + + /** + * @param aBuffer - pointer to a buffer + * @param aLength - the length of the buffer + * + * @return the actual length of the filepath. + */ + static uint32_t GetLogFile(char* aBuffer, size_t aLength); + + /** + * @param aAddTimestamp If we should log a time stamp with every message. + */ + static void SetAddTimestamp(bool aAddTimestamp); + + /** + * @param aIsSync If we should flush the file after every logged message. + */ + static void SetIsSync(bool aIsSync); + + /** + * Indicates whether or not the given log level is enabled. + */ + bool ShouldLog(LogLevel aLevel) const { return mLevel >= aLevel; } + + /** + * Retrieves the log module's current level. + */ + LogLevel Level() const { return mLevel; } + + /** + * Sets the log module's level. + */ + void SetLevel(LogLevel level); + + /** + * Print a log message for this module. + */ + void Printv(LogLevel aLevel, const char* aFmt, va_list aArgs) const + MOZ_FORMAT_PRINTF(3, 0); + + void Printv(LogLevel aLevel, const TimeStamp* aStart, const char* aFmt, + va_list aArgs) const MOZ_FORMAT_PRINTF(4, 0); + + /** + * Retrieves the module name. + */ + const char* Name() const { return mName; } + + private: + friend class LogModuleManager; + + explicit LogModule(const char* aName, LogLevel aLevel) + : mName(strdup(aName)), mLevel(aLevel) {} + + LogModule(LogModule&) = delete; + LogModule& operator=(const LogModule&) = delete; + + char* mName; + + Atomic<LogLevel, Relaxed> mLevel; +}; + +/** + * Helper class that lazy loads the given log module. This is safe to use for + * declaring static references to log modules and can be used as a replacement + * for accessing a LogModule directly. + * + * Example usage: + * static LazyLogModule sLayoutLog("layout"); + * + * void Foo() { + * MOZ_LOG(sLayoutLog, LogLevel::Verbose, ("Entering foo")); + * } + */ +class LazyLogModule final { + public: + explicit constexpr LazyLogModule(const char* aLogName) + : mLogName(aLogName), mLog(nullptr) {} + + MOZ_NEVER_INLINE_DEBUG operator LogModule*() { + // NB: The use of an atomic makes the reading and assignment of mLog + // thread-safe. There is a small chance that mLog will be set more + // than once, but that's okay as it will be set to the same LogModule + // instance each time. Also note LogModule::Get is thread-safe. + LogModule* tmp = mLog; + if (MOZ_UNLIKELY(!tmp)) { + tmp = LogModule::Get(mLogName); + mLog = tmp; + } + + return tmp; + } + + private: + const char* const mLogName; + + Atomic<LogModule*, ReleaseAcquire> mLog; +}; + +namespace detail { + +inline bool log_test(const LogModule* module, LogLevel level) { + MOZ_ASSERT(level != LogLevel::Disabled); + return module && module->ShouldLog(level); +} + +void log_print(const LogModule* aModule, LogLevel aLevel, const char* aFmt, ...) + MOZ_FORMAT_PRINTF(3, 4); + +void log_print(const LogModule* aModule, LogLevel aLevel, TimeStamp* aStart, + const char* aFmt, ...) MOZ_FORMAT_PRINTF(4, 5); +} // namespace detail + +} // namespace mozilla + +// Helper macro used convert MOZ_LOG's third parameter, |_args|, from a +// parenthesized form to a varargs form. For example: +// ("%s", "a message") => "%s", "a message" +#define MOZ_LOG_EXPAND_ARGS(...) __VA_ARGS__ + +#if MOZ_LOGGING_ENABLED +# define MOZ_LOG_TEST(_module, _level) \ + MOZ_UNLIKELY(mozilla::detail::log_test(_module, _level)) +#else +// Define away MOZ_LOG_TEST here so the compiler will fold away entire +// logging blocks via dead code elimination, e.g.: +// +// if (MOZ_LOG_TEST(...)) { +// ...compute things to log and log them... +// } +# define MOZ_LOG_TEST(_module, _level) false +#endif + +// The natural definition of the MOZ_LOG macro would expand to: +// +// do { +// if (MOZ_LOG_TEST(_module, _level)) { +// mozilla::detail::log_print(_module, ...); +// } +// } while (0) +// +// However, since _module is a LazyLogModule, and we need to call +// LazyLogModule::operator() to get a LogModule* for the MOZ_LOG_TEST +// macro and for the logging call, we'll wind up doing *two* calls, one +// for each, rather than a single call. The compiler is not able to +// fold the two calls into one, and the extra call can have a +// significant effect on code size. (Making LazyLogModule::operator() a +// `const` function does not have any effect.) +// +// Therefore, we will have to make a single call ourselves. But again, +// the natural definition: +// +// do { +// ::mozilla::LogModule* real_module = _module; +// if (MOZ_LOG_TEST(real_module, _level)) { +// mozilla::detail::log_print(real_module, ...); +// } +// } while (0) +// +// also has a problem: if logging is disabled, then we will call +// LazyLogModule::operator() unnecessarily, and the compiler will not be +// able to optimize away the call as dead code. We would like to avoid +// such a scenario, as the whole point of disabling logging is for the +// logging statements to not generate any code. +// +// Therefore, we need different definitions of MOZ_LOG, depending on +// whether logging is enabled or not. (We need an actual definition of +// MOZ_LOG even when logging is disabled to ensure the compiler sees that +// variables only used during logging code are actually used, even if the +// code will never be executed.) Hence, the following code. +// +// MOZ_LOG_DURATION takes a start time, and will generate a time range +// in the logs. Also, if 'profilermarkers' is used in the env var +// MOZ_LOG, MOZ_LOG_DURATION will generate a marker with a time +// duration instead of a single point in time. +#if MOZ_LOGGING_ENABLED +# define MOZ_LOG(_module, _level, _args) \ + do { \ + const ::mozilla::LogModule* moz_real_module = _module; \ + if (MOZ_LOG_TEST(moz_real_module, _level)) { \ + mozilla::detail::log_print(moz_real_module, _level, \ + MOZ_LOG_EXPAND_ARGS _args); \ + } \ + } while (0) +# define MOZ_LOG_DURATION(_module, _level, start, _args) \ + do { \ + const ::mozilla::LogModule* moz_real_module = _module; \ + if (MOZ_LOG_TEST(moz_real_module, _level)) { \ + mozilla::detail::log_print(moz_real_module, _level, start, \ + MOZ_LOG_EXPAND_ARGS _args); \ + } \ + } while (0) +#else +# define MOZ_LOG(_module, _level, _args) \ + do { \ + if (MOZ_LOG_TEST(_module, _level)) { \ + mozilla::detail::log_print(_module, _level, \ + MOZ_LOG_EXPAND_ARGS _args); \ + } \ + } while (0) +# define MOZ_LOG_DURATION(_module, _level, start, _args) \ + do { \ + if (MOZ_LOG_TEST(_module, _level)) { \ + mozilla::detail::log_print(_module, _level, start, \ + MOZ_LOG_EXPAND_ARGS _args); \ + } \ + } while (0) +#endif + +// This #define is a Logging.h-only knob! Don't encourage people to get fancy +// with their log definitions by exporting it outside of Logging.h. +#undef MOZ_LOGGING_ENABLED + +#endif // mozilla_logging_h |