log.cpp 4.36 KB
Newer Older
1
2
3
/** Logging and debugging routines
 *
 * @author Steffen Vogel <stvogel@eonerc.rwth-aachen.de>
Steffen Vogel's avatar
Steffen Vogel committed
4
 * @copyright 2014-2021, Institute for Automation of Complex Power Systems, EONERC
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
 * @license GNU General Public License (version 3)
 *
 * VILLAScommon
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *********************************************************************************/

23
24
25
#include <list>
#include <algorithm>

26
27
#include <fnmatch.h>

28
29
30
31
#include <spdlog/sinks/syslog_sink.h>
#include <spdlog/sinks/basic_file_sink.h>

#include <villas/log.hpp>
Steffen Vogel's avatar
Steffen Vogel committed
32
#include <villas/terminal.hpp>
33
34
35
36
37
38
39
#include <villas/exceptions.hpp>

using namespace villas;

/** The global log instance */
Log villas::logging;

Steffen Vogel's avatar
Steffen Vogel committed
40
Log::Log(Level lvl) :
Steffen Vogel's avatar
Steffen Vogel committed
41
42
	level(lvl),
	pattern("%H:%M:%S %^%l%$ %n: %v")
43
44
45
46
47
{
	char *p = getenv("VILLAS_LOG_PREFIX");
	if (p)
		prefix = p;

Steffen Vogel's avatar
Steffen Vogel committed
48
49
	sinks = std::make_shared<DistSink::element_type>();

Steffen Vogel's avatar
Steffen Vogel committed
50
51
52
	setLevel(level);
	setPattern(pattern);

53
	/* Default sink */
Steffen Vogel's avatar
Steffen Vogel committed
54
	sink = std::make_shared<spdlog::sinks::stderr_color_sink_mt>();
55
56
57
58
59
60

	sinks->add_sink(sink);
}

int Log::getWidth()
{
Steffen Vogel's avatar
Steffen Vogel committed
61
	int width = Terminal::getCols() - 50;
62
63
64
65
66
67
68
69
70

	if (!prefix.empty())
		width -= prefix.length();

	return width;
}

Logger Log::get(const std::string &name)
{
Steffen Vogel's avatar
Steffen Vogel committed
71
	Logger logger = spdlog::get(name);
72

Steffen Vogel's avatar
Steffen Vogel committed
73
	if (not logger) {
Steffen Vogel's avatar
Steffen Vogel committed
74
		logger = std::make_shared<Logger::element_type>(name, sink);
75

Steffen Vogel's avatar
Steffen Vogel committed
76
77
78
		logger->set_level(level);
		logger->set_pattern(prefix + pattern);

79
80
81
82
83
		for (auto &expr : expressions) {
			if (!fnmatch(expr.name.c_str(), name.c_str(), FNM_EXTMATCH))
				logger->set_level(expr.level);
		}

Steffen Vogel's avatar
Steffen Vogel committed
84
85
86
		spdlog::register_logger(logger);
	}

87
88
89
	return logger;
}

Steffen Vogel's avatar
Steffen Vogel committed
90
void Log::parse(json_t *json)
91
{
Steffen Vogel's avatar
Steffen Vogel committed
92
93
94
	const char *level = nullptr;
	const char *path = nullptr;
	const char *pattern = nullptr;
95

96
	int ret, syslog = 0;
97
98

	json_error_t err;
Steffen Vogel's avatar
Steffen Vogel committed
99
	json_t *json_expressions = nullptr;
100

Steffen Vogel's avatar
Steffen Vogel committed
101
	ret = json_unpack_ex(json, &err, JSON_STRICT, "{ s?: s, s?: s, s?: o, s?: b, s?: s }",
102
103
104
105
106
107
108
		"level", &level,
		"file", &path,
		"expressions", &json_expressions,
		"syslog", &syslog,
		"pattern", &pattern
	);
	if (ret)
Steffen Vogel's avatar
Steffen Vogel committed
109
		throw ConfigError(json, err, "node-config-logging");
110
111
112
113
114
115
116

	if (level)
		setLevel(level);

	if (path) {
		auto sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>(path);

Steffen Vogel's avatar
Steffen Vogel committed
117
		sinks->add_sink(sink);
118
119
120
	}

	if (syslog) {
121
#if SPDLOG_VERSION >= 10400
Steffen Vogel's avatar
Steffen Vogel committed
122
		auto sink = std::make_shared<spdlog::sinks::syslog_sink_mt>("villas", LOG_PID, LOG_DAEMON, true);
123
124
125
#else
		auto sink = std::make_shared<spdlog::sinks::syslog_sink_mt>("villas", LOG_PID, LOG_DAEMON);
#endif
Steffen Vogel's avatar
Steffen Vogel committed
126
		sinks->add_sink(sink);
127
128
129
130
	}

	if (json_expressions) {
		if (!json_is_array(json_expressions))
131
			throw ConfigError(json_expressions, "node-config-logging-expressions", "The 'expressions' setting must be a list of objects.");
132
133
134

		size_t i;
		json_t *json_expression;
135
136
		json_array_foreach(json_expressions, i, json_expression)
			expressions.emplace_back(json_expression);
137
138
139
	}
}

Steffen Vogel's avatar
Steffen Vogel committed
140
141
142
143
144
void Log::setPattern(const std::string &pat)
{
	pattern = pat;

	spdlog::set_pattern(pattern, spdlog::pattern_time_type::utc);
145
	sinks->set_pattern(pattern);
Steffen Vogel's avatar
Steffen Vogel committed
146
147
}

148
149
void Log::setLevel(Level lvl)
{
150
151
	level = lvl;

152
	spdlog::set_level(lvl);
153
	sinks->set_level(lvl);
154
155
156
157
}

void Log::setLevel(const std::string &lvl)
{
158
159
160
161
162
163
	std::list<std::string> l = SPDLOG_LEVEL_NAMES;

	auto it = std::find(l.begin(), l.end(), lvl);
	if (it == l.end())
		throw RuntimeError("Invalid log level {}", lvl);

164
	setLevel(spdlog::level::from_str(lvl));
165
166
}

Steffen Vogel's avatar
Steffen Vogel committed
167
168
169
170
Log::Level Log::getLevel() const
{
	return level;
}
Steffen Vogel's avatar
Steffen Vogel committed
171
172
173

std::string Log::getLevelName() const
{
Steffen Vogel's avatar
Steffen Vogel committed
174
175
176
	auto sv = spdlog::level::to_string_view(level);

	return std::string(sv.data());
Steffen Vogel's avatar
Steffen Vogel committed
177
}
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197

Log::Expression::Expression(json_t *json)
{
	int ret;

	const char *nme;
	const char *lvl;

	json_error_t err;

	ret = json_unpack_ex(json, &err, JSON_STRICT, "{ s: s, s: s }",
		"name", &nme,
		"level", &lvl
	);
	if (ret)
		throw ConfigError(json, err, "node-config-logging-expressions");

	level = spdlog::level::from_str(lvl);
	name = nme;
}