diff --git a/CHANGELOG.md b/CHANGELOG.md index 72dd1df3eb..bd51b8bb6d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -108,6 +108,7 @@ Feature #4548: Weapon priority: use the actual chance to hit the target instead of weapon skill Feature #4549: Weapon priority: use the actual damage in weapon rating calculations Feature #4550: Weapon priority: make ranged weapon bonus more sensible + Feature #4581: Use proper logging system Task #2490: Don't open command prompt window on Release-mode builds automatically Task #4545: Enable is_pod string test diff --git a/apps/opencs/main.cpp b/apps/opencs/main.cpp index ebc686c233..6058e73f9e 100644 --- a/apps/opencs/main.cpp +++ b/apps/opencs/main.cpp @@ -8,7 +8,7 @@ #include #include -#include +#include #include "model/doc/messages.hpp" #include "model/world/universalid.hpp" @@ -80,5 +80,5 @@ int runApplication(int argc, char *argv[]) int main(int argc, char *argv[]) { - return wrapApplication(&runApplication, argc, argv, "/openmw-cs.log"); + return wrapApplication(&runApplication, argc, argv, "OpenMW-CS"); } diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index d9fff69528..bbe1267b18 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -5,7 +5,7 @@ #include #include #include -#include +#include #include "engine.hpp" @@ -265,7 +265,7 @@ extern "C" int SDL_main(int argc, char**argv) int main(int argc, char**argv) #endif { - return wrapApplication(&runApplication, argc, argv, "/openmw.log"); + return wrapApplication(&runApplication, argc, argv, "OpenMW"); } // Platform specific for Windows when there is no console built into the executable. diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index a941190998..551767f8f0 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -85,7 +85,11 @@ add_component_dir (esmterrain ) add_component_dir (misc - utf8stream stringops resourcehelpers rng debugging messageformatparser + utf8stream stringops resourcehelpers rng messageformatparser + ) + +add_component_dir (debug + debugging debuglog ) IF(NOT WIN32 AND NOT APPLE) diff --git a/components/debug/debugging.cpp b/components/debug/debugging.cpp new file mode 100644 index 0000000000..bec97207a4 --- /dev/null +++ b/components/debug/debugging.cpp @@ -0,0 +1,103 @@ +#include "debugging.hpp" + +namespace Debug +{ + std::streamsize DebugOutputBase::write(const char *str, std::streamsize size) + { + // Skip debug level marker + Level level = getLevelMarker(str); + if (level != NoLevel) + { + writeImpl(str+1, size-1, level); + return size; + } + + writeImpl(str, size, NoLevel); + return size; + } + + Level DebugOutputBase::getLevelMarker(const char *str) + { + if (unsigned(*str) <= unsigned(Marker)) + { + return Level(*str); + } + + return NoLevel; + } + + void DebugOutputBase::fillCurrentDebugLevel() + { + const char* env = getenv("OPENMW_DEBUG_LEVEL"); + if (env) + { + std::string value(env); + if (value == "ERROR") + CurrentDebugLevel = Error; + else if (value == "WARNING") + CurrentDebugLevel = Warning; + else if (value == "INFO") + CurrentDebugLevel = Info; + else if (value == "VERBOSE") + CurrentDebugLevel = Verbose; + + return; + } + + CurrentDebugLevel = Verbose; + } +} + +int wrapApplication(int (*innerApplication)(int argc, char *argv[]), int argc, char *argv[], const std::string& appName) +{ + // Some objects used to redirect cout and cerr + // Scope must be here, so this still works inside the catch block for logging exceptions + std::streambuf* cout_rdbuf = std::cout.rdbuf (); + std::streambuf* cerr_rdbuf = std::cerr.rdbuf (); + + int ret = 0; + try + { + Files::ConfigurationManager cfgMgr; +#if defined(_WIN32) && defined(_DEBUG) + // Redirect cout and cerr to VS debug output when running in debug mode + boost::iostreams::stream_buffer sb; + sb.open(Debug::DebugOutput()); + std::cout.rdbuf (&sb); + std::cerr.rdbuf (&sb); +#else + // Redirect cout and cerr to the log file + const std::string logName = Misc::StringUtils::lowerCase(appName) + ".log"; + boost::filesystem::ofstream logfile; + logfile.open (boost::filesystem::path(cfgMgr.getLogPath() / logName)); + + boost::iostreams::stream_buffer coutsb; + boost::iostreams::stream_buffer cerrsb; + std::ostream oldcout(cout_rdbuf); + std::ostream oldcerr(cerr_rdbuf); + coutsb.open (Debug::Tee(logfile, oldcout)); + cerrsb.open (Debug::Tee(logfile, oldcerr)); + + std::cout.rdbuf (&coutsb); + std::cerr.rdbuf (&cerrsb); +#endif + ret = innerApplication(argc, argv); + } + catch (std::exception& e) + { +#if (defined(__APPLE__) || defined(__linux) || defined(__unix) || defined(__posix)) + if (!isatty(fileno(stdin))) +#endif + SDL_ShowSimpleMessageBox(0, (appName + ": Fatal error").c_str(), e.what(), NULL); + + Log(Debug::Error) << "ERROR: " << e.what(); + + ret = 1; + } + + // Restore cout and cerr + std::cout.rdbuf(cout_rdbuf); + std::cerr.rdbuf(cerr_rdbuf); + + return ret; +} diff --git a/components/debug/debugging.hpp b/components/debug/debugging.hpp new file mode 100644 index 0000000000..59536d685c --- /dev/null +++ b/components/debug/debugging.hpp @@ -0,0 +1,129 @@ +#ifndef DEBUG_DEBUGGING_H +#define DEBUG_DEBUGGING_H + +#include +#include + +#include + +#include + +#include "debuglog.hpp" + +namespace Debug +{ + // ANSI colors for terminal + enum Color + { + Reset = 0, + DarkGray = 90, + Red = 91, + Yellow = 93 + }; + + class DebugOutputBase : public boost::iostreams::sink + { + public: + DebugOutputBase() + { + if (CurrentDebugLevel == NoLevel) + fillCurrentDebugLevel(); + } + + virtual std::streamsize write(const char *str, std::streamsize size); + + protected: + static Level getLevelMarker(const char *str); + + static void fillCurrentDebugLevel(); + + virtual std::streamsize writeImpl(const char *str, std::streamsize size, Level debugLevel) + { + return size; + } + + char mDebugLevel; + }; + +#if defined(_WIN32) && defined(_DEBUG) + class DebugOutput : public DebugOutputBase + { + public: + std::streamsize writeImpl(const char *str, std::streamsize size, Level debugLevel) + { + // Make a copy for null termination + std::string tmp (str, static_cast(size)); + // Write string to Visual Studio Debug output + OutputDebugString (tmp.c_str ()); + return size; + } + + virtual ~DebugOutput() {} + }; +#else + + class Tee : public DebugOutputBase + { + public: + Tee(std::ostream &stream, std::ostream &stream2) + : out(stream), out2(stream2) + { + // TODO: check which stream is stderr? + mUseColor = useColoredOutput(); + + mColors[Error] = Red; + mColors[Warning] = Yellow; + mColors[Info] = Reset; + mColors[Verbose] = DarkGray; + mColors[NoLevel] = Reset; + } + + virtual std::streamsize writeImpl(const char *str, std::streamsize size, Level debugLevel) + { + out.write (str, size); + out.flush(); + + if(mUseColor) + { + out2 << "\033[0;" << mColors[debugLevel] << "m"; + out2.write (str, size); + out2 << "\033[0;" << Reset << "m"; + } + else + { + out2.write(str, size); + } + out2.flush(); + + return size; + } + + virtual ~Tee() {} + + private: + + static bool useColoredOutput() + { + // Note: cmd.exe in Win10 should support ANSI colors, but in its own way. +#if defined(_WIN32) + return 0; +#else + char *term = getenv("TERM"); + bool useColor = term && !getenv("NO_COLOR") && isatty(fileno(stderr)); + + return useColor; +#endif + } + + std::ostream &out; + std::ostream &out2; + bool mUseColor; + + std::map mColors; + }; +#endif +} + +int wrapApplication(int (*innerApplication)(int argc, char *argv[]), int argc, char *argv[], const std::string& appName); + +#endif diff --git a/components/debug/debuglog.cpp b/components/debug/debuglog.cpp new file mode 100644 index 0000000000..510c638614 --- /dev/null +++ b/components/debug/debuglog.cpp @@ -0,0 +1,8 @@ +#include "debuglog.hpp" + +namespace Debug +{ + Level CurrentDebugLevel = Level::NoLevel; +} + +std::mutex Log::sLock; diff --git a/components/debug/debuglog.hpp b/components/debug/debuglog.hpp new file mode 100644 index 0000000000..bfd7d1196e --- /dev/null +++ b/components/debug/debuglog.hpp @@ -0,0 +1,66 @@ +#ifndef DEBUG_LOG_H +#define DEBUG_LOG_H + +#include +#include +#include + +namespace Debug +{ + enum Level + { + NoLevel = 0, + Error = 1, + Warning = 2, + Info = 3, + Verbose = 4, + Marker = Verbose + }; + + extern Level CurrentDebugLevel; +} + +class Log +{ + static std::mutex sLock; + + std::unique_lock mLock; +public: + // Locks a global lock while the object is alive + Log(Debug::Level level) : + mLock(sLock), + mLevel(level) + { + if (mLevel <= Debug::CurrentDebugLevel) + std::cout << static_cast(mLevel); + } + + // Perfect forwarding wrappers to give the chain of objects to cout + template + Log& operator<<(T&& rhs) + { + if (mLevel <= Debug::CurrentDebugLevel) + std::cout << std::forward(rhs); + + return *this; + } + template + Log& operator<<(const T& rhs) + { + if (mLevel <= Debug::CurrentDebugLevel) + std::cout << std::forward(rhs); + + return *this; + } + + ~Log() + { + if (mLevel <= Debug::CurrentDebugLevel) + std::cout << std::endl; + } + +private: + Debug::Level mLevel; +}; + +#endif diff --git a/components/misc/debugging.hpp b/components/misc/debugging.hpp deleted file mode 100644 index c0c3f5a176..0000000000 --- a/components/misc/debugging.hpp +++ /dev/null @@ -1,108 +0,0 @@ -#ifndef MISC_DEBUGGING_H -#define MISC_DEBUGGING_H - -#include -#include - -#include - -namespace Misc -{ -#if defined(_WIN32) && defined(_DEBUG) - - class DebugOutput : public boost::iostreams::sink - { - public: - std::streamsize write(const char *str, std::streamsize size) - { - // Make a copy for null termination - std::string tmp (str, static_cast(size)); - // Write string to Visual Studio Debug output - OutputDebugString (tmp.c_str ()); - return size; - } - }; -#else - class Tee : public boost::iostreams::sink - { - public: - Tee(std::ostream &stream, std::ostream &stream2) - : out(stream), out2(stream2) - { - } - - std::streamsize write(const char *str, std::streamsize size) - { - out.write (str, size); - out.flush(); - out2.write (str, size); - out2.flush(); - return size; - } - - private: - std::ostream &out; - std::ostream &out2; - }; -#endif -} - -int wrapApplication(int (*innerApplication)(int argc, char *argv[]), int argc, char *argv[], const std::string& logName) -{ - // Some objects used to redirect cout and cerr - // Scope must be here, so this still works inside the catch block for logging exceptions - std::streambuf* cout_rdbuf = std::cout.rdbuf (); - std::streambuf* cerr_rdbuf = std::cerr.rdbuf (); - -#if !(defined(_WIN32) && defined(_DEBUG)) - boost::iostreams::stream_buffer coutsb; - boost::iostreams::stream_buffer cerrsb; -#endif - - std::ostream oldcout(cout_rdbuf); - std::ostream oldcerr(cerr_rdbuf); - - boost::filesystem::ofstream logfile; - - int ret = 0; - try - { - Files::ConfigurationManager cfgMgr; -#if defined(_WIN32) && defined(_DEBUG) - // Redirect cout and cerr to VS debug output when running in debug mode - boost::iostreams::stream_buffer sb; - sb.open(Misc::DebugOutput()); - std::cout.rdbuf (&sb); - std::cerr.rdbuf (&sb); -#else - // Redirect cout and cerr to the log file - logfile.open (boost::filesystem::path(cfgMgr.getLogPath() / logName)); - - coutsb.open (Misc::Tee(logfile, oldcout)); - cerrsb.open (Misc::Tee(logfile, oldcerr)); - - std::cout.rdbuf (&coutsb); - std::cerr.rdbuf (&cerrsb); -#endif - ret = innerApplication(argc, argv); - } - catch (std::exception& e) - { -#if (defined(__APPLE__) || defined(__linux) || defined(__unix) || defined(__posix)) - if (!isatty(fileno(stdin))) -#endif - SDL_ShowSimpleMessageBox(0, "OpenMW: Fatal error", e.what(), NULL); - - std::cerr << "\nERROR: " << e.what() << std::endl; - - ret = 1; - } - - // Restore cout and cerr - std::cout.rdbuf(cout_rdbuf); - std::cerr.rdbuf(cerr_rdbuf); - - return ret; -} - -#endif diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 4e7f6d511e..5857a39872 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -9,6 +9,7 @@ #include // resource +#include #include #include #include @@ -992,7 +993,7 @@ namespace NifOsg int uvSet = *it; if (uvSet >= (int)data->uvlist.size()) { - std::cerr << "Warning: out of bounds UV set " << uvSet << " on TriShape \"" << triShape->name << "\" in " << mFilename << std::endl; + Log(Debug::Verbose) << "Out of bounds UV set " << uvSet << " on TriShape \"" << triShape->name << "\" in " << mFilename; if (!data->uvlist.empty()) geometry->setTexCoordArray(textureStage, new osg::Vec2Array(data->uvlist[0].size(), &data->uvlist[0][0]), osg::Array::BIND_PER_VERTEX); continue; @@ -1279,7 +1280,7 @@ namespace NifOsg const Nif::NiTexturingProperty::Texture& tex = texprop->textures[i]; if(tex.texture.empty() && texprop->controller.empty()) { - std::cerr << "Warning: texture layer " << i << " is in use but empty in " << mFilename << std::endl; + Log(Debug::Verbose) << "Texture layer " << i << " is in use but empty in " << mFilename; continue; }