diff options
Diffstat (limited to 'utils/logging')
-rw-r--r-- | utils/logging/Kyuafile | 6 | ||||
-rw-r--r-- | utils/logging/Makefile.am.inc | 53 | ||||
-rw-r--r-- | utils/logging/macros.hpp | 68 | ||||
-rw-r--r-- | utils/logging/macros_test.cpp | 115 | ||||
-rw-r--r-- | utils/logging/operations.cpp | 303 | ||||
-rw-r--r-- | utils/logging/operations.hpp | 54 | ||||
-rw-r--r-- | utils/logging/operations_fwd.hpp | 54 | ||||
-rw-r--r-- | utils/logging/operations_test.cpp | 354 |
8 files changed, 1007 insertions, 0 deletions
diff --git a/utils/logging/Kyuafile b/utils/logging/Kyuafile new file mode 100644 index 000000000000..0853a335c6ae --- /dev/null +++ b/utils/logging/Kyuafile @@ -0,0 +1,6 @@ +syntax(2) + +test_suite("kyua") + +atf_test_program{name="macros_test"} +atf_test_program{name="operations_test"} diff --git a/utils/logging/Makefile.am.inc b/utils/logging/Makefile.am.inc new file mode 100644 index 000000000000..7d88f16859d7 --- /dev/null +++ b/utils/logging/Makefile.am.inc @@ -0,0 +1,53 @@ +# Copyright 2011 The Kyua Authors. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of Google Inc. nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +UTILS_CFLAGS += $(LUTOK_CFLAGS) +UTILS_LIBS += $(LUTOK_LIBS) + +libutils_a_CPPFLAGS += $(LUTOK_CFLAGS) +libutils_a_SOURCES += utils/logging/macros.hpp +libutils_a_SOURCES += utils/logging/operations.cpp +libutils_a_SOURCES += utils/logging/operations.hpp +libutils_a_SOURCES += utils/logging/operations_fwd.hpp + +if WITH_ATF +tests_utils_loggingdir = $(pkgtestsdir)/utils/logging + +tests_utils_logging_DATA = utils/logging/Kyuafile +EXTRA_DIST += $(tests_utils_logging_DATA) + +tests_utils_logging_PROGRAMS = utils/logging/macros_test +utils_logging_macros_test_SOURCES = utils/logging/macros_test.cpp +utils_logging_macros_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS) +utils_logging_macros_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS) + +tests_utils_logging_PROGRAMS += utils/logging/operations_test +utils_logging_operations_test_SOURCES = utils/logging/operations_test.cpp +utils_logging_operations_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS) +utils_logging_operations_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS) +endif diff --git a/utils/logging/macros.hpp b/utils/logging/macros.hpp new file mode 100644 index 000000000000..73dd0a60ef87 --- /dev/null +++ b/utils/logging/macros.hpp @@ -0,0 +1,68 @@ +// Copyright 2011 The Kyua Authors. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// * Neither the name of Google Inc. nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +/// \file utils/logging/macros.hpp +/// Convenience macros to simplify usage of the logging library. +/// +/// This file <em>must not be included from other header files</em>. + +#if !defined(UTILS_LOGGING_MACROS_HPP) +#define UTILS_LOGGING_MACROS_HPP + +#include "utils/logging/operations.hpp" + + +/// Logs a debug message. +/// +/// \param message The message to log. +#define LD(message) utils::logging::log(utils::logging::level_debug, \ + __FILE__, __LINE__, message) + + +/// Logs an error message. +/// +/// \param message The message to log. +#define LE(message) utils::logging::log(utils::logging::level_error, \ + __FILE__, __LINE__, message) + + +/// Logs an informational message. +/// +/// \param message The message to log. +#define LI(message) utils::logging::log(utils::logging::level_info, \ + __FILE__, __LINE__, message) + + +/// Logs a warning message. +/// +/// \param message The message to log. +#define LW(message) utils::logging::log(utils::logging::level_warning, \ + __FILE__, __LINE__, message) + + +#endif // !defined(UTILS_LOGGING_MACROS_HPP) diff --git a/utils/logging/macros_test.cpp b/utils/logging/macros_test.cpp new file mode 100644 index 000000000000..fe3ee63cd533 --- /dev/null +++ b/utils/logging/macros_test.cpp @@ -0,0 +1,115 @@ +// Copyright 2011 The Kyua Authors. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// * Neither the name of Google Inc. nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "utils/logging/macros.hpp" + +#include <fstream> +#include <string> + +#include <atf-c++.hpp> + +#include "utils/datetime.hpp" +#include "utils/fs/path.hpp" +#include "utils/logging/operations.hpp" + +namespace datetime = utils::datetime; +namespace fs = utils::fs; +namespace logging = utils::logging; + + +ATF_TEST_CASE_WITHOUT_HEAD(ld); +ATF_TEST_CASE_BODY(ld) +{ + logging::set_persistency("debug", fs::path("test.log")); + datetime::set_mock_now(2011, 2, 21, 18, 30, 0, 0); + LD("Debug message"); + + std::ifstream input("test.log"); + ATF_REQUIRE(input); + + std::string line; + ATF_REQUIRE(std::getline(input, line).good()); + ATF_REQUIRE_MATCH("20110221-183000 D .*: Debug message", line); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(le); +ATF_TEST_CASE_BODY(le) +{ + logging::set_persistency("debug", fs::path("test.log")); + datetime::set_mock_now(2011, 2, 21, 18, 30, 0, 0); + LE("Error message"); + + std::ifstream input("test.log"); + ATF_REQUIRE(input); + + std::string line; + ATF_REQUIRE(std::getline(input, line).good()); + ATF_REQUIRE_MATCH("20110221-183000 E .*: Error message", line); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(li); +ATF_TEST_CASE_BODY(li) +{ + logging::set_persistency("debug", fs::path("test.log")); + datetime::set_mock_now(2011, 2, 21, 18, 30, 0, 0); + LI("Info message"); + + std::ifstream input("test.log"); + ATF_REQUIRE(input); + + std::string line; + ATF_REQUIRE(std::getline(input, line).good()); + ATF_REQUIRE_MATCH("20110221-183000 I .*: Info message", line); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(lw); +ATF_TEST_CASE_BODY(lw) +{ + logging::set_persistency("debug", fs::path("test.log")); + datetime::set_mock_now(2011, 2, 21, 18, 30, 0, 0); + LW("Warning message"); + + std::ifstream input("test.log"); + ATF_REQUIRE(input); + + std::string line; + ATF_REQUIRE(std::getline(input, line).good()); + ATF_REQUIRE_MATCH("20110221-183000 W .*: Warning message", line); +} + + +ATF_INIT_TEST_CASES(tcs) +{ + ATF_ADD_TEST_CASE(tcs, ld); + ATF_ADD_TEST_CASE(tcs, le); + ATF_ADD_TEST_CASE(tcs, li); + ATF_ADD_TEST_CASE(tcs, lw); +} diff --git a/utils/logging/operations.cpp b/utils/logging/operations.cpp new file mode 100644 index 000000000000..88f25361fa18 --- /dev/null +++ b/utils/logging/operations.cpp @@ -0,0 +1,303 @@ +// Copyright 2011 The Kyua Authors. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// * Neither the name of Google Inc. nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "utils/logging/operations.hpp" + +extern "C" { +#include <unistd.h> +} + +#include <stdexcept> +#include <string> +#include <utility> +#include <vector> + +#include "utils/datetime.hpp" +#include "utils/format/macros.hpp" +#include "utils/fs/path.hpp" +#include "utils/optional.ipp" +#include "utils/sanity.hpp" +#include "utils/stream.hpp" + +namespace datetime = utils::datetime; +namespace fs = utils::fs; +namespace logging = utils::logging; + +using utils::none; +using utils::optional; + + +/// The general idea for the application-wide logging goes like this: +/// +/// 1. The application starts. Logging is initialized to capture _all_ log +/// messages into memory regardless of their level by issuing a call to the +/// set_inmemory() function. +/// +/// 2. The application offers the user a way to select the logging level and a +/// file into which to store the log. +/// +/// 3. The application calls set_persistency providing a new log level and a log +/// file. This must be done as early as possible, to minimize the chances of an +/// early crash not capturing any logs. +/// +/// 4. At this point, any log messages stored into memory are flushed to disk +/// respecting the provided log level. +/// +/// 5. The internal state of the logging module is updated to only capture +/// messages that are of the provided log level (or below) and is configured to +/// directly send messages to disk. +/// +/// 6. The user may choose to call set_inmemory() again at a later stage, which +/// will cause the log to be flushed and messages to be recorded in memory +/// again. This is useful in case the logs are being sent to either stdout or +/// stderr and the process forks and wants to keep those child channels +/// unpolluted. +/// +/// The call to set_inmemory() should only be performed by the user-facing +/// application. Tests should skip this call so that the logging messages go to +/// stderr by default, thus generating a useful log to debug the tests. + + +namespace { + + +/// Constant string to strftime to format timestamps. +static const char* timestamp_format = "%Y%m%d-%H%M%S"; + + +/// Mutable global state. +struct global_state { + /// Current log level. + logging::level log_level; + + /// Indicates whether set_persistency() will be called automatically or not. + bool auto_set_persistency; + + /// First time recorded by the logging module. + optional< datetime::timestamp > first_timestamp; + + /// In-memory record of log entries before persistency is enabled. + std::vector< std::pair< logging::level, std::string > > backlog; + + /// Stream to the currently open log file. + std::auto_ptr< std::ostream > logfile; + + global_state() : + log_level(logging::level_debug), + auto_set_persistency(true) + { + } +}; + + +/// Single instance of the mutable global state. +/// +/// Note that this is a raw pointer that we intentionally leak. We must do +/// this, instead of making all of the singleton's members static values, +/// because we want other destructors in the program to be able to log critical +/// conditions. If we use complex types in this translation unit, they may be +/// destroyed before the logging methods in the destructors get a chance to run +/// thus resulting in a premature crash. By using a plain pointer, we ensure +/// this state never gets cleaned up. +static struct global_state* globals_singleton = NULL; + + +/// Gets the singleton instance of global_state. +/// +/// \return A pointer to the unique global_state instance. +static struct global_state* +get_globals(void) +{ + if (globals_singleton == NULL) { + globals_singleton = new global_state(); + } + return globals_singleton; +} + + +/// Converts a level to a printable character. +/// +/// \param level The level to convert. +/// +/// \return The printable character, to be used in log messages. +static char +level_to_char(const logging::level level) +{ + switch (level) { + case logging::level_error: return 'E'; + case logging::level_warning: return 'W'; + case logging::level_info: return 'I'; + case logging::level_debug: return 'D'; + default: UNREACHABLE; + } +} + + +} // anonymous namespace + + +/// Generates a standard log name. +/// +/// This always adds the same timestamp to the log name for a particular run. +/// Also, the timestamp added to the file name corresponds to the first +/// timestamp recorded by the module; it does not necessarily contain the +/// current value of "now". +/// +/// \param logdir The path to the directory in which to place the log. +/// \param progname The name of the program that is generating the log. +/// +/// \return A string representation of the log name based on \p logdir and +/// \p progname. +fs::path +logging::generate_log_name(const fs::path& logdir, const std::string& progname) +{ + struct global_state* globals = get_globals(); + + if (!globals->first_timestamp) + globals->first_timestamp = datetime::timestamp::now(); + // Update kyua(1) if you change the name format. + return logdir / (F("%s.%s.log") % progname % + globals->first_timestamp.get().strftime(timestamp_format)); +} + + +/// Logs an entry to the log file. +/// +/// If the log is not yet set to persistent mode, the entry is recorded in the +/// in-memory backlog. Otherwise, it is just written to disk. +/// +/// \param message_level The level of the entry. +/// \param file The file from which the log message is generated. +/// \param line The line from which the log message is generated. +/// \param user_message The raw message to store. +void +logging::log(const level message_level, const char* file, const int line, + const std::string& user_message) +{ + struct global_state* globals = get_globals(); + + const datetime::timestamp now = datetime::timestamp::now(); + if (!globals->first_timestamp) + globals->first_timestamp = now; + + if (globals->auto_set_persistency) { + // These values are hardcoded here for testing purposes. The + // application should call set_inmemory() by itself during + // initialization to avoid this, so that it has explicit control on how + // the call to set_persistency() happens. + set_persistency("debug", fs::path("/dev/stderr")); + globals->auto_set_persistency = false; + } + + if (message_level > globals->log_level) + return; + + // Update doc/troubleshooting.texi if you change the log format. + const std::string message = F("%s %s %s %s:%s: %s") % + now.strftime(timestamp_format) % level_to_char(message_level) % + ::getpid() % file % line % user_message; + if (globals->logfile.get() == NULL) + globals->backlog.push_back(std::make_pair(message_level, message)); + else { + INV(globals->backlog.empty()); + (*globals->logfile) << message << '\n'; + globals->logfile->flush(); + } +} + + +/// Sets the logging to record messages in memory for later flushing. +/// +/// Can be called after set_persistency to flush logs and set recording to be +/// in-memory again. +void +logging::set_inmemory(void) +{ + struct global_state* globals = get_globals(); + + globals->auto_set_persistency = false; + + if (globals->logfile.get() != NULL) { + INV(globals->backlog.empty()); + globals->logfile->flush(); + globals->logfile.reset(NULL); + } +} + + +/// Makes the log persistent. +/// +/// Calling this function flushes the in-memory log, if any, to disk and sets +/// the logging module to send log entries to disk from this point onwards. +/// There is no way back, and the caller program should execute this function as +/// early as possible to ensure that a crash at startup does not discard too +/// many useful log entries. +/// +/// Any log entries above the provided new_level are discarded. +/// +/// \param new_level The new log level. +/// \param path The file to write the logs to. +/// +/// \throw std::range_error If the given log level is invalid. +/// \throw std::runtime_error If the given file cannot be created. +void +logging::set_persistency(const std::string& new_level, const fs::path& path) +{ + struct global_state* globals = get_globals(); + + globals->auto_set_persistency = false; + + PRE(globals->logfile.get() == NULL); + + // Update doc/troubleshooting.info if you change the log levels. + if (new_level == "debug") + globals->log_level = level_debug; + else if (new_level == "error") + globals->log_level = level_error; + else if (new_level == "info") + globals->log_level = level_info; + else if (new_level == "warning") + globals->log_level = level_warning; + else + throw std::range_error(F("Unrecognized log level '%s'") % new_level); + + try { + globals->logfile = utils::open_ostream(path); + } catch (const std::runtime_error& unused_error) { + throw std::runtime_error(F("Failed to create log file %s") % path); + } + + for (std::vector< std::pair< logging::level, std::string > >::const_iterator + iter = globals->backlog.begin(); iter != globals->backlog.end(); + ++iter) { + if ((*iter).first <= globals->log_level) + (*globals->logfile) << (*iter).second << '\n'; + } + globals->logfile->flush(); + globals->backlog.clear(); +} diff --git a/utils/logging/operations.hpp b/utils/logging/operations.hpp new file mode 100644 index 000000000000..1bb72219dcae --- /dev/null +++ b/utils/logging/operations.hpp @@ -0,0 +1,54 @@ +// Copyright 2011 The Kyua Authors. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// * Neither the name of Google Inc. nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +/// \file utils/logging/operations.hpp +/// Stateless logging facilities. + +#if !defined(UTILS_LOGGING_OPERATIONS_HPP) +#define UTILS_LOGGING_OPERATIONS_HPP + +#include "utils/logging/operations_fwd.hpp" + +#include <string> + +#include "utils/fs/path_fwd.hpp" + +namespace utils { +namespace logging { + + +fs::path generate_log_name(const fs::path&, const std::string&); +void log(const level, const char*, const int, const std::string&); +void set_inmemory(void); +void set_persistency(const std::string&, const fs::path&); + + +} // namespace logging +} // namespace utils + +#endif // !defined(UTILS_LOGGING_OPERATIONS_HPP) diff --git a/utils/logging/operations_fwd.hpp b/utils/logging/operations_fwd.hpp new file mode 100644 index 000000000000..0e3edd7993ec --- /dev/null +++ b/utils/logging/operations_fwd.hpp @@ -0,0 +1,54 @@ +// Copyright 2015 The Kyua Authors. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// * Neither the name of Google Inc. nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +/// \file utils/logging/operations_fwd.hpp +/// Forward declarations for utils/logging/operations.hpp + +#if !defined(UTILS_LOGGING_OPERATIONS_FWD_HPP) +#define UTILS_LOGGING_OPERATIONS_FWD_HPP + +namespace utils { +namespace logging { + + +/// Severity levels for log messages. +/// +/// This enumeration must be sorted from the most severe message to the least +/// severe. +enum level { + level_error = 0, + level_warning, + level_info, + level_debug, +}; + + +} // namespace logging +} // namespace utils + +#endif // !defined(UTILS_LOGGING_OPERATIONS_FWD_HPP) diff --git a/utils/logging/operations_test.cpp b/utils/logging/operations_test.cpp new file mode 100644 index 000000000000..402f36e62904 --- /dev/null +++ b/utils/logging/operations_test.cpp @@ -0,0 +1,354 @@ +// Copyright 2011 The Kyua Authors. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// * Neither the name of Google Inc. nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "utils/logging/operations.hpp" + +extern "C" { +#include <unistd.h> +} + +#include <fstream> +#include <string> + +#include <atf-c++.hpp> + +#include "utils/datetime.hpp" +#include "utils/format/macros.hpp" +#include "utils/fs/operations.hpp" +#include "utils/fs/path.hpp" + +namespace datetime = utils::datetime; +namespace fs = utils::fs; +namespace logging = utils::logging; + + +ATF_TEST_CASE_WITHOUT_HEAD(generate_log_name__before_log); +ATF_TEST_CASE_BODY(generate_log_name__before_log) +{ + datetime::set_mock_now(2011, 2, 21, 18, 10, 0, 0); + ATF_REQUIRE_EQ(fs::path("/some/dir/foobar.20110221-181000.log"), + logging::generate_log_name(fs::path("/some/dir"), "foobar")); + + datetime::set_mock_now(2011, 2, 21, 18, 10, 1, 987654); + logging::log(logging::level_info, "file", 123, "A message"); + + datetime::set_mock_now(2011, 2, 21, 18, 10, 2, 123); + ATF_REQUIRE_EQ(fs::path("/some/dir/foobar.20110221-181000.log"), + logging::generate_log_name(fs::path("/some/dir"), "foobar")); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(generate_log_name__after_log); +ATF_TEST_CASE_BODY(generate_log_name__after_log) +{ + datetime::set_mock_now(2011, 2, 21, 18, 15, 0, 0); + logging::log(logging::level_info, "file", 123, "A message"); + datetime::set_mock_now(2011, 2, 21, 18, 15, 1, 987654); + logging::log(logging::level_info, "file", 123, "A message"); + + datetime::set_mock_now(2011, 2, 21, 18, 15, 2, 123); + ATF_REQUIRE_EQ(fs::path("/some/dir/foobar.20110221-181500.log"), + logging::generate_log_name(fs::path("/some/dir"), "foobar")); + + datetime::set_mock_now(2011, 2, 21, 18, 15, 3, 1); + logging::log(logging::level_info, "file", 123, "A message"); + + datetime::set_mock_now(2011, 2, 21, 18, 15, 4, 91); + ATF_REQUIRE_EQ(fs::path("/some/dir/foobar.20110221-181500.log"), + logging::generate_log_name(fs::path("/some/dir"), "foobar")); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(log); +ATF_TEST_CASE_BODY(log) +{ + logging::set_inmemory(); + + datetime::set_mock_now(2011, 2, 21, 18, 10, 0, 0); + logging::log(logging::level_debug, "f1", 1, "Debug message"); + + datetime::set_mock_now(2011, 2, 21, 18, 10, 1, 987654); + logging::log(logging::level_error, "f2", 2, "Error message"); + + logging::set_persistency("debug", fs::path("test.log")); + + datetime::set_mock_now(2011, 2, 21, 18, 10, 2, 123); + logging::log(logging::level_info, "f3", 3, "Info message"); + + datetime::set_mock_now(2011, 2, 21, 18, 10, 3, 456); + logging::log(logging::level_warning, "f4", 4, "Warning message"); + + std::ifstream input("test.log"); + ATF_REQUIRE(input); + + const pid_t pid = ::getpid(); + + std::string line; + ATF_REQUIRE(std::getline(input, line).good()); + ATF_REQUIRE_EQ( + (F("20110221-181000 D %s f1:1: Debug message") % pid).str(), line); + ATF_REQUIRE(std::getline(input, line).good()); + ATF_REQUIRE_EQ( + (F("20110221-181001 E %s f2:2: Error message") % pid).str(), line); + ATF_REQUIRE(std::getline(input, line).good()); + ATF_REQUIRE_EQ( + (F("20110221-181002 I %s f3:3: Info message") % pid).str(), line); + ATF_REQUIRE(std::getline(input, line).good()); + ATF_REQUIRE_EQ( + (F("20110221-181003 W %s f4:4: Warning message") % pid).str(), line); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(set_inmemory__reset); +ATF_TEST_CASE_BODY(set_inmemory__reset) +{ + logging::set_persistency("debug", fs::path("test.log")); + + datetime::set_mock_now(2011, 2, 21, 18, 20, 0, 654321); + logging::log(logging::level_debug, "file", 123, "Debug message"); + logging::set_inmemory(); + logging::log(logging::level_debug, "file", 123, "Debug message 2"); + + std::ifstream input("test.log"); + ATF_REQUIRE(input); + + const pid_t pid = ::getpid(); + + std::string line; + ATF_REQUIRE(std::getline(input, line).good()); + ATF_REQUIRE_EQ( + (F("20110221-182000 D %s file:123: Debug message") % pid).str(), line); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(set_persistency__no_backlog); +ATF_TEST_CASE_BODY(set_persistency__no_backlog) +{ + logging::set_persistency("debug", fs::path("test.log")); + + datetime::set_mock_now(2011, 2, 21, 18, 20, 0, 654321); + logging::log(logging::level_debug, "file", 123, "Debug message"); + + std::ifstream input("test.log"); + ATF_REQUIRE(input); + + const pid_t pid = ::getpid(); + + std::string line; + ATF_REQUIRE(std::getline(input, line).good()); + ATF_REQUIRE_EQ( + (F("20110221-182000 D %s file:123: Debug message") % pid).str(), line); +} + + +/// Creates a log for testing purposes, buffering messages on start. +/// +/// \param level The level of the desired log. +/// \param path The output file. +static void +create_log(const std::string& level, const std::string& path) +{ + logging::set_inmemory(); + + datetime::set_mock_now(2011, 3, 19, 11, 40, 0, 100); + logging::log(logging::level_debug, "file1", 11, "Debug 1"); + + datetime::set_mock_now(2011, 3, 19, 11, 40, 1, 200); + logging::log(logging::level_error, "file2", 22, "Error 1"); + + datetime::set_mock_now(2011, 3, 19, 11, 40, 2, 300); + logging::log(logging::level_info, "file3", 33, "Info 1"); + + datetime::set_mock_now(2011, 3, 19, 11, 40, 3, 400); + logging::log(logging::level_warning, "file4", 44, "Warning 1"); + + logging::set_persistency(level, fs::path(path)); + + datetime::set_mock_now(2011, 3, 19, 11, 40, 4, 500); + logging::log(logging::level_debug, "file1", 11, "Debug 2"); + + datetime::set_mock_now(2011, 3, 19, 11, 40, 5, 600); + logging::log(logging::level_error, "file2", 22, "Error 2"); + + datetime::set_mock_now(2011, 3, 19, 11, 40, 6, 700); + logging::log(logging::level_info, "file3", 33, "Info 2"); + + datetime::set_mock_now(2011, 3, 19, 11, 40, 7, 800); + logging::log(logging::level_warning, "file4", 44, "Warning 2"); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(set_persistency__some_backlog__debug); +ATF_TEST_CASE_BODY(set_persistency__some_backlog__debug) +{ + create_log("debug", "test.log"); + + std::ifstream input("test.log"); + ATF_REQUIRE(input); + + const pid_t pid = ::getpid(); + + std::string line; + ATF_REQUIRE(std::getline(input, line).good()); + ATF_REQUIRE_EQ( + (F("20110319-114000 D %s file1:11: Debug 1") % pid).str(), line); + ATF_REQUIRE(std::getline(input, line).good()); + ATF_REQUIRE_EQ( + (F("20110319-114001 E %s file2:22: Error 1") % pid).str(), line); + ATF_REQUIRE(std::getline(input, line).good()); + ATF_REQUIRE_EQ( + (F("20110319-114002 I %s file3:33: Info 1") % pid).str(), line); + ATF_REQUIRE(std::getline(input, line).good()); + ATF_REQUIRE_EQ( + (F("20110319-114003 W %s file4:44: Warning 1") % pid).str(), line); + ATF_REQUIRE(std::getline(input, line).good()); + ATF_REQUIRE_EQ( + (F("20110319-114004 D %s file1:11: Debug 2") % pid).str(), line); + ATF_REQUIRE(std::getline(input, line).good()); + ATF_REQUIRE_EQ( + (F("20110319-114005 E %s file2:22: Error 2") % pid).str(), line); + ATF_REQUIRE(std::getline(input, line).good()); + ATF_REQUIRE_EQ( + (F("20110319-114006 I %s file3:33: Info 2") % pid).str(), line); + ATF_REQUIRE(std::getline(input, line).good()); + ATF_REQUIRE_EQ( + (F("20110319-114007 W %s file4:44: Warning 2") % pid).str(), line); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(set_persistency__some_backlog__error); +ATF_TEST_CASE_BODY(set_persistency__some_backlog__error) +{ + create_log("error", "test.log"); + + std::ifstream input("test.log"); + ATF_REQUIRE(input); + + const pid_t pid = ::getpid(); + + std::string line; + ATF_REQUIRE(std::getline(input, line).good()); + ATF_REQUIRE_EQ( + (F("20110319-114001 E %s file2:22: Error 1") % pid).str(), line); + ATF_REQUIRE(std::getline(input, line).good()); + ATF_REQUIRE_EQ( + (F("20110319-114005 E %s file2:22: Error 2") % pid).str(), line); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(set_persistency__some_backlog__info); +ATF_TEST_CASE_BODY(set_persistency__some_backlog__info) +{ + create_log("info", "test.log"); + + std::ifstream input("test.log"); + ATF_REQUIRE(input); + + const pid_t pid = ::getpid(); + + std::string line; + ATF_REQUIRE(std::getline(input, line).good()); + ATF_REQUIRE_EQ( + (F("20110319-114001 E %s file2:22: Error 1") % pid).str(), line); + ATF_REQUIRE(std::getline(input, line).good()); + ATF_REQUIRE_EQ( + (F("20110319-114002 I %s file3:33: Info 1") % pid).str(), line); + ATF_REQUIRE(std::getline(input, line).good()); + ATF_REQUIRE_EQ( + (F("20110319-114003 W %s file4:44: Warning 1") % pid).str(), line); + ATF_REQUIRE(std::getline(input, line).good()); + ATF_REQUIRE_EQ( + (F("20110319-114005 E %s file2:22: Error 2") % pid).str(), line); + ATF_REQUIRE(std::getline(input, line).good()); + ATF_REQUIRE_EQ( + (F("20110319-114006 I %s file3:33: Info 2") % pid).str(), line); + ATF_REQUIRE(std::getline(input, line).good()); + ATF_REQUIRE_EQ( + (F("20110319-114007 W %s file4:44: Warning 2") % pid).str(), line); +} + + +ATF_TEST_CASE_WITHOUT_HEAD(set_persistency__some_backlog__warning); +ATF_TEST_CASE_BODY(set_persistency__some_backlog__warning) +{ + create_log("warning", "test.log"); + + std::ifstream input("test.log"); + ATF_REQUIRE(input); + + const pid_t pid = ::getpid(); + + std::string line; + ATF_REQUIRE(std::getline(input, line).good()); + ATF_REQUIRE_EQ( + (F("20110319-114001 E %s file2:22: Error 1") % pid).str(), line); + ATF_REQUIRE(std::getline(input, line).good()); + ATF_REQUIRE_EQ( + (F("20110319-114003 W %s file4:44: Warning 1") % pid).str(), line); + ATF_REQUIRE(std::getline(input, line).good()); + ATF_REQUIRE_EQ( + (F("20110319-114005 E %s file2:22: Error 2") % pid).str(), line); + ATF_REQUIRE(std::getline(input, line).good()); + ATF_REQUIRE_EQ( + (F("20110319-114007 W %s file4:44: Warning 2") % pid).str(), line); +} + + +ATF_TEST_CASE(set_persistency__fail); +ATF_TEST_CASE_HEAD(set_persistency__fail) +{ + set_md_var("require.user", "unprivileged"); +} +ATF_TEST_CASE_BODY(set_persistency__fail) +{ + ATF_REQUIRE_THROW_RE(std::range_error, "'foobar'", + logging::set_persistency("foobar", fs::path("log"))); + + fs::mkdir(fs::path("dir"), 0644); + ATF_REQUIRE_THROW_RE(std::runtime_error, "dir/fail.log", + logging::set_persistency("debug", + fs::path("dir/fail.log"))); +} + + +ATF_INIT_TEST_CASES(tcs) +{ + ATF_ADD_TEST_CASE(tcs, generate_log_name__before_log); + ATF_ADD_TEST_CASE(tcs, generate_log_name__after_log); + + ATF_ADD_TEST_CASE(tcs, log); + + ATF_ADD_TEST_CASE(tcs, set_inmemory__reset); + + ATF_ADD_TEST_CASE(tcs, set_persistency__no_backlog); + ATF_ADD_TEST_CASE(tcs, set_persistency__some_backlog__debug); + ATF_ADD_TEST_CASE(tcs, set_persistency__some_backlog__error); + ATF_ADD_TEST_CASE(tcs, set_persistency__some_backlog__info); + ATF_ADD_TEST_CASE(tcs, set_persistency__some_backlog__warning); + ATF_ADD_TEST_CASE(tcs, set_persistency__fail); +} |