1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286
|
/*************************************************************************
* 2019 © Janek Kozicki *
* *
* This program is free software; it is licensed under the terms of the *
* GNU General Public License v2 or later. See file LICENSE for details. *
*************************************************************************/
#ifdef YADE_BOOST_LOG
#include <lib/base/Logging.hpp>
#include <core/Omega.hpp>
#include <boost/core/null_deleter.hpp>
#include <boost/filesystem.hpp>
#include <boost/log/utility/setup/file.hpp>
#include <boost/phoenix/bind/bind_function.hpp>
#include <fstream>
#include <ostream>
SINGLETON_SELF(Logging);
bool logFilterLevels(
boost::log::value_ref<Logging::SeverityLevel, tag::severity> const& level, boost::log::value_ref<std::string, tag::class_name_tag> const& name)
{
auto itEnd = Logging::instance().getClassLogLevels().end();
short int itDefault = Logging::instance().getDefaultLogLevel();
if (not level) { // something is seriously broken, we resort to std::cerr to report that.
std::cerr << "LOGGER Warning: Logging::SeverityLevel is missing. Expect problems with logging.\n";
return true;
}
if (name) {
auto it = Logging::instance().getClassLogLevels().find(name.get());
if ((it != itEnd) and (it->second >= 0)) { return level <= it->second; }
}
// this is triggered for LOG_NOFILTER macros. I comment this out, because they are legal now. Although I could
// slg.add_attribute("NameTag", boost::log::attributes::constant< std::string >("NoFilter"));
// inside the LOG_NOFILTER macro. And then they would become filterable. Then they would need a different name. Because eNOFILTER is level 0 now.
// I don't expect that a logger inside .hpp without a class logger available will be needed, so I don't do this now.
//else {
// std::cerr << "LOGGER Warning: class_name_tag needed for filtering is missing. Expect problems with logging.\n";
//}
return level <= itDefault;
}
// Setup the common formatter for all sinks
Logging::Logging()
: defaultLogLevel { (short int)(SeverityLevel::eWARN) }
, classLogLevels { { "Default", defaultLogLevel } }
, sink { boost::make_shared<TextSink>() }
, streamClog(&std::clog, boost::null_deleter())
, streamCerr(&std::cerr, boost::null_deleter())
, streamCout(&std::cout, boost::null_deleter())
, colors { false }
, esc { char { 27 } }
, lastOutputStream { "clog" }
, logger {}
{
sink->locked_backend()->add_stream(streamClog);
updateFormatter();
sink->set_filter(boost::phoenix::bind(&logFilterLevels, severity.or_none(), class_name_tag.or_none()));
boost::log::core::get()->add_sink(sink);
logger = createNamedLogger("Logging");
}
void Logging::updateFormatter()
{
boost::log::formatter fmt = boost::log::expressions::stream
<< "<" << severity << "> "
<< boost::log::expressions::if_(boost::log::expressions::has_attr(
class_name_tag))[boost::log::expressions::stream << colorNameTag() << class_name_tag << colorEnd()]
<< boost::log::expressions::smessage;
sink->set_formatter(fmt);
}
// It is possible in boost::log to have different output sinks (e.g. different log files), each with a different filtering level.
// For now it is not necessary so I don't use it. If it becomes necessary in the future, see following examples:
// https://www.boost.org/doc/libs/1_70_0/libs/log/doc/html/log/detailed/expressions.html#log.detailed.expressions.formatters.conditional
// https://www.boost.org/doc/libs/1_70_0/libs/log/example/doc/tutorial_filtering.cpp
// To do this multiple variables like the variable 'sink' below would have to be defined. Each with a different filter level.
// Below all add_stream(…), remove_stream(…) calls are to the same sink. So they all have the same filtering level.
void Logging::setOutputStream(const std::string& name, bool reset)
{
LOG_INFO("setting new stream to '" << name << "'");
if (name.empty()) { throw std::runtime_error("Cannot use empty stream name."); }
lastOutputStream = name;
if (reset) {
sink->locked_backend()->remove_stream(streamClog);
sink->locked_backend()->remove_stream(streamCerr);
sink->locked_backend()->remove_stream(streamCout);
sink->locked_backend()->remove_stream(streamFile);
streamFile = boost::shared_ptr<std::ostream> {};
for (const auto& oldFile : streamOld) {
sink->locked_backend()->remove_stream(oldFile);
}
streamOld.clear();
}
switch (hash(name.c_str())) {
case hash("clog"): sink->locked_backend()->add_stream(streamClog); break;
case hash("cerr"): sink->locked_backend()->add_stream(streamCerr); break;
case hash("cout"): sink->locked_backend()->add_stream(streamCout); break;
default: {
if (not reset and streamFile) {
LOG_WARN("adding a new log file without resetting the old one means that the logs will go to both files.");
streamOld.push_back(streamFile);
}
streamFile = boost::make_shared<std::ofstream>(name.c_str());
sink->locked_backend()->add_stream(streamFile);
sink->locked_backend()->auto_flush(true); // make sure that log is written immediately to file without long time buffering.
}
}
}
void Logging::readConfigFile(const std::string& fname)
{
LOG_INFO("Loading " << fname);
if (boost::filesystem::exists(fname)) {
std::ifstream f(fname);
if (f.is_open()) {
std::string line;
while (getline(f, line)) {
if (line.empty() or line[0] == '#') { continue; }
std::stringstream ss(line);
std::string option = "";
ss >> line;
ss >> option;
try {
int val = boost::lexical_cast<int>(option);
switch (hash(line.c_str())) {
case hash("colors"): setUseColors(bool(val)); break;
default:
setNamedLogLevel(line, val);
break;
// other special strings can be added here to set various settings
// for example to configure colors for each type of message.
}
} catch (const boost::bad_lexical_cast&) { // option is not a number
switch (hash(line.c_str())) {
case hash("output"): setOutputStream(option, true); break;
default: LOG_WARN("invalid value (" << option << ") in config file \"" << fname << "\"."); break;
}
}
}
}
} else {
LOG_ERROR(fname << " does not exist");
}
}
void Logging::saveConfigFile(const std::string& fname)
{
LOG_INFO("Saving " << fname);
std::ofstream f(fname);
if (f.is_open()) {
f << "# YADE LOG config file\n";
f << "# special keywords are:\n";
f << "# 1. \"Default\" to set default filter level\n";
f << "# 2. \"colors\" to indicate if colors should be used, use 0 or 1.\n";
f << "# 3. \"output\" to redirect output to cout, cerr, clog stream or to a file\n";
f << "colors " << int(colors) << "\n";
f << "output " << lastOutputStream << "\n";
f << "Default " << getDefaultLogLevel() << "\n";
for (const auto& a : classLogLevels) {
if ((a.second != -1) and (a.first != "Default")) { f << a.first << " " << a.second << "\n"; }
}
} else {
throw std::runtime_error("Cannot open file to save logging config.");
}
}
std::string Logging::defaultConfigFileName() { return yade::Omega::instance().confDir + "/logging.conf"; }
void Logging::setDefaultLogLevel(short int level)
{
classLogLevels["Default"] = level;
defaultLogLevel = level;
}
short int Logging::getNamedLogLevel(const std::string& name) const { return findFilterName(name); }
void Logging::setNamedLogLevel(const std::string& name, short int level)
{
LOG_INFO("setting \"" << name << "\" log level to " << level)
if (level < (short int)(SeverityLevel::eNOFILTER) or level > (short int)(SeverityLevel::eTRACE)) {
LOG_ERROR("Cannot use level = " << level << ", if this is from loading config file, then comment out this line with '#'")
throw std::runtime_error("The level must be >= NOFILTER (0) and <= TRACE (6), other values are not meaningful. To unset level to \"Default\" "
"level use function unsetLevel(…).");
}
if (level > maxLogLevel) {
// apparently the macros LOG_* are compiled away. So use std::cerr to make sure that the message is printed.
std::cerr << "LOGGER Warning: setting \"" << name << "\" log level higher than MAX_LOG_LEVEL=" << maxLogLevel
<< " will have no effect. Logs will not be printed, they were removed during compilation.\n";
std::cerr << "LOGGER Warning: to be able to use \"" << name << "\"=" << level
<< " you have to recompile yade with cmake option MAX_LOG_LEVEL=" << level << " or higher.\n";
}
if (name == "Default") {
setDefaultLogLevel(level);
} else {
findFilterName(name) = level;
}
}
void Logging::unsetNamedLogLevel(const std::string& name)
{
if (name == "Default") {
// unsetting Default will result in printing everything.
classLogLevels["Default"] = (short int)(SeverityLevel::eTRACE);
} else {
// unsetting anything else will result in printing it at Default level.
findFilterName(name) = -1;
}
}
boost::log::sources::severity_logger<Logging::SeverityLevel> Logging::createNamedLogger(std::string name)
{
boost::log::sources::severity_logger<Logging::SeverityLevel> l;
l.add_attribute("NameTag", boost::log::attributes::constant<std::string>(name));
classLogLevels[name] = -1;
return l;
};
const short int& Logging::findFilterName(const std::string& name) const
{
auto it = classLogLevels.find(name);
if (it == classLogLevels.end()) {
throw std::runtime_error(
name
+ " is not recognized. Did you forget CREATE_LOGGER; and DECLARE_LOGGER(Classname); macros? Or maybe "
"CREATE_CPP_LOCAL_LOGGER(\"filename.cpp\"); macro?\n");
}
return it->second;
}
short int& Logging::findFilterName(const std::string& name)
{
// Call the const version of this function, remove const from the returned result: const short int& → short int&
return const_cast<short int&>((static_cast<const Logging&>(*this)).findFilterName(name));
// Now the named log level can be modified.
}
// https://misc.flogisoft.com/bash/tip_colors_and_formatting
// https://stackoverflow.com/questions/18968979/how-to-get-colorized-output-with-cmake , use ${Esc} for printing colors
void Logging::setUseColors(bool use)
{
colors = use;
updateFormatter();
}
std::string Logging::colorSeverity(Logging::SeverityLevel level)
{
if (not colors) return "";
switch (level) {
case SeverityLevel::eNOFILTER: return esc + "[36m";
case SeverityLevel::eFATAL: return esc + "[91m";
case SeverityLevel::eERROR: return esc + "[31m";
case SeverityLevel::eWARN: return esc + "[95m";
case SeverityLevel::eINFO: return esc + "[96m";
case SeverityLevel::eDEBUG: return esc + "[94m";
case SeverityLevel::eTRACE: return esc + "[92m";
default: return "";
}
}
std::string Logging::colorNameTag()
{
if (not colors) return "";
return esc + "[93m";
}
std::string Logging::colorLineNumber()
{
if (not colors) return "";
return esc + "[93m";
}
std::string Logging::colorFunction()
{
if (not colors) return "";
return esc + "[32m";
}
std::string Logging::colorEnd()
{
if (not colors) return "";
return esc + "[0m";
}
#endif
|