Cogs.Core
LogManager.cpp
1#include "LogManager.h"
2
3#include "Consumer.h"
4#include "Logger.h"
5
6#include "../HashFunctions.h"
7#include "../Platform/Atomic.h"
8
9#include <cstdio>
10#include <set>
11
12#if defined( _WIN32 )
13 #include <Windows.h>
14 #include <dbghelp.h>
15#elif !defined( EMSCRIPTEN )
16 #include <cxxabi.h> // Needs libc++-dev on Ubuntu.
17 #include <dlfcn.h>
18 #include <errno.h>
19 #include <pthread.h>
20 #include <signal.h>
21 #include <stdint.h>
22 #include <unistd.h>
23 #include <unwind.h>
24#endif // _WIN32
25
26namespace {
27 Cogs::Atomic<Cogs::Logging::Category> minimumCategory(Cogs::Logging::Category::Trace);
28
29#if defined( _WIN32 )
30 LPTOP_LEVEL_EXCEPTION_FILTER previousFilter = nullptr;
31#endif
32
33 Cogs::Mutex& getConsumersMutex() {
34 static Cogs::Mutex mutex;
35 return mutex;
36 }
37
38 std::set<Cogs::Logging::Consumer*>& getConsumers() {
39 static std::set<Cogs::Logging::Consumer*> consumers;
40 return consumers;
41 }
42}
43
44namespace Cogs::Logging {
45
49 void consumeMessage(const char* source, Category category, uint32_t errorNumber, const char* filename, int lineNumber, _Printf_format_string_ const char* fmt, va_list argptr) {
50 if (category >= minimumCategory.load()) {
51 LockGuard lock(getConsumersMutex());
52
53 // Buffer for encoded log. Individual logs longer is truncated.
54 static char buffer[16000]{};
55
56 if (!filename) {
57 filename = "";
58 }
59
60 int rv = std::vsnprintf(buffer, sizeof(buffer), fmt, argptr);
61 if (0 <= rv) {
62 // Output to buffer follows C++11 spec and is null terminated (possibly truncated).
63 for (Consumer* consumer : getConsumers()) {
64 if (category >= consumer->getMinimumCategory()) {
65 consumer->consumeMessage(source, category, errorNumber, buffer, filename, lineNumber);
66 }
67 }
68 }
69 else {
70 // Format error: Log format given. Output to buffer follows C++11 spec and is null terminated (possibly truncated).
71 std::snprintf(buffer, sizeof(buffer), "Invalid log format: %s", fmt);
72 for (Consumer* consumer : getConsumers()) {
73 if (category >= consumer->getMinimumCategory()) {
74 // Just passing buffer. std::snprintf returns null terminated. Seldom called.
75 consumer->consumeMessage(source, category, errorNumber, buffer, filename, lineNumber);
76 }
77 }
78 }
79 }
80 }
81
82#if defined( _WIN32 )
83 LONG WINAPI exceptionHandler(EXCEPTION_POINTERS* ep) {
84 void* addresses[200];
85 int noofaddresses = CaptureStackBackTrace(0, 199, addresses, nullptr);
86
87 const Cogs::Logging::Log logger = Cogs::Logging::getLogger("Crash");
88 LOG_FATAL(logger, "Caught unhandled exception.");
89
90 for(int idx = 0; idx < noofaddresses; ++idx) {
91 char local[sizeof(IMAGEHLP_SYMBOL64) + 992];
92 IMAGEHLP_SYMBOL64* symbol = reinterpret_cast<IMAGEHLP_SYMBOL64*>(local);
93 DWORD64 realaddr = reinterpret_cast<DWORD64>(addresses[idx]) - 1;
94 DWORD64 symdisp;
95
96 memset(local, 0, sizeof(local));
97
98 symbol->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL64);
99 symbol->MaxNameLength = sizeof(local) - sizeof(IMAGEHLP_SYMBOL64);
100
101 if(SymGetSymFromAddr64(GetCurrentProcess(), realaddr, &symdisp, symbol) || (!GetLastError() && symbol->Name[0])) {
102 char symname[4096];
103 DWORD linedisp;
104 IMAGEHLP_LINE64 line;
105
106 UnDecorateSymbolName(symbol->Name, symname, sizeof(symname), UNDNAME_COMPLETE);
107
108 memset(&line, 0, sizeof(line));
109 line.SizeOfStruct = sizeof(line);
110
111 if(SymGetLineFromAddr64(GetCurrentProcess(), realaddr, &linedisp, &line)) {
112 LOG_FATAL(logger, "%p - %s (%s:%d)", reinterpret_cast<void*>(realaddr), symname, line.FileName, line.LineNumber);
113 }
114 else {
115 LOG_FATAL(logger, "%p - %s", reinterpret_cast<void*>(realaddr), symname);
116 }
117 }
118 else {
119 LOG_FATAL(logger, "%p - (Unknown)", reinterpret_cast<void*>(realaddr));
120 }
121 }
122 return previousFilter ? previousFilter(ep) : EXCEPTION_CONTINUE_SEARCH;
123 }
124#elif !defined( EMSCRIPTEN )
126 void** current;
127 void** end;
128 };
129
130 _Unwind_Reason_Code unwindCallback(struct _Unwind_Context* context, void* arg)
131 {
132 BacktraceState* state = static_cast<BacktraceState*>(arg);
133 uintptr_t pc = _Unwind_GetIP(context);
134
135 if (pc) {
136 if (state->current == state->end) {
137 return _URC_END_OF_STACK;
138 }
139 *state->current++ = reinterpret_cast<void*>(pc);
140 }
141 return _URC_NO_REASON;
142 }
143
144 void printFrame(void* address) {
145 Dl_info info;
146 char hexAddr[20] = { '0', 'x', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', ':', ' ' };
147 uint64_t addrValue = reinterpret_cast<uint64_t>(address);
148 int file = fileno(stderr);
149
150 for (int idx = 18; addrValue; addrValue >>= 4) {
151 hexAddr[--idx] = "0123456789ABCDEF"[addrValue & 0x0F];
152 }
153
154 write(file, hexAddr, 20);
155
156 if (dladdr(address, &info)) {
157 if (info.dli_sname) {
158 int status;
159 char* demangled = abi::__cxa_demangle(info.dli_sname, nullptr, nullptr, &status);
160
161 if (demangled) {
162 if (status == 0) {
163 write(file, demangled, strlen(demangled));
164 }
165 free(demangled);
166 }
167 else {
168 write(file, info.dli_sname, strlen(info.dli_sname));
169 }
170 }
171 else if (info.dli_fname) {
172 write(file, info.dli_fname, strlen(info.dli_fname));
173 }
174 }
175 write(file, "\n", 1);
176 }
177
178 void signalHandler(int sig) {
179 static volatile sig_atomic_t inProgress = 0;
180 void* addresses[200];
181 BacktraceState state = { addresses, addresses + 200 };
182 struct sigaction sigact = {};
183
184 if (!inProgress) {
185 inProgress = 1;
186
187 sigact.sa_handler = SIG_DFL;
188 sigaction(sig, &sigact, nullptr);
189 sigaction(SIGABRT, &sigact, nullptr);
190
191 _Unwind_Backtrace(&unwindCallback, &state);
192
193 switch (sig) {
194 case SIGILL: write(fileno(stderr), "Caught SIGILL.\n", 15); break;
195 case SIGABRT: write(fileno(stderr), "Caught SIGABRT.\n", 16); break;
196 case SIGFPE: write(fileno(stderr), "Caught SIGFPE.\n", 15); break;
197 case SIGSEGV: write(fileno(stderr), "Caught SIGSEGV.\n", 16); break;
198 case SIGBUS: write(fileno(stderr), "Caught SIGBUS.\n", 15); break;
199 }
200
201 for (int idx = 0, addrCount = static_cast<int>(state.current - addresses); idx < addrCount; ++idx) {
202 printFrame(addresses[idx]);
203 }
204 fsync(fileno(stderr));
205 }
206 raise(sig);
207 }
208#endif
209}
210
216 Consumer::setDefaultMinimumCategory(category);
217}
218
220 // Cannot lock mutex as setMinimumCategory also locks same mutex calling Logging::updateMinimumCategory.
221 // Only a problem of list of consumers changed.
222 for (Consumer* consumer : getConsumers()) {
223 consumer->setMinimumCategory(category);
224 }
225}
226
233 LockGuard lock(getConsumersMutex());
234 Category category = Category::Fatal;
235
236 for (Consumer* consumer : getConsumers()) {
237 category = std::min(category, consumer->getMinimumCategory());
238 }
239 minimumCategory = category;
240}
241
246#if defined( _WIN32 )
247 SymSetOptions(SYMOPT_UNDNAME | SYMOPT_LOAD_LINES | SYMOPT_ALLOW_ABSOLUTE_SYMBOLS | SYMOPT_INCLUDE_32BIT_MODULES);
248 SymInitialize(GetCurrentProcess(), nullptr, 1);
249 SetUnhandledExceptionFilter(&exceptionHandler);
250#elif !defined( EMSCRIPTEN )
251 struct sigaction sigact = {};
252
253 sigact.sa_handler = &signalHandler;
254 sigemptyset(&sigact.sa_mask);
255
256 sigaddset(&sigact.sa_mask, SIGILL);
257 sigaddset(&sigact.sa_mask, SIGABRT);
258 sigaddset(&sigact.sa_mask, SIGFPE);
259 sigaddset(&sigact.sa_mask, SIGSEGV);
260 sigaddset(&sigact.sa_mask, SIGBUS);
261
262 sigaction(SIGILL, &sigact, nullptr);
263 sigaction(SIGABRT, &sigact, nullptr);
264 sigaction(SIGFPE, &sigact, nullptr);
265 sigaction(SIGSEGV, &sigact, nullptr);
266 sigaction(SIGBUS, &sigact, nullptr);
267#endif // _WIN32
268}
269
270
272 LockGuard lock(getConsumersMutex());
273
274 getConsumers().insert(consumer);
275}
276
278 LockGuard lock(getConsumersMutex());
279 std::set<Consumer*>& consumers = getConsumers();
280 auto i = consumers.find(consumer);
281
282 if (i != consumers.end()) {
283 consumers.erase(i);
284 }
285}
286
291 switch (Cogs::hashLowercase(category)) {
292 case Cogs::hash("trace"): return Logging::Category::Trace;
293 case Cogs::hash("debug"): return Logging::Category::Debug;
294 case Cogs::hash("info"): return Logging::Category::Info;
295 case Cogs::hash("warning"): return Logging::Category::Warning;
296 case Cogs::hash("error"): return Logging::Category::Error;
297 case Cogs::hash("fatal"): return Logging::Category::Fatal;
298 default: return Logging::Category::Debug;
299 }
300}
301
302bool Cogs::Logging::isErrorGroup(uint32_t errorNumber, ErrorGroup group) {
303 return (errorNumber & group) == group;
304}
305
306void Cogs::Logging::log(const char* message, const char* source, const Category category, uint32_t errorNumber) {
307 consumeMessage(source, category, errorNumber, "", 0, message, nullptr);
308}
309
310void Cogs::Logging::log(const char* source, const Category category, uint32_t errorNumber, _Printf_format_string_ const char * fmt, ...) {
311 va_list argptr;
312
313 va_start(argptr, fmt);
314 consumeMessage(source, category, errorNumber, "", 0, fmt, argptr);
315 va_end(argptr);
316}
317
318void Cogs::Logging::logArgs(const char* source, const Category category, uint32_t errorNumber, const char * fmt, va_list argptr) {
319 consumeMessage(source, category, errorNumber, "", 0, fmt, argptr);
320}
321
322void Cogs::Logging::logFileLine(const char* filename, const int lineNumber, const char * source, const Category category, uint32_t errorNumber, _Printf_format_string_ const char * fmt, ...) {
323 va_list argptr;
324
325 va_start(argptr, fmt);
326 consumeMessage(source, category, errorNumber, filename, lineNumber, fmt, argptr);
327 va_end(argptr);
328}
329
330void Cogs::Logging::logFileLineArgs(const char* filename, const int lineNumber, const char * source, const Category category, uint32_t errorNumber, const char* fmt, va_list argptr) {
331 consumeMessage(source, category, errorNumber, filename, lineNumber, fmt, argptr);
332}
333
Consumer is the base class for objects that want to consume log messages through the LogManager.
Definition: Consumer.h:18
Log implementation class.
Definition: LogManager.h:139
Provides a weakly referenced view over the contents of a string.
Definition: StringView.h:24
Contains logging functionality for the Cogs native library.
void COGSFOUNDATION_API enableUnhandledExceptionLogging()
Enable catching and logging of hardware exceptions (crashes) before exiting. (Windows only - for now....
Definition: LogManager.cpp:245
void COGSFOUNDATION_API registerConsumer(Consumer *consumer)
Registers the specified consumer with the LogManager.
Definition: LogManager.cpp:271
void COGSFOUNDATION_API logFileLineArgs(const char *file, const int line, const char *source, const Category category, uint32_t errorNumber, const char *fmt, va_list args)
Log the given formatted string with argument list and file/line information.
Definition: LogManager.cpp:330
constexpr Log getLogger(const char(&name)[LEN]) noexcept
Definition: LogManager.h:180
Category COGSFOUNDATION_API parseCategoryString(const StringView category)
Utility function that takes a loglevel name as a string and returns the corresponding log level enum ...
Definition: LogManager.cpp:290
void COGSFOUNDATION_API logFileLine(const char *file, const int line, const char *source, const Category category, uint32_t errorNumber, _Printf_format_string_ const char *fmt,...) VALIDATE_ARGS(6)
Logs the given formatted string (using printf formatting rules) with file/line information and source...
Definition: LogManager.cpp:322
bool COGSFOUNDATION_API isErrorGroup(uint32_t errorNumber, ErrorGroup group)
Tests whether the specified error number is from the given group.
Definition: LogManager.cpp:302
void COGSFOUNDATION_API updateLoggerCategory(Category category)
Definition: LogManager.cpp:219
void COGSFOUNDATION_API setLoggerCategory(Category category)
Sets the default category level for loggers created after this call.
Definition: LogManager.cpp:215
ErrorGroup
ErrorGroup values define the top 16-bits of module specific error numbers.
Definition: LogManager.h:48
void consumeMessage(const char *source, Category category, uint32_t errorNumber, const char *filename, int lineNumber, _Printf_format_string_ const char *fmt, va_list argptr)
Forwards the incoming log message to all interested consumers.
Definition: LogManager.cpp:49
void COGSFOUNDATION_API logArgs(const char *source, const Category category, uint32_t errorNumber, const char *fmt, va_list args)
Log the given formatted string with argument list.
Definition: LogManager.cpp:318
Category
Logging categories used to filter log messages.
Definition: LogManager.h:31
void COGSFOUNDATION_API log(const char *message, const char *source, const Category category, uint32_t errorNumber)
Logs the given message with source and category.
Definition: LogManager.cpp:306
void updateMinimumCategory()
Internal.
Definition: LogManager.cpp:232
void COGSFOUNDATION_API unregisterConsumer(Consumer *consumer)
Removes the specified consumer from the LogManager.
Definition: LogManager.cpp:277
constexpr size_t hash() noexcept
Simple getter function that returns the initial value for fnv1a hashing.
Definition: HashFunctions.h:62
COGSFOUNDATION_API size_t hashLowercase(std::string_view str, size_t hashValue=Cogs::hash()) noexcept
Get the hash code of the string converted to lowercase.