Scorum
log_configurator.cpp
Go to the documentation of this file.
1 #include <boost/algorithm/string/classification.hpp>
2 #include <boost/algorithm/string/join.hpp>
3 #include <boost/algorithm/string/predicate.hpp>
4 #include <boost/algorithm/string/split.hpp>
5 #include <boost/program_options.hpp>
6 #include <boost/filesystem.hpp>
7 
8 #include <iostream>
9 #include <fstream>
10 
11 #include <fc/log/console_appender.hpp>
12 #include <fc/log/file_appender.hpp>
13 #include <fc/log/gelf_appender.hpp>
14 #include <fc/log/logger.hpp>
15 #include <fc/log/logger_config.hpp>
16 #include <fc/io/json.hpp>
17 #include <fc/network/ip.hpp>
18 #include <fc/exception/exception.hpp>
19 
23 
24 #define LOG_APPENDER "log-appender"
25 #define LOGGER "log-logger"
26 #define DEFAULT_GELF_APPENDER_PORT 12201
27 
28 namespace bpo = boost::program_options;
29 
31 
33 {
34  std::string appender;
35  std::string stream;
36 };
37 
39 {
41  size_t rotation_limit_hours = 30 * 24;
42 };
43 
45 {
46  std::string host_name;
47  std::string additional_info;
48 };
49 
51 {
52  std::string name;
53  std::string level;
54  std::string appender;
55 };
56 
57 FC_REFLECT(appender_args, (appender)(stream))
58 FC_REFLECT_DERIVED(file_appender_args, (appender_args), (rotation_interval_minutes)(rotation_limit_hours))
59 FC_REFLECT_DERIVED(gelf_appender_args, (appender_args), (host_name)(additional_info))
60 FC_REFLECT(logger_args, (name)(level)(appender))
61 
62 namespace logger {
63 
64 fc::appender_config get_console_config(const std::string& s)
65 {
66  auto appender = fc::json::from_string(s).as<appender_args>();
67 
68  auto stream = fc::variant(appender.stream).as<fc::console_appender::stream::type>();
69 
70  fc::console_appender::config config;
71  config.level_colors.emplace_back(
72  fc::console_appender::level_color(fc::log_level::debug, fc::console_appender::color::white));
73  config.level_colors.emplace_back(
74  fc::console_appender::level_color(fc::log_level::info, fc::console_appender::color::green));
75  config.level_colors.emplace_back(
76  fc::console_appender::level_color(fc::log_level::warn, fc::console_appender::color::brown));
77  config.level_colors.emplace_back(
78  fc::console_appender::level_color(fc::log_level::error, fc::console_appender::color::red));
79  config.stream = stream;
80 
81  return fc::appender_config::create_config<fc::console_appender>(appender.appender, fc::variant(config));
82 }
83 
84 fc::appender_config get_file_config(const std::string& s, const boost::filesystem::path& pwd)
85 {
86  auto file_appender = fc::json::from_string(s).as<file_appender_args>();
87 
88  fc::path file_name = file_appender.stream;
89 
90  if (file_name.is_relative())
91  {
92  file_name = fc::absolute(pwd) / file_name;
93  }
94 
95  ilog(file_name.generic_string());
96 
97  if (fc::is_directory(file_name))
98  {
99  FC_THROW_EXCEPTION(fc::invalid_arg_exception, "appender stream ${name} is not a file", ("name", file_name));
100  }
101 
102  // construct a default file appender config here
103  // filename will be taken from ini file, everything else hard-coded here
104  fc::file_appender::config config;
105  config.filename = file_name;
106  config.flush = true;
107  config.rotate = true;
108  config.rotation_interval = fc::minutes(file_appender.rotation_interval_minutes);
109  config.rotation_limit = fc::hours(file_appender.rotation_limit_hours);
110 
111  return fc::appender_config::create_config<fc::file_appender>(file_appender.appender, fc::variant(config));
112 }
113 
114 fc::appender_config get_gelf_config(const std::string& s, const std::string& optional_info = "")
115 {
116  auto gelf_appender = fc::json::from_string(s).as<gelf_appender_args>();
117 
118  // check if stream can be network endpoint
119  std::string endpoint = gelf_appender.stream;
120  if (endpoint.find(':') == std::string::npos)
121  {
122  endpoint += ":"; // add default port
123  endpoint += boost::lexical_cast<std::string>(DEFAULT_GELF_APPENDER_PORT);
124  }
125  fc::ip::endpoint::from_string(endpoint); // if stream is not a network endpoint it will throw
126 
127  fc::gelf_appender::config config;
128  config.endpoint = endpoint;
129  config.host_name = gelf_appender.host_name;
130  config.version = (fc::string)SCORUM_BLOCKCHAIN_VERSION;
131  config.additional_info = optional_info;
132 
133  if (!gelf_appender.additional_info.empty())
134  {
135  if (!config.additional_info.empty())
136  config.additional_info += ": ";
137  config.additional_info += gelf_appender.additional_info;
138  }
139 
140  return fc::appender_config::create_config<fc::gelf_appender>(gelf_appender.appender, fc::variant(config));
141 }
142 
143 fc::optional<fc::logging_config> load_logging_config_from_options(const boost::program_options::variables_map& args,
144  const boost::filesystem::path& pwd)
145 {
146  // clang-format off
147  try
148  {
149  fc::logging_config logging_config;
150  bool found_logging_config = false;
151 
152  if (args.count(LOG_APPENDER))
153  {
154  std::vector<std::string> appenders = args[LOG_APPENDER].as<std::vector<std::string>>();
155 
156  for (const std::string& s : appenders)
157  {
158  //check if appender is a console appender
159  try
160  {
161  logging_config.appenders.push_back(get_console_config(s));
162 
163  found_logging_config = true;
164  continue;
165  }
166  catch (fc::bad_cast_exception&)
167  {
168  }
169 
170  //check if appender is a gelf appender
171  try
172  {
173  //check if stream can be network endpoint
174  std::string witness;
175  if (args.count("witness"))
176  {
177  try
178  {
179  const std::vector<std::string>& witnesses = args["witness"].as<std::vector<std::string>>();
180  if (!witnesses.empty())
181  {
182  witness = "witness " + boost::algorithm::join(witnesses, " ");
183  }
184  }
185  FC_CAPTURE_AND_LOG(())
186  }
187 
188  logging_config.appenders.push_back(get_gelf_config(s, witness));
189 
190  found_logging_config = true;
191  continue;
192  }
193  catch (fc::exception& e)
194  {
195  }
196 
197  //check if appender is a file appender
198  try
199  {
200  logging_config.appenders.push_back(get_file_config(s, pwd));
201 
202  found_logging_config = true;
203  continue;
204  }
205  catch (fc::exception&)
206  {
207  }
208  }
209  }
210 
211  if (args.count(LOGGER))
212  {
213  std::vector<std::string> loggers = args[LOGGER].as<std::vector<std::string>>();
214 
215  for (std::string& s : loggers)
216  {
217  auto logger = fc::json::from_string(s).as<logger_args>();
218 
219  fc::logger_config logger_config(logger.name);
220  logger_config.level = fc::variant(logger.level).as<fc::log_level>();
221  boost::split(logger_config.appenders, logger.appender, boost::is_any_of(" ,"), boost::token_compress_on);
222  logging_config.loggers.push_back(logger_config);
223 
224  found_logging_config = true;
225  }
226  }
227 
228  if (found_logging_config)
229  return logging_config;
230  else
231  return fc::optional<fc::logging_config>();
232  }
233  FC_RETHROW_EXCEPTIONS(warn, "")
234  // clang-format on
235 }
236 
237 void set_logging_program_options(boost::program_options::options_description& options)
238 {
239  // clang-format off
240  std::vector<std::string> default_appender({
241  R"({"appender":"stderr","stream":"std_error"})",
242  LOG_APPENDER R"( = {"appender":"p2p","stream":"logs/p2p.log","rotation_interval_minutes":"120", "rotation_limit_hours":"720"})",
243  LOG_APPENDER R"( = {"appender":"node","stream":"logs/node.log","rotation_interval_minutes":"120", "rotation_limit_hours":"720"})",
244  "# " LOG_APPENDER R"( = {"appender":"remote","stream":"127.0.0.1:12201", "host_name":"", "additional_info":""})"
245  });
246  std::string str_default_appender = boost::algorithm::join(default_appender, "\n");
247 
248 
249  std::vector< std::string > default_logger(
250  { R"({"name":"default","level":"info","appender":"stderr, node"})",
251  LOGGER R"( = {"name":"p2p","level":"info","appender":"p2p"})" });
252  std::string str_default_logger = boost::algorithm::join(default_logger, "\n");
253 
254 
255  std::vector< std::string > default_appender_description(
256  { R"(Console appender definition json: {"appender", "stream"})" ,
257  R"(File appender definition json: {"appender", "stream", "rotation_interval_minutes", "rotation_limit_hours"})",
258  R"(Gelf appender definition json: {"appender", "stream", "host_name", "additional_info"})" });
259  std::string str_default_appender_description = boost::algorithm::join(default_appender_description, "\n# ");
260 
261  auto default_value = [](const std::vector<std::string>& args, const std::string& str)
262  {
263  return boost::program_options::value<std::vector<std::string>>()->composing()->default_value(args, str);
264  };
265 
266  options.add_options()
267  (LOG_APPENDER, default_value(default_appender, str_default_appender), str_default_appender_description.c_str())
268  (LOGGER, default_value(default_logger, str_default_logger), R"(Logger definition json: {"name", "level", "appender"})" );
269  // clang-format on
270 }
271 }
#define SCORUM_BLOCKCHAIN_VERSION
Definition: config.hpp:91
FC_REFLECT(appender_args,(appender)(stream)) FC_REFLECT_DERIVED(file_appender_args
()() host_name(additional_info)) FC_REFLECT(logger_args
() rotation_interval_minutes(rotation_limit_hours)) FC_REFLECT_DERIVED(gelf_appender_args
#define DEFAULT_GELF_APPENDER_PORT
()()() name() level(appender)) namespace logger
#define LOGGER
#define LOG_APPENDER
fc::optional< fc::logging_config > load_logging_config_from_options(const boost::program_options::variables_map &args, const boost::filesystem::path &pwd)
void set_logging_program_options(boost::program_options::options_description &options)
std::string appender
std::string stream
std::string additional_info
std::string level
std::string appender
std::string name