4#include "Stroika/Foundation/StroikaPreComp.h"
6#if qStroika_HasComponent_syslog
10#include "Stroika/Foundation/Cache/SynchronizedCallerStalenessCache.h"
13#include "Stroika/Foundation/Characters/SDKChar.h"
15#include "Stroika/Foundation/Containers/Collection.h"
17#include "Stroika/Foundation/Debug/Debugger.h"
19#include "Stroika/Foundation/Execution/BlockingQueue.h"
20#include "Stroika/Foundation/Execution/Common.h"
22#include "Stroika/Foundation/Execution/Process.h"
25#include "Stroika/Foundation/Execution/TimeOutException.h"
27#include "Stroika/Foundation/Streams/TextToBinary.h"
39using namespace Stroika::Foundation::Traversal;
40using namespace IO::FileSystem;
53struct Logger::Rep_ : enable_shared_from_this<Logger::Rep_> {
54 using PriorityAndMessageType_ = pair<Logger::Priority, String>;
55 bool fBufferingEnabled_{
false};
60 bool fOutQMaybeNeedsFlush_{
true};
63 struct LastMsgInfoType_ {
65 unsigned int fRepeatCount_{};
70 atomic<Time::DurationSeconds> fMaxWindow_{};
74 void FlushSuppressedDuplicates_ (
bool forceEvenIfNotOutOfDate =
false)
76#if USE_NOISY_TRACE_IN_THIS_MODULE_
79 auto lastMsgsLocked = fLastMessages_.
rwget ();
86 if (not lastMsgsLocked->empty ()) {
87 Time::Duration suppressDuplicatesThreshold = fSuppressDuplicatesThreshold_.
cget ()->value_or (0s);
88 for (
auto i = lastMsgsLocked->begin (); i != lastMsgsLocked->end ();) {
89 bool writeThisOne = forceEvenIfNotOutOfDate or i->fValue.fLastSentAt + suppressDuplicatesThreshold < Time::GetTickCount ();
91 switch (i->fValue.fRepeatCount_) {
96 for (shared_ptr<IAppenderRep> tmp : fAppenders_.load ()) {
97 tmp->Log (i->fKey.first, i->fKey.second);
102 for (shared_ptr<IAppenderRep> tmp : fAppenders_.load ()) {
103 tmp->Log (i->fKey.first,
"[{} duplicates suppressed]: {}"_f(i->fValue.fRepeatCount_ - 1, i->fKey.second));
107 lastMsgsLocked->Remove (i, &i);
117#if USE_NOISY_TRACE_IN_THIS_MODULE_
120 if (not fAppenders_.
cget ()->empty ()) {
123 if (p.has_value ()) {
125 for (shared_ptr<IAppenderRep> tmp : fAppenders_.load ()) {
126 tmp->Log (p->first, p->second);
134 fOutQMaybeNeedsFlush_ =
false;
136 void UpdateBookkeepingThread_ ()
140 auto bktLck = fBookkeepingThread_.
rwget ();
141 if (bktLck.cref () !=
nullptr) {
142 bktLck->AbortAndWaitForDone ();
147 Time::Duration suppressDuplicatesThreshold = fSuppressDuplicatesThreshold_.
cget ()->value_or (0s);
148 bool suppressDuplicates = suppressDuplicatesThreshold > 0s;
149 static const String kThreadName_{
"Logger Bookkeeping"sv};
150 if (suppressDuplicates or fBufferingEnabled_) {
152 shared_ptr<Rep_> useRepInThread = shared_from_this ();
153 if (suppressDuplicates) {
155 [suppressDuplicatesThreshold, useRepInThread] () {
158 Duration time2Wait = max<Duration> (2s, suppressDuplicatesThreshold);
159 useRepInThread->FlushSuppressedDuplicates_ ();
160 if (
auto p = useRepInThread->fOutMsgQ_.RemoveHeadIfPossible (time2Wait)) {
162 for (shared_ptr<IAppenderRep> tmp : useRepInThread->fAppenders_.load ()) {
163 IgnoreExceptionsExceptThreadAbortForCall (tmp->Log (p->first, p->second));
172 [useRepInThread] () {
176 auto p = useRepInThread->fOutMsgQ_.RemoveHead ();
178 for (shared_ptr<IAppenderRep> tmp : useRepInThread->fAppenders_.load ()) {
179 tmp->Log (p.first, p.second);
186 newBookKeepThread.
Start ();
187 fBookkeepingThread_ = newBookKeepThread;
195#if qStroika_Foundation_Debug_AssertionsChecked
198 Assert (&
sThe ==
this);
199 Assert (fRep_ ==
nullptr);
203void Logger::Shutdown_ ()
210 bool changed =
false;
213 [[maybe_unused]] lock_guard critSec{fRep_->fSuppressDuplicatesThreshold_};
214 if (fRep_->fSuppressDuplicatesThreshold_.load ()) {
215 fRep_->fSuppressDuplicatesThreshold_.store (nullopt);
219 if (fRep_->fBufferingEnabled_) {
220 fRep_->fBufferingEnabled_ =
false;
224 fRep_->UpdateBookkeepingThread_ ();
230 Ensure (fRep_->fBookkeepingThread_.load () ==
nullptr);
236 return fRep_->fAppenders_;
254 fRep_->fAppenders_.rwget ()->Add (rep);
257void Logger::Log_ (Priority logLevel,
const String& msg)
260 if (not fRep_->fAppenders_->empty ()) {
261 auto p = make_pair (logLevel, msg);
262 if (fRep_->fSuppressDuplicatesThreshold_.cget ()->has_value ()) {
263 auto lastMsgLocked = fRep_->fLastMessages_.rwget ();
265 if (
auto msgInfo = lastMsgLocked->Lookup (p)) {
266 Rep_::LastMsgInfoType_ mi = *msgInfo;
268 mi.fLastSentAt = Time::GetTickCount ();
269 lastMsgLocked->Add (p, mi);
273 lastMsgLocked->Add (p, Rep_::LastMsgInfoType_{Time::GetTickCount ()});
277 fRep_->fOutQMaybeNeedsFlush_ =
true;
278 fRep_->fOutMsgQ_.AddTail (p);
281 if (fRep_->fOutQMaybeNeedsFlush_) {
285 for (shared_ptr<IAppenderRep> tmp : fRep_->fAppenders_.load ()) {
286 tmp->Log (p.first, p.second);
296 if (fRep_->fBufferingEnabled_ != logBufferingEnabled) {
297 fRep_->fBufferingEnabled_ = logBufferingEnabled;
298 fRep_->UpdateBookkeepingThread_ ();
312 return fRep_->fBufferingEnabled_;
318 return fRep_->fSuppressDuplicatesThreshold_.load ();
324 suppressDuplicatesThreshold)};
325 Require (not suppressDuplicatesThreshold.has_value () or *suppressDuplicatesThreshold > 0.0s);
327 [[maybe_unused]] lock_guard critSec{fRep_->fSuppressDuplicatesThreshold_};
328 if (fRep_->fSuppressDuplicatesThreshold_ != suppressDuplicatesThreshold) {
329 fRep_->fSuppressDuplicatesThreshold_ = suppressDuplicatesThreshold;
330 fRep_->UpdateBookkeepingThread_ ();
334#if qStroika_Foundation_Debug_DefaultTracingOn
335void Logger::Log (Priority logLevel,
const wchar_t* format, ...)
338 va_start (argsList, format);
339 String msg = Characters::FormatV (format, argsList);
341 DbgTrace (
"Logger::Log ({}, \"{}\")"_f, logLevel, msg);
343 Log_ (logLevel, msg);
346 DbgTrace (
"...suppressed by WouldLog"_f);
351#if qStroika_HasComponent_syslog
358 string mkMsg_ (
const String& applicationName)
360 return Characters::CString::Format (
"%s[%d]", applicationName.
AsNarrowSDKString (Characters::eIgnoreErrors).c_str (), GetCurrentProcessID ());
363Logger::SysLogAppender::SysLogAppender (
const String& applicationName)
364 : fApplicationName_{mkMsg_ (applicationName)}
366 ::openlog (fApplicationName_.c_str (), 0, LOG_DAEMON);
369Logger::SysLogAppender::SysLogAppender (
const String& applicationName,
int facility)
370 : fApplicationName_{mkMsg_ (applicationName)}
372 ::openlog (fApplicationName_.c_str (), 0, facility);
375Logger::SysLogAppender::~SysLogAppender ()
380void Logger::SysLogAppender::Log (Priority logLevel,
const String& message)
383 int sysLogLevel = LOG_NOTICE;
385 case Priority::eDebug:
386 sysLogLevel = LOG_DEBUG;
388 case Priority::eInfo:
389 sysLogLevel = LOG_INFO;
391 case Priority::eNotice:
392 sysLogLevel = LOG_NOTICE;
394 case Priority::eWarning:
395 sysLogLevel = LOG_WARNING;
397 case Priority::eError:
398 sysLogLevel = LOG_ERR;
400 case Priority::eCriticalError:
401 sysLogLevel = LOG_CRIT;
403 case Priority::eAlertError:
404 sysLogLevel = LOG_ALERT;
406 case Priority::eEmergency:
407 sysLogLevel = LOG_EMERG;
413 ::syslog (sysLogLevel,
"%s", message.
AsNarrowSDKString (Characters::eIgnoreErrors).c_str ());
422struct Logger::StreamAppender::Rep_ {
424 template <
typename T>
426 : fWriter_ (Streams::TextToBinary::Writer::New (out))
429 void Log (Priority logLevel,
const String& message)
432 fWriter_.rwget ()->Write (
441 : fRep_ (make_shared<Rep_> (out))
446 : fRep_ (make_shared<Rep_> (out))
450void Logger::StreamAppender::Log (Priority logLevel,
const String& message)
452 fRep_->Log (logLevel, message);
460struct Logger::FileAppender::Rep_ {
462 Rep_ (
const filesystem::path& fileName,
bool truncateOnOpen)
463 : fOut_ (StreamAppender (FileOutputStream::New (fileName, truncateOnOpen ? FileOutputStream::eStartFromStart : FileOutputStream::eAppend)))
466 void Log (Priority logLevel,
const String& message)
468 fOut_.Log (logLevel, message);
472 StreamAppender fOut_;
475Logger::FileAppender::FileAppender (
const filesystem::path& fileName,
bool truncateOnOpen)
476 : fRep_ (make_shared<Rep_> (fileName, truncateOnOpen))
480void Logger::FileAppender::Log (Priority logLevel,
const String& message)
482 fRep_->Log (logLevel, message);
485#if qStroika_Foundation_Common_Platform_Windows
491Logger::WindowsEventLogAppender::WindowsEventLogAppender (
const String& eventSourceName)
492 : fEventSourceName_{eventSourceName}
496void Logger::WindowsEventLogAppender::Log (Priority logLevel,
const String& message)
501 WORD eventType = EVENTLOG_ERROR_TYPE;
503 case Priority::eDebug:
504 eventType = EVENTLOG_INFORMATION_TYPE;
506 case Priority::eInfo:
507 eventType = EVENTLOG_INFORMATION_TYPE;
509 case Priority::eNotice:
510 eventType = EVENTLOG_INFORMATION_TYPE;
512 case Priority::eWarning:
513 eventType = EVENTLOG_WARNING_TYPE;
515 case Priority::eError:
516 eventType = EVENTLOG_ERROR_TYPE;
518 case Priority::eAlertError:
519 eventType = EVENTLOG_ERROR_TYPE;
521 case Priority::eEmergency:
522 eventType = EVENTLOG_ERROR_TYPE;
525 constexpr auto CATEGORY_Normal = 0x00000001L;
526 WORD eventCategoryID = CATEGORY_Normal;
529 constexpr auto EVENT_Message = 0x00000064L;
530 const DWORD kEventID = EVENT_Message;
531 HANDLE hEventSource = ::RegisterEventSource (NULL, fEventSourceName_.AsSDKString ().c_str ());
532 Verify (hEventSource != NULL);
533 [[maybe_unused]]
auto&& cleanup =
Execution::Finally ([hEventSource] ()
noexcept {
Verify (::DeregisterEventSource (hEventSource)); });
536 constexpr PSID kUserSid_ =
nullptr;
537 constexpr bool kWriteOldWay_ =
true;
538 if constexpr (kWriteOldWay_) {
539 auto rslt = ::ReportEvent (hEventSource, eventType, eventCategoryID, kEventID, kUserSid_, (WORD)1, 0, &msg,
nullptr);
541 DbgTrace (
"lasterr={}"_f, ::GetLastError ());
548 DWORD dataSize =
static_cast<DWORD
> ((tmp.size () + 1) *
sizeof (
SDKChar));
549 Verify (::ReportEvent (hEventSource, eventType, eventCategoryID, kEventID, kUserSid_, 0, dataSize,
nullptr, (LPVOID)msg));
559Logger::Activator::Activator (
const Options& options)
562 Assert (
sThe.fRep_ ==
nullptr);
563 sThe.fRep_ = make_shared<Rep_> ();
565 if (options.fLogBufferingEnabled) {
568 if (options.fMinLogLevel) {
573Logger::Activator::~Activator ()
576 Assert (
sThe.fRep_ !=
nullptr);
591 Logger::sThe.
Log (Logger::eCriticalError,
"Backtrace: {}"_f, Debug::BackTrace::Capture ());
592 Logger::sThe.
Log (Logger::eCriticalError,
"Uncaught exception: {}"_f, std::current_exception ());
594 std::_Exit (EXIT_FAILURE);
605 DbgTrace (
"Fatal Signal: {} encountered"_f, Execution::SignalToName (signal));
606 Logger::sThe.
Log (Logger::eCriticalError,
"Fatal Signal: {}; Aborting..."_f, Execution::SignalToName (signal));
607 Logger::sThe.
Log (Logger::eCriticalError,
"Backtrace: {}"_f, Debug::BackTrace::Capture ());
609 std::_Exit (EXIT_FAILURE);
#define WeakAssertNotReached()
#define RequireNotReached()
#define RequireNotNull(p)
time_point< RealtimeClock, DurationSeconds > TimePointSeconds
TimePointSeconds is a simpler approach to chrono::time_point, which doesn't require using templates e...
#define Stroika_Foundation_Debug_OptionalizeTraceArgs(...)
LRUCache implements a simple least-recently-used caching strategy, with optional hashing (of keys) to...
String is like std::u32string, except it is much easier to use, often much more space efficient,...
nonvirtual tuple< const wchar_t *, wstring_view > c_str(Memory::StackBuffer< wchar_t > *possibleBackingStore) const
static String FromSDKString(const SDKChar *from)
nonvirtual string AsNarrowSDKString() const
nonvirtual SDKString AsSDKString() const
A Collection<T> is a container to manage an un-ordered collection of items, without equality defined ...
nonvirtual optional< T > RemoveHeadIfPossible(Time::DurationSeconds timeout=0s)
nonvirtual bool WouldLog(Priority logLevel) const
nonvirtual optional< Time::Duration > GetSuppressDuplicates() const
nonvirtual void SetAppenders(const shared_ptr< IAppenderRep > &rep)
void Log(Priority logLevel, const wchar_t *format,...)
nonvirtual void SetMinLogLevel(Priority minLogLevel)
nonvirtual Traversal::Iterable< shared_ptr< IAppenderRep > > GetAppenders() const
nonvirtual void SetSuppressDuplicates(const optional< Time::Duration > &suppressDuplicatesThreshold)
nonvirtual bool GetBufferingEnabled() const
nonvirtual void AddAppender(const shared_ptr< IAppenderRep > &rep)
nonvirtual void SetBufferingEnabled(bool logBufferingEnabled)
Wrap any object with Synchronized<> and it can be used similarly to the base type,...
nonvirtual WritableReference rwget()
get a read-write smart pointer to the underlying Synchronized<> object, holding the full lock the who...
nonvirtual ReadableReference cget() const
get a read-only smart pointer to the underlying Synchronized<> object, holding the readlock the whole...
Thread::Ptr is a (unsynchronized) smart pointer referencing an internally synchronized std::thread ob...
nonvirtual void Start() const
nonvirtual void SetThreadPriority(Priority priority=Priority::eNormal) const
OutputStream<>::Ptr is Smart pointer to a stream-based sink of data.
Duration is a chrono::duration<double> (=.
Iterable<T> is a base class for containers which easily produce an Iterator<T> to traverse them.
conditional_t< qTargetPlatformSDKUseswchar_t, wchar_t, char > SDKChar
basic_string< SDKChar > SDKString
Ptr New(const function< void()> &fun2CallOnce, const optional< Characters::String > &name, const optional< Configuration > &configuration)
void DefaultLoggingCrashSignalHandler(Execution::SignalID signal) noexcept
void DefaultLoggingFatalErrorHandler(const Characters::SDKChar *msg) noexcept
auto Finally(FUNCTION &&f) -> Private_::FinallySentry< FUNCTION >