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"
28#include "Stroika/Foundation/Streams/TextToBinary.h"
40using namespace Stroika::Foundation::Traversal;
41using namespace IO::FileSystem;
44using Memory::MakeSharedPtr;
55struct Logger::Rep_ : enable_shared_from_this<Logger::Rep_> {
56 using PriorityAndMessageType_ = pair<Logger::Priority, String>;
57 bool fBufferingEnabled_{
false};
62 bool fOutQMaybeNeedsFlush_{
true};
65 struct LastMsgInfoType_ {
67 unsigned int fRepeatCount_{};
72 atomic<Time::DurationSeconds> fMaxWindow_{};
76 void FlushSuppressedDuplicates_ (
bool forceEvenIfNotOutOfDate =
false)
78#if USE_NOISY_TRACE_IN_THIS_MODULE_
81 auto lastMsgsLocked = fLastMessages_.
rwget ();
88 if (not lastMsgsLocked->empty ()) {
89 Time::Duration suppressDuplicatesThreshold = fSuppressDuplicatesThreshold_.
cget ()->value_or (0s);
90 for (
auto i = lastMsgsLocked->begin (); i != lastMsgsLocked->end ();) {
91 bool writeThisOne = forceEvenIfNotOutOfDate or i->fValue.fLastSentAt + suppressDuplicatesThreshold < Time::GetTickCount ();
93 switch (i->fValue.fRepeatCount_) {
98 for (shared_ptr<IAppenderRep> tmp : fAppenders_.load ()) {
99 tmp->Log (i->fKey.first, i->fKey.second);
104 for (shared_ptr<IAppenderRep> tmp : fAppenders_.load ()) {
105 tmp->Log (i->fKey.first,
"[{} duplicates suppressed]: {}"_f(i->fValue.fRepeatCount_ - 1, i->fKey.second));
109 lastMsgsLocked->Remove (i, &i);
119#if USE_NOISY_TRACE_IN_THIS_MODULE_
122 if (not fAppenders_.
cget ()->empty ()) {
125 if (p.has_value ()) {
127 for (shared_ptr<IAppenderRep> tmp : fAppenders_.load ()) {
128 tmp->Log (p->first, p->second);
136 fOutQMaybeNeedsFlush_ =
false;
138 void UpdateBookkeepingThread_ ()
142 auto bktLck = fBookkeepingThread_.
rwget ();
143 if (bktLck.cref () !=
nullptr) {
144 bktLck->AbortAndWaitForDone ();
149 Time::Duration suppressDuplicatesThreshold = fSuppressDuplicatesThreshold_.
cget ()->value_or (0s);
150 bool suppressDuplicates = suppressDuplicatesThreshold > 0s;
151 static const String kThreadName_{
"Logger Bookkeeping"sv};
152 if (suppressDuplicates or fBufferingEnabled_) {
154 shared_ptr<Rep_> useRepInThread = shared_from_this ();
155 if (suppressDuplicates) {
157 [suppressDuplicatesThreshold, useRepInThread] () {
160 Duration time2Wait = max<Duration> (2s, suppressDuplicatesThreshold);
161 useRepInThread->FlushSuppressedDuplicates_ ();
162 if (
auto p = useRepInThread->fOutMsgQ_.RemoveHeadIfPossible (time2Wait)) {
164 for (shared_ptr<IAppenderRep> tmp : useRepInThread->fAppenders_.load ()) {
165 IgnoreExceptionsExceptThreadAbortForCall (tmp->Log (p->first, p->second));
174 [useRepInThread] () {
178 auto p = useRepInThread->fOutMsgQ_.RemoveHead ();
180 for (shared_ptr<IAppenderRep> tmp : useRepInThread->fAppenders_.load ()) {
181 tmp->Log (p.first, p.second);
188 newBookKeepThread.
Start ();
189 fBookkeepingThread_ = newBookKeepThread;
197#if qStroika_Foundation_Debug_AssertionsChecked
200 Assert (&
sThe ==
this);
201 Assert (fRep_ ==
nullptr);
205void Logger::Shutdown_ ()
212 bool changed =
false;
215 [[maybe_unused]] lock_guard critSec{fRep_->fSuppressDuplicatesThreshold_};
216 if (fRep_->fSuppressDuplicatesThreshold_.load ()) {
217 fRep_->fSuppressDuplicatesThreshold_.store (nullopt);
221 if (fRep_->fBufferingEnabled_) {
222 fRep_->fBufferingEnabled_ =
false;
226 fRep_->UpdateBookkeepingThread_ ();
232 Ensure (fRep_->fBookkeepingThread_.load () ==
nullptr);
238 return fRep_->fAppenders_;
256 fRep_->fAppenders_.rwget ()->Add (rep);
259void Logger::Log_ (Priority logLevel,
const String& msg)
262 if (not fRep_->fAppenders_->empty ()) {
263 auto p = make_pair (logLevel, msg);
264 if (fRep_->fSuppressDuplicatesThreshold_.cget ()->has_value ()) {
265 auto lastMsgLocked = fRep_->fLastMessages_.rwget ();
267 if (
auto msgInfo = lastMsgLocked->Lookup (p)) {
268 Rep_::LastMsgInfoType_ mi = *msgInfo;
270 mi.fLastSentAt = Time::GetTickCount ();
271 lastMsgLocked->Add (p, mi);
275 lastMsgLocked->Add (p, Rep_::LastMsgInfoType_{Time::GetTickCount ()});
279 fRep_->fOutQMaybeNeedsFlush_ =
true;
280 fRep_->fOutMsgQ_.AddTail (p);
283 if (fRep_->fOutQMaybeNeedsFlush_) {
287 for (shared_ptr<IAppenderRep> tmp : fRep_->fAppenders_.load ()) {
288 tmp->Log (p.first, p.second);
298 if (fRep_->fBufferingEnabled_ != logBufferingEnabled) {
299 fRep_->fBufferingEnabled_ = logBufferingEnabled;
300 fRep_->UpdateBookkeepingThread_ ();
314 return fRep_->fBufferingEnabled_;
320 return fRep_->fSuppressDuplicatesThreshold_.load ();
326 suppressDuplicatesThreshold)};
327 Require (not suppressDuplicatesThreshold.has_value () or *suppressDuplicatesThreshold > 0.0s);
329 [[maybe_unused]] lock_guard critSec{fRep_->fSuppressDuplicatesThreshold_};
330 if (fRep_->fSuppressDuplicatesThreshold_ != suppressDuplicatesThreshold) {
331 fRep_->fSuppressDuplicatesThreshold_ = suppressDuplicatesThreshold;
332 fRep_->UpdateBookkeepingThread_ ();
336#if qStroika_Foundation_Debug_DefaultTracingOn
337void Logger::Log (Priority logLevel,
const wchar_t* format, ...)
340 va_start (argsList, format);
341 String msg = Characters::FormatV (format, argsList);
343 DbgTrace (
"Logger::Log ({}, \"{}\")"_f, logLevel, msg);
345 Log_ (logLevel, msg);
348 DbgTrace (
"...suppressed by WouldLog"_f);
353#if qStroika_HasComponent_syslog
360 string mkMsg_ (
const String& applicationName)
362 return Characters::CString::Format (
"%s[%d]", applicationName.
AsNarrowSDKString (Characters::eIgnoreErrors).c_str (), GetCurrentProcessID ());
365Logger::SysLogAppender::SysLogAppender (
const String& applicationName)
366 : fApplicationName_{mkMsg_ (applicationName)}
368 ::openlog (fApplicationName_.c_str (), 0, LOG_DAEMON);
371Logger::SysLogAppender::SysLogAppender (
const String& applicationName,
int facility)
372 : fApplicationName_{mkMsg_ (applicationName)}
374 ::openlog (fApplicationName_.c_str (), 0, facility);
377Logger::SysLogAppender::~SysLogAppender ()
382void Logger::SysLogAppender::Log (Priority logLevel,
const String& message)
385 int sysLogLevel = LOG_NOTICE;
387 case Priority::eDebug:
388 sysLogLevel = LOG_DEBUG;
390 case Priority::eInfo:
391 sysLogLevel = LOG_INFO;
393 case Priority::eNotice:
394 sysLogLevel = LOG_NOTICE;
396 case Priority::eWarning:
397 sysLogLevel = LOG_WARNING;
399 case Priority::eError:
400 sysLogLevel = LOG_ERR;
402 case Priority::eCriticalError:
403 sysLogLevel = LOG_CRIT;
405 case Priority::eAlertError:
406 sysLogLevel = LOG_ALERT;
408 case Priority::eEmergency:
409 sysLogLevel = LOG_EMERG;
415 ::syslog (sysLogLevel,
"%s", message.
AsNarrowSDKString (Characters::eIgnoreErrors).c_str ());
424struct Logger::StreamAppender::Rep_ {
426 template <
typename T>
428 : fWriter_ (Streams::TextToBinary::Writer::New (out))
431 void Log (Priority logLevel,
const String& message)
434 fWriter_.rwget ()->Write (
452void Logger::StreamAppender::Log (Priority logLevel,
const String& message)
454 fRep_->Log (logLevel, message);
462struct Logger::FileAppender::Rep_ {
464 Rep_ (
const filesystem::path& fileName,
bool truncateOnOpen)
465 : fOut_ (StreamAppender (FileOutputStream::New (fileName, truncateOnOpen ? FileOutputStream::eStartFromStart : FileOutputStream::eAppend)))
468 void Log (Priority logLevel,
const String& message)
470 fOut_.Log (logLevel, message);
474 StreamAppender fOut_;
477Logger::FileAppender::FileAppender (
const filesystem::path& fileName,
bool truncateOnOpen)
482void Logger::FileAppender::Log (Priority logLevel,
const String& message)
484 fRep_->Log (logLevel, message);
487#if qStroika_Foundation_Common_Platform_Windows
493Logger::WindowsEventLogAppender::WindowsEventLogAppender (
const String& eventSourceName)
494 : fEventSourceName_{eventSourceName}
498void Logger::WindowsEventLogAppender::Log (Priority logLevel,
const String& message)
503 WORD eventType = EVENTLOG_ERROR_TYPE;
505 case Priority::eDebug:
506 eventType = EVENTLOG_INFORMATION_TYPE;
508 case Priority::eInfo:
509 eventType = EVENTLOG_INFORMATION_TYPE;
511 case Priority::eNotice:
512 eventType = EVENTLOG_INFORMATION_TYPE;
514 case Priority::eWarning:
515 eventType = EVENTLOG_WARNING_TYPE;
517 case Priority::eError:
518 eventType = EVENTLOG_ERROR_TYPE;
520 case Priority::eAlertError:
521 eventType = EVENTLOG_ERROR_TYPE;
523 case Priority::eEmergency:
524 eventType = EVENTLOG_ERROR_TYPE;
527 constexpr auto CATEGORY_Normal = 0x00000001L;
528 WORD eventCategoryID = CATEGORY_Normal;
531 constexpr auto EVENT_Message = 0x00000064L;
532 const DWORD kEventID = EVENT_Message;
533 HANDLE hEventSource = ::RegisterEventSource (NULL, fEventSourceName_.AsSDKString ().c_str ());
534 Verify (hEventSource != NULL);
535 [[maybe_unused]]
auto&& cleanup =
Execution::Finally ([hEventSource] ()
noexcept {
Verify (::DeregisterEventSource (hEventSource)); });
538 constexpr PSID kUserSid_ =
nullptr;
539 constexpr bool kWriteOldWay_ =
true;
540 if constexpr (kWriteOldWay_) {
541 auto rslt = ::ReportEvent (hEventSource, eventType, eventCategoryID, kEventID, kUserSid_, (WORD)1, 0, &msg,
nullptr);
543 DbgTrace (
"lasterr={}"_f, ::GetLastError ());
550 DWORD dataSize =
static_cast<DWORD
> ((tmp.size () + 1) *
sizeof (
SDKChar));
551 Verify (::ReportEvent (hEventSource, eventType, eventCategoryID, kEventID, kUserSid_, 0, dataSize,
nullptr, (LPVOID)msg));
561Logger::Activator::Activator (
const Options& options)
564 Assert (
sThe.fRep_ ==
nullptr);
565 sThe.fRep_ = MakeSharedPtr<Rep_> ();
567 if (options.fLogBufferingEnabled) {
570 if (options.fMinLogLevel) {
575Logger::Activator::~Activator ()
578 Assert (
sThe.fRep_ !=
nullptr);
593 Logger::sThe.
Log (Logger::eCriticalError,
"Backtrace: {}"_f, Debug::BackTrace::Capture ());
594 Logger::sThe.
Log (Logger::eCriticalError,
"Uncaught exception: {}"_f, std::current_exception ());
596 std::_Exit (EXIT_FAILURE);
607 DbgTrace (
"Fatal Signal: {} encountered"_f, Execution::SignalToName (signal));
608 Logger::sThe.
Log (Logger::eCriticalError,
"Fatal Signal: {}; Aborting..."_f, Execution::SignalToName (signal));
609 Logger::sThe.
Log (Logger::eCriticalError,
"Backtrace: {}"_f, Debug::BackTrace::Capture ());
611 std::_Exit (EXIT_FAILURE);
#define WeakAssertNotReached()
#define RequireNotReached()
#define RequireNotNull(p)
auto MakeSharedPtr(ARGS_TYPE &&... args) -> shared_ptr< T >
same as make_shared, but if type T has block allocation, then use block allocation for the 'shared pa...
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 >