1 | /* |
2 | --------------------------------------------------------------------------- |
3 | Open Asset Import Library (assimp) |
4 | --------------------------------------------------------------------------- |
5 | |
6 | Copyright (c) 2006-2019, 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 "Win32DebugLogStream.h" |
50 | #include "StdOStreamLogStream.h" |
51 | #include "FileLogStream.h" |
52 | #include <assimp/StringUtils.h> |
53 | |
54 | #include <assimp/DefaultIOSystem.h> |
55 | #include <assimp/NullLogger.hpp> |
56 | #include <assimp/DefaultLogger.hpp> |
57 | #include <assimp/ai_assert.h> |
58 | #include <iostream> |
59 | #include <stdio.h> |
60 | |
61 | #ifndef ASSIMP_BUILD_SINGLETHREADED |
62 | # include <thread> |
63 | # include <mutex> |
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 | m_uiErrorSeverity( uiErrorSev ), |
84 | m_pStream( pStream ) { |
85 | // empty |
86 | } |
87 | |
88 | // Destructor |
89 | ~LogStreamInfo() { |
90 | delete m_pStream; |
91 | } |
92 | }; |
93 | |
94 | // ---------------------------------------------------------------------------------- |
95 | // Construct a default log stream |
96 | LogStream* LogStream::createDefaultStream(aiDefaultLogStream streams, |
97 | const char* name /*= "AssimpLog.txt"*/, |
98 | IOSystem* io /*= NULL*/) |
99 | { |
100 | switch (streams) |
101 | { |
102 | // This is a platform-specific feature |
103 | case aiDefaultLogStream_DEBUGGER: |
104 | #ifdef WIN32 |
105 | return new Win32DebugLogStream(); |
106 | #else |
107 | return nullptr; |
108 | #endif |
109 | |
110 | // Platform-independent default streams |
111 | case aiDefaultLogStream_STDERR: |
112 | return new StdOStreamLogStream(std::cerr); |
113 | case aiDefaultLogStream_STDOUT: |
114 | return new StdOStreamLogStream(std::cout); |
115 | case aiDefaultLogStream_FILE: |
116 | return (name && *name ? new FileLogStream(name,io) : nullptr ); |
117 | default: |
118 | // We don't know this default log stream, so raise an assertion |
119 | ai_assert(false); |
120 | |
121 | }; |
122 | |
123 | // For compilers without dead code path detection |
124 | return NULL; |
125 | } |
126 | |
127 | // ---------------------------------------------------------------------------------- |
128 | // Creates the only singleton instance |
129 | Logger *DefaultLogger::create(const char* name /*= "AssimpLog.txt"*/, |
130 | LogSeverity severity /*= NORMAL*/, |
131 | unsigned int defStreams /*= aiDefaultLogStream_DEBUGGER | aiDefaultLogStream_FILE*/, |
132 | IOSystem* io /*= NULL*/) { |
133 | // enter the mutex here to avoid concurrency problems |
134 | #ifndef ASSIMP_BUILD_SINGLETHREADED |
135 | std::lock_guard<std::mutex> lock(loggerMutex); |
136 | #endif |
137 | |
138 | if ( m_pLogger && !isNullLogger() ) { |
139 | delete m_pLogger; |
140 | } |
141 | |
142 | m_pLogger = new DefaultLogger( severity ); |
143 | |
144 | // Attach default log streams |
145 | // Stream the log to the MSVC debugger? |
146 | if ( defStreams & aiDefaultLogStream_DEBUGGER ) { |
147 | m_pLogger->attachStream( pStream: LogStream::createDefaultStream( streams: aiDefaultLogStream_DEBUGGER ) ); |
148 | } |
149 | |
150 | // Stream the log to COUT? |
151 | if ( defStreams & aiDefaultLogStream_STDOUT ) { |
152 | m_pLogger->attachStream( pStream: LogStream::createDefaultStream( streams: aiDefaultLogStream_STDOUT ) ); |
153 | } |
154 | |
155 | // Stream the log to CERR? |
156 | if ( defStreams & aiDefaultLogStream_STDERR ) { |
157 | m_pLogger->attachStream( pStream: LogStream::createDefaultStream( streams: aiDefaultLogStream_STDERR ) ); |
158 | } |
159 | |
160 | // Stream the log to a file |
161 | if ( defStreams & aiDefaultLogStream_FILE && name && *name ) { |
162 | m_pLogger->attachStream( pStream: LogStream::createDefaultStream( streams: aiDefaultLogStream_FILE, name, io ) ); |
163 | } |
164 | |
165 | return m_pLogger; |
166 | } |
167 | |
168 | // ---------------------------------------------------------------------------------- |
169 | void Logger::debug(const char* message) { |
170 | |
171 | // SECURITY FIX: otherwise it's easy to produce overruns since |
172 | // sometimes importers will include data from the input file |
173 | // (i.e. node names) in their messages. |
174 | if (strlen(s: message)>MAX_LOG_MESSAGE_LENGTH) { |
175 | return; |
176 | } |
177 | return OnDebug(message); |
178 | } |
179 | |
180 | // ---------------------------------------------------------------------------------- |
181 | void Logger::info(const char* message) { |
182 | |
183 | // SECURITY FIX: see above |
184 | if (strlen(s: message)>MAX_LOG_MESSAGE_LENGTH) { |
185 | return; |
186 | } |
187 | return OnInfo(message); |
188 | } |
189 | |
190 | // ---------------------------------------------------------------------------------- |
191 | void Logger::warn(const char* message) { |
192 | |
193 | // SECURITY FIX: see above |
194 | if (strlen(s: message)>MAX_LOG_MESSAGE_LENGTH) { |
195 | return; |
196 | } |
197 | return OnWarn(essage: message); |
198 | } |
199 | |
200 | // ---------------------------------------------------------------------------------- |
201 | void Logger::error(const char* message) { |
202 | // SECURITY FIX: see above |
203 | if (strlen(s: message)>MAX_LOG_MESSAGE_LENGTH) { |
204 | return; |
205 | } |
206 | return OnError(message); |
207 | } |
208 | |
209 | // ---------------------------------------------------------------------------------- |
210 | void DefaultLogger::set( Logger *logger ) { |
211 | // enter the mutex here to avoid concurrency problems |
212 | #ifndef ASSIMP_BUILD_SINGLETHREADED |
213 | std::lock_guard<std::mutex> lock(loggerMutex); |
214 | #endif |
215 | |
216 | if ( nullptr == logger ) { |
217 | logger = &s_pNullLogger; |
218 | } |
219 | if ( nullptr != m_pLogger && !isNullLogger() ) { |
220 | delete m_pLogger; |
221 | } |
222 | |
223 | DefaultLogger::m_pLogger = logger; |
224 | } |
225 | |
226 | // ---------------------------------------------------------------------------------- |
227 | bool DefaultLogger::isNullLogger() { |
228 | return m_pLogger == &s_pNullLogger; |
229 | } |
230 | |
231 | // ---------------------------------------------------------------------------------- |
232 | Logger *DefaultLogger::get() { |
233 | return m_pLogger; |
234 | } |
235 | |
236 | // ---------------------------------------------------------------------------------- |
237 | // Kills the only instance |
238 | void DefaultLogger::kill() { |
239 | // enter the mutex here to avoid concurrency problems |
240 | #ifndef ASSIMP_BUILD_SINGLETHREADED |
241 | std::lock_guard<std::mutex> lock(loggerMutex); |
242 | #endif |
243 | |
244 | if ( m_pLogger == &s_pNullLogger ) { |
245 | return; |
246 | } |
247 | delete m_pLogger; |
248 | m_pLogger = &s_pNullLogger; |
249 | } |
250 | |
251 | // ---------------------------------------------------------------------------------- |
252 | // Debug message |
253 | void DefaultLogger::OnDebug( const char* message ) { |
254 | if ( m_Severity == Logger::NORMAL ) { |
255 | return; |
256 | } |
257 | |
258 | static const size_t Size = MAX_LOG_MESSAGE_LENGTH + 16; |
259 | char msg[Size]; |
260 | ai_snprintf(s: msg, maxlen: Size, format: "Debug, T%u: %s" , GetThreadID(), message); |
261 | |
262 | WriteToStreams( message: msg, ErrorSev: Logger::Debugging ); |
263 | } |
264 | |
265 | // ---------------------------------------------------------------------------------- |
266 | // Logs an info |
267 | void DefaultLogger::OnInfo( const char* message ){ |
268 | static const size_t Size = MAX_LOG_MESSAGE_LENGTH + 16; |
269 | char msg[Size]; |
270 | ai_snprintf(s: msg, maxlen: Size, format: "Info, T%u: %s" , GetThreadID(), message ); |
271 | |
272 | WriteToStreams( message: msg , ErrorSev: Logger::Info ); |
273 | } |
274 | |
275 | // ---------------------------------------------------------------------------------- |
276 | // Logs a warning |
277 | void DefaultLogger::OnWarn( const char* message ) { |
278 | static const size_t Size = MAX_LOG_MESSAGE_LENGTH + 16; |
279 | char msg[Size]; |
280 | ai_snprintf(s: msg, maxlen: Size, format: "Warn, T%u: %s" , GetThreadID(), message ); |
281 | |
282 | WriteToStreams( message: msg, ErrorSev: Logger::Warn ); |
283 | } |
284 | |
285 | // ---------------------------------------------------------------------------------- |
286 | // Logs an error |
287 | void DefaultLogger::OnError( const char* message ) { |
288 | static const size_t Size = MAX_LOG_MESSAGE_LENGTH + 16; |
289 | char msg[ Size ]; |
290 | ai_snprintf(s: msg, maxlen: Size, format: "Error, T%u: %s" , GetThreadID(), message ); |
291 | |
292 | WriteToStreams( message: msg, ErrorSev: Logger::Err ); |
293 | } |
294 | |
295 | // ---------------------------------------------------------------------------------- |
296 | // Will attach a new stream |
297 | bool DefaultLogger::attachStream( LogStream *pStream, unsigned int severity ) { |
298 | if ( nullptr == pStream ) { |
299 | return false; |
300 | } |
301 | |
302 | if (0 == severity) { |
303 | severity = Logger::Info | Logger::Err | Logger::Warn | Logger::Debugging; |
304 | } |
305 | |
306 | for ( StreamIt it = m_StreamArray.begin(); |
307 | it != m_StreamArray.end(); |
308 | ++it ) |
309 | { |
310 | if ( (*it)->m_pStream == pStream ) { |
311 | (*it)->m_uiErrorSeverity |= severity; |
312 | return true; |
313 | } |
314 | } |
315 | |
316 | LogStreamInfo *pInfo = new LogStreamInfo( severity, pStream ); |
317 | m_StreamArray.push_back( x: pInfo ); |
318 | return true; |
319 | } |
320 | |
321 | // ---------------------------------------------------------------------------------- |
322 | // Detach a stream |
323 | bool DefaultLogger::detatchStream( LogStream *pStream, unsigned int severity ) { |
324 | if ( nullptr == pStream ) { |
325 | return false; |
326 | } |
327 | |
328 | if (0 == severity) { |
329 | severity = SeverityAll; |
330 | } |
331 | |
332 | bool res( false ); |
333 | for ( StreamIt it = m_StreamArray.begin(); it != m_StreamArray.end(); ++it ) { |
334 | if ( (*it)->m_pStream == pStream ) { |
335 | (*it)->m_uiErrorSeverity &= ~severity; |
336 | if ( (*it)->m_uiErrorSeverity == 0 ) { |
337 | // don't delete the underlying stream 'cause the caller gains ownership again |
338 | (**it).m_pStream = nullptr; |
339 | delete *it; |
340 | m_StreamArray.erase( position: it ); |
341 | res = true; |
342 | break; |
343 | } |
344 | return true; |
345 | } |
346 | } |
347 | return res; |
348 | } |
349 | |
350 | // ---------------------------------------------------------------------------------- |
351 | // Constructor |
352 | DefaultLogger::DefaultLogger(LogSeverity severity) |
353 | : Logger ( severity ) |
354 | , noRepeatMsg (false) |
355 | , lastLen( 0 ) { |
356 | lastMsg[0] = '\0'; |
357 | } |
358 | |
359 | // ---------------------------------------------------------------------------------- |
360 | // Destructor |
361 | DefaultLogger::~DefaultLogger() { |
362 | for ( StreamIt it = m_StreamArray.begin(); it != m_StreamArray.end(); ++it ) { |
363 | // also frees the underlying stream, we are its owner. |
364 | delete *it; |
365 | } |
366 | } |
367 | |
368 | // ---------------------------------------------------------------------------------- |
369 | // Writes message to stream |
370 | void DefaultLogger::WriteToStreams(const char *message, ErrorSeverity ErrorSev ) { |
371 | ai_assert(nullptr != message); |
372 | |
373 | // Check whether this is a repeated message |
374 | if (! ::strncmp( s1: message,s2: lastMsg, n: lastLen-1)) |
375 | { |
376 | if (!noRepeatMsg) |
377 | { |
378 | noRepeatMsg = true; |
379 | message = "Skipping one or more lines with the same contents\n" ; |
380 | } |
381 | else return; |
382 | } |
383 | else |
384 | { |
385 | // append a new-line character to the message to be printed |
386 | lastLen = ::strlen(s: message); |
387 | ::memcpy(dest: lastMsg,src: message,n: lastLen+1); |
388 | ::strcat(dest: lastMsg+lastLen,src: "\n" ); |
389 | |
390 | message = lastMsg; |
391 | noRepeatMsg = false; |
392 | ++lastLen; |
393 | } |
394 | for ( ConstStreamIt it = m_StreamArray.begin(); |
395 | it != m_StreamArray.end(); |
396 | ++it) |
397 | { |
398 | if ( ErrorSev & (*it)->m_uiErrorSeverity ) |
399 | (*it)->m_pStream->write( message); |
400 | } |
401 | } |
402 | |
403 | // ---------------------------------------------------------------------------------- |
404 | // Returns thread id, if not supported only a zero will be returned. |
405 | unsigned int DefaultLogger::GetThreadID() |
406 | { |
407 | // fixme: we can get this value via std::threads |
408 | // std::this_thread::get_id().hash() returns a (big) size_t, not sure if this is useful in this case. |
409 | #ifdef WIN32 |
410 | return (unsigned int)::GetCurrentThreadId(); |
411 | #else |
412 | return 0; // not supported |
413 | #endif |
414 | } |
415 | |
416 | // ---------------------------------------------------------------------------------- |
417 | |
418 | } // !namespace Assimp |
419 | |