Mercurial > hg > orthanc-stone
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 |