4#include "Stroika/Foundation/StroikaPreComp.h"
6#if qStroika_HasComponent_syslog
12#include "Stroika/Foundation/Characters/SDKChar.h"
14#include "Stroika/Foundation/Containers/Collection.h"
15#include "Stroika/Foundation/Containers/Mapping.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"
27#include "Stroika/Foundation/Streams/TextToBinary.h"
39using namespace Stroika::Foundation::Traversal;
40using namespace IO::FileSystem;
43using Memory::MakeSharedPtr;
54struct Logger::Rep_ : enable_shared_from_this<Logger::Rep_> {
55 using PriorityAndMessageType_ = pair<Logger::Priority, String>;
56 bool fBufferingEnabled_{
false};
61 bool fOutQMaybeNeedsFlush_{
true};
64 struct LastMsgInfoType_ {
66 unsigned int fRepeatCount_{};
75 void FlushSuppressedDuplicates_ (
bool forceEvenIfNotOutOfDate =
false)
77#if USE_NOISY_TRACE_IN_THIS_MODULE_
80 auto lastMsgsLocked = fLastMessages_.
rwget ();
87 if (not lastMsgsLocked->empty ()) {
88 Time::Duration suppressDuplicatesThreshold = fSuppressDuplicatesThreshold_.
cget ()->value_or (0s);
89 for (
auto i = lastMsgsLocked->begin (); i != lastMsgsLocked->end ();) {
90 bool writeThisOne = forceEvenIfNotOutOfDate or i->fValue.fLastSentAt + suppressDuplicatesThreshold < Time::GetTickCount ();
92 switch (i->fValue.fRepeatCount_) {
97 for (shared_ptr<IAppenderRep> tmp : fAppenders_.load ()) {
98 tmp->Log (i->fKey.first, i->fKey.second);
103 for (shared_ptr<IAppenderRep> tmp : fAppenders_.load ()) {
104 tmp->Log (i->fKey.first,
"[{} duplicates suppressed]: {}"_f(i->fValue.fRepeatCount_ - 1, i->fKey.second));
108 lastMsgsLocked->Remove (i, &i);
118#if USE_NOISY_TRACE_IN_THIS_MODULE_
121 if (not fAppenders_.
cget ()->empty ()) {
124 if (p.has_value ()) {
126 for (shared_ptr<IAppenderRep> tmp : fAppenders_.load ()) {
127 tmp->Log (p->first, p->second);
135 fOutQMaybeNeedsFlush_ =
false;
137 void UpdateBookkeepingThread_ ()
141 auto bktLck = fBookkeepingThread_.
rwget ();
142 if (bktLck.cref () !=
nullptr) {
143 bktLck->AbortAndWaitForDone ();
148 Time::Duration suppressDuplicatesThreshold = fSuppressDuplicatesThreshold_.
cget ()->value_or (0s);
149 bool suppressDuplicates = suppressDuplicatesThreshold > 0s;
150 static const String kThreadName_{
"Logger Bookkeeping"sv};
151 if (suppressDuplicates or fBufferingEnabled_) {
153 shared_ptr<Rep_> useRepInThread = shared_from_this ();
154 if (suppressDuplicates) {
156 [suppressDuplicatesThreshold, useRepInThread] () {
159 Duration time2Wait = max<Duration> (2s, suppressDuplicatesThreshold);
160 useRepInThread->FlushSuppressedDuplicates_ ();
161 if (
auto p = useRepInThread->fOutMsgQ_.RemoveHeadIfPossible (time2Wait)) {
163 for (shared_ptr<IAppenderRep> tmp : useRepInThread->fAppenders_.load ()) {
164 IgnoreExceptionsExceptThreadAbortForCall (tmp->Log (p->first, p->second));
173 [useRepInThread] () {
177 auto p = useRepInThread->fOutMsgQ_.RemoveHead ();
179 for (shared_ptr<IAppenderRep> tmp : useRepInThread->fAppenders_.load ()) {
180 tmp->Log (p.first, p.second);
187 newBookKeepThread.
Start ();
188 fBookkeepingThread_ = newBookKeepThread;
196#if qStroika_Foundation_Debug_AssertionsChecked
199 Assert (&
sThe ==
this);
200 Assert (fRep_ ==
nullptr);
204void Logger::Shutdown_ ()
211 bool changed =
false;
214 [[maybe_unused]] lock_guard critSec{fRep_->fSuppressDuplicatesThreshold_};
215 if (fRep_->fSuppressDuplicatesThreshold_.load ()) {
216 fRep_->fSuppressDuplicatesThreshold_.store (nullopt);
220 if (fRep_->fBufferingEnabled_) {
221 fRep_->fBufferingEnabled_ =
false;
225 fRep_->UpdateBookkeepingThread_ ();
231 Ensure (fRep_->fBookkeepingThread_.load () ==
nullptr);
237 return fRep_->fAppenders_;
255 fRep_->fAppenders_.rwget ()->Add (rep);
258void Logger::Log_ (Priority logLevel,
const String& msg)
261 if (not fRep_->fAppenders_->empty ()) {
262 auto p = make_pair (logLevel, msg);
263 if (fRep_->fSuppressDuplicatesThreshold_.cget ()->has_value ()) {
264 auto lastMsgLocked = fRep_->fLastMessages_.rwget ();
266 if (
auto msgInfo = lastMsgLocked->Lookup (p)) {
267 Rep_::LastMsgInfoType_ mi = *msgInfo;
269 mi.fLastSentAt = Time::GetTickCount ();
270 lastMsgLocked->Add (p, mi);
274 lastMsgLocked->Add (p, Rep_::LastMsgInfoType_{Time::GetTickCount ()});
278 fRep_->fOutQMaybeNeedsFlush_ =
true;
279 fRep_->fOutMsgQ_.AddTail (p);
282 if (fRep_->fOutQMaybeNeedsFlush_) {
286 for (shared_ptr<IAppenderRep> tmp : fRep_->fAppenders_.load ()) {
287 tmp->Log (p.first, p.second);
297 if (fRep_->fBufferingEnabled_ != logBufferingEnabled) {
298 fRep_->fBufferingEnabled_ = logBufferingEnabled;
299 fRep_->UpdateBookkeepingThread_ ();
313 return fRep_->fBufferingEnabled_;
319 return fRep_->fSuppressDuplicatesThreshold_.load ();
325 suppressDuplicatesThreshold)};
326 Require (not suppressDuplicatesThreshold.has_value () or *suppressDuplicatesThreshold > 0.0s);
328 [[maybe_unused]] lock_guard critSec{fRep_->fSuppressDuplicatesThreshold_};
329 if (fRep_->fSuppressDuplicatesThreshold_ != suppressDuplicatesThreshold) {
330 fRep_->fSuppressDuplicatesThreshold_ = suppressDuplicatesThreshold;
331 fRep_->UpdateBookkeepingThread_ ();
335#if qStroika_Foundation_Debug_DefaultTracingOn
336void Logger::Log (Priority logLevel,
const wchar_t* format, ...)
339 va_start (argsList, format);
342 DbgTrace (
"Logger::Log ({}, \"{}\")"_f, logLevel, msg);
344 Log_ (logLevel, msg);
347 DbgTrace (
"...suppressed by WouldLog"_f);
352#if qStroika_HasComponent_syslog
359 string mkMsg_ (
const String& applicationName)
361 return Characters::CString::Format (
"%s[%d]", applicationName.
AsNarrowSDKString (Characters::eIgnoreErrors).c_str (), GetCurrentProcessID ());
364Logger::SysLogAppender::SysLogAppender (
const String& applicationName)
365 : fApplicationName_{mkMsg_ (applicationName)}
367 ::openlog (fApplicationName_.c_str (), 0, LOG_DAEMON);
370Logger::SysLogAppender::SysLogAppender (
const String& applicationName,
int facility)
371 : fApplicationName_{mkMsg_ (applicationName)}
373 ::openlog (fApplicationName_.c_str (), 0, facility);
376Logger::SysLogAppender::~SysLogAppender ()
381void Logger::SysLogAppender::Log (Priority logLevel,
const String& message)
384 int sysLogLevel = LOG_NOTICE;
386 case Priority::eDebug:
387 sysLogLevel = LOG_DEBUG;
389 case Priority::eInfo:
390 sysLogLevel = LOG_INFO;
392 case Priority::eNotice:
393 sysLogLevel = LOG_NOTICE;
395 case Priority::eWarning:
396 sysLogLevel = LOG_WARNING;
398 case Priority::eError:
399 sysLogLevel = LOG_ERR;
401 case Priority::eCriticalError:
402 sysLogLevel = LOG_CRIT;
404 case Priority::eAlertError:
405 sysLogLevel = LOG_ALERT;
407 case Priority::eEmergency:
408 sysLogLevel = LOG_EMERG;
414 ::syslog (sysLogLevel,
"%s", message.
AsNarrowSDKString (Characters::eIgnoreErrors).c_str ());
423struct Logger::StreamAppender::Rep_ {
425 template <
typename T>
427 : fWriter_ (Streams::TextToBinary::Writer::New (out))
430 void Log (Priority logLevel,
const String& message)
433 fWriter_.rwget ()->Write (
451void Logger::StreamAppender::Log (Priority logLevel,
const String& message)
453 fRep_->Log (logLevel, message);
461struct Logger::FileAppender::Rep_ {
463 Rep_ (
const filesystem::path& fileName,
bool truncateOnOpen)
464 : fOut_ (StreamAppender (FileOutputStream::New (fileName, truncateOnOpen ? FileOutputStream::eStartFromStart : FileOutputStream::eAppend)))
467 void Log (Priority logLevel,
const String& message)
469 fOut_.Log (logLevel, message);
473 StreamAppender fOut_;
476Logger::FileAppender::FileAppender (
const filesystem::path& fileName,
bool truncateOnOpen)
481void Logger::FileAppender::Log (Priority logLevel,
const String& message)
483 fRep_->Log (logLevel, message);
486#if qStroika_Platform_Windows
492Logger::WindowsEventLogAppender::WindowsEventLogAppender (
const String& eventSourceName)
493 : fEventSourceName_{eventSourceName}
497void Logger::WindowsEventLogAppender::Log (Priority logLevel,
const String& message)
502 WORD eventType = EVENTLOG_ERROR_TYPE;
504 case Priority::eDebug:
505 eventType = EVENTLOG_INFORMATION_TYPE;
507 case Priority::eInfo:
508 eventType = EVENTLOG_INFORMATION_TYPE;
510 case Priority::eNotice:
511 eventType = EVENTLOG_INFORMATION_TYPE;
513 case Priority::eWarning:
514 eventType = EVENTLOG_WARNING_TYPE;
516 case Priority::eError:
517 eventType = EVENTLOG_ERROR_TYPE;
519 case Priority::eAlertError:
520 eventType = EVENTLOG_ERROR_TYPE;
522 case Priority::eEmergency:
523 eventType = EVENTLOG_ERROR_TYPE;
526 constexpr auto CATEGORY_Normal = 0x00000001L;
527 WORD eventCategoryID = CATEGORY_Normal;
530 constexpr auto EVENT_Message = 0x00000064L;
531 const DWORD kEventID = EVENT_Message;
532 HANDLE hEventSource = ::RegisterEventSource (NULL, fEventSourceName_.AsSDKString ().c_str ());
533 Verify (hEventSource != NULL);
534 [[maybe_unused]]
auto&& cleanup =
Execution::Finally ([hEventSource] ()
noexcept {
Verify (::DeregisterEventSource (hEventSource)); });
537 constexpr PSID kUserSid_ =
nullptr;
538 constexpr bool kWriteOldWay_ =
true;
539 if constexpr (kWriteOldWay_) {
540 auto rslt = ::ReportEvent (hEventSource, eventType, eventCategoryID, kEventID, kUserSid_, (WORD)1, 0, &msg,
nullptr);
542 DbgTrace (
"lasterr={}"_f, ::GetLastError ());
549 DWORD dataSize =
static_cast<DWORD
> ((tmp.size () + 1) *
sizeof (
SDKChar));
550 Verify (::ReportEvent (hEventSource, eventType, eventCategoryID, kEventID, kUserSid_, 0, dataSize,
nullptr, (LPVOID)msg));
560Logger::Activator::Activator (
const Options& options)
563 Assert (
sThe.fRep_ ==
nullptr);
564 sThe.fRep_ = MakeSharedPtr<Rep_> ();
566 if (options.fLogBufferingEnabled) {
569 if (options.fMinLogLevel) {
574Logger::Activator::~Activator ()
577 Assert (
sThe.fRep_ !=
nullptr);
592 Logger::sThe.
Log (Logger::eCriticalError,
"Backtrace: {}"_f, Debug::BackTrace::Capture ());
593 Logger::sThe.
Log (Logger::eCriticalError,
"Uncaught exception: {}"_f, std::current_exception ());
595 std::_Exit (EXIT_FAILURE);
606 DbgTrace (
"Fatal Signal: {} encountered"_f, Execution::SignalToName (signal));
607 Logger::sThe.
Log (Logger::eCriticalError,
"Fatal Signal: {}; Aborting..."_f, Execution::SignalToName (signal));
608 Logger::sThe.
Log (Logger::eCriticalError,
"Backtrace: {}"_f, Debug::BackTrace::Capture ());
610 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(...)
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 ...
A Mapping uniquely associates two elements: a key and a value (use Assocation to allow multiple value...
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.
String FormatV(const wchar_t *format, va_list argsList)
printf-style formatting of a va_list, into a String - with one difference from vswprintf: s takes a c...
basic_string< SDKChar > SDKString
conditional_t< qStroika_Foundation_Characters_SDKUseswchar_t, wchar_t, char > SDKChar
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 >