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