comparison OrthancFramework/Sources/Logging.cpp @ 4044:d25f4c0fa160 framework

splitting code into OrthancFramework and OrthancServer
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 10 Jun 2020 20:30:34 +0200
parents Core/Logging.cpp@05a363186da6
children bf7b9edf6b81
comparison
equal deleted inserted replaced
4043:6c6239aec462 4044:d25f4c0fa160
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 * Copyright (C) 2017-2020 Osimis S.A., Belgium
6 *
7 * This program is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License as
9 * published by the Free Software Foundation, either version 3 of the
10 * License, or (at your option) any later version.
11 *
12 * In addition, as a special exception, the copyright holders of this
13 * program give permission to link the code of its release with the
14 * OpenSSL project's "OpenSSL" library (or with modified versions of it
15 * that use the same license as the "OpenSSL" library), and distribute
16 * the linked executables. You must obey the GNU General Public License
17 * in all respects for all of the code used other than "OpenSSL". If you
18 * modify file(s) with this exception, you may extend this exception to
19 * your version of the file(s), but you are not obligated to do so. If
20 * you do not wish to do so, delete this exception statement from your
21 * version. If you delete this exception statement from all source files
22 * in the program, then also delete it here.
23 *
24 * This program is distributed in the hope that it will be useful, but
25 * WITHOUT ANY WARRANTY; without even the implied warranty of
26 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
27 * General Public License for more details.
28 *
29 * You should have received a copy of the GNU General Public License
30 * along with this program. If not, see <http://www.gnu.org/licenses/>.
31 **/
32
33
34 #include "PrecompiledHeaders.h"
35 #include "Logging.h"
36
37 #include "OrthancException.h"
38
39
40 namespace Orthanc
41 {
42 namespace Logging
43 {
44 const char* EnumerationToString(LogLevel level)
45 {
46 switch (level)
47 {
48 case LogLevel_ERROR:
49 return "ERROR";
50
51 case LogLevel_WARNING:
52 return "WARNING";
53
54 case LogLevel_INFO:
55 return "INFO";
56
57 case LogLevel_TRACE:
58 return "TRACE";
59
60 default:
61 throw OrthancException(ErrorCode_ParameterOutOfRange);
62 }
63 }
64
65
66 LogLevel StringToLogLevel(const char *level)
67 {
68 if (strcmp(level, "ERROR") == 0)
69 {
70 return LogLevel_ERROR;
71 }
72 else if (strcmp(level, "WARNING") == 0)
73 {
74 return LogLevel_WARNING;
75 }
76 else if (strcmp(level, "INFO") == 0)
77 {
78 return LogLevel_INFO;
79 }
80 else if (strcmp(level, "TRACE") == 0)
81 {
82 return LogLevel_TRACE;
83 }
84 else
85 {
86 throw OrthancException(ErrorCode_InternalError);
87 }
88 }
89 }
90 }
91
92
93 #if ORTHANC_ENABLE_LOGGING != 1
94
95 namespace Orthanc
96 {
97 namespace Logging
98 {
99 void InitializePluginContext(void* pluginContext)
100 {
101 }
102
103 void Initialize()
104 {
105 }
106
107 void Finalize()
108 {
109 }
110
111 void Reset()
112 {
113 }
114
115 void Flush()
116 {
117 }
118
119 void EnableInfoLevel(bool enabled)
120 {
121 }
122
123 void EnableTraceLevel(bool enabled)
124 {
125 }
126
127 bool IsTraceLevelEnabled()
128 {
129 return false;
130 }
131
132 bool IsInfoLevelEnabled()
133 {
134 return false;
135 }
136
137 void SetTargetFile(const std::string& path)
138 {
139 }
140
141 void SetTargetFolder(const std::string& path)
142 {
143 }
144 }
145 }
146
147
148 #elif ORTHANC_ENABLE_LOGGING_STDIO == 1
149
150 /*********************************************************
151 * Logger compatible with <stdio.h> OR logger that sends its
152 * output to the emscripten html5 api (depending on the
153 * definition of __EMSCRIPTEN__)
154 *********************************************************/
155
156 #include <stdio.h>
157
158 #ifdef __EMSCRIPTEN__
159 # include <emscripten/html5.h>
160 #endif
161
162 namespace Orthanc
163 {
164 namespace Logging
165 {
166 static bool infoEnabled_ = false;
167 static bool traceEnabled_ = false;
168
169 #ifdef __EMSCRIPTEN__
170 static void ErrorLogFunc(const char* msg)
171 {
172 emscripten_console_error(msg);
173 }
174
175 static void WarningLogFunc(const char* msg)
176 {
177 emscripten_console_warn(msg);
178 }
179
180 static void InfoLogFunc(const char* msg)
181 {
182 emscripten_console_log(msg);
183 }
184
185 static void TraceLogFunc(const char* msg)
186 {
187 emscripten_console_log(msg);
188 }
189 #else /* __EMSCRIPTEN__ not #defined */
190 static void ErrorLogFunc(const char* msg)
191 {
192 fprintf(stderr, "E: %s\n", msg);
193 }
194
195 static void WarningLogFunc(const char*)
196 {
197 fprintf(stdout, "W: %s\n", msg);
198 }
199
200 static void InfoLogFunc(const char*)
201 {
202 fprintf(stdout, "I: %s\n", msg);
203 }
204
205 static void TraceLogFunc(const char*)
206 {
207 fprintf(stdout, "T: %s\n", msg);
208 }
209 #endif /* __EMSCRIPTEN__ */
210
211
212 InternalLogger::~InternalLogger()
213 {
214 std::string message = messageStream_.str();
215
216 switch (level_)
217 {
218 case LogLevel_ERROR:
219 ErrorLogFunc(message.c_str());
220 break;
221
222 case LogLevel_WARNING:
223 WarningLogFunc(message.c_str());
224 break;
225
226 case LogLevel_INFO:
227 if (infoEnabled_)
228 {
229 InfoLogFunc(message.c_str());
230 // TODO: stone_console_info(message_.c_str());
231 }
232 break;
233
234 case LogLevel_TRACE:
235 if (traceEnabled_)
236 {
237 TraceLogFunc(message.c_str());
238 }
239 break;
240
241 default:
242 {
243 std::stringstream ss;
244 ss << "Unknown log level (" << level_ << ") for message: " << message;
245 std::string s = ss.str();
246 ErrorLogFunc(s.c_str());
247 }
248 }
249 }
250
251 void InitializePluginContext(void* pluginContext)
252 {
253 }
254
255 void Initialize()
256 {
257 }
258
259 void Finalize()
260 {
261 }
262
263 void Reset()
264 {
265 }
266
267 void Flush()
268 {
269 }
270
271 void EnableInfoLevel(bool enabled)
272 {
273 infoEnabled_ = enabled;
274
275 if (!enabled)
276 {
277 // Also disable the "TRACE" level when info-level debugging is disabled
278 traceEnabled_ = false;
279 }
280 }
281
282 bool IsInfoLevelEnabled()
283 {
284 return infoEnabled_;
285 }
286
287 void EnableTraceLevel(bool enabled)
288 {
289 traceEnabled_ = enabled;
290 }
291
292 bool IsTraceLevelEnabled()
293 {
294 return traceEnabled_;
295 }
296
297 void SetTargetFile(const std::string& path)
298 {
299 }
300
301 void SetTargetFolder(const std::string& path)
302 {
303 }
304 }
305 }
306
307
308 #else
309
310 /*********************************************************
311 * Logger compatible with the Orthanc plugin SDK, or that
312 * mimics behavior from Google Log.
313 *********************************************************/
314
315 #include <cassert>
316
317 namespace
318 {
319 /**
320 * This is minimal implementation of the context for an Orthanc
321 * plugin, limited to the logging facilities, and that is binary
322 * compatible with the definitions of "OrthancCPlugin.h"
323 **/
324 typedef enum
325 {
326 _OrthancPluginService_LogInfo = 1,
327 _OrthancPluginService_LogWarning = 2,
328 _OrthancPluginService_LogError = 3,
329 _OrthancPluginService_INTERNAL = 0x7fffffff
330 } _OrthancPluginService;
331
332 typedef struct _OrthancPluginContext_t
333 {
334 void* pluginsManager;
335 const char* orthancVersion;
336 void (*Free) (void* buffer);
337 int32_t (*InvokeService) (struct _OrthancPluginContext_t* context,
338 _OrthancPluginService service,
339 const void* params);
340 } OrthancPluginContext;
341 }
342
343
344 #include "Enumerations.h"
345 #include "SystemToolbox.h"
346
347 #include <fstream>
348 #include <boost/filesystem.hpp>
349 #include <boost/thread.hpp>
350 #include <boost/date_time/posix_time/posix_time.hpp>
351
352
353 namespace
354 {
355 struct LoggingStreamsContext
356 {
357 std::string targetFile_;
358 std::string targetFolder_;
359
360 std::ostream* error_;
361 std::ostream* warning_;
362 std::ostream* info_;
363
364 std::unique_ptr<std::ofstream> file_;
365
366 LoggingStreamsContext() :
367 error_(&std::cerr),
368 warning_(&std::cerr),
369 info_(&std::cerr)
370 {
371 }
372 };
373 }
374
375
376
377 static std::unique_ptr<LoggingStreamsContext> loggingStreamsContext_;
378 static boost::mutex loggingStreamsMutex_;
379 static Orthanc::Logging::NullStream nullStream_;
380 static OrthancPluginContext* pluginContext_ = NULL;
381 static bool infoEnabled_ = false;
382 static bool traceEnabled_ = false;
383
384
385 namespace Orthanc
386 {
387 namespace Logging
388 {
389 static void GetLogPath(boost::filesystem::path& log,
390 boost::filesystem::path& link,
391 const std::string& suffix,
392 const std::string& directory)
393 {
394 /**
395 From Google Log documentation:
396
397 Unless otherwise specified, logs will be written to the filename
398 "<program name>.<hostname>.<user name>.log<suffix>.",
399 followed by the date, time, and pid (you can't prevent the date,
400 time, and pid from being in the filename).
401
402 In this implementation : "hostname" and "username" are not used
403 **/
404
405 boost::posix_time::ptime now = boost::posix_time::second_clock::local_time();
406 boost::filesystem::path root(directory);
407 boost::filesystem::path exe(SystemToolbox::GetPathToExecutable());
408
409 if (!boost::filesystem::exists(root) ||
410 !boost::filesystem::is_directory(root))
411 {
412 throw OrthancException(ErrorCode_CannotWriteFile);
413 }
414
415 char date[64];
416 sprintf(date, "%04d%02d%02d-%02d%02d%02d.%d",
417 static_cast<int>(now.date().year()),
418 now.date().month().as_number(),
419 now.date().day().as_number(),
420 static_cast<int>(now.time_of_day().hours()),
421 static_cast<int>(now.time_of_day().minutes()),
422 static_cast<int>(now.time_of_day().seconds()),
423 SystemToolbox::GetProcessId());
424
425 std::string programName = exe.filename().replace_extension("").string();
426
427 log = (root / (programName + ".log" + suffix + "." + std::string(date)));
428 link = (root / (programName + ".log" + suffix));
429 }
430
431
432 static void PrepareLogFolder(std::unique_ptr<std::ofstream>& file,
433 const std::string& suffix,
434 const std::string& directory)
435 {
436 boost::filesystem::path log, link;
437 GetLogPath(log, link, suffix, directory);
438
439 #if !defined(_WIN32) && (defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__)))
440 boost::filesystem::remove(link);
441 boost::filesystem::create_symlink(log.filename(), link);
442 #endif
443
444 file.reset(new std::ofstream(log.string().c_str()));
445 }
446
447
448 // "loggingStreamsMutex_" must be locked
449 static void CheckFile(std::unique_ptr<std::ofstream>& f)
450 {
451 if (loggingStreamsContext_->file_.get() == NULL ||
452 !loggingStreamsContext_->file_->is_open())
453 {
454 throw OrthancException(ErrorCode_CannotWriteFile);
455 }
456 }
457
458
459 static void GetLinePrefix(std::string& prefix,
460 LogLevel level,
461 const char* file,
462 int line)
463 {
464 boost::filesystem::path path(file);
465 boost::posix_time::ptime now = boost::posix_time::microsec_clock::local_time();
466 boost::posix_time::time_duration duration = now.time_of_day();
467
468 /**
469 From Google Log documentation:
470
471 "Log lines have this form:
472
473 Lmmdd hh:mm:ss.uuuuuu threadid file:line] msg...
474
475 where the fields are defined as follows:
476
477 L A single character, representing the log level (eg 'I' for INFO)
478 mm The month (zero padded; ie May is '05')
479 dd The day (zero padded)
480 hh:mm:ss.uuuuuu Time in hours, minutes and fractional seconds
481 threadid The space-padded thread ID as returned by GetTID() (this matches the PID on Linux)
482 file The file name
483 line The line number
484 msg The user-supplied message"
485
486 In this implementation, "threadid" is not printed.
487 **/
488
489 char c;
490 switch (level)
491 {
492 case LogLevel_ERROR:
493 c = 'E';
494 break;
495
496 case LogLevel_WARNING:
497 c = 'W';
498 break;
499
500 case LogLevel_INFO:
501 c = 'I';
502 break;
503
504 case LogLevel_TRACE:
505 c = 'T';
506 break;
507
508 default:
509 throw OrthancException(ErrorCode_InternalError);
510 }
511
512 char date[64];
513 sprintf(date, "%c%02d%02d %02d:%02d:%02d.%06d ",
514 c,
515 now.date().month().as_number(),
516 now.date().day().as_number(),
517 static_cast<int>(duration.hours()),
518 static_cast<int>(duration.minutes()),
519 static_cast<int>(duration.seconds()),
520 static_cast<int>(duration.fractional_seconds()));
521
522 prefix = (std::string(date) + path.filename().string() + ":" +
523 boost::lexical_cast<std::string>(line) + "] ");
524 }
525
526
527 void InitializePluginContext(void* pluginContext)
528 {
529 assert(sizeof(_OrthancPluginService) == sizeof(int32_t));
530
531 boost::mutex::scoped_lock lock(loggingStreamsMutex_);
532 loggingStreamsContext_.reset(NULL);
533 pluginContext_ = reinterpret_cast<OrthancPluginContext*>(pluginContext);
534 }
535
536
537 void Initialize()
538 {
539 boost::mutex::scoped_lock lock(loggingStreamsMutex_);
540
541 if (loggingStreamsContext_.get() == NULL)
542 {
543 loggingStreamsContext_.reset(new LoggingStreamsContext);
544 }
545 }
546
547 void Finalize()
548 {
549 boost::mutex::scoped_lock lock(loggingStreamsMutex_);
550 loggingStreamsContext_.reset(NULL);
551 }
552
553 void Reset()
554 {
555 // Recover the old logging context
556 std::unique_ptr<LoggingStreamsContext> old;
557
558 {
559 boost::mutex::scoped_lock lock(loggingStreamsMutex_);
560 if (loggingStreamsContext_.get() == NULL)
561 {
562 return;
563 }
564 else
565 {
566 #if __cplusplus < 201103L
567 old.reset(loggingStreamsContext_.release());
568 #else
569 old = std::move(loggingStreamsContext_);
570 #endif
571
572 // Create a new logging context,
573 loggingStreamsContext_.reset(new LoggingStreamsContext);
574 }
575 }
576
577 if (!old->targetFolder_.empty())
578 {
579 SetTargetFolder(old->targetFolder_);
580 }
581 else if (!old->targetFile_.empty())
582 {
583 SetTargetFile(old->targetFile_);
584 }
585 }
586
587
588 void EnableInfoLevel(bool enabled)
589 {
590 infoEnabled_ = enabled;
591
592 if (!enabled)
593 {
594 // Also disable the "TRACE" level when info-level debugging is disabled
595 traceEnabled_ = false;
596 }
597 }
598
599 bool IsInfoLevelEnabled()
600 {
601 return infoEnabled_;
602 }
603
604 void EnableTraceLevel(bool enabled)
605 {
606 traceEnabled_ = enabled;
607
608 if (enabled)
609 {
610 // Also enable the "INFO" level when trace-level debugging is enabled
611 infoEnabled_ = true;
612 }
613 }
614
615 bool IsTraceLevelEnabled()
616 {
617 return traceEnabled_;
618 }
619
620
621 void SetTargetFolder(const std::string& path)
622 {
623 boost::mutex::scoped_lock lock(loggingStreamsMutex_);
624 if (loggingStreamsContext_.get() != NULL)
625 {
626 PrepareLogFolder(loggingStreamsContext_->file_, "" /* no suffix */, path);
627 CheckFile(loggingStreamsContext_->file_);
628
629 loggingStreamsContext_->targetFile_.clear();
630 loggingStreamsContext_->targetFolder_ = path;
631 loggingStreamsContext_->warning_ = loggingStreamsContext_->file_.get();
632 loggingStreamsContext_->error_ = loggingStreamsContext_->file_.get();
633 loggingStreamsContext_->info_ = loggingStreamsContext_->file_.get();
634 }
635 }
636
637
638 void SetTargetFile(const std::string& path)
639 {
640 boost::mutex::scoped_lock lock(loggingStreamsMutex_);
641
642 if (loggingStreamsContext_.get() != NULL)
643 {
644 loggingStreamsContext_->file_.reset(new std::ofstream(path.c_str(), std::fstream::app));
645 CheckFile(loggingStreamsContext_->file_);
646
647 loggingStreamsContext_->targetFile_ = path;
648 loggingStreamsContext_->targetFolder_.clear();
649 loggingStreamsContext_->warning_ = loggingStreamsContext_->file_.get();
650 loggingStreamsContext_->error_ = loggingStreamsContext_->file_.get();
651 loggingStreamsContext_->info_ = loggingStreamsContext_->file_.get();
652 }
653 }
654
655
656 InternalLogger::InternalLogger(LogLevel level,
657 const char* file,
658 int line) :
659 lock_(loggingStreamsMutex_, boost::defer_lock_t()),
660 level_(level),
661 stream_(&nullStream_) // By default, logging to "/dev/null" is simulated
662 {
663 if (pluginContext_ != NULL)
664 {
665 // We are logging using the Orthanc plugin SDK
666
667 if (level == LogLevel_TRACE)
668 {
669 // No trace level in plugins, directly exit as the stream is
670 // set to "/dev/null"
671 return;
672 }
673 else
674 {
675 pluginStream_.reset(new std::stringstream);
676 stream_ = pluginStream_.get();
677 }
678 }
679 else
680 {
681 // We are logging in a standalone application, not inside an Orthanc plugin
682
683 if ((level == LogLevel_INFO && !infoEnabled_) ||
684 (level == LogLevel_TRACE && !traceEnabled_))
685 {
686 // This logging level is disabled, directly exit as the
687 // stream is set to "/dev/null"
688 return;
689 }
690
691 std::string prefix;
692 GetLinePrefix(prefix, level, file, line);
693
694 {
695 // We lock the global mutex. The mutex is locked until the
696 // destructor is called: No change in the output can be done.
697 lock_.lock();
698
699 if (loggingStreamsContext_.get() == NULL)
700 {
701 fprintf(stderr, "ERROR: Trying to log a message after the finalization of the logging engine\n");
702 lock_.unlock();
703 return;
704 }
705
706 switch (level)
707 {
708 case LogLevel_ERROR:
709 stream_ = loggingStreamsContext_->error_;
710 break;
711
712 case LogLevel_WARNING:
713 stream_ = loggingStreamsContext_->warning_;
714 break;
715
716 case LogLevel_INFO:
717 case LogLevel_TRACE:
718 stream_ = loggingStreamsContext_->info_;
719 break;
720
721 default:
722 throw OrthancException(ErrorCode_InternalError);
723 }
724
725 if (stream_ == &nullStream_)
726 {
727 // The logging is disabled for this level, we can release
728 // the global mutex.
729 lock_.unlock();
730 }
731 else
732 {
733 try
734 {
735 (*stream_) << prefix;
736 }
737 catch (...)
738 {
739 // Something is going really wrong, probably running out of
740 // memory. Fallback to a degraded mode.
741 stream_ = loggingStreamsContext_->error_;
742 (*stream_) << "E???? ??:??:??.?????? ] ";
743 }
744 }
745 }
746 }
747 }
748
749
750 InternalLogger::~InternalLogger()
751 {
752 if (pluginStream_.get() != NULL)
753 {
754 // We are logging through the Orthanc SDK
755
756 std::string message = pluginStream_->str();
757
758 if (pluginContext_ != NULL)
759 {
760 switch (level_)
761 {
762 case LogLevel_ERROR:
763 pluginContext_->InvokeService(pluginContext_, _OrthancPluginService_LogError, message.c_str());
764 break;
765
766 case LogLevel_WARNING:
767 pluginContext_->InvokeService(pluginContext_, _OrthancPluginService_LogWarning, message.c_str());
768 break;
769
770 case LogLevel_INFO:
771 pluginContext_->InvokeService(pluginContext_, _OrthancPluginService_LogInfo, message.c_str());
772 break;
773
774 default:
775 break;
776 }
777 }
778 }
779 else if (stream_ != &nullStream_)
780 {
781 *stream_ << "\n";
782 stream_->flush();
783 }
784 }
785
786
787 void Flush()
788 {
789 if (pluginContext_ != NULL)
790 {
791 boost::mutex::scoped_lock lock(loggingStreamsMutex_);
792
793 if (loggingStreamsContext_.get() != NULL &&
794 loggingStreamsContext_->file_.get() != NULL)
795 {
796 loggingStreamsContext_->file_->flush();
797 }
798 }
799 }
800
801
802 void SetErrorWarnInfoLoggingStreams(std::ostream& errorStream,
803 std::ostream& warningStream,
804 std::ostream& infoStream)
805 {
806 boost::mutex::scoped_lock lock(loggingStreamsMutex_);
807
808 loggingStreamsContext_.reset(new LoggingStreamsContext);
809 loggingStreamsContext_->error_ = &errorStream;
810 loggingStreamsContext_->warning_ = &warningStream;
811 loggingStreamsContext_->info_ = &infoStream;
812 }
813 }
814 }
815
816
817 #endif // ORTHANC_ENABLE_LOGGING