Line data Source code
1 : // Copyright (c) 2013-2022 Made to Order Software Corp. All Rights Reserved
2 : //
3 : // https://snapwebsites.org/project/snaplogger
4 : // contact@m2osw.com
5 : //
6 : // This program is free software; you can redistribute it and/or modify
7 : // it under the terms of the GNU General Public License as published by
8 : // the Free Software Foundation; either version 2 of the License, or
9 : // (at your option) any later version.
10 : //
11 : // This program is distributed in the hope that it will be useful,
12 : // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 : // GNU General Public License for more details.
15 : //
16 : // You should have received a copy of the GNU General Public License along
17 : // with this program; if not, write to the Free Software Foundation, Inc.,
18 : // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 :
20 : /** \file
21 : * \brief Appenders are used to append data to somewhere.
22 : *
23 : * This file declares the base appender class.
24 : */
25 :
26 : // self
27 : //
28 : #include "snaplogger/file_appender.h"
29 :
30 : #include "snaplogger/exception.h"
31 : #include "snaplogger/guard.h"
32 : #include "snaplogger/map_diagnostic.h"
33 : #include "snaplogger/syslog_appender.h"
34 :
35 :
36 : // advgetopt lib
37 : //
38 : #include <advgetopt/validator_size.h>
39 :
40 :
41 : // snapdev lib
42 : //
43 : #include <snapdev/lockfile.h>
44 :
45 :
46 : // C++ lib
47 : //
48 : #include <iostream>
49 :
50 :
51 : // C lib
52 : //
53 : #include <fcntl.h>
54 : #include <syslog.h>
55 : #include <sys/stat.h>
56 : #include <sys/types.h>
57 : #include <unistd.h>
58 :
59 :
60 : // last include
61 : //
62 : #include <snapdev/poison.h>
63 :
64 :
65 :
66 : namespace snaplogger
67 : {
68 :
69 :
70 : namespace
71 : {
72 :
73 :
74 7 : APPENDER_FACTORY(file);
75 :
76 :
77 : }
78 : // no name namespace
79 :
80 :
81 :
82 0 : file_appender::file_appender(std::string const & name)
83 0 : : appender(name, "file")
84 : {
85 0 : }
86 :
87 :
88 0 : file_appender::~file_appender()
89 : {
90 0 : }
91 :
92 :
93 0 : void file_appender::set_config(advgetopt::getopt const & opts)
94 : {
95 0 : guard g;
96 :
97 0 : appender::set_config(opts);
98 :
99 : // PATH
100 : //
101 0 : std::string const path_field(get_name() + "::path");
102 0 : if(opts.is_defined(path_field))
103 : {
104 0 : f_path = opts.get_string(path_field);
105 : }
106 0 : else if(opts.is_defined("path"))
107 : {
108 0 : f_path = opts.get_string("path");
109 : }
110 :
111 : // FILENAME
112 : //
113 0 : std::string const filename_field(get_name() + "::filename");
114 0 : if(opts.is_defined(filename_field))
115 : {
116 0 : f_filename = opts.get_string(filename_field);
117 : }
118 : // else -- we'll try to dynamically determine a filename when we
119 : // reach the process_message() function
120 :
121 : // MAXIMUM SIZE
122 : //
123 0 : std::string const maximum_size_field(get_name() + "::maximum_size");
124 0 : if(opts.is_defined(maximum_size_field))
125 : {
126 0 : std::string const size_str(opts.get_string(maximum_size_field));
127 : #pragma GCC diagnostic push
128 : #pragma GCC diagnostic ignored "-Wpedantic"
129 0 : __int128 size(0);
130 0 : if(advgetopt::validator_size::convert_string(
131 : size_str
132 : , advgetopt::validator_size::VALIDATOR_SIZE_DEFAULT_FLAGS
133 : , size))
134 : {
135 0 : f_maximum_size = std::max(size, static_cast<__int128>(INT64_MAX));
136 : }
137 : #pragma GCC diagnostic pop
138 : }
139 :
140 : // ON OVERFLOW
141 : //
142 0 : std::string const on_overflow_field(get_name() + "::on_overflow");
143 0 : if(opts.is_defined(on_overflow_field))
144 : {
145 0 : f_on_overflow = opts.get_string(on_overflow_field);
146 : }
147 :
148 : // LOCK
149 : //
150 0 : std::string const lock_field(get_name() + "::lock");
151 0 : if(opts.is_defined(lock_field))
152 : {
153 0 : f_lock = opts.get_string(lock_field) == "true";
154 : }
155 :
156 : // FLUSH
157 : //
158 0 : std::string const flush_field(get_name() + "::flush");
159 0 : if(opts.is_defined(flush_field))
160 : {
161 0 : f_flush = opts.get_string(flush_field) == "true";
162 : }
163 :
164 : // SECURE
165 : //
166 0 : std::string const secure_field(get_name() + "::secure");
167 0 : if(opts.is_defined(secure_field))
168 : {
169 0 : f_secure = opts.get_string(secure_field) != "false";
170 : }
171 :
172 : // FALLBACK TO CONSOLE
173 : //
174 0 : std::string const fallback_to_console_field(get_name() + "::fallback_to_console");
175 0 : if(opts.is_defined(fallback_to_console_field))
176 : {
177 0 : f_fallback_to_console = opts.get_string(fallback_to_console_field) == "true";
178 : }
179 :
180 : // FALLBACK TO SYSLOG
181 : //
182 0 : std::string const fallback_to_syslog_field(get_name() + "::fallback_to_syslog");
183 0 : if(opts.is_defined(fallback_to_syslog_field))
184 : {
185 0 : f_fallback_to_syslog = opts.get_string(fallback_to_syslog_field) == "true";
186 : }
187 0 : }
188 :
189 :
190 0 : void file_appender::reopen()
191 : {
192 0 : guard g;
193 :
194 0 : f_fd.reset();
195 0 : f_initialized = false;
196 0 : }
197 :
198 :
199 0 : void file_appender::set_filename(std::string const & filename)
200 : {
201 0 : guard g;
202 :
203 0 : if(f_filename != filename)
204 : {
205 0 : f_filename = filename;
206 0 : f_initialized = false;
207 : }
208 0 : }
209 :
210 :
211 0 : void file_appender::process_message(message const & msg, std::string const & formatted_message)
212 : {
213 0 : guard g;
214 :
215 : // verify whether the output file is too large, if so rename it .log.1
216 : // and create a new file; the process will delete an existing .log.1 if
217 : // present; as a result we make sure that files never grow over a
218 : // user specified maximum; by default this feature uses a maximum size
219 : // of 10Mb
220 : //
221 0 : if(f_maximum_size > 0
222 0 : && !!f_fd)
223 : {
224 0 : struct stat s = {};
225 0 : int const r(fstat(f_fd.get(), &s));
226 0 : if(r != 0
227 0 : || s.st_size >= f_maximum_size)
228 : {
229 0 : if(!f_limit_reached)
230 : {
231 0 : f_limit_reached = true;
232 :
233 : // TODO: look at properly formatting this message
234 : //
235 0 : output_message(msg, "-- file size limit reached, this will be the last message --", false);
236 : }
237 :
238 0 : if(f_on_overflow == "skip")
239 : {
240 0 : return;
241 : }
242 :
243 0 : if(f_on_overflow == "fatal")
244 : {
245 0 : throw fatal_error("logger's output file is full");
246 : }
247 :
248 0 : if(f_on_overflow == "rotate")
249 : {
250 : // poorman's log rotate replacing the .1 if it exists
251 : // this way we avoid the compression which is really
252 : // slow...
253 : //
254 0 : std::string one(f_filename + ".1");
255 0 : snapdev::NOT_USED(unlink(one.c_str()));
256 0 : if(rename(f_filename.c_str(), one.c_str()) != 0)
257 : {
258 0 : snapdev::NOT_USED(unlink(f_filename.c_str()));
259 : }
260 : }
261 0 : else if(f_on_overflow == "logrotate")
262 : {
263 : // TODO: run logrotate properly
264 : //
265 : // right now, we could only run it as ourselves
266 : // also we do want to specify which configuration
267 : // file to use otherwise it would attempt to
268 : // rotate everything which is not what we want
269 : //
270 0 : if(system("/usr/sbin/logrotate /etc/logrotate.conf") != 0)
271 : {
272 : // assume our rotation failed
273 : //
274 0 : return;
275 : }
276 : }
277 : else
278 : {
279 : // act like "skip" (we could also throw an error, but that
280 : // would then act like "fatal" which may not be the best
281 : // default action)
282 : //
283 0 : return;
284 : }
285 :
286 : // force a reopen as when we do a logrotate with an event
287 : // (we would not yet have received the event here)
288 : //
289 0 : reopen();
290 : }
291 : else
292 : {
293 0 : f_limit_reached = false;
294 : }
295 : }
296 :
297 0 : if(!f_initialized)
298 : {
299 0 : f_initialized = true;
300 :
301 0 : if(f_filename.empty())
302 : {
303 : // try to generate a filename
304 : //
305 0 : map_diagnostics_t map(get_map_diagnostics());
306 0 : auto const it(map.find("progname"));
307 0 : if(it == map.end())
308 : {
309 0 : return;
310 : }
311 0 : if(it->second.empty())
312 : {
313 0 : return;
314 : }
315 :
316 0 : f_filename = f_path + '/';
317 0 : if(f_secure)
318 : {
319 0 : f_filename += "secure/";
320 : }
321 0 : f_filename += it->second;
322 0 : f_filename += ".log";
323 : }
324 0 : else if(f_filename.find('/') == std::string::npos)
325 : {
326 0 : f_filename = f_path + '/' + f_filename;
327 : }
328 0 : std::string::size_type pos(f_filename.rfind('/'));
329 0 : if(pos == std::string::npos)
330 : {
331 0 : pos = 0;
332 : }
333 0 : if(f_filename.find('.', pos + 1) == std::string::npos)
334 : {
335 0 : f_filename += ".log";
336 : }
337 :
338 0 : if(access(f_filename.c_str(), R_OK | W_OK) != 0
339 0 : && errno != ENOENT)
340 : {
341 0 : return;
342 : }
343 :
344 0 : int flags(O_CREAT | O_WRONLY | O_APPEND | O_CLOEXEC | O_LARGEFILE | O_NOCTTY);
345 0 : int mode(S_IRUSR | S_IWUSR);
346 0 : if(!f_secure)
347 : {
348 0 : mode |= S_IRGRP;
349 : }
350 :
351 0 : f_fd.reset(open(f_filename.c_str(), flags, mode));
352 :
353 : // verify that the file isn't already too big
354 : //
355 0 : struct stat s = {};
356 0 : int const r(fstat(f_fd.get(), &s));
357 0 : if(r != 0
358 0 : || s.st_size >= f_maximum_size)
359 : {
360 0 : return;
361 : }
362 : }
363 :
364 0 : output_message(msg, formatted_message, true);
365 : }
366 :
367 :
368 0 : void file_appender::output_message(message const & msg, std::string const & formatted_message, bool allow_fallbacks)
369 : {
370 0 : if(!f_fd)
371 : {
372 0 : return;
373 : }
374 :
375 0 : std::unique_ptr<snapdev::lockfd> lock_file;
376 0 : if(f_lock)
377 : {
378 0 : lock_file = std::make_unique<snapdev::lockfd>(f_fd.get(), snapdev::lockfd::mode_t::LOCKFILE_EXCLUSIVE);
379 : }
380 :
381 0 : ssize_t const l(write(f_fd.get(), formatted_message.c_str(), formatted_message.length()));
382 0 : if(static_cast<size_t>(l) != formatted_message.length())
383 : {
384 : // how could we report that? we are the logger...
385 : //
386 0 : if(allow_fallbacks)
387 : {
388 0 : if(f_fallback_to_console
389 0 : && isatty(fileno(stdout)))
390 : {
391 0 : std::cout << formatted_message.c_str();
392 : }
393 0 : else if(f_fallback_to_syslog)
394 : {
395 : // in this case we skip on the openlog() call...
396 : //
397 0 : int const priority(syslog_appender::message_severity_to_syslog_priority(msg.get_severity()));
398 0 : syslog(priority, "%s", formatted_message.c_str());
399 : }
400 : }
401 : }
402 : }
403 :
404 :
405 :
406 :
407 :
408 6 : } // snaplogger namespace
409 : // vim: ts=4 sw=4 et
|