Line data Source code
1 : /**
2 : * Copyright Soramitsu Co., Ltd. All Rights Reserved.
3 : * SPDX-License-Identifier: Apache-2.0
4 : */
5 :
6 : #include "main/iroha_conf_loader.hpp"
7 :
8 : #include <fstream>
9 : #include <limits>
10 : #include <sstream>
11 :
12 : #include <rapidjson/document.h>
13 : #include <rapidjson/error/en.h>
14 : #include <rapidjson/istreamwrapper.h>
15 : #include <rapidjson/rapidjson.h>
16 : #include <boost/algorithm/string/join.hpp>
17 : #include <boost/range/adaptor/map.hpp>
18 : #include "main/iroha_conf_literals.hpp"
19 :
20 : /// The length of the string around the error place to print in case of JSON
21 : /// syntax error.
22 : static constexpr size_t kBadJsonPrintLength = 15;
23 :
24 : /// The offset of printed chunk towards file start from the error position.
25 : static constexpr size_t kBadJsonPrintOffsset = 5;
26 :
27 : static_assert(kBadJsonPrintOffsset <= kBadJsonPrintLength,
28 : "The place of error is out of the printed string boundaries!");
29 :
30 : /**
31 : * Adds the children logger configs from parent logger JSON object to parent
32 : * logger config. The parent logger JSON object is searched for the children
33 : * config section, and the children configs are parsed and created if the
34 : * section is present.
35 : * @param path - current config node path used to denote the possible error
36 : * place.
37 : * @param parent_config - the parent logger config
38 : * @param parent_obj - the parent logger json configuration
39 : */
40 : void addChildrenLoggerConfigs(const std::string &path,
41 : const logger::LoggerManagerTreePtr &parent_config,
42 : const rapidjson::Value::ConstObject &parent_obj);
43 :
44 : /**
45 : * Overrides the logger configuration with the values from JSON object.
46 : * @param path - current config node path used to denote the possible error
47 : * place.
48 : * @param cfg - the configuration to use as base
49 : * @param obj - the JSON object to take overrides from
50 : */
51 : void updateLoggerConfig(const std::string &path,
52 : logger::LoggerConfig &cfg,
53 : const rapidjson::Value::ConstObject &obj);
54 :
55 : /**
56 : * Gets a value by a key from a JSON object, if present.
57 : * @param path - current config node path used to denote the possible error
58 : * place.
59 : * @param dest - the variable to store the value
60 : * @param obj - the source JSON object
61 : * @param key - the key for the requested value
62 : * @return true if the value was loaded, otherwise false.
63 : */
64 : template <typename TDest, typename TKey>
65 : bool tryGetValByKey(const std::string &path,
66 : TDest &dest,
67 : const rapidjson::Value::ConstObject &obj,
68 : const TKey &key);
69 :
70 : /**
71 : * Gets an optional value by a key from a JSON object.
72 : * @param path - current config node path used to denote the possible error
73 : * place.
74 : * @param obj - the source JSON object
75 : * @param key - the key for the requested value
76 : * @return the value if present in the JSON object, otherwise boost::none.
77 : */
78 : template <typename TDest, typename TKey>
79 : boost::optional<TDest> getOptValByKey(const std::string &path,
80 : const rapidjson::Value::ConstObject &obj,
81 : const TKey &key);
82 :
83 : /**
84 : * Adds one sublevel to the path denoting a place in config tree.
85 : * @param parent - the location of the sublevel
86 : * @param child - the name of sublevel
87 : * @return the path to the sublevel
88 : */
89 : template <typename TChild>
90 : inline std::string sublevelPath(std::string parent, TChild child) {
91 0 : std::stringstream child_sstream;
92 0 : child_sstream << child;
93 0 : return std::move(parent) + "/" + child_sstream.str();
94 0 : }
95 :
96 : /**
97 : * Gets a value by a key from a JSON object. Throws an exception if the value
98 : * was not loaded.
99 : * @param path - current config node path used to denote the possible error
100 : * place.
101 : * @param dest - the variable to store the value
102 : * @param obj - the source JSON object
103 : * @param key - the key for the requested value
104 : */
105 : template <typename TDest, typename TKey>
106 : void getValByKey(const std::string &path,
107 : TDest &dest,
108 : const rapidjson::Value::ConstObject &obj,
109 : const TKey &key);
110 :
111 : /**
112 : * Throws a runtime exception if the given condition is false.
113 : * @param condition
114 : * @param error - error message
115 : */
116 : inline void assert_fatal(bool condition, std::string error) {
117 0 : if (!condition) {
118 0 : throw std::runtime_error(error);
119 : }
120 0 : }
121 :
122 : // ------------ getVal(path, dst, src) ------------
123 : // getVal is a set of functions that load the value from rapidjson::Value to a
124 : // given destination variable. They check the JSON type and throw exception if
125 : // it is wrong. The path argument is used to denote the possible error place.
126 :
127 : template <typename TDest>
128 : typename std::enable_if<not std::numeric_limits<TDest>::is_integer>::type
129 : getVal(const std::string &path, TDest &, const rapidjson::Value &) {
130 : BOOST_THROW_EXCEPTION(
131 : std::runtime_error("Wrong type. Should never reach here."));
132 : }
133 :
134 : template <typename TDest>
135 : typename std::enable_if<std::numeric_limits<TDest>::is_integer>::type getVal(
136 : const std::string &path, TDest &dest, const rapidjson::Value &src) {
137 0 : assert_fatal(src.IsInt64(), path + " must be an integer");
138 0 : const int64_t val = src.GetInt64();
139 0 : assert_fatal(val >= std::numeric_limits<TDest>::min()
140 0 : && val <= std::numeric_limits<TDest>::max(),
141 0 : path + ": integer value out of range");
142 0 : dest = val;
143 0 : }
144 :
145 : template <>
146 : void getVal<bool>(const std::string &path,
147 : bool &dest,
148 : const rapidjson::Value &src) {
149 0 : assert_fatal(src.IsBool(), path + " must be a boolean");
150 0 : dest = src.GetBool();
151 0 : }
152 :
153 : template <>
154 : void getVal<std::string>(const std::string &path,
155 : std::string &dest,
156 : const rapidjson::Value &src) {
157 0 : assert_fatal(src.IsString(), path + " must be a string");
158 0 : dest = src.GetString();
159 0 : }
160 :
161 : template <>
162 : void getVal<logger::LogLevel>(const std::string &path,
163 : logger::LogLevel &dest,
164 : const rapidjson::Value &src) {
165 0 : std::string level_str;
166 0 : getVal(path, level_str, src);
167 0 : const auto it = config_members::LogLevels.find(level_str);
168 0 : if (it == config_members::LogLevels.end()) {
169 0 : BOOST_THROW_EXCEPTION(std::runtime_error(
170 : "Wrong log level at " + path + ": must be one of '"
171 : + boost::algorithm::join(
172 : config_members::LogLevels | boost::adaptors::map_keys, "', '")
173 : + "'."));
174 : }
175 0 : dest = it->second;
176 0 : }
177 :
178 : template <>
179 : void getVal<logger::LogPatterns>(const std::string &path,
180 : logger::LogPatterns &dest,
181 : const rapidjson::Value &src) {
182 0 : assert_fatal(src.IsObject(),
183 0 : path + " must be a map from log level to pattern");
184 0 : for (const auto &pattern_entry : src.GetObject()) {
185 : logger::LogLevel level;
186 0 : std::string pattern_str;
187 0 : getVal(sublevelPath(path, "(level name)"), level, pattern_entry.name);
188 0 : getVal(sublevelPath(path, "(pattern)"), pattern_str, pattern_entry.value);
189 0 : dest.setPattern(level, pattern_str);
190 0 : }
191 0 : }
192 :
193 : template <>
194 : void getVal<logger::LoggerManagerTreePtr>(const std::string &path,
195 : logger::LoggerManagerTreePtr &dest,
196 : const rapidjson::Value &src) {
197 0 : assert_fatal(src.IsObject(), path + " must be a logger tree config");
198 0 : logger::LoggerConfig root_config{logger::kDefaultLogLevel,
199 0 : logger::getDefaultLogPatterns()};
200 0 : updateLoggerConfig(path, root_config, src.GetObject());
201 0 : dest = std::make_shared<logger::LoggerManagerTree>(
202 0 : std::make_shared<const logger::LoggerConfig>(std::move(root_config)));
203 0 : addChildrenLoggerConfigs(path, dest, src.GetObject());
204 0 : }
205 :
206 : template <>
207 : void getVal<IrohadConfig>(const std::string &path,
208 : IrohadConfig &dest,
209 : const rapidjson::Value &src) {
210 0 : assert_fatal(src.IsObject(),
211 0 : path + " Irohad config top element must be an object.");
212 0 : const auto obj = src.GetObject();
213 0 : getValByKey(path, dest.block_store_path, obj, config_members::BlockStorePath);
214 0 : getValByKey(path, dest.torii_port, obj, config_members::ToriiPort);
215 0 : getValByKey(path, dest.internal_port, obj, config_members::InternalPort);
216 0 : getValByKey(path, dest.pg_opt, obj, config_members::PgOpt);
217 0 : getValByKey(
218 0 : path, dest.max_proposal_size, obj, config_members::MaxProposalSize);
219 0 : getValByKey(path, dest.proposal_delay, obj, config_members::ProposalDelay);
220 0 : getValByKey(path, dest.vote_delay, obj, config_members::VoteDelay);
221 0 : getValByKey(path, dest.mst_support, obj, config_members::MstSupport);
222 0 : getValByKey(
223 0 : path, dest.mst_expiration_time, obj, config_members::MstExpirationTime);
224 0 : getValByKey(
225 0 : path, dest.max_round_delay_ms, obj, config_members::MaxRoundsDelay);
226 0 : getValByKey(path,
227 0 : dest.stale_stream_max_rounds,
228 : obj,
229 : config_members::StaleStreamMaxRounds);
230 0 : getValByKey(path, dest.logger_manager, obj, config_members::LogSection);
231 0 : }
232 :
233 : // ------------ end of getVal(path, dst, src) ------------
234 :
235 : void updateLoggerConfig(const std::string &path,
236 : logger::LoggerConfig &cfg,
237 : const rapidjson::Value::ConstObject &obj) {
238 0 : tryGetValByKey(path, cfg.log_level, obj, config_members::LogLevel);
239 0 : tryGetValByKey(path, cfg.patterns, obj, config_members::LogPatternsSection);
240 0 : }
241 :
242 : template <typename TDest, typename TKey>
243 : bool tryGetValByKey(const std::string &path,
244 : TDest &dest,
245 : const rapidjson::Value::ConstObject &obj,
246 : const TKey &key) {
247 0 : const auto it = obj.FindMember(key);
248 0 : if (it == obj.MemberEnd()) {
249 0 : return false;
250 : } else {
251 0 : getVal(sublevelPath(path, key), dest, it->value);
252 0 : return true;
253 : }
254 0 : }
255 :
256 : template <typename TDest, typename TKey>
257 : bool tryGetValByKey(const std::string &path,
258 : boost::optional<TDest> &dest,
259 : const rapidjson::Value::ConstObject &obj,
260 : const TKey &key) {
261 0 : dest = getOptValByKey<TDest>(path, obj, key);
262 0 : return true; // value loaded any way, either from file or boost::none
263 : }
264 :
265 : template <typename TDest, typename TKey>
266 : boost::optional<TDest> getOptValByKey(const std::string &path,
267 : const rapidjson::Value::ConstObject &obj,
268 : const TKey &key) {
269 0 : TDest val;
270 0 : return boost::make_optional(tryGetValByKey(path, val, obj, key), val);
271 0 : }
272 :
273 : /**
274 : * Gets a value by a key from a JSON object. Throws an exception if the value
275 : * was not loaded.
276 : * @param path - current config node path used to denote the possible error
277 : * place.
278 : * @param dest - the variable to store the value
279 : * @param obj - the source JSON object
280 : * @param key - the key for the requested value
281 : */
282 : template <typename TDest, typename TKey>
283 : void getValByKey(const std::string &path,
284 : TDest &dest,
285 : const rapidjson::Value::ConstObject &obj,
286 : const TKey &key) {
287 0 : assert_fatal(tryGetValByKey(path, dest, obj, key),
288 0 : path + " has no key '" + key + "'.");
289 0 : }
290 :
291 : void addChildrenLoggerConfigs(const std::string &path,
292 : const logger::LoggerManagerTreePtr &parent_config,
293 : const rapidjson::Value::ConstObject &parent_obj) {
294 0 : const auto it = parent_obj.FindMember(config_members::LogChildrenSection);
295 0 : if (it != parent_obj.MemberEnd()) {
296 : auto children_section_path =
297 0 : sublevelPath(path, config_members::LogChildrenSection);
298 0 : for (const auto &child_json : it->value.GetObject()) {
299 0 : assert_fatal(child_json.name.IsString(),
300 0 : "Child logger key must be a string holding its tag.");
301 0 : assert_fatal(child_json.value.IsObject(),
302 0 : "Child logger value must be a JSON object.");
303 0 : auto child_tag = child_json.name.GetString();
304 0 : const auto child_obj = child_json.value.GetObject();
305 0 : auto child_path = sublevelPath(children_section_path, child_tag);
306 0 : auto child_conf = parent_config->registerChild(
307 0 : std::move(child_tag),
308 0 : getOptValByKey<logger::LogLevel>(
309 : child_path, child_obj, config_members::LogLevel),
310 0 : getOptValByKey<logger::LogPatterns>(
311 : child_path, child_obj, config_members::LogPatternsSection));
312 0 : addChildrenLoggerConfigs(std::move(child_path), child_conf, child_obj);
313 0 : }
314 0 : }
315 0 : }
316 :
317 : std::string reportJsonParsingError(const rapidjson::Document &doc,
318 : const std::string &conf_path,
319 : std::istream &input) {
320 0 : const size_t error_offset = doc.GetErrorOffset();
321 : // This ensures the unsigned string beginning position does not cross zero:
322 0 : const size_t print_offset =
323 0 : std::max(error_offset, kBadJsonPrintOffsset) - kBadJsonPrintOffsset;
324 0 : input.seekg(print_offset);
325 0 : std::string json_error_buf(kBadJsonPrintLength, 0);
326 0 : input.readsome(&json_error_buf[0], kBadJsonPrintLength);
327 0 : return "JSON parse error [" + conf_path + "] " + "(near `" + json_error_buf
328 0 : + "'): " + std::string(rapidjson::GetParseError_En(doc.GetParseError()));
329 0 : }
330 :
331 : IrohadConfig parse_iroha_config(const std::string &conf_path) {
332 : const rapidjson::Document doc{[&conf_path] {
333 0 : rapidjson::Document doc;
334 0 : std::ifstream ifs_iroha(conf_path);
335 0 : rapidjson::IStreamWrapper isw(ifs_iroha);
336 0 : doc.ParseStream(isw);
337 0 : assert_fatal(not doc.HasParseError(),
338 0 : reportJsonParsingError(doc, conf_path, ifs_iroha));
339 0 : return doc;
340 0 : }()};
341 :
342 0 : IrohadConfig config;
343 0 : getVal("", config, doc);
344 0 : return config;
345 0 : }
|