diff options
Diffstat (limited to 'linden/indra/llcommon/llerror.cpp')
-rw-r--r-- | linden/indra/llcommon/llerror.cpp | 1043 |
1 files changed, 1028 insertions, 15 deletions
diff --git a/linden/indra/llcommon/llerror.cpp b/linden/indra/llcommon/llerror.cpp index 29f4500..57e098e 100644 --- a/linden/indra/llcommon/llerror.cpp +++ b/linden/indra/llcommon/llerror.cpp | |||
@@ -1,8 +1,9 @@ | |||
1 | /** | 1 | /** |
2 | * @file llerror.cpp | 2 | * @file llerror.cpp |
3 | * @brief Function to crash. | 3 | * @date December 2006 |
4 | * @brief error message system | ||
4 | * | 5 | * |
5 | * Copyright (c) 2001-2007, Linden Research, Inc. | 6 | * Copyright (c) 2006-2007, Linden Research, Inc. |
6 | * | 7 | * |
7 | * The source code in this file ("Source Code") is provided by Linden Lab | 8 | * The source code in this file ("Source Code") is provided by Linden Lab |
8 | * to you under the terms of the GNU General Public License, version 2.0 | 9 | * to you under the terms of the GNU General Public License, version 2.0 |
@@ -24,36 +25,1048 @@ | |||
24 | * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, | 25 | * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, |
25 | * COMPLETENESS OR PERFORMANCE. | 26 | * COMPLETENESS OR PERFORMANCE. |
26 | */ | 27 | */ |
28 | |||
27 | #include "linden_common.h" | 29 | #include "linden_common.h" |
28 | 30 | ||
29 | #include "llerror.h" | 31 | #include "llerror.h" |
32 | #include "llerrorcontrol.h" | ||
33 | |||
34 | #include "llapp.h" | ||
35 | #include "llapr.h" | ||
36 | extern apr_thread_mutex_t *gLogMutexp; | ||
37 | #include "llfile.h" | ||
38 | #include "llfixedbuffer.h" | ||
39 | #include "lllivefile.h" | ||
40 | #include "llsd.h" | ||
41 | #include "llsdserialize.h" | ||
42 | |||
43 | #include <algorithm> | ||
44 | #include <cctype> | ||
45 | #include <map> | ||
46 | #include <sstream> | ||
47 | #if !LL_WINDOWS | ||
48 | #include <stdio.h> | ||
49 | #include <syslog.h> | ||
50 | #endif | ||
51 | #include <time.h> | ||
52 | #if LL_WINDOWS | ||
53 | #include <windows.h> | ||
54 | #endif | ||
55 | #include <vector> | ||
56 | |||
57 | |||
58 | #ifdef __GNUC__ | ||
59 | #include <cxxabi.h> | ||
60 | #endif | ||
61 | |||
62 | namespace { | ||
63 | #if !LL_WINDOWS | ||
64 | class RecordToSyslog : public LLError::Recorder | ||
65 | { | ||
66 | public: | ||
67 | RecordToSyslog(const std::string& identity) | ||
68 | : mIdentity(identity) | ||
69 | { | ||
70 | openlog(mIdentity.c_str(), LOG_CONS|LOG_PID, LOG_LOCAL0); | ||
71 | // we need to set the string from a local copy of the string | ||
72 | // since apparanetly openlog expects the const char* to remain | ||
73 | // valid even after it returns (presumably until closelog) | ||
74 | } | ||
75 | |||
76 | ~RecordToSyslog() | ||
77 | { | ||
78 | closelog(); | ||
79 | } | ||
80 | |||
81 | virtual void recordMessage(LLError::ELevel level, | ||
82 | const std::string& message) | ||
83 | { | ||
84 | int syslogPriority = LOG_CRIT; | ||
85 | switch (level) { | ||
86 | case LLError::LEVEL_DEBUG: syslogPriority = LOG_DEBUG; break; | ||
87 | case LLError::LEVEL_INFO: syslogPriority = LOG_INFO; break; | ||
88 | case LLError::LEVEL_WARN: syslogPriority = LOG_WARNING; break; | ||
89 | case LLError::LEVEL_ERROR: syslogPriority = LOG_CRIT; break; | ||
90 | default: syslogPriority = LOG_CRIT; | ||
91 | } | ||
92 | |||
93 | syslog(syslogPriority, "%s", message.c_str()); | ||
94 | } | ||
95 | private: | ||
96 | std::string mIdentity; | ||
97 | }; | ||
98 | #endif | ||
99 | |||
100 | class RecordToFile : public LLError::Recorder | ||
101 | { | ||
102 | public: | ||
103 | RecordToFile(const std::string& filename) | ||
104 | { | ||
105 | mFile.open(filename.c_str(), llofstream::out | llofstream::app); | ||
106 | if (!mFile) | ||
107 | { | ||
108 | llinfos << "Error setting log file to " << filename << llendl; | ||
109 | } | ||
110 | } | ||
111 | |||
112 | ~RecordToFile() | ||
113 | { | ||
114 | mFile.close(); | ||
115 | } | ||
116 | |||
117 | bool okay() { return mFile; } | ||
118 | |||
119 | virtual bool wantsTime() { return true; } | ||
120 | |||
121 | virtual void recordMessage(LLError::ELevel level, | ||
122 | const std::string& message) | ||
123 | { | ||
124 | mFile << message << std::endl; | ||
125 | // mFile.flush(); | ||
126 | // *FIX: should we do this? | ||
127 | } | ||
128 | |||
129 | private: | ||
130 | llofstream mFile; | ||
131 | }; | ||
132 | |||
133 | |||
134 | class RecordToStderr : public LLError::Recorder | ||
135 | { | ||
136 | public: | ||
137 | RecordToStderr(bool timestamp) : mTimestamp(timestamp) { } | ||
30 | 138 | ||
31 | LLErrorBuffer gErrorBuffer; | 139 | virtual bool wantsTime() { return mTimestamp; } |
32 | LLErrorStream gErrorStream(&gErrorBuffer); | 140 | |
141 | virtual void recordMessage(LLError::ELevel level, | ||
142 | const std::string& message) | ||
143 | { | ||
144 | fprintf(stderr, "%s\n", message.c_str()); | ||
145 | } | ||
146 | |||
147 | private: | ||
148 | bool mTimestamp; | ||
149 | }; | ||
33 | 150 | ||
151 | class RecordToFixedBuffer : public LLError::Recorder | ||
152 | { | ||
153 | public: | ||
154 | RecordToFixedBuffer(LLFixedBuffer& buffer) : mBuffer(buffer) { } | ||
155 | |||
156 | virtual void recordMessage(LLError::ELevel level, | ||
157 | const std::string& message) | ||
158 | { | ||
159 | mBuffer.addLine(message.c_str()); | ||
160 | } | ||
161 | |||
162 | private: | ||
163 | LLFixedBuffer& mBuffer; | ||
164 | }; | ||
34 | 165 | ||
35 | void _llcrash_and_loop() | 166 | #if LL_WINDOWS |
167 | class RecordToWinDebug: public LLError::Recorder | ||
168 | { | ||
169 | public: | ||
170 | virtual void recordMessage(LLError::ELevel level, | ||
171 | const std::string& message) | ||
172 | { | ||
173 | llutf16string utf16str = | ||
174 | wstring_to_utf16str(utf8str_to_wstring(message)); | ||
175 | utf16str += '\n'; | ||
176 | OutputDebugString(utf16str.c_str()); | ||
177 | } | ||
178 | }; | ||
179 | #endif | ||
180 | } | ||
181 | |||
182 | |||
183 | namespace | ||
36 | { | 184 | { |
37 | // Now, we go kaboom! | 185 | std::string className(const std::type_info& type) |
38 | U32* crash = NULL; | 186 | { |
187 | #ifdef __GNUC__ | ||
188 | // GCC: type_info::name() returns a mangled class name, must demangle | ||
189 | |||
190 | static size_t abi_name_len = 100; | ||
191 | static char* abi_name_buf = (char*)malloc(abi_name_len); | ||
192 | // warning: above is voodoo inferred from the GCC manual, | ||
193 | // do NOT change | ||
194 | |||
195 | int status; | ||
196 | // We don't use status, and shouldn't have to pass apointer to it | ||
197 | // but gcc 3.3 libstc++'s implementation of demangling is broken | ||
198 | // and fails without. | ||
199 | |||
200 | char* name = abi::__cxa_demangle(type.name(), | ||
201 | abi_name_buf, &abi_name_len, &status); | ||
202 | // this call can realloc the abi_name_buf pointer (!) | ||
203 | |||
204 | return name ? name : type.name(); | ||
39 | 205 | ||
40 | *crash = 0; | 206 | #elif LL_WINDOWS |
207 | // DevStudio: type_info::name() includes the text "class " at the start | ||
41 | 208 | ||
42 | while(TRUE) | 209 | static const std::string class_prefix = "class "; |
210 | |||
211 | std::string name = type.name(); | ||
212 | std::string::size_type p = name.find(class_prefix); | ||
213 | if (p == std::string::npos) | ||
214 | { | ||
215 | return name; | ||
216 | } | ||
217 | |||
218 | return name.substr(p + class_prefix.size()); | ||
219 | |||
220 | #else | ||
221 | return type.name(); | ||
222 | #endif | ||
223 | } | ||
224 | |||
225 | std::string functionName(const std::string& preprocessor_name) | ||
43 | { | 226 | { |
227 | #if LL_WINDOWS | ||
228 | // DevStudio: the __FUNCTION__ macro string includes | ||
229 | // the type and/or namespace prefixes | ||
230 | |||
231 | std::string::size_type p = preprocessor_name.rfind(':'); | ||
232 | if (p == std::string::npos) | ||
233 | { | ||
234 | return preprocessor_name; | ||
235 | } | ||
236 | return preprocessor_name.substr(p + 1); | ||
237 | |||
238 | #else | ||
239 | return preprocessor_name; | ||
240 | #endif | ||
241 | } | ||
242 | |||
243 | |||
244 | class LogControlFile : public LLLiveFile | ||
245 | { | ||
246 | LOG_CLASS(LogControlFile); | ||
247 | |||
248 | public: | ||
249 | static LogControlFile& fromDirectory(const std::string& dir); | ||
250 | |||
251 | virtual void loadFile(); | ||
252 | |||
253 | private: | ||
254 | LogControlFile(const std::string &filename) | ||
255 | : LLLiveFile(filename) | ||
256 | { } | ||
257 | }; | ||
258 | |||
259 | LogControlFile& LogControlFile::fromDirectory(const std::string& dir) | ||
260 | { | ||
261 | std::string dirBase = dir + "/"; | ||
262 | // NB: We have no abstraction in llcommon for the "proper" | ||
263 | // delimiter but it turns out that "/" works on all three platforms | ||
264 | |||
265 | std::string file = dirBase + "logcontrol-dev.xml"; | ||
266 | |||
267 | llstat stat_info; | ||
268 | if (LLFile::stat(file.c_str(), &stat_info)) { | ||
269 | // NB: stat returns non-zero if it can't read the file, for example | ||
270 | // if it doesn't exist. LLFile has no better abstraction for | ||
271 | // testing for file existence. | ||
272 | |||
273 | file = dirBase + "logcontrol.xml"; | ||
274 | } | ||
275 | return * new LogControlFile(file); | ||
276 | // NB: This instance is never freed | ||
277 | } | ||
278 | |||
279 | void LogControlFile::loadFile() | ||
280 | { | ||
281 | LLSD configuration; | ||
282 | |||
283 | { | ||
284 | llifstream file(filename().c_str()); | ||
285 | if (file.is_open()) | ||
286 | { | ||
287 | LLSDSerialize::fromXML(configuration, file); | ||
288 | } | ||
289 | |||
290 | if (configuration.isUndefined()) | ||
291 | { | ||
292 | llwarns << filename() << " missing, ill-formed," | ||
293 | " or simply undefined; not changing configuration" | ||
294 | << llendl; | ||
295 | return; | ||
296 | } | ||
297 | } | ||
298 | |||
299 | LLError::configure(configuration); | ||
300 | llinfos << "logging reconfigured from " << filename() << llendl; | ||
301 | } | ||
302 | |||
303 | |||
304 | typedef std::map<std::string, LLError::ELevel> LevelMap; | ||
305 | typedef std::vector<LLError::Recorder*> Recorders; | ||
306 | typedef std::vector<LLError::CallSite*> CallSiteVector; | ||
307 | |||
308 | class Globals | ||
309 | { | ||
310 | public: | ||
311 | std::ostringstream messageStream; | ||
312 | bool messageStreamInUse; | ||
313 | |||
314 | void addCallSite(LLError::CallSite&); | ||
315 | void invalidateCallSites(); | ||
316 | |||
317 | static Globals& get(); | ||
318 | // return the one instance of the globals | ||
319 | |||
320 | private: | ||
321 | CallSiteVector callSites; | ||
322 | |||
323 | Globals() | ||
324 | : messageStreamInUse(false) | ||
325 | { } | ||
326 | |||
327 | }; | ||
328 | |||
329 | void Globals::addCallSite(LLError::CallSite& site) | ||
330 | { | ||
331 | callSites.push_back(&site); | ||
332 | } | ||
44 | 333 | ||
45 | // Loop forever, in case the crash didn't work? | 334 | void Globals::invalidateCallSites() |
335 | { | ||
336 | for (CallSiteVector::const_iterator i = callSites.begin(); | ||
337 | i != callSites.end(); | ||
338 | ++i) | ||
339 | { | ||
340 | (*i)->invalidate(); | ||
341 | } | ||
342 | |||
343 | callSites.clear(); | ||
344 | } | ||
345 | |||
346 | Globals& Globals::get() | ||
347 | { | ||
348 | /* This pattern, of returning a reference to a static function | ||
349 | variable, is to ensure that this global is constructed before | ||
350 | it is used, no matter what the global initializeation sequence | ||
351 | is. | ||
352 | See C++ FAQ Lite, sections 10.12 through 10.14 | ||
353 | */ | ||
354 | static Globals* globals = new Globals; | ||
355 | return *globals; | ||
46 | } | 356 | } |
47 | } | 357 | } |
48 | 358 | ||
49 | LLScopedErrorLevel::LLScopedErrorLevel(LLErrorBuffer::ELevel error_level) | 359 | namespace LLError |
50 | { | 360 | { |
51 | mOrigErrorLevel = gErrorStream.getErrorLevel(); | 361 | class Settings |
52 | gErrorStream.setErrorLevel(error_level); | 362 | { |
363 | public: | ||
364 | bool printLocation; | ||
365 | |||
366 | LLError::ELevel defaultLevel; | ||
367 | |||
368 | LevelMap functionLevelMap; | ||
369 | LevelMap classLevelMap; | ||
370 | LevelMap fileLevelMap; | ||
371 | |||
372 | LLError::FatalFunction crashFunction; | ||
373 | LLError::TimeFunction timeFunction; | ||
374 | |||
375 | Recorders recorders; | ||
376 | Recorder* fileRecorder; | ||
377 | Recorder* fixedBufferRecorder; | ||
378 | std::string fileRecorderFileName; | ||
379 | |||
380 | int shouldLogCallCounter; | ||
381 | |||
382 | static Settings& get(); | ||
383 | |||
384 | static void reset(); | ||
385 | static Settings* saveAndReset(); | ||
386 | static void restore(Settings*); | ||
387 | |||
388 | private: | ||
389 | Settings() | ||
390 | : printLocation(false), | ||
391 | defaultLevel(LLError::LEVEL_DEBUG), | ||
392 | crashFunction(NULL), | ||
393 | timeFunction(NULL), | ||
394 | fileRecorder(NULL), | ||
395 | fixedBufferRecorder(NULL), | ||
396 | shouldLogCallCounter(0) | ||
397 | { } | ||
398 | |||
399 | static Settings*& getPtr(); | ||
400 | }; | ||
401 | |||
402 | Settings& Settings::get() | ||
403 | { | ||
404 | Settings* p = getPtr(); | ||
405 | if (!p) | ||
406 | { | ||
407 | reset(); | ||
408 | p = getPtr(); | ||
409 | } | ||
410 | return *p; | ||
411 | } | ||
412 | |||
413 | void Settings::reset() | ||
414 | { | ||
415 | Globals::get().invalidateCallSites(); | ||
416 | |||
417 | Settings*& p = getPtr(); | ||
418 | delete p; | ||
419 | p = new Settings(); | ||
420 | } | ||
421 | |||
422 | Settings* Settings::saveAndReset() | ||
423 | { | ||
424 | Globals::get().invalidateCallSites(); | ||
425 | |||
426 | Settings*& p = getPtr(); | ||
427 | Settings* originalSettings = p; | ||
428 | p = new Settings(); | ||
429 | return originalSettings; | ||
430 | } | ||
431 | |||
432 | void Settings::restore(Settings* originalSettings) | ||
433 | { | ||
434 | Globals::get().invalidateCallSites(); | ||
435 | |||
436 | Settings*& p = getPtr(); | ||
437 | delete p; | ||
438 | p = originalSettings; | ||
439 | } | ||
440 | |||
441 | Settings*& Settings::getPtr() | ||
442 | { | ||
443 | static Settings* currentSettings = NULL; | ||
444 | return currentSettings; | ||
445 | } | ||
53 | } | 446 | } |
54 | 447 | ||
448 | namespace LLError | ||
449 | { | ||
450 | CallSite::CallSite(ELevel level, | ||
451 | const char* file, int line, | ||
452 | const std::type_info& class_info, const char* function) | ||
453 | : mLevel(level), mFile(file), mLine(line), | ||
454 | mClassInfo(class_info), mFunction(function), | ||
455 | mCached(false), mShouldLog(false) | ||
456 | { } | ||
457 | |||
458 | |||
459 | void CallSite::invalidate() | ||
460 | { mCached = false; } | ||
461 | } | ||
462 | |||
463 | namespace | ||
464 | { | ||
465 | bool shouldLogToStderr() | ||
466 | { | ||
467 | #if LL_DARWIN | ||
468 | // On Mac OS X, stderr from apps launched from the Finder goes to the | ||
469 | // console log. It's generally considered bad form to spam too much | ||
470 | // there. | ||
471 | |||
472 | // If stdin is a tty, assume the user launched from the command line and | ||
473 | // therefore wants to see stderr. Otherwise, assume we've been launched | ||
474 | // from the finder and shouldn't spam stderr. | ||
475 | return isatty(0); | ||
476 | #else | ||
477 | return true; | ||
478 | #endif | ||
479 | } | ||
480 | |||
481 | bool stderrLogWantsTime() | ||
482 | { | ||
483 | #if LL_WINDOWS | ||
484 | return false; | ||
485 | #else | ||
486 | return true; | ||
487 | #endif | ||
488 | } | ||
489 | |||
490 | |||
491 | void commonInit(const std::string& dir) | ||
492 | { | ||
493 | LLError::Settings::reset(); | ||
494 | |||
495 | LLError::setDefaultLevel(LLError::LEVEL_INFO); | ||
496 | LLError::setFatalFunction(LLError::crashAndLoop); | ||
497 | LLError::setTimeFunction(LLError::utcTime); | ||
498 | |||
499 | if (shouldLogToStderr()) | ||
500 | { | ||
501 | LLError::addRecorder(new RecordToStderr(stderrLogWantsTime())); | ||
502 | } | ||
503 | |||
504 | #if LL_WINDOWS | ||
505 | LLError::addRecorder(new RecordToWinDebug); | ||
506 | #endif | ||
55 | 507 | ||
56 | LLScopedErrorLevel::~LLScopedErrorLevel() | 508 | LogControlFile& e = LogControlFile::fromDirectory(dir); |
509 | e.addToEventTimer(); | ||
510 | } | ||
511 | } | ||
512 | |||
513 | namespace LLError | ||
57 | { | 514 | { |
58 | gErrorStream.setErrorLevel(mOrigErrorLevel); | 515 | void initForServer(const std::string& identity) |
516 | { | ||
517 | std::string dir = LLApp::instance()->getOption("configdir"); | ||
518 | commonInit(dir); | ||
519 | #if !LL_WINDOWS | ||
520 | addRecorder(new RecordToSyslog(identity)); | ||
521 | #endif | ||
522 | } | ||
523 | |||
524 | void initForApplication(const std::string& dir) | ||
525 | { | ||
526 | commonInit(dir); | ||
527 | } | ||
528 | |||
529 | void setPrintLocation(bool print) | ||
530 | { | ||
531 | Settings& s = Settings::get(); | ||
532 | s.printLocation = print; | ||
533 | } | ||
534 | |||
535 | void setFatalFunction(FatalFunction f) | ||
536 | { | ||
537 | Settings& s = Settings::get(); | ||
538 | s.crashFunction = f; | ||
539 | } | ||
540 | |||
541 | void setTimeFunction(TimeFunction f) | ||
542 | { | ||
543 | Settings& s = Settings::get(); | ||
544 | s.timeFunction = f; | ||
545 | } | ||
546 | |||
547 | void setDefaultLevel(ELevel level) | ||
548 | { | ||
549 | Globals& g = Globals::get(); | ||
550 | Settings& s = Settings::get(); | ||
551 | g.invalidateCallSites(); | ||
552 | s.defaultLevel = level; | ||
553 | } | ||
554 | |||
555 | void setFunctionLevel(const std::string& function_name, ELevel level) | ||
556 | { | ||
557 | Globals& g = Globals::get(); | ||
558 | Settings& s = Settings::get(); | ||
559 | g.invalidateCallSites(); | ||
560 | s.functionLevelMap[function_name] = level; | ||
561 | } | ||
562 | |||
563 | void setClassLevel(const std::string& class_name, ELevel level) | ||
564 | { | ||
565 | Globals& g = Globals::get(); | ||
566 | Settings& s = Settings::get(); | ||
567 | g.invalidateCallSites(); | ||
568 | s.classLevelMap[class_name] = level; | ||
569 | } | ||
570 | |||
571 | void setFileLevel(const std::string& file_name, ELevel level) | ||
572 | { | ||
573 | Globals& g = Globals::get(); | ||
574 | Settings& s = Settings::get(); | ||
575 | g.invalidateCallSites(); | ||
576 | s.fileLevelMap[file_name] = level; | ||
577 | } | ||
59 | } | 578 | } |
579 | |||
580 | namespace { | ||
581 | LLError::ELevel decodeLevel(std::string name) | ||
582 | { | ||
583 | static LevelMap level_names; | ||
584 | if (level_names.empty()) | ||
585 | { | ||
586 | level_names["ALL"] = LLError::LEVEL_ALL; | ||
587 | level_names["DEBUG"] = LLError::LEVEL_DEBUG; | ||
588 | level_names["INFO"] = LLError::LEVEL_INFO; | ||
589 | level_names["WARN"] = LLError::LEVEL_WARN; | ||
590 | level_names["ERROR"] = LLError::LEVEL_ERROR; | ||
591 | level_names["NONE"] = LLError::LEVEL_NONE; | ||
592 | } | ||
593 | |||
594 | std::transform(name.begin(), name.end(), name.begin(), toupper); | ||
595 | |||
596 | LevelMap::const_iterator i = level_names.find(name); | ||
597 | if (i == level_names.end()) | ||
598 | { | ||
599 | llwarns << "unrecognized logging level: '" << name << "'" << llendl; | ||
600 | return LLError::LEVEL_INFO; | ||
601 | } | ||
602 | |||
603 | return i->second; | ||
604 | } | ||
605 | |||
606 | void setLevels(LevelMap& map, const LLSD& list, LLError::ELevel level) | ||
607 | { | ||
608 | LLSD::array_const_iterator i, end; | ||
609 | for (i = list.beginArray(), end = list.endArray(); i != end; ++i) | ||
610 | { | ||
611 | map[*i] = level; | ||
612 | } | ||
613 | } | ||
614 | } | ||
615 | |||
616 | namespace LLError | ||
617 | { | ||
618 | void configure(const LLSD& config) | ||
619 | { | ||
620 | Globals& g = Globals::get(); | ||
621 | Settings& s = Settings::get(); | ||
622 | |||
623 | g.invalidateCallSites(); | ||
624 | s.functionLevelMap.clear(); | ||
625 | s.classLevelMap.clear(); | ||
626 | s.fileLevelMap.clear(); | ||
627 | |||
628 | setPrintLocation(config["print-location"]); | ||
629 | setDefaultLevel(decodeLevel(config["default-level"])); | ||
630 | |||
631 | LLSD sets = config["settings"]; | ||
632 | LLSD::array_const_iterator a, end; | ||
633 | for (a = sets.beginArray(), end = sets.endArray(); a != end; ++a) | ||
634 | { | ||
635 | const LLSD& entry = *a; | ||
636 | |||
637 | ELevel level = decodeLevel(entry["level"]); | ||
638 | |||
639 | setLevels(s.functionLevelMap, entry["functions"], level); | ||
640 | setLevels(s.classLevelMap, entry["classes"], level); | ||
641 | setLevels(s.fileLevelMap, entry["files"], level); | ||
642 | } | ||
643 | } | ||
644 | } | ||
645 | |||
646 | |||
647 | namespace LLError | ||
648 | { | ||
649 | Recorder::~Recorder() | ||
650 | { } | ||
651 | |||
652 | // virtual | ||
653 | bool Recorder::wantsTime() | ||
654 | { return false; } | ||
655 | |||
656 | |||
657 | |||
658 | void addRecorder(Recorder* recorder) | ||
659 | { | ||
660 | if (recorder == NULL) | ||
661 | { | ||
662 | return; | ||
663 | } | ||
664 | Settings& s = Settings::get(); | ||
665 | s.recorders.push_back(recorder); | ||
666 | } | ||
667 | |||
668 | void removeRecorder(Recorder* recorder) | ||
669 | { | ||
670 | if (recorder == NULL) | ||
671 | { | ||
672 | return; | ||
673 | } | ||
674 | Settings& s = Settings::get(); | ||
675 | s.recorders.erase( | ||
676 | std::remove(s.recorders.begin(), s.recorders.end(), recorder), | ||
677 | s.recorders.end()); | ||
678 | } | ||
679 | } | ||
680 | |||
681 | namespace LLError | ||
682 | { | ||
683 | void logToFile(const std::string& file_name) | ||
684 | { | ||
685 | LLError::Settings& s = LLError::Settings::get(); | ||
686 | |||
687 | removeRecorder(s.fileRecorder); | ||
688 | delete s.fileRecorder; | ||
689 | s.fileRecorder = NULL; | ||
690 | s.fileRecorderFileName.clear(); | ||
691 | |||
692 | if (file_name.empty()) | ||
693 | { | ||
694 | return; | ||
695 | } | ||
696 | |||
697 | RecordToFile* f = new RecordToFile(file_name); | ||
698 | if (!f->okay()) | ||
699 | { | ||
700 | delete f; | ||
701 | return; | ||
702 | } | ||
703 | |||
704 | s.fileRecorderFileName = file_name; | ||
705 | s.fileRecorder = f; | ||
706 | addRecorder(f); | ||
707 | } | ||
708 | |||
709 | void logToFixedBuffer(LLFixedBuffer* fixedBuffer) | ||
710 | { | ||
711 | LLError::Settings& s = LLError::Settings::get(); | ||
712 | |||
713 | removeRecorder(s.fixedBufferRecorder); | ||
714 | delete s.fixedBufferRecorder; | ||
715 | s.fixedBufferRecorder = NULL; | ||
716 | |||
717 | if (!fixedBuffer) | ||
718 | { | ||
719 | return; | ||
720 | } | ||
721 | |||
722 | s.fixedBufferRecorder = new RecordToFixedBuffer(*fixedBuffer); | ||
723 | addRecorder(s.fixedBufferRecorder); | ||
724 | } | ||
725 | |||
726 | std::string logFileName() | ||
727 | { | ||
728 | LLError::Settings& s = LLError::Settings::get(); | ||
729 | return s.fileRecorderFileName; | ||
730 | } | ||
731 | } | ||
732 | |||
733 | namespace | ||
734 | { | ||
735 | void writeToRecorders(LLError::ELevel level, const std::string& message) | ||
736 | { | ||
737 | LLError::Settings& s = LLError::Settings::get(); | ||
738 | |||
739 | std::string messageWithTime; | ||
740 | |||
741 | for (Recorders::const_iterator i = s.recorders.begin(); | ||
742 | i != s.recorders.end(); | ||
743 | ++i) | ||
744 | { | ||
745 | LLError::Recorder* r = *i; | ||
746 | |||
747 | if (r->wantsTime() && s.timeFunction != NULL) | ||
748 | { | ||
749 | if (messageWithTime.empty()) | ||
750 | { | ||
751 | messageWithTime = s.timeFunction() + " " + message; | ||
752 | } | ||
753 | |||
754 | r->recordMessage(level, messageWithTime); | ||
755 | } | ||
756 | else | ||
757 | { | ||
758 | r->recordMessage(level, message); | ||
759 | } | ||
760 | } | ||
761 | } | ||
762 | } | ||
763 | |||
764 | |||
765 | /* | ||
766 | Recorder formats: | ||
767 | |||
768 | $type = "ERROR" | "WARNING" | "ALERT" | "INFO" | "DEBUG" | ||
769 | $loc = "$file($line)" | ||
770 | $msg = "$loc : " if FATAL or printing loc | ||
771 | "" otherwise | ||
772 | $msg += "$type: " | ||
773 | $msg += contents of stringstream | ||
774 | |||
775 | $time = "%Y-%m-%dT%H:%M:%SZ" if UTC | ||
776 | or "%Y-%m-%dT%H:%M:%S %Z" if local | ||
777 | |||
778 | syslog: "$msg" | ||
779 | file: "$time $msg\n" | ||
780 | stderr: "$time $msg\n" except on windows, "$msg\n" | ||
781 | fixedbuf: "$msg" | ||
782 | winddebug: "$msg\n" | ||
783 | |||
784 | Note: if FATAL, an additional line gets logged first, with $msg set to | ||
785 | "$loc : error" | ||
786 | |||
787 | You get: | ||
788 | llfoo.cpp(42) : error | ||
789 | llfoo.cpp(42) : ERROR: something | ||
790 | |||
791 | */ | ||
792 | |||
793 | namespace { | ||
794 | bool checkLevelMap(const LevelMap& map, const std::string& key, | ||
795 | LLError::ELevel& level) | ||
796 | { | ||
797 | LevelMap::const_iterator i = map.find(key); | ||
798 | if (i == map.end()) | ||
799 | { | ||
800 | return false; | ||
801 | } | ||
802 | |||
803 | level = i->second; | ||
804 | return true; | ||
805 | } | ||
806 | |||
807 | class LogLock | ||
808 | { | ||
809 | public: | ||
810 | LogLock(); | ||
811 | ~LogLock(); | ||
812 | bool ok() const { return mOK; } | ||
813 | private: | ||
814 | bool mLocked; | ||
815 | bool mOK; | ||
816 | }; | ||
817 | |||
818 | LogLock::LogLock() | ||
819 | : mLocked(false), mOK(false) | ||
820 | { | ||
821 | if (!gLogMutexp) | ||
822 | { | ||
823 | mOK = true; | ||
824 | return; | ||
825 | } | ||
826 | |||
827 | const int MAX_RETRIES = 5; | ||
828 | for (int attempts = 0; attempts < MAX_RETRIES; ++attempts) | ||
829 | { | ||
830 | apr_status_t s = apr_thread_mutex_trylock(gLogMutexp); | ||
831 | if (!APR_STATUS_IS_EBUSY(s)) | ||
832 | { | ||
833 | mLocked = true; | ||
834 | mOK = true; | ||
835 | return; | ||
836 | } | ||
837 | |||
838 | ms_sleep(1); | ||
839 | //apr_thread_yield(); | ||
840 | // Just yielding won't necessarily work, I had problems with | ||
841 | // this on Linux - doug 12/02/04 | ||
842 | } | ||
843 | |||
844 | // We're hosed, we can't get the mutex. Blah. | ||
845 | std::cerr << "LogLock::LogLock: failed to get mutex for log" | ||
846 | << std::endl; | ||
847 | } | ||
848 | |||
849 | LogLock::~LogLock() | ||
850 | { | ||
851 | if (mLocked) | ||
852 | { | ||
853 | apr_thread_mutex_unlock(gLogMutexp); | ||
854 | } | ||
855 | } | ||
856 | } | ||
857 | |||
858 | namespace LLError | ||
859 | { | ||
860 | bool Log::shouldLog(CallSite& site) | ||
861 | { | ||
862 | LogLock lock; | ||
863 | if (!lock.ok()) | ||
864 | { | ||
865 | return false; | ||
866 | } | ||
867 | |||
868 | Globals& g = Globals::get(); | ||
869 | Settings& s = Settings::get(); | ||
870 | |||
871 | s.shouldLogCallCounter += 1; | ||
872 | |||
873 | std::string class_name = className(site.mClassInfo); | ||
874 | std::string function_name = functionName(site.mFunction); | ||
875 | if (site.mClassInfo != typeid(NoClassInfo)) | ||
876 | { | ||
877 | function_name = class_name + "::" + function_name; | ||
878 | } | ||
879 | |||
880 | ELevel compareLevel = s.defaultLevel; | ||
881 | |||
882 | checkLevelMap(s.functionLevelMap, function_name, compareLevel) | ||
883 | || checkLevelMap(s.classLevelMap, class_name, compareLevel) | ||
884 | || checkLevelMap(s.fileLevelMap, abbreviateFile(site.mFile), compareLevel); | ||
885 | |||
886 | site.mCached = true; | ||
887 | g.addCallSite(site); | ||
888 | return site.mShouldLog = site.mLevel >= compareLevel; | ||
889 | } | ||
890 | |||
891 | |||
892 | std::ostringstream* Log::out() | ||
893 | { | ||
894 | LogLock lock; | ||
895 | if (lock.ok()) | ||
896 | { | ||
897 | Globals& g = Globals::get(); | ||
898 | |||
899 | if (!g.messageStreamInUse) | ||
900 | { | ||
901 | g.messageStreamInUse = true; | ||
902 | return &g.messageStream; | ||
903 | } | ||
904 | } | ||
905 | |||
906 | return new std::ostringstream; | ||
907 | } | ||
908 | |||
909 | void Log::flush(std::ostringstream* out, const CallSite& site) | ||
910 | { | ||
911 | LogLock lock; | ||
912 | if (!lock.ok()) | ||
913 | { | ||
914 | return; | ||
915 | } | ||
916 | |||
917 | Globals& g = Globals::get(); | ||
918 | Settings& s = Settings::get(); | ||
919 | |||
920 | std::string message = out->str(); | ||
921 | if (out == &g.messageStream) | ||
922 | { | ||
923 | g.messageStream.clear(); | ||
924 | g.messageStream.str(""); | ||
925 | g.messageStreamInUse = false; | ||
926 | } | ||
927 | else | ||
928 | { | ||
929 | delete out; | ||
930 | } | ||
931 | |||
932 | if (site.mLevel == LEVEL_ERROR) | ||
933 | { | ||
934 | std::ostringstream fatalMessage; | ||
935 | fatalMessage << abbreviateFile(site.mFile) | ||
936 | << "(" << site.mLine << ") : error"; | ||
937 | |||
938 | writeToRecorders(site.mLevel, fatalMessage.str()); | ||
939 | } | ||
940 | |||
941 | |||
942 | std::ostringstream prefix; | ||
943 | |||
944 | switch (site.mLevel) | ||
945 | { | ||
946 | case LEVEL_DEBUG: prefix << "DEBUG: "; break; | ||
947 | case LEVEL_INFO: prefix << "INFO: "; break; | ||
948 | case LEVEL_WARN: prefix << "WARNING: "; break; | ||
949 | case LEVEL_ERROR: prefix << "ERROR: "; break; | ||
950 | default: prefix << "XXX: "; break; | ||
951 | }; | ||
952 | |||
953 | if (s.printLocation) | ||
954 | { | ||
955 | prefix << abbreviateFile(site.mFile) | ||
956 | << "(" << site.mLine << ") : "; | ||
957 | } | ||
958 | |||
959 | if (message.find(functionName(site.mFunction)) == std::string::npos) | ||
960 | { | ||
961 | #if LL_WINDOWS | ||
962 | // DevStudio: __FUNCTION__ already includes the full class name | ||
963 | #else | ||
964 | if (site.mClassInfo != typeid(NoClassInfo)) | ||
965 | { | ||
966 | prefix << className(site.mClassInfo) << "::"; | ||
967 | } | ||
968 | #endif | ||
969 | prefix << site.mFunction << ": "; | ||
970 | } | ||
971 | |||
972 | prefix << message; | ||
973 | message = prefix.str(); | ||
974 | |||
975 | writeToRecorders(site.mLevel, message); | ||
976 | |||
977 | if (site.mLevel == LEVEL_ERROR && s.crashFunction) | ||
978 | { | ||
979 | s.crashFunction(message); | ||
980 | } | ||
981 | } | ||
982 | } | ||
983 | |||
984 | |||
985 | |||
986 | |||
987 | namespace LLError | ||
988 | { | ||
989 | Settings* saveAndResetSettings() | ||
990 | { | ||
991 | return Settings::saveAndReset(); | ||
992 | } | ||
993 | |||
994 | void restoreSettings(Settings* s) | ||
995 | { | ||
996 | return Settings::restore(s); | ||
997 | } | ||
998 | |||
999 | std::string removePrefix(std::string& s, const std::string& p) | ||
1000 | { | ||
1001 | std::string::size_type where = s.find(p); | ||
1002 | if (where == std::string::npos) | ||
1003 | { | ||
1004 | return s; | ||
1005 | } | ||
1006 | |||
1007 | return std::string(s, where + p.size()); | ||
1008 | } | ||
1009 | |||
1010 | void replaceChar(std::string& s, char old, char replacement) | ||
1011 | { | ||
1012 | std::string::size_type i = 0; | ||
1013 | std::string::size_type len = s.length(); | ||
1014 | for ( ; i < len; i++ ) | ||
1015 | { | ||
1016 | if (s[i] == old) | ||
1017 | { | ||
1018 | s[i] = replacement; | ||
1019 | } | ||
1020 | } | ||
1021 | } | ||
1022 | |||
1023 | std::string abbreviateFile(const std::string& filePath) | ||
1024 | { | ||
1025 | std::string f = filePath; | ||
1026 | #if LL_WINDOWS | ||
1027 | replaceChar(f, '\\', '/'); | ||
1028 | #endif | ||
1029 | static std::string indra_prefix = "indra/"; | ||
1030 | f = removePrefix(f, indra_prefix); | ||
1031 | |||
1032 | #if LL_DARWIN | ||
1033 | static std::string newview_prefix = "newview/../"; | ||
1034 | f = removePrefix(f, newview_prefix); | ||
1035 | #endif | ||
1036 | |||
1037 | return f; | ||
1038 | } | ||
1039 | |||
1040 | int shouldLogCallCount() | ||
1041 | { | ||
1042 | Settings& s = Settings::get(); | ||
1043 | return s.shouldLogCallCounter; | ||
1044 | } | ||
1045 | |||
1046 | void crashAndLoop(const std::string& message) | ||
1047 | { | ||
1048 | // Now, we go kaboom! | ||
1049 | int* crash = NULL; | ||
1050 | |||
1051 | *crash = 0; | ||
1052 | |||
1053 | while(true) | ||
1054 | { | ||
1055 | // Loop forever, in case the crash didn't work? | ||
1056 | } | ||
1057 | } | ||
1058 | |||
1059 | std::string utcTime() | ||
1060 | { | ||
1061 | time_t now = time(NULL); | ||
1062 | const size_t BUF_SIZE = 64; | ||
1063 | char time_str[BUF_SIZE]; /* Flawfinder: ignore */ | ||
1064 | |||
1065 | int chars = strftime(time_str, BUF_SIZE, | ||
1066 | "%Y-%m-%dT%H:%M:%SZ", | ||
1067 | gmtime(&now)); | ||
1068 | |||
1069 | return chars ? time_str : "time error"; | ||
1070 | } | ||
1071 | } | ||
1072 | |||