comparison Resources/Orthanc/Core/Logging.cpp @ 16:ff1e935768e7

reorganization
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 09 Nov 2016 20:06:52 +0100
parents Framework/Orthanc/Core/Logging.cpp@da2cf3ace87a
children 7207a407bcd8
comparison
equal deleted inserted replaced
15:da2cf3ace87a 16:ff1e935768e7
1 /**
2 * Orthanc - A Lightweight, RESTful DICOM Store
3 * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
4 * Department, University Hospital of Liege, Belgium
5 *
6 * This program is free software: you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License as
8 * published by the Free Software Foundation, either version 3 of the
9 * License, or (at your option) any later version.
10 *
11 * In addition, as a special exception, the copyright holders of this
12 * program give permission to link the code of its release with the
13 * OpenSSL project's "OpenSSL" library (or with modified versions of it
14 * that use the same license as the "OpenSSL" library), and distribute
15 * the linked executables. You must obey the GNU General Public License
16 * in all respects for all of the code used other than "OpenSSL". If you
17 * modify file(s) with this exception, you may extend this exception to
18 * your version of the file(s), but you are not obligated to do so. If
19 * you do not wish to do so, delete this exception statement from your
20 * version. If you delete this exception statement from all source files
21 * in the program, then also delete it here.
22 *
23 * This program is distributed in the hope that it will be useful, but
24 * WITHOUT ANY WARRANTY; without even the implied warranty of
25 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
26 * General Public License for more details.
27 *
28 * You should have received a copy of the GNU General Public License
29 * along with this program. If not, see <http://www.gnu.org/licenses/>.
30 **/
31
32
33 #include "PrecompiledHeaders.h"
34 #include "Logging.h"
35
36 #if ORTHANC_ENABLE_LOGGING != 1
37
38 namespace Orthanc
39 {
40 namespace Logging
41 {
42 void Initialize()
43 {
44 }
45
46 void Finalize()
47 {
48 }
49
50 void Reset()
51 {
52 }
53
54 void Flush()
55 {
56 }
57
58 void EnableInfoLevel(bool enabled)
59 {
60 }
61
62 void EnableTraceLevel(bool enabled)
63 {
64 }
65
66 void SetTargetFile(const std::string& path)
67 {
68 }
69
70 void SetTargetFolder(const std::string& path)
71 {
72 }
73 }
74 }
75
76 #else
77
78 /*********************************************************
79 * Internal logger of Orthanc, that mimics some
80 * behavior from Google Log.
81 *********************************************************/
82
83 #include "OrthancException.h"
84 #include "Enumerations.h"
85 #include "Toolbox.h"
86 #include "SystemToolbox.h"
87
88 #include <fstream>
89 #include <boost/filesystem.hpp>
90 #include <boost/thread.hpp>
91
92 #if BOOST_HAS_DATE_TIME == 1
93 # include <boost/date_time/posix_time/posix_time.hpp>
94 #else
95 # error Boost::date_time is required
96 #endif
97
98
99 namespace
100 {
101 struct LoggingContext
102 {
103 bool infoEnabled_;
104 bool traceEnabled_;
105 std::string targetFile_;
106 std::string targetFolder_;
107
108 std::ostream* error_;
109 std::ostream* warning_;
110 std::ostream* info_;
111
112 std::auto_ptr<std::ofstream> file_;
113
114 LoggingContext() :
115 infoEnabled_(false),
116 traceEnabled_(false),
117 error_(&std::cerr),
118 warning_(&std::cerr),
119 info_(&std::cerr)
120 {
121 }
122 };
123 }
124
125
126
127 static std::auto_ptr<LoggingContext> loggingContext_;
128 static boost::mutex loggingMutex_;
129
130
131
132 namespace Orthanc
133 {
134 namespace Logging
135 {
136 static void GetLogPath(boost::filesystem::path& log,
137 boost::filesystem::path& link,
138 const std::string& suffix,
139 const std::string& directory)
140 {
141 /**
142 From Google Log documentation:
143
144 Unless otherwise specified, logs will be written to the filename
145 "<program name>.<hostname>.<user name>.log<suffix>.",
146 followed by the date, time, and pid (you can't prevent the date,
147 time, and pid from being in the filename).
148
149 In this implementation : "hostname" and "username" are not used
150 **/
151
152 boost::posix_time::ptime now = boost::posix_time::second_clock::local_time();
153 boost::filesystem::path root(directory);
154 boost::filesystem::path exe(SystemToolbox::GetPathToExecutable());
155
156 if (!boost::filesystem::exists(root) ||
157 !boost::filesystem::is_directory(root))
158 {
159 throw OrthancException(ErrorCode_CannotWriteFile);
160 }
161
162 char date[64];
163 sprintf(date, "%04d%02d%02d-%02d%02d%02d.%d",
164 static_cast<int>(now.date().year()),
165 now.date().month().as_number(),
166 now.date().day().as_number(),
167 now.time_of_day().hours(),
168 now.time_of_day().minutes(),
169 now.time_of_day().seconds(),
170 SystemToolbox::GetProcessId());
171
172 std::string programName = exe.filename().replace_extension("").string();
173
174 log = (root / (programName + ".log" + suffix + "." + std::string(date)));
175 link = (root / (programName + ".log" + suffix));
176 }
177
178
179 static void PrepareLogFolder(std::auto_ptr<std::ofstream>& file,
180 const std::string& suffix,
181 const std::string& directory)
182 {
183 boost::filesystem::path log, link;
184 GetLogPath(log, link, suffix, directory);
185
186 #if !defined(_WIN32) && (defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__)))
187 boost::filesystem::remove(link);
188 boost::filesystem::create_symlink(log.filename(), link);
189 #endif
190
191 file.reset(new std::ofstream(log.string().c_str()));
192 }
193
194
195 void Initialize()
196 {
197 boost::mutex::scoped_lock lock(loggingMutex_);
198 loggingContext_.reset(new LoggingContext);
199 }
200
201 void Finalize()
202 {
203 boost::mutex::scoped_lock lock(loggingMutex_);
204 loggingContext_.reset(NULL);
205 }
206
207 void Reset()
208 {
209 // Recover the old logging context
210 std::auto_ptr<LoggingContext> old;
211
212 {
213 boost::mutex::scoped_lock lock(loggingMutex_);
214 if (loggingContext_.get() == NULL)
215 {
216 return;
217 }
218 else
219 {
220 old = loggingContext_;
221
222 // Create a new logging context,
223 loggingContext_.reset(new LoggingContext);
224 }
225 }
226
227 EnableInfoLevel(old->infoEnabled_);
228 EnableTraceLevel(old->traceEnabled_);
229
230 if (!old->targetFolder_.empty())
231 {
232 SetTargetFolder(old->targetFolder_);
233 }
234 else if (!old->targetFile_.empty())
235 {
236 SetTargetFile(old->targetFile_);
237 }
238 }
239
240 void EnableInfoLevel(bool enabled)
241 {
242 boost::mutex::scoped_lock lock(loggingMutex_);
243 assert(loggingContext_.get() != NULL);
244
245 loggingContext_->infoEnabled_ = enabled;
246
247 if (!enabled)
248 {
249 // Also disable the "TRACE" level when info-level debugging is disabled
250 loggingContext_->traceEnabled_ = false;
251 }
252 }
253
254 void EnableTraceLevel(bool enabled)
255 {
256 boost::mutex::scoped_lock lock(loggingMutex_);
257 assert(loggingContext_.get() != NULL);
258
259 loggingContext_->traceEnabled_ = enabled;
260
261 if (enabled)
262 {
263 // Also enable the "INFO" level when trace-level debugging is enabled
264 loggingContext_->infoEnabled_ = true;
265 }
266 }
267
268
269 static void CheckFile(std::auto_ptr<std::ofstream>& f)
270 {
271 if (loggingContext_->file_.get() == NULL ||
272 !loggingContext_->file_->is_open())
273 {
274 throw OrthancException(ErrorCode_CannotWriteFile);
275 }
276 }
277
278 void SetTargetFolder(const std::string& path)
279 {
280 boost::mutex::scoped_lock lock(loggingMutex_);
281 assert(loggingContext_.get() != NULL);
282
283 PrepareLogFolder(loggingContext_->file_, "" /* no suffix */, path);
284 CheckFile(loggingContext_->file_);
285
286 loggingContext_->targetFile_.clear();
287 loggingContext_->targetFolder_ = path;
288 loggingContext_->warning_ = loggingContext_->file_.get();
289 loggingContext_->error_ = loggingContext_->file_.get();
290 loggingContext_->info_ = loggingContext_->file_.get();
291 }
292
293
294 void SetTargetFile(const std::string& path)
295 {
296 boost::mutex::scoped_lock lock(loggingMutex_);
297 assert(loggingContext_.get() != NULL);
298
299 loggingContext_->file_.reset(new std::ofstream(path.c_str(), std::fstream::app));
300 CheckFile(loggingContext_->file_);
301
302 loggingContext_->targetFile_ = path;
303 loggingContext_->targetFolder_.clear();
304 loggingContext_->warning_ = loggingContext_->file_.get();
305 loggingContext_->error_ = loggingContext_->file_.get();
306 loggingContext_->info_ = loggingContext_->file_.get();
307 }
308
309
310 InternalLogger::InternalLogger(const char* level,
311 const char* file,
312 int line) :
313 lock_(loggingMutex_),
314 stream_(&null_) // By default, logging to "/dev/null" is simulated
315 {
316 if (loggingContext_.get() == NULL)
317 {
318 fprintf(stderr, "ERROR: Trying to log a message after the finalization of the logging engine\n");
319 return;
320 }
321
322 try
323 {
324 LogLevel l = StringToLogLevel(level);
325
326 if ((l == LogLevel_Info && !loggingContext_->infoEnabled_) ||
327 (l == LogLevel_Trace && !loggingContext_->traceEnabled_))
328 {
329 // This logging level is disabled, directly exit and unlock
330 // the mutex to speed-up things. The stream is set to "/dev/null"
331 lock_.unlock();
332 return;
333 }
334
335 // Compute the header of the line, temporary release the lock as
336 // this is a time-consuming operation
337 lock_.unlock();
338 std::string header;
339
340 {
341 boost::filesystem::path path(file);
342 boost::posix_time::ptime now = boost::posix_time::microsec_clock::local_time();
343 boost::posix_time::time_duration duration = now.time_of_day();
344
345 /**
346 From Google Log documentation:
347
348 "Log lines have this form:
349
350 Lmmdd hh:mm:ss.uuuuuu threadid file:line] msg...
351
352 where the fields are defined as follows:
353
354 L A single character, representing the log level (eg 'I' for INFO)
355 mm The month (zero padded; ie May is '05')
356 dd The day (zero padded)
357 hh:mm:ss.uuuuuu Time in hours, minutes and fractional seconds
358 threadid The space-padded thread ID as returned by GetTID() (this matches the PID on Linux)
359 file The file name
360 line The line number
361 msg The user-supplied message"
362
363 In this implementation, "threadid" is not printed.
364 **/
365
366 char date[32];
367 sprintf(date, "%c%02d%02d %02d:%02d:%02d.%06d ",
368 level[0],
369 now.date().month().as_number(),
370 now.date().day().as_number(),
371 duration.hours(),
372 duration.minutes(),
373 duration.seconds(),
374 static_cast<int>(duration.fractional_seconds()));
375
376 header = std::string(date) + path.filename().string() + ":" + boost::lexical_cast<std::string>(line) + "] ";
377 }
378
379
380 // The header is computed, we now re-lock the mutex to access
381 // the stream objects. Pay attention that "loggingContext_",
382 // "infoEnabled_" or "traceEnabled_" might have changed while
383 // the mutex was unlocked.
384 lock_.lock();
385
386 if (loggingContext_.get() == NULL)
387 {
388 fprintf(stderr, "ERROR: Trying to log a message after the finalization of the logging engine\n");
389 return;
390 }
391
392 switch (l)
393 {
394 case LogLevel_Error:
395 stream_ = loggingContext_->error_;
396 break;
397
398 case LogLevel_Warning:
399 stream_ = loggingContext_->warning_;
400 break;
401
402 case LogLevel_Info:
403 if (loggingContext_->infoEnabled_)
404 {
405 stream_ = loggingContext_->info_;
406 }
407
408 break;
409
410 case LogLevel_Trace:
411 if (loggingContext_->traceEnabled_)
412 {
413 stream_ = loggingContext_->info_;
414 }
415
416 break;
417
418 default:
419 throw OrthancException(ErrorCode_InternalError);
420 }
421
422 if (stream_ == &null_)
423 {
424 // The logging is disabled for this level. The stream is the
425 // "null_" member of this object, so we can release the global
426 // mutex.
427 lock_.unlock();
428 }
429
430 (*stream_) << header;
431 }
432 catch (...)
433 {
434 // Something is going really wrong, probably running out of
435 // memory. Fallback to a degraded mode.
436 stream_ = loggingContext_->error_;
437 (*stream_) << "E???? ??:??:??.?????? ] ";
438 }
439 }
440
441
442 InternalLogger::~InternalLogger()
443 {
444 if (stream_ != &null_)
445 {
446 #if defined(_WIN32)
447 *stream_ << "\r\n";
448 #else
449 *stream_ << "\n";
450 #endif
451
452 stream_->flush();
453 }
454 }
455
456
457 void Flush()
458 {
459 boost::mutex::scoped_lock lock(loggingMutex_);
460
461 if (loggingContext_.get() != NULL &&
462 loggingContext_->file_.get() != NULL)
463 {
464 loggingContext_->file_->flush();
465 }
466 }
467 }
468 }
469
470 #endif // ORTHANC_ENABLE_LOGGING