1 #include <wayfire/util/log.hpp>
2 #include <sstream>
3 #include <functional>
4 #include <iostream>
5 #include <map>
6 #include <chrono>
7 #include <iomanip>
8 
9 template<>
to_string(void * arg)10 std::string wf::log::to_string<void*>(void *arg)
11 {
12     if (!arg)
13     {
14         return "(null)";
15     }
16 
17     std::ostringstream out;
18     out << arg;
19     return out.str();
20 }
21 
22 template<>
to_string(bool arg)23 std::string wf::log::to_string(bool arg)
24 {
25     return arg ? "true" : "false";
26 }
27 
28 /**
29  * A singleton to hold log configuration.
30  */
31 struct log_global_t
32 {
33     std::reference_wrapper<std::ostream> out = std::ref(std::cout);
34 
35     wf::log::log_level_t level = wf::log::LOG_LEVEL_INFO;
36     wf::log::color_mode_t color_mode = wf::log::LOG_COLOR_MODE_OFF;
37     std::string strip_path = "";
38 
39     std::string clear_color = "";
40 
getlog_global_t41     static log_global_t& get()
42     {
43         static log_global_t instance;
44         return instance;
45     }
46 
47   private:
log_global_tlog_global_t48     log_global_t()
49     {}
50 };
51 
initialize_logging(std::ostream & output_stream,log_level_t minimum_level,color_mode_t color_mode,std::string strip_path)52 void wf::log::initialize_logging(std::ostream& output_stream,
53     log_level_t minimum_level, color_mode_t color_mode, std::string strip_path)
54 {
55     auto& state = log_global_t::get();
56     state.out   = std::ref(output_stream);
57     state.level = minimum_level;
58     state.color_mode = color_mode;
59     state.strip_path = strip_path;
60 
61     if (state.color_mode == LOG_COLOR_MODE_ON)
62     {
63         state.clear_color = "\033[0m";
64     }
65 }
66 
67 /** Get the line prefix for the given log level */
get_level_prefix(wf::log::log_level_t level)68 static std::string get_level_prefix(wf::log::log_level_t level)
69 {
70     bool color = log_global_t::get().color_mode == wf::log::LOG_COLOR_MODE_ON;
71     static std::map<wf::log::log_level_t, std::string> color_codes =
72     {
73         {wf::log::LOG_LEVEL_DEBUG, "\033[0m"},
74         {wf::log::LOG_LEVEL_INFO, "\033[0;34m"},
75         {wf::log::LOG_LEVEL_WARN, "\033[0;33m"},
76         {wf::log::LOG_LEVEL_ERROR, "\033[1;31m"},
77     };
78 
79     static std::map<wf::log::log_level_t, std::string> line_prefix =
80     {
81         {wf::log::LOG_LEVEL_DEBUG, "DD"},
82         {wf::log::LOG_LEVEL_INFO, "II"},
83         {wf::log::LOG_LEVEL_WARN, "WW"},
84         {wf::log::LOG_LEVEL_ERROR, "EE"},
85     };
86 
87     if (color)
88     {
89         return color_codes[level] + line_prefix[level];
90     }
91 
92     return line_prefix[level];
93 }
94 
95 /** Get the current time and date in a suitable format. */
get_formatted_date_time()96 static std::string get_formatted_date_time()
97 {
98     using namespace std::chrono;
99     auto now = system_clock::now();
100     auto tt  = system_clock::to_time_t(now);
101     auto ms  = duration_cast<milliseconds>(now.time_since_epoch()) % 1000;
102 
103     std::ostringstream out;
104     out << std::put_time(std::localtime(&tt), "%d-%m-%y %H:%M:%S.");
105     out << std::setfill('0') << std::setw(3) << ms.count();
106     return out.str();
107 }
108 
109 /** Strip the strip_path from the given path. */
strip_path(const std::string & path)110 static std::string strip_path(const std::string& path)
111 {
112     auto prefix = log_global_t::get().strip_path;
113     if ((prefix.length() > 0) && (path.find(prefix) == 0))
114     {
115         return path.substr(prefix.length());
116     }
117 
118     std::string skip_chars = "./";
119     size_t idx = path.find_first_not_of(skip_chars);
120     if (idx != std::string::npos)
121     {
122         return path.substr(idx);
123     }
124 
125     return path;
126 }
127 
128 /**
129  * Log a plain message to the given output stream.
130  * The output format is:
131  *
132  * LL DD-MM-YY HH:MM:SS.MSS - [source:line] message
133  *
134  * @param level The log level of the passed message.
135  * @param contents The message to be printed.
136  * @param source The file where the message originates from. The prefix
137  *  strip_path specified in initialize_logging will be removed, if it exists.
138  * @param line The line number of @source
139  */
log_plain(log_level_t level,const std::string & contents,const std::string & source,int line_nr)140 void wf::log::log_plain(log_level_t level, const std::string& contents,
141     const std::string& source, int line_nr)
142 {
143     auto& state = log_global_t::get();
144     if (state.level > level)
145     {
146         return;
147     }
148 
149     std::string path_info;
150     if (!source.empty())
151     {
152         path_info = wf::log::detail::format_concat(
153             "[", strip_path(source), ":", line_nr, "] ");
154     }
155 
156     state.out.get() <<
157         wf::log::detail::format_concat(
158         get_level_prefix(level), " ",
159         get_formatted_date_time(),
160         " - ", path_info, contents) <<
161         state.clear_color << std::endl;
162 }
163