1 ///////////////////////////////////////////////////////////////////////////// 2 // 3 // Copyright (c) 2020, The Regents of the University of California 4 // All rights reserved. 5 // 6 // BSD 3-Clause License 7 // 8 // Redistribution and use in source and binary forms, with or without 9 // modification, are permitted provided that the following conditions are met: 10 // 11 // * Redistributions of source code must retain the above copyright notice, this 12 // list of conditions and the following disclaimer. 13 // 14 // * Redistributions in binary form must reproduce the above copyright notice, 15 // this list of conditions and the following disclaimer in the documentation 16 // and/or other materials provided with the distribution. 17 // 18 // * Neither the name of the copyright holder nor the names of its 19 // contributors may be used to endorse or promote products derived from 20 // this software without specific prior written permission. 21 // 22 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 23 // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 24 // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 25 // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 26 // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 27 // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 28 // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 29 // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 30 // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 31 // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 32 // POSSIBILITY OF SUCH DAMAGE. 33 // 34 /////////////////////////////////////////////////////////////////////////////// 35 36 #pragma once 37 38 #include <string> 39 #include <vector> 40 #include <array> 41 #include <map> 42 #include <string_view> 43 #include <cstdlib> 44 #include <type_traits> 45 46 #include "spdlog/spdlog.h" 47 #include "spdlog/fmt/ostr.h" 48 49 namespace utl { 50 51 #define FOREACH_TOOL(X) \ 52 X(ANT) \ 53 X(CTS) \ 54 X(DPL) \ 55 X(DRT) \ 56 X(FIN) \ 57 X(FLW) \ 58 X(GPL) \ 59 X(GRT) \ 60 X(GUI) \ 61 X(PAD) \ 62 X(IFP) \ 63 X(MPL) \ 64 X(ODB) \ 65 X(ORD) \ 66 X(PAR) \ 67 X(PDN) \ 68 X(PDR) \ 69 X(PPL) \ 70 X(PSM) \ 71 X(PSN) \ 72 X(RCX) \ 73 X(RMP) \ 74 X(RSZ) \ 75 X(STA) \ 76 X(STT) \ 77 X(TAP) \ 78 X(UKN) \ 79 80 #define GENERATE_ENUM(ENUM) ENUM, 81 #define GENERATE_STRING(STRING) #STRING, 82 83 enum ToolId 84 { 85 FOREACH_TOOL(GENERATE_ENUM) 86 SIZE // the number of tools, do not put anything after this 87 }; 88 89 class Logger 90 { 91 public: 92 // Use nullptr if messages or metrics are not logged to a file. 93 Logger(const char* filename = nullptr, 94 const char *metrics_filename = nullptr); 95 ~Logger(); 96 static ToolId findToolId(const char *tool_name); 97 98 template <typename... Args> report(const std::string & message,const Args &...args)99 inline void report(const std::string& message, 100 const Args&... args) 101 { 102 logger_->log(spdlog::level::level_enum::off, message, args...); 103 } 104 105 // Do NOT call this directly, use the debugPrint macro instead (defined below) 106 template <typename... Args> debug(ToolId tool,int level,const std::string & message,const Args &...args)107 inline void debug(ToolId tool, 108 int level, 109 const std::string& message, 110 const Args&... args) 111 { 112 // Message counters do NOT apply to debug messages. 113 logger_->log(spdlog::level::level_enum::debug, 114 "[{} {}-{:04d}] " + message, 115 level_names[spdlog::level::level_enum::debug], 116 tool_names_[tool], 117 level, 118 args...); 119 logger_->flush(); 120 } 121 122 template <typename... Args> info(ToolId tool,int id,const std::string & message,const Args &...args)123 inline void info(ToolId tool, 124 int id, 125 const std::string& message, 126 const Args&... args) 127 { 128 log(tool, spdlog::level::level_enum::info, id, message, args...); 129 } 130 131 template <typename... Args> warn(ToolId tool,int id,const std::string & message,const Args &...args)132 inline void warn(ToolId tool, 133 int id, 134 const std::string& message, 135 const Args&... args) 136 { 137 log(tool, spdlog::level::level_enum::warn, id, message, args...); 138 } 139 140 template <typename... Args> 141 __attribute__((noreturn)) error(ToolId tool,int id,const std::string & message,const Args &...args)142 inline void error(ToolId tool, 143 int id, 144 const std::string& message, 145 const Args&... args) 146 { 147 log(tool, spdlog::level::err, id, message, args...); 148 char tool_id[32]; 149 sprintf(tool_id, "%s-%04d", tool_names_[tool], id); 150 std::runtime_error except(tool_id); 151 // Exception should be caught by swig error handler. 152 throw except; 153 } 154 155 template <typename... Args> 156 __attribute__((noreturn)) critical(ToolId tool,int id,const std::string & message,const Args &...args)157 void critical(ToolId tool, 158 int id, 159 const std::string& message, 160 const Args&... args) 161 { 162 log(tool, spdlog::level::level_enum::critical, id, message, args...); 163 exit(EXIT_FAILURE); 164 } 165 166 // For logging to the metrics file. This is a much more restricted 167 // API as we are writing JSON not user messages. 168 // Note: these methods do no escaping so avoid special characters. 169 template <typename T, 170 typename = std::enable_if_t<std::is_arithmetic_v<T>>> metric(const std::string_view metric,T value)171 inline void metric(const std::string_view metric, 172 T value) 173 { 174 log_metric(metric, value); 175 } 176 metric(const std::string_view metric,const std::string & value)177 inline void metric(const std::string_view metric, 178 const std::string& value) 179 { 180 log_metric(metric, '"' + value + '"'); 181 } 182 183 void setDebugLevel(ToolId tool, const char* group, int level); 184 debugCheck(ToolId tool,const char * group,int level)185 bool debugCheck(ToolId tool, const char* group, int level) const { 186 if (!debug_on_) { 187 return false; 188 } 189 auto& groups = debug_group_level_[tool]; 190 auto it = groups.find(group); 191 return (it != groups.end() && level <= it->second); 192 } 193 194 void addSink(spdlog::sink_ptr sink); 195 void addMetricsSink(const char *metrics_filename); 196 197 private: 198 template <typename... Args> log(ToolId tool,spdlog::level::level_enum level,int id,const std::string & message,const Args &...args)199 inline void log(ToolId tool, 200 spdlog::level::level_enum level, 201 int id, 202 const std::string& message, 203 const Args&... args) 204 { 205 assert(id >= 0 && id <= max_message_id); 206 auto& counter = message_counters_[tool][id]; 207 auto count = counter++; 208 if (count < max_message_print) { 209 logger_->log(level, 210 "[{} {}-{:04d}] " + message, 211 level_names[level], 212 tool_names_[tool], 213 id, 214 args...); 215 return; 216 } 217 218 if (count == max_message_print) { 219 logger_->log(level, 220 "[{} {}-{:04d}] message limit reached, " 221 "this message will no longer print", 222 level_names[level], 223 tool_names_[tool], 224 id); 225 } else { 226 counter--; // to avoid counter overflow 227 } 228 } 229 230 template <typename Value> log_metric(const std::string_view metric,const Value & value)231 inline void log_metric(const std::string_view metric, 232 const Value& value) 233 { 234 metrics_logger_->info(" {}\"{}\" : {}", 235 first_metric_ ? " " : ", ", 236 metric, 237 value); 238 first_metric_ = false; 239 } 240 241 // Allows for lookup by a compatible key (ie string_view) 242 // to avoid constructing a key (string) just for lookup 243 struct StringViewCmp { 244 using is_transparent = std::true_type; // enabler operatorStringViewCmp245 bool operator()(const std::string_view a, const std::string_view b) const { 246 return a < b; 247 } 248 }; 249 using DebugGroups = std::map<std::string, int, StringViewCmp>; 250 251 static constexpr int max_message_id = 9999; 252 253 // Stop issuing messages of a given tool/id when this limit is hit. 254 static int max_message_print; 255 256 std::vector<spdlog::sink_ptr> sinks_; 257 std::shared_ptr<spdlog::logger> logger_; 258 std::shared_ptr<spdlog::logger> metrics_logger_; 259 260 // This matrix is pre-allocated so it can be safely updated 261 // from multiple threads without locks. 262 using MessageCounter = std::array<short, max_message_id + 1>; 263 std::array<MessageCounter, ToolId::SIZE> message_counters_; 264 std::array<DebugGroups, ToolId::SIZE> debug_group_level_; 265 bool debug_on_; 266 bool first_metric_; 267 static constexpr const char *level_names[] = {"TRACE", 268 "DEBUG", 269 "INFO", 270 "WARNING", 271 "ERROR", 272 "CRITICAL", 273 "OFF"}; 274 static constexpr const char *pattern_ = "%v"; 275 static constexpr const char* tool_names_[] = { FOREACH_TOOL(GENERATE_STRING) }; 276 }; 277 278 // Use this macro for any debug messages. It avoids evaluating message and varargs 279 // when no message is issued. 280 #define debugPrint(logger, tool, group, level, ...) \ 281 if (logger->debugCheck(tool, group, level)) { \ 282 logger->debug(tool, level, ##__VA_ARGS__); \ 283 } 284 285 #undef FOREACH_TOOL 286 #undef GENERATE_ENUM 287 #undef GENERATE_STRING 288 289 } // namespace utl 290