CyberEngineMkIII
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Pages
CYBLogger.cpp
Go to the documentation of this file.
1 #include "CYB.hpp"
3 
4 #include <cstdio>
5 #include <ctime>
6 #include <cstring>
7 
8 CYB::API::String::Dynamic CYB::Engine::Logger::TimeString(const int AHour, const int AMinute, const int ASecond, const bool AColons) {
9  char Buffer[50];
10  memset(Buffer, 0, 50);
11  int Length;
12  if (AColons)
13  Length = sprintf(Buffer, u8"[%02d:%02d:%02d]", AHour, AMinute, ASecond);
14  else
15  Length = sprintf(Buffer, u8"%02d-%02d-%02d", AHour, AMinute, ASecond);
16 
17  API::Assert::LessThan(-1, Length);
18  API::Assert::LessThan(Length, 50);
19 
21 }
22 
24  auto Time(time(0)); // get time now
25  auto Now(localtime(&Time));
26  const auto Hour(Now->tm_hour), Min(Now->tm_min), Sec(Now->tm_sec);
27  return TimeString(Hour, Min, Sec, AColons);
28 }
29 
31  try {
32 
33  auto Time(time(0)); // get time now
34  auto Now(localtime(&Time));
35 
36  const int Year(Now->tm_year + 1900), Month(Now->tm_mon + 1), Day(Now->tm_mday), Hour(Now->tm_hour), Min(Now->tm_min), Sec(Now->tm_sec);
37 
38  char Buffer[50];
39  memset(Buffer, 0, 50);
40  const auto Length(sprintf(Buffer, u8"Engine Log %d-%d-%d %s.txt", Year, Month, Day, TimeString(Hour, Min, Sec, false).CString()));
41 
42  API::Assert::LessThan(-1, Length);
43  API::Assert::LessThan(Length, 50);
44 
45  API::String::Static AsStatic(Buffer);
46  API::String::UTF8 Formatted(AsStatic);
47 
48  auto LogPath(ABaseLogPath);
49  LogPath.Append(std::move(Formatted), false, false);
50 
52  }
53  catch (CYB::Exception::SystemData& AException) {
55  throw;
56  }
58  return OpenFileImpl(ABaseLogPath);
59 }
61  //All allocation is done using the Logger's Allocator
63  return OpenFileImpl(ThePath);
64 }
65 
67  FHeap(Parameters::LOGGER_HEAP_INITIAL_COMMIT_SIZE),
68  FContext(FHeap, AEmergencyLogger, true),
69  FFile(OpenFile()),
70  FThread(nullptr),
71  FCancelled(false),
72  FDevLog(
73 #ifdef DEBUG
74  true
75 #else
76  false
77 #endif
78  )
79 {
80  FQueueHead.reset(static_cast<Allocator&>(Context::GetContext().FAllocator).RawObject<LogEntry>());
81  FQueueHead->FLevel = Level::INFO;
82  FQueueHead->FMessage = FormatLogMessage(API::String::Static(u8"CyberEngineMkIII logger started..."), Level::INFO);
83 
84  FQueueTail = FQueueHead.get();
85 
86  //Flush this message in case it all goes downhill from here
87  EmptyQueue();
88 
90 }
91 
93  Log(API::String::Static(u8"Logger shutting down"), Level::INFO);
94  FContext.MakeCurrent(); //At this point, Core's Context is dead and we won't use it again
95  //using PushContext won't help as we'll try to dealloc strings in FFile with the wrong allocator
96  CancelThreadedOperation();
97  FThread().Wait();
98  EmptyQueue();
99 }
100 
102  //Emergency log the message
103  auto& Message(AEntry->FMessage);
104  const auto ByteIndex(Message.IndexOfByte(':', 3) + 1); //+1 to comp for the space
105  API::Assert::LessThan(0, ByteIndex);
106  API::Assert::LessThan(ByteIndex, Message.RawLength());
107  AEmergency.Log(API::String::Static(Message.CString() + ByteIndex), AEntry->FLevel);
108  if (AEntry->FNext != nullptr)
109  LogShutdownForEntry(std::move(AEntry->FNext), AEmergency);
110 }
111 
113  API::UniquePointer<LogEntry> Queue, NextNode;
114  {
115  API::LockGuard Lock(FQueueLock);
116  Queue = std::move(FQueueHead);
117  FQueueTail = nullptr;
118  FFileLock.Lock();
119  }
120 
121  API::LockGuard FLock(FFileLock, true);
122 
123  for (; Queue.get() != nullptr; Queue = std::move(NextNode)) {
124  NextNode = std::move(Queue->FNext);
125  const auto Len(static_cast<unsigned int>(Queue->FMessage.RawLength()));
126  auto Written(0ULL);
127  do {
128  const auto CurrentWrite(FFile.Write(Queue->FMessage.CString() + Written, Len - Written));
129  if (CurrentWrite == 0) {
130  //CurrentContext will most certainly be FContext at this point
131  //but just in case we add some weird behaviour overrides in the future....
132  auto& EmergencyLogger(Context::GetContext().FLogger);
133  EmergencyLogger.SetDebugLogging(true);
134  EmergencyLogger.Log(API::String::Static(u8"Failed to write to primary log. Message follows:"), Level::ERR);
135  EmergencyLogger.Log(Queue->FMessage, Queue->FLevel);
136  if (NextNode != nullptr) {
137  EmergencyLogger.Log(API::String::Static(u8"Remaining entries follow:"), Level::INFO);
138  LogShutdownForEntry(std::move(NextNode), EmergencyLogger);
139  }
141  }
142  Written += CurrentWrite;
143  } while (Written < Len);
144  API::Assert::Equal<unsigned long long>(Written, Len);
145  }
146 }
147 
149  return *this;
150 }
151 
153  PushContext Push(FContext);
154  while (!FCancelled.load(std::memory_order_relaxed)) {
155  if(!FPaused.load(std::memory_order_acquire))
156  EmptyQueue();
158  }
159 }
160 
162  FCancelled.store(true, std::memory_order_relaxed);
163 }
164 
166  const char* LevelString;
167  switch (ALevel) {
168  case Level::DEV:
169  LevelString = u8"Debug: ";
170  break;
171  case Level::INFO:
172  LevelString = u8"Info: ";
173  break;
174  case Level::WARN:
175  LevelString = u8"Warning: ";
176  break;
177  case Level::ERR:
178  LevelString = u8"ERROR: ";
179  break;
180  default:
182  }
183 
184  const auto ThreadID(Core::GetCore().ThreadID());
185  API::String::Dynamic Tabs(":");
186  if (ThreadID > 1) {
187  const API::String::Static Tab(u8"\t");
188  for (auto I(1ULL); I < ThreadID; ++I)
189  Tabs += Tab; //This is pretty performant considering the allocater works in chunks
190  }
191  else
192  Tabs += API::String::Static(" ");
193 
194  return TimeString(true) + Tabs + API::String::Static(LevelString) + AMessage;
195 }
196 
197 void CYB::Engine::Logger::Log(const API::String::CStyle& AMessage, const Level ALevel) {
198  if (ALevel != Level::DEV || FDevLog.load(std::memory_order_relaxed)) {
199  PushContext Push(FContext); //Use ourselves for allocation
200  bool CritFail(false);
201  for (auto I(0U); I < (Parameters::LOGGER_HEAP_RETRY_COUNT + 1) && !CritFail; ++I) {
202  try {
203  API::UniquePointer<LogEntry> Entry(static_cast<Allocator&>(Context::GetContext().FAllocator).RawObject<LogEntry>());
204  Entry->FMessage = API::String::Dynamic(u8"\n") + FormatLogMessage(AMessage, ALevel);
205  Entry->FLevel = ALevel;
206  auto Tmp(Entry.get());
207 
208  API::LockGuard Lock(FQueueLock);
209  if (FQueueTail != nullptr)
210  FQueueTail->FNext = std::move(Entry);
211  else
212  FQueueHead = std::move(Entry);
213  FQueueTail = Tmp;
214  break;
215  }
216  catch (CYB::Exception::SystemData& AException) {
217  API::Assert::Equal<unsigned int>(AException.FErrorCode, CYB::Exception::SystemData::HEAP_ALLOCATION_FAILURE);
218  try {
219  //Empty the queue, free the memory, and try again
220  EmptyQueue();
221  }
222  catch (CYB::Exception::SystemData& AInnerException) {
223  API::Assert::Equal<unsigned int>(AInnerException.FErrorCode, CYB::Exception::SystemData::STREAM_NOT_WRITABLE);
224  //Now give up
225  CritFail = true;
226  }
227  }
228  }
229  }
230 }
231 
232 void CYB::Engine::Logger::Flush(void) const noexcept {
233  do {
234  {
235  API::LockGuard QueueLock(FQueueLock), FileLock(FFileLock);
236  if (FQueueHead == nullptr)
237  break;
238  }
240  } while (true);
241 }
242 
244  return FFile.GetPath()();
245 }
246 
247 void CYB::Engine::Logger::SetDebugLogging(const bool AEnable) noexcept {
248  FDevLog.store(AEnable, std::memory_order_release);
249 }
250 
251 void CYB::Engine::Logger::Pause(void) noexcept {
252  API::LockGuard QueueLock(FQueueLock), FileLock(FFileLock);
253  FPaused.store(true, std::memory_order_relaxed);
254 }
255 
256 void CYB::Engine::Logger::Resume(void) noexcept {
257  FPaused.store(false, std::memory_order_relaxed);
258 }
A variable length UTF-8 string.
Definition: UTF8String.hpp:8
void Flush(void) const noexceptfinaloverride
Delays execution until all pending logs from the current thread have been written to the output...
Definition: CYBLogger.cpp:232
A RAII locking mechanism.
Definition: LockGuard.hpp:7
The number of times to try and log again after a failed allocation before giving up.
#define DEBUG
Should be defined or not by user before all inclusions of CyberEngine.hpp. Enables engine debugging l...
void Pause(void) noexcept
Put the logging thread to sleep.
Definition: CYBLogger.cpp:251
static API::String::Dynamic FormatLogMessage(const API::String::CStyle &AMessage, const Level ALevel)
Prepend the level and time to a log message.
Definition: CYBLogger.cpp:165
The directory for storing data relevant only to this execution.
static API::String::Dynamic TimeString(const int AHour, const int AMinute, const int ASecond, const bool AColons)
Retrieve a string of the given time.
Definition: CYBLogger.cpp:8
The File must not exist. It will be created.
static Platform::System::File OpenFile(void)
Prepares the logging File for writing. May block for one second if the preferred filename is taken in...
Definition: CYBLogger.cpp:60
Contains the basic File interface. Does not perform locking of any kind, be aware of possible race co...
Definition: CYBFile.hpp:6
A string pointing to unchanging data in the stack above it or the data segment. Must have UTF-8 encod...
Definition: StaticString.hpp:7
void CancelThreadedOperation(void) override
Stops the writer thread. Does not guarantee an empty queue.
Definition: CYBLogger.cpp:161
Object< AObject > ConstructObject(AArgs &&...AArguments)
Allocates the Object specified by AObject using a specified Constructor.
Used for string of allocated variable length.
Context switching RAII helper.
Definition: CYBInterop.hpp:64
Generic information.
static void LessThan(const AType &ALHS, const AType &ARHS) noexcept
Less than assertion function. May not be evaluated.
Definition of a platform thread object.
Definition: CYBThread.hpp:7
const API::String::CStyle & CurrentLog(void) const noexceptfinaloverride
Retrieve the string representation of the Path of the File the Logger is currently writing to...
Definition: CYBLogger.cpp:243
Context FContext
The Context to be used when calling the Logger.
Definition: CYBLogger.hpp:18
const unsigned int FErrorCode
The assigned error code.
Definition: Exception.hpp:18
Exceptions caused by external call failures or invalid external data. Only classifies ones that can p...
Definition: Exception.hpp:65
Compilation configuration variables.
API::Interop::Object< Platform::System::Thread > FThread
The thread used for writing to the log file.
Definition: CYBLogger.hpp:26
A heap has no block large enough for a requested allocation and expansion failed. ...
Definition: Exception.hpp:74
void BeginThreadedOperation(void) override
Writer thread. Runs in a loop until CancelThreadedOperation is called.
Definition: CYBLogger.cpp:152
Allocator & FAllocator
The Allocator.
Definition: Context.hpp:11
API::UniquePointer< LogEntry > FQueueHead
The message queue head.
Definition: CYBLogger.hpp:24
void Resume(void) noexcept
Wake the logging thread.
Definition: CYBLogger.cpp:256
A basic char contained string.
Definition: CStyleString.hpp:7
void Log(const API::String::CStyle &AMessage, const Level ALevel) finaloverride
Log a message. Will be written to a text file on the Path returned by CurrentLog. ...
Definition: CYBLogger.cpp:197
The basic multithreading interface.
Definition: Threadable.hpp:6
Logger(API::Logger &AEmergencyLogger)
Initializes and starts the Logger. Changes the current Context. May block for one second if the prefe...
Definition: CYBLogger.cpp:66
static Platform::System::File OpenFileImpl(const Platform::System::Path &ABasePath)
Prepares the logging File for writing. May block for one second if the preferred filename is taken in...
Definition: CYBLogger.cpp:30
Precompiled header for inter-engine operations.
Level
The severity of the log.
Definition: Logger.hpp:10
A file that was to be created exists.
Definition: Exception.hpp:70
UniquePointer that uses our Allocator.
Used for manipulating Paths. Paths will always exist either as a file or directory. Paths are '/' delimited when forming though may not be while retrieving. File names ".." will ascend a directory and '.' represents a no-op.
Definition: CYBPath.hpp:10
static void LogShutdownForEntry(API::UniquePointer< LogEntry > &&AEntry, API::Logger &AEmergency) noexcept
Takes a LogEntry Queue and logs it out to the current given emergency logger.
Definition: CYBLogger.cpp:101
static Core & GetCore(void) noexcept
Retrieve the Core singleton.
Definition: CYBCore.cpp:45
~Logger()
Shutdown the Logger and empty the queue.
Definition: CYBLogger.cpp:92
static void Sleep(const unsigned int AMilliseconds) noexcept
Put the thread to sleep for at least AMilliseconds.
The interface for writing safe logs.
Definition: Logger.hpp:7
Exceptions indicating an API contract violation. Should not be anticipated.
Definition: Exception.hpp:32
API::Threadable & SelfAsThreadable(void) noexcept
Returns the current object as a Threadable reference. Used to get around some weird VS construction i...
Definition: CYBLogger.cpp:148
Used for object which aren't allocatables.
void EmptyQueue(void)
Empty FQueue and write it into FFile.
Definition: CYBLogger.cpp:112
Generic error for write failures. See functions for further documentation.
Definition: Exception.hpp:73
static Context & GetContext(void) noexcept
Get the API's Context.
Definition: CYBInterop.cpp:35
void SetDebugLogging(const bool AEnable) noexceptfinaloverride
Enable/Disable filtering of Level::DEV logs.
Definition: CYBLogger.cpp:247
An operation was attempted with an invalid enum code.
Definition: Exception.hpp:36
LogEntry * FQueueTail
The message queue tail.
Definition: CYBLogger.hpp:25