/src/assimp/code/Common/DefaultLogger.cpp
Line | Count | Source |
1 | | /* |
2 | | --------------------------------------------------------------------------- |
3 | | Open Asset Import Library (assimp) |
4 | | --------------------------------------------------------------------------- |
5 | | |
6 | | Copyright (c) 2006-2025, assimp team |
7 | | |
8 | | |
9 | | |
10 | | All rights reserved. |
11 | | |
12 | | Redistribution and use of this software in source and binary forms, |
13 | | with or without modification, are permitted provided that the following |
14 | | conditions are met: |
15 | | |
16 | | * Redistributions of source code must retain the above |
17 | | copyright notice, this list of conditions and the |
18 | | following disclaimer. |
19 | | |
20 | | * Redistributions in binary form must reproduce the above |
21 | | copyright notice, this list of conditions and the |
22 | | following disclaimer in the documentation and/or other |
23 | | materials provided with the distribution. |
24 | | |
25 | | * Neither the name of the assimp team, nor the names of its |
26 | | contributors may be used to endorse or promote products |
27 | | derived from this software without specific prior |
28 | | written permission of the assimp team. |
29 | | |
30 | | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
31 | | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
32 | | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
33 | | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
34 | | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
35 | | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
36 | | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
37 | | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
38 | | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
39 | | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
40 | | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
41 | | --------------------------------------------------------------------------- |
42 | | */ |
43 | | |
44 | | /** @file DefaultLogger.cpp |
45 | | * @brief Implementation of DefaultLogger (and Logger) |
46 | | */ |
47 | | |
48 | | // Default log streams |
49 | | #include "FileLogStream.h" |
50 | | #include "StdOStreamLogStream.h" |
51 | | #include "Win32DebugLogStream.h" |
52 | | #include <assimp/StringUtils.h> |
53 | | |
54 | | #include <assimp/DefaultIOSystem.h> |
55 | | #include <assimp/ai_assert.h> |
56 | | #include <stdio.h> |
57 | | #include <assimp/DefaultLogger.hpp> |
58 | | #include <assimp/NullLogger.hpp> |
59 | | #include <iostream> |
60 | | |
61 | | #ifndef ASSIMP_BUILD_SINGLETHREADED |
62 | | #include <mutex> |
63 | | #include <thread> |
64 | | std::mutex loggerMutex; |
65 | | #endif |
66 | | |
67 | | namespace Assimp { |
68 | | |
69 | | // ---------------------------------------------------------------------------------- |
70 | | NullLogger DefaultLogger::s_pNullLogger; |
71 | | Logger *DefaultLogger::m_pLogger = &DefaultLogger::s_pNullLogger; |
72 | | |
73 | | static const unsigned int SeverityAll = Logger::Info | Logger::Err | Logger::Warn | Logger::Debugging; |
74 | | |
75 | | // ---------------------------------------------------------------------------------- |
76 | | // Represents a log-stream + its error severity |
77 | | struct LogStreamInfo { |
78 | | unsigned int m_uiErrorSeverity; |
79 | | LogStream *m_pStream; |
80 | | |
81 | | // Constructor |
82 | | LogStreamInfo(unsigned int uiErrorSev, LogStream *pStream) : |
83 | 0 | m_uiErrorSeverity(uiErrorSev), |
84 | 0 | m_pStream(pStream) { |
85 | | // empty |
86 | 0 | } |
87 | | |
88 | | // Destructor |
89 | 0 | ~LogStreamInfo() { |
90 | 0 | delete m_pStream; |
91 | 0 | } |
92 | | }; |
93 | | |
94 | | // ---------------------------------------------------------------------------------- |
95 | | // Construct a default log stream |
96 | | LogStream *LogStream::createDefaultStream(aiDefaultLogStream streams, |
97 | | const char *name /*= "AssimpLog.txt"*/, |
98 | 0 | IOSystem *io /*= nullptr*/) { |
99 | 0 | switch (streams) { |
100 | | // This is a platform-specific feature |
101 | 0 | case aiDefaultLogStream_DEBUGGER: |
102 | | #ifdef WIN32 |
103 | | return new Win32DebugLogStream(); |
104 | | #else |
105 | 0 | return nullptr; |
106 | 0 | #endif |
107 | | |
108 | | // Platform-independent default streams |
109 | 0 | case aiDefaultLogStream_STDERR: |
110 | 0 | return new StdOStreamLogStream(std::cerr); |
111 | 0 | case aiDefaultLogStream_STDOUT: |
112 | 0 | return new StdOStreamLogStream(std::cout); |
113 | 0 | case aiDefaultLogStream_FILE: |
114 | 0 | return (name && *name ? new FileLogStream(name, io) : nullptr); |
115 | 0 | default: |
116 | | // We don't know this default log stream, so raise an assertion |
117 | 0 | ai_assert(false); |
118 | 0 | }; |
119 | | |
120 | | // For compilers without dead code path detection |
121 | 0 | return nullptr; |
122 | 0 | } |
123 | | |
124 | | // ---------------------------------------------------------------------------------- |
125 | | // Creates the only singleton instance |
126 | | Logger *DefaultLogger::create(const char *name /*= "AssimpLog.txt"*/, |
127 | | LogSeverity severity /*= NORMAL*/, |
128 | | unsigned int defStreams /*= aiDefaultLogStream_DEBUGGER | aiDefaultLogStream_FILE*/, |
129 | 0 | IOSystem *io /*= nullptr*/) { |
130 | | // enter the mutex here to avoid concurrency problems |
131 | | #ifndef ASSIMP_BUILD_SINGLETHREADED |
132 | | std::lock_guard<std::mutex> lock(loggerMutex); |
133 | | #endif |
134 | |
|
135 | 0 | if (m_pLogger && !isNullLogger()) { |
136 | 0 | delete m_pLogger; |
137 | 0 | } |
138 | |
|
139 | 0 | m_pLogger = new DefaultLogger(severity); |
140 | | |
141 | | // Attach default log streams |
142 | | // Stream the log to the MSVC debugger? |
143 | 0 | if (defStreams & aiDefaultLogStream_DEBUGGER) { |
144 | 0 | m_pLogger->attachStream(LogStream::createDefaultStream(aiDefaultLogStream_DEBUGGER)); |
145 | 0 | } |
146 | | |
147 | | // Stream the log to COUT? |
148 | 0 | if (defStreams & aiDefaultLogStream_STDOUT) { |
149 | 0 | m_pLogger->attachStream(LogStream::createDefaultStream(aiDefaultLogStream_STDOUT)); |
150 | 0 | } |
151 | | |
152 | | // Stream the log to CERR? |
153 | 0 | if (defStreams & aiDefaultLogStream_STDERR) { |
154 | 0 | m_pLogger->attachStream(LogStream::createDefaultStream(aiDefaultLogStream_STDERR)); |
155 | 0 | } |
156 | | |
157 | | // Stream the log to a file |
158 | 0 | if (defStreams & aiDefaultLogStream_FILE && name && *name) { |
159 | 0 | m_pLogger->attachStream(LogStream::createDefaultStream(aiDefaultLogStream_FILE, name, io)); |
160 | 0 | } |
161 | |
|
162 | 0 | return m_pLogger; |
163 | 0 | } |
164 | | |
165 | | // ---------------------------------------------------------------------------------- |
166 | 11.6k | void Logger::debug(const char *message) { |
167 | | |
168 | | // SECURITY FIX: otherwise it's easy to produce overruns since |
169 | | // sometimes importers will include data from the input file |
170 | | // (i.e. node names) in their messages. |
171 | 11.6k | if (strlen(message) > MAX_LOG_MESSAGE_LENGTH) { |
172 | 0 | return OnDebug("<fixme: long message discarded>"); |
173 | 0 | } |
174 | 11.6k | return OnDebug(message); |
175 | 11.6k | } |
176 | | |
177 | | // ---------------------------------------------------------------------------------- |
178 | 172k | void Logger::verboseDebug(const char *message) { |
179 | | |
180 | | // SECURITY FIX: see above |
181 | 172k | if (strlen(message) > MAX_LOG_MESSAGE_LENGTH) { |
182 | 0 | return OnVerboseDebug("<fixme: long message discarded>"); |
183 | 0 | } |
184 | 172k | return OnVerboseDebug(message); |
185 | 172k | } |
186 | | |
187 | | // ---------------------------------------------------------------------------------- |
188 | 21.0k | void Logger::info(const char *message) { |
189 | | |
190 | | // SECURITY FIX: see above |
191 | 21.0k | if (strlen(message) > MAX_LOG_MESSAGE_LENGTH) { |
192 | 21 | return OnInfo("<fixme: long message discarded>"); |
193 | 21 | } |
194 | 20.9k | return OnInfo(message); |
195 | 21.0k | } |
196 | | |
197 | | // ---------------------------------------------------------------------------------- |
198 | 1.85M | void Logger::warn(const char *message) { |
199 | | |
200 | | // SECURITY FIX: see above |
201 | 1.85M | if (strlen(message) > MAX_LOG_MESSAGE_LENGTH) { |
202 | 526 | return OnWarn("<fixme: long message discarded>"); |
203 | 526 | } |
204 | 1.85M | return OnWarn(message); |
205 | 1.85M | } |
206 | | |
207 | | // ---------------------------------------------------------------------------------- |
208 | 1.83M | void Logger::error(const char *message) { |
209 | | // SECURITY FIX: see above |
210 | 1.83M | if (strlen(message) > MAX_LOG_MESSAGE_LENGTH) { |
211 | 58 | return OnError("<fixme: long message discarded>"); |
212 | 58 | } |
213 | 1.83M | return OnError(message); |
214 | 1.83M | } |
215 | | |
216 | | // ---------------------------------------------------------------------------------- |
217 | 0 | void DefaultLogger::set(Logger *logger) { |
218 | | // enter the mutex here to avoid concurrency problems |
219 | | #ifndef ASSIMP_BUILD_SINGLETHREADED |
220 | | std::lock_guard<std::mutex> lock(loggerMutex); |
221 | | #endif |
222 | |
|
223 | 0 | if (nullptr == logger) { |
224 | 0 | m_pLogger = &s_pNullLogger; |
225 | 0 | } |
226 | 0 | else { |
227 | 0 | m_pLogger = logger; |
228 | 0 | } |
229 | 0 | } |
230 | | |
231 | | // ---------------------------------------------------------------------------------- |
232 | 12.9k | bool DefaultLogger::isNullLogger() { |
233 | 12.9k | return m_pLogger == &s_pNullLogger; |
234 | 12.9k | } |
235 | | |
236 | | // ---------------------------------------------------------------------------------- |
237 | 3.89M | Logger *DefaultLogger::get() { |
238 | 3.89M | return m_pLogger; |
239 | 3.89M | } |
240 | | |
241 | | // ---------------------------------------------------------------------------------- |
242 | | // Kills the only instance |
243 | 0 | void DefaultLogger::kill() { |
244 | | // enter the mutex here to avoid concurrency problems |
245 | | #ifndef ASSIMP_BUILD_SINGLETHREADED |
246 | | std::lock_guard<std::mutex> lock(loggerMutex); |
247 | | #endif |
248 | |
|
249 | 0 | if (m_pLogger == &s_pNullLogger) { |
250 | 0 | return; |
251 | 0 | } |
252 | 0 | delete m_pLogger; |
253 | 0 | m_pLogger = &s_pNullLogger; |
254 | 0 | } |
255 | | |
256 | | // ---------------------------------------------------------------------------------- |
257 | | // Debug message |
258 | 0 | void DefaultLogger::OnDebug(const char *message) { |
259 | 0 | if (m_Severity < Logger::DEBUGGING) { |
260 | 0 | return; |
261 | 0 | } |
262 | | |
263 | 0 | static const size_t Size = MAX_LOG_MESSAGE_LENGTH + 16; |
264 | 0 | char msg[Size]; |
265 | 0 | ai_snprintf(msg, Size, "Debug, T%u: %s", GetThreadID(), message); |
266 | |
|
267 | 0 | WriteToStreams(msg, Logger::Debugging); |
268 | 0 | } |
269 | | |
270 | | // Verbose debug message |
271 | 0 | void DefaultLogger::OnVerboseDebug(const char *message) { |
272 | 0 | if (m_Severity < Logger::VERBOSE) { |
273 | 0 | return; |
274 | 0 | } |
275 | | |
276 | 0 | static const size_t Size = MAX_LOG_MESSAGE_LENGTH + 16; |
277 | 0 | char msg[Size]; |
278 | 0 | ai_snprintf(msg, Size, "Debug, T%u: %s", GetThreadID(), message); |
279 | |
|
280 | 0 | WriteToStreams(msg, Logger::Debugging); |
281 | 0 | } |
282 | | |
283 | | // ---------------------------------------------------------------------------------- |
284 | | // Logs an info |
285 | 0 | void DefaultLogger::OnInfo(const char *message) { |
286 | 0 | static const size_t Size = MAX_LOG_MESSAGE_LENGTH + 16; |
287 | 0 | char msg[Size]; |
288 | 0 | ai_snprintf(msg, Size, "Info, T%u: %s", GetThreadID(), message); |
289 | |
|
290 | 0 | WriteToStreams(msg, Logger::Info); |
291 | 0 | } |
292 | | |
293 | | // ---------------------------------------------------------------------------------- |
294 | | // Logs a warning |
295 | 0 | void DefaultLogger::OnWarn(const char *message) { |
296 | 0 | static const size_t Size = MAX_LOG_MESSAGE_LENGTH + 16; |
297 | 0 | char msg[Size]; |
298 | 0 | ai_snprintf(msg, Size, "Warn, T%u: %s", GetThreadID(), message); |
299 | |
|
300 | 0 | WriteToStreams(msg, Logger::Warn); |
301 | 0 | } |
302 | | |
303 | | // ---------------------------------------------------------------------------------- |
304 | | // Logs an error |
305 | 0 | void DefaultLogger::OnError(const char *message) { |
306 | 0 | static const size_t Size = MAX_LOG_MESSAGE_LENGTH + 16; |
307 | 0 | char msg[Size]; |
308 | 0 | ai_snprintf(msg, Size, "Error, T%u: %s", GetThreadID(), message); |
309 | |
|
310 | 0 | WriteToStreams(msg, Logger::Err); |
311 | 0 | } |
312 | | |
313 | | // ---------------------------------------------------------------------------------- |
314 | | // Will attach a new stream |
315 | 0 | bool DefaultLogger::attachStream(LogStream *pStream, unsigned int severity) { |
316 | 0 | if (nullptr == pStream) { |
317 | 0 | return false; |
318 | 0 | } |
319 | | |
320 | 0 | if (0 == severity) { |
321 | 0 | severity = SeverityAll; |
322 | 0 | } |
323 | |
|
324 | | #ifndef ASSIMP_BUILD_SINGLETHREADED |
325 | | std::lock_guard<std::mutex> lock(m_arrayMutex); |
326 | | #endif |
327 | |
|
328 | 0 | for (StreamIt it = m_StreamArray.begin(); |
329 | 0 | it != m_StreamArray.end(); |
330 | 0 | ++it) { |
331 | 0 | if ((*it)->m_pStream == pStream) { |
332 | 0 | (*it)->m_uiErrorSeverity |= severity; |
333 | 0 | return true; |
334 | 0 | } |
335 | 0 | } |
336 | | |
337 | 0 | m_StreamArray.push_back(new LogStreamInfo(severity, pStream)); |
338 | |
|
339 | 0 | return true; |
340 | 0 | } |
341 | | |
342 | | // ---------------------------------------------------------------------------------- |
343 | | // Detach a stream |
344 | 0 | bool DefaultLogger::detachStream(LogStream *pStream, unsigned int severity) { |
345 | 0 | if (nullptr == pStream) { |
346 | 0 | return false; |
347 | 0 | } |
348 | | |
349 | 0 | if (0 == severity) { |
350 | 0 | severity = SeverityAll; |
351 | 0 | } |
352 | |
|
353 | | #ifndef ASSIMP_BUILD_SINGLETHREADED |
354 | | std::lock_guard<std::mutex> lock(m_arrayMutex); |
355 | | #endif |
356 | |
|
357 | 0 | bool res(false); |
358 | 0 | for (StreamIt it = m_StreamArray.begin(); it != m_StreamArray.end(); ++it) { |
359 | 0 | if ((*it)->m_pStream == pStream) { |
360 | 0 | (*it)->m_uiErrorSeverity &= ~severity; |
361 | 0 | if ((*it)->m_uiErrorSeverity == 0) { |
362 | | // don't delete the underlying stream 'cause the caller gains ownership again |
363 | 0 | (**it).m_pStream = nullptr; |
364 | 0 | delete *it; |
365 | 0 | m_StreamArray.erase(it); |
366 | 0 | res = true; |
367 | 0 | break; |
368 | 0 | } |
369 | 0 | return true; |
370 | 0 | } |
371 | 0 | } |
372 | 0 | return res; |
373 | 0 | } |
374 | | |
375 | | // ---------------------------------------------------------------------------------- |
376 | | // Constructor |
377 | | DefaultLogger::DefaultLogger(LogSeverity severity) : |
378 | 0 | Logger(severity), noRepeatMsg(false), lastLen(0) { |
379 | 0 | lastMsg[0] = '\0'; |
380 | 0 | } |
381 | | |
382 | | // ---------------------------------------------------------------------------------- |
383 | | // Destructor |
384 | 0 | DefaultLogger::~DefaultLogger() { |
385 | 0 | for (StreamIt it = m_StreamArray.begin(); it != m_StreamArray.end(); ++it) { |
386 | | // also frees the underlying stream, we are its owner. |
387 | 0 | delete *it; |
388 | 0 | } |
389 | 0 | } |
390 | | |
391 | | // ---------------------------------------------------------------------------------- |
392 | | // Writes message to stream |
393 | 0 | void DefaultLogger::WriteToStreams(const char *message, ErrorSeverity ErrorSev) { |
394 | 0 | ai_assert(nullptr != message); |
395 | |
|
396 | | #ifndef ASSIMP_BUILD_SINGLETHREADED |
397 | | std::lock_guard<std::mutex> lock(m_arrayMutex); |
398 | | #endif |
399 | | |
400 | | // Check whether this is a repeated message |
401 | 0 | auto thisLen = ::strlen(message); |
402 | 0 | if (thisLen == lastLen - 1 && !::strncmp(message, lastMsg, lastLen - 1)) { |
403 | 0 | if (!noRepeatMsg) { |
404 | 0 | noRepeatMsg = true; |
405 | 0 | message = "Skipping one or more lines with the same contents\n"; |
406 | 0 | } |
407 | 0 | return; |
408 | 0 | } else { |
409 | | // append a new-line character to the message to be printed |
410 | 0 | lastLen = thisLen; |
411 | 0 | ::memcpy(lastMsg, message, lastLen + 1); |
412 | 0 | ::strcat(lastMsg + lastLen, "\n"); |
413 | |
|
414 | 0 | message = lastMsg; |
415 | 0 | noRepeatMsg = false; |
416 | 0 | ++lastLen; |
417 | 0 | } |
418 | 0 | for (ConstStreamIt it = m_StreamArray.begin(); |
419 | 0 | it != m_StreamArray.end(); |
420 | 0 | ++it) { |
421 | 0 | if (ErrorSev & (*it)->m_uiErrorSeverity) |
422 | 0 | (*it)->m_pStream->write(message); |
423 | 0 | } |
424 | 0 | } |
425 | | |
426 | | // ---------------------------------------------------------------------------------- |
427 | | // Returns thread id, if not supported only a zero will be returned. |
428 | 0 | unsigned int DefaultLogger::GetThreadID() { |
429 | | // fixme: we can get this value via std::threads |
430 | | // std::this_thread::get_id().hash() returns a (big) size_t, not sure if this is useful in this case. |
431 | | #ifdef WIN32 |
432 | | return (unsigned int)::GetCurrentThreadId(); |
433 | | #else |
434 | 0 | return 0; // not supported |
435 | 0 | #endif |
436 | 0 | } |
437 | | |
438 | | // ---------------------------------------------------------------------------------- |
439 | | |
440 | | } // namespace Assimp |