diff options
Diffstat (limited to 'Runtime/Utilities/LogAssert.cpp')
-rw-r--r-- | Runtime/Utilities/LogAssert.cpp | 1294 |
1 files changed, 1294 insertions, 0 deletions
diff --git a/Runtime/Utilities/LogAssert.cpp b/Runtime/Utilities/LogAssert.cpp new file mode 100644 index 0000000..6a863b3 --- /dev/null +++ b/Runtime/Utilities/LogAssert.cpp @@ -0,0 +1,1294 @@ +#include "UnityPrefix.h" +#include "LogAssert.h" +#include <stdarg.h> +#include "PathNameUtility.h" +#include <sys/stat.h> +#include <list> +#include "Configuration/UnityConfigure.h" +#include "Runtime/Threads/AtomicOps.h" +#if !UNITY_EXTERNAL_TOOL && !UNITY_PLUGIN +#include "Runtime/Threads/Thread.h" +#include "Runtime/Threads/Mutex.h" +#include "Runtime/Threads/ThreadSpecificValue.h" +#include "Runtime/Misc/PlayerSettings.h" +#endif +#include "Runtime/Profiler/Profiler.h" + +#if UNITY_OSX || UNITY_IPHONE || UNITY_LINUX || UNITY_TIZEN +#include <syslog.h> +#include <sys/fcntl.h> +#include <unistd.h> +#include "Runtime/Utilities/File.h" +#endif +#if UNITY_NACL +#include "PlatformDependent/PepperPlugin/UnityInstance.h" +#endif + +#if UNITY_BB10 +#include <fcntl.h> +#include <stdio.h> +#include <unistd.h> +#endif +#if UNITY_WIN +#if !UNITY_WP8 +#include <ShlObj.h> +#endif +#include "PlatformDependent/Win/PathUnicodeConversion.h" +#include "FileUtilities.h" +#include "PlatformDependent/Win/WinUtils.h" +#include "io.h" +#endif + +#if UNITY_TIZEN +#include <osp/FBaseLog.h> +#endif +#if UNITY_EDITOR +#include "File.h" +#include "Editor/Src/Utility/Analytics.h" +#endif + +#if WEBPLUG +#include "ErrorExit.h" +#endif + +#include "Runtime/Scripting/ScriptingUtility.h" + +#if UNITY_WII +#include "Platformdependent/wii/WiiDbgUtils.h" +#endif +#if UNITY_ANDROID +#include <android/log.h> + +int CsToAndroid[LogType_NumLevels] = +{ + ANDROID_LOG_ERROR, // LogType_Error = 0, + ANDROID_LOG_ERROR, // LogType_Assert = 1, + ANDROID_LOG_WARN, // LogType_Warning = 2, + ANDROID_LOG_INFO, // LogType_Log = 3, + ANDROID_LOG_ERROR, // LogType_Exception = 4, + ANDROID_LOG_DEBUG // LogType_Debug = 5, +}; +#endif + +using namespace std; + +#if UNITY_WP8 + #include "PlatformDependent\MetroPlayer\MetroUtils.h" +#endif + +#if WEBPLUG && !UNITY_WIN +#define CAP_LOG_OUTPUT_SIZE 1 +#else +#define CAP_LOG_OUTPUT_SIZE 0 +#endif + +#if UNITY_WIN && DEBUGMODE && !UNITY_WP8 + extern "C" WINBASEAPI BOOL WINAPI IsDebuggerPresent( VOID ); + #define LOG_TO_WINDOWS_DEBUGGER \ + char buf[4096]; buf[4095]=0; \ + vsnprintf( buf, sizeof(buf)-1, log, list ); \ + OutputDebugStringA( buf ) +#elif UNITY_WP8 && UNITY_DEVELOPER_BUILD + #define LOG_TO_WINDOWS_DEBUGGER \ + char buf[4096]; buf[4095]=0; \ + vsnprintf (buf, sizeof(buf) - 1, log, list); \ + auto str = ConvertUtf8ToString (buf); \ + s_WinRTBridge->WP8Utility->OutputLogMessage (str) +#else + #define LOG_TO_WINDOWS_DEBUGGER +#endif + +#if UNITY_FLASH +extern "C" void Ext_Flash_LogCallstack(); +#endif + +static LogEntryHandler gCurrentLogEntryHandler = NULL; +static std::list<LogEntryHandler> *gCleanLogEntryHandlers = NULL; + +void ReleaseLogHandlers() +{ + if (gCleanLogEntryHandlers != NULL) + { + delete gCleanLogEntryHandlers; + gCleanLogEntryHandlers = NULL; + } +} + +void SetLogEntryHandler(LogEntryHandler newHandler) +{ + gCurrentLogEntryHandler = newHandler; +} + +void AddCleanLogEntryHandler(LogEntryHandler newHandler) +{ + if (gCleanLogEntryHandlers == NULL) + gCleanLogEntryHandlers = new std::list<LogEntryHandler>(); + + gCleanLogEntryHandlers->push_back(newHandler); +} + +FILE* gConsoleFile = NULL; +FILE* gReproductionLogFile = NULL; + +bool DefaultCleanLogHandlerv (LogType logType, const char* log, va_list list); + +void InitializeCleanedLogFile (FILE* file) +{ + Assert(gReproductionLogFile == NULL); + Assert(gCleanLogEntryHandlers == NULL || count(gCleanLogEntryHandlers->begin(), gCleanLogEntryHandlers->end(), &DefaultCleanLogHandlerv) == 0); + + gReproductionLogFile = file; + + AddCleanLogEntryHandler(&DefaultCleanLogHandlerv); +} + +static StaticString gConsolePath; +#if CAP_LOG_OUTPUT_SIZE +static int gConsoleSizeCheck = 0; +#endif +#if !UNITY_EXTERNAL_TOOL +static UNITY_TLS_VALUE(int) gRecursionLock; +#endif + +enum { kMaxLogSize = 2000000 }; + + + +#if UNITY_OSX || UNITY_IPHONE || UNITY_LINUX || UNITY_BB10 || UNITY_TIZEN +fpos_t gStdOutPosition; +int gStdoutFd; +void ResetStdout() +{ + fflush(stdout); + dup2(gStdoutFd, fileno(stdout)); + close(gStdoutFd); + clearerr(stdout); + fsetpos(stdout, &gStdOutPosition); +} +#endif + +#if UNITY_XENON || UNITY_PS3 +void LogOutputToSpecificFile (const char* path) +{ + if (path == NULL) + return; + + gConsolePath = path; + gConsoleFile = fopen(path, "w"); + if(gConsoleFile) + fclose(gConsoleFile); + +} +#elif !UNITY_PS3 && !UNITY_ANDROID && !UNITY_PEPPER && !UNITY_FLASH + +void LogOutputToSpecificFile (const char* path) +{ +#if UNITY_OSX || UNITY_IPHONE || UNITY_LINUX || UNITY_BB10 || UNITY_TIZEN + + // Save the stdout position for later + fgetpos(stdout, &gStdOutPosition); + gStdoutFd = dup(fileno(stdout)); + + if (path == NULL || strlen(path) == 0) + { + gConsoleFile = stdout; + } + else + { + gConsoleFile = fopen(path, "w"); + int fd = open(path, O_WRONLY, 0); + dup2(fd, 1); + dup2(fd, 2); + close(fd); + } + +#elif UNITY_WII + + // TODO: see if logging to the host machine (windows) works + if (path == NULL) + return; + + gConsolePath = path; + gConsoleFile = fopen(path, "w"); + +#else + + // On windows just default to editor.log instead + if (path == NULL) + return; + + gConsolePath = path; + gConsoleFile = fopen(path, "w"); +#endif +} +#endif + +#if SUPPORT_ENVIRONMENT_VARIABLES +std::string GetStringFromEnv(const char *envName) +{ + const char* env = getenv( envName ); + std::string result; + if( env != NULL && env[0] != 0 ) + result.append(env); + return result; +} +std::string GetCustomLogFile() +{ + return GetStringFromEnv("UNITY_LOG_FILE"); +} +std::string GetCleanedLogFile() +{ + return GetStringFromEnv("UNITY_CLEANED_LOG_FILE"); +} +#endif + +#if UNITY_WIN + +string SetLogFilePath(string const& path) +{ + Assert(gConsolePath.empty()); + + gConsolePath = path; + + #if !UNITY_EDITOR && !UNITY_EXTERNAL_TOOL && !UNITY_WINRT + + if (!gConsolePath.empty()) + { + string const customLogFile = GetCustomLogFile(); + + if (!customLogFile.empty()) + { + gConsolePath = customLogFile.c_str(); + } + } + + #endif + + return gConsolePath.c_str(); +} + +namespace +{ + int gStdOutFd = -1; + int gStdErrFd = -1; + FILE* gStdOutFile = NULL; + FILE* gStdErrFile = NULL; + + void CloseConsoleWin() + { + gConsoleFile = NULL; + + if (NULL != gStdOutFile) + { + int const result = fclose(gStdOutFile); + //Assert(0 == result); + + gStdOutFile = NULL; + } + + if (NULL != gStdErrFile) + { + int const result = fclose(gStdErrFile); + //Assert(0 == result); + + gStdErrFile = NULL; + } + + if (-1 != gStdOutFd) + { + int const result = _dup2(gStdOutFd, 1); + //Assert(0 == result); + + gStdOutFd = -1; + } + + if (-1 != gStdErrFd) + { + int const result = _dup2(gStdErrFd, 2); + //Assert(0 == result); + + gStdErrFd = -1; + } + gConsolePath.clear(); + } + + void OpenConsoleWin() + { + // don't assert in this function because it might cause stack overflow + std::wstring widePath; + + //Assert(NULL == gConsoleFile); + + // check for no log file + + if (gConsolePath.empty()) + { + gConsoleFile = stdout; + return; + } + + // duplicate stdout and stderr file descriptors so they can be restored later + + gStdOutFd = _dup(1); + //Assert(-1 != gStdOutFd); + + if (-1 == gStdOutFd) + { + goto error; + } + + gStdErrFd = _dup(2); + //Assert(-1 != gStdErrFd); + + if (-1 == gStdErrFd) + { + goto error; + } + + // reassign stdout and stderr file pointers + + ConvertUnityPathName(gConsolePath, widePath); + + gStdOutFile = _wfreopen(widePath.c_str(), L"a", stdout); + //Assert(NULL != gStdOutFile); + + if (NULL == gStdOutFile) + { + goto error; + } + + gStdErrFile = _wfreopen(widePath.c_str(), L"a", stderr); + //Assert(NULL != gStdErrFile); + + if (NULL == gStdErrFile) + { + goto error; + } + + // redirect stderr to stdout + + int const error = _dup2(1, 2); + //Assert(0 == error); + + if (0 != error) + { + goto error; + } + + // disable stdout and stderr buffering + + setbuf(stdout, NULL); + setbuf(stderr, NULL); + + // done + + gConsoleFile = stdout; + return; + + // failed + + error: + + CloseConsoleWin(); + + gConsoleFile = stdout; + return; + } +} + +#endif + +#if (UNITY_OSX || UNITY_LINUX) && !UNITY_EXTERNAL_TOOL +// Get the logfile path, relative to the user's home directory, using a system-specific subpath +static std::string GetHomedirLogfile (const std::string &relativePath) +{ + string folder = getenv ("HOME"); + if (folder.empty ()) + return ""; + + // Create log file and parent folders + folder = AppendPathName( folder, relativePath); + CreateDirectoryRecursive (folder); + + #if UNITY_EDITOR + std::string result = AppendPathName( folder, "Editor.log" ); + // move any existing log file into Editor-prev.log + if( IsFileCreated(result) ) + MoveReplaceFile( result, AppendPathName(folder,"Editor-prev.log" ) ); + #else + std::string result = GetCustomLogFile(); + if (result.empty()) + result = AppendPathName( folder, "Player.log" ); + #endif + + return result; +} + +// Open the console path and redirect stdout/stderr if appropriate +static void OpenConsoleFile () +{ + #if UNITY_EDITOR + gConsoleFile = fopen(gConsolePath.c_str(), "w"); + #else + gConsoleFile = fopen(gConsolePath.c_str(), "w+"); + #endif + + #if !WEBPLUG + // Save the stdout position for later + fgetpos(stdout, &gStdOutPosition); + gStdoutFd = dup(fileno(stdout)); + + if (gConsoleFile) + { + int fd = fileno (gConsoleFile); + + if (dup2 (fd, fileno (stdout)) < 0) + fprintf (stderr, "Failed to redirect stdout to the console file %s.\n", gConsolePath.c_str ()); + if (dup2 (fd, fileno (stderr)) < 0) + fprintf (stderr, "Failed to redirect stderr to the console file %s.\n", gConsolePath.c_str ()); + } + #endif +} +#endif + +FILE* OpenConsole () +{ + if (gConsoleFile != NULL) + return gConsoleFile; + + #define PLATFORM_ALWAYS_USES_STDOUT_FOR_LOG (UNITY_XENON || UNITY_IPHONE || UNITY_ANDROID || UNITY_PEPPER || UNITY_FLASH || UNITY_WII || UNITY_PS3 || UNITY_EXTERNAL_TOOL || UNITY_WEBGL || UNITY_BB10 || UNITY_TIZEN || ENABLE_GFXDEVICE_REMOTE_PROCESS_WORKER ) + + #if GAMERELEASE && !UNITY_EXTERNAL_TOOL && !WEBPLUG && !UNITY_PLUGIN && !PLATFORM_ALWAYS_USES_STDOUT_FOR_LOG + if (GetPlayerSettingsPtr() == NULL) + return stdout; + + if (!GetPlayerSettings().GetUsePlayerLog()) + { + gConsoleFile = stdout; + return gConsoleFile; + } + #endif + + #if PLATFORM_ALWAYS_USES_STDOUT_FOR_LOG + + #if UNITY_NACL + // in nacl, we can't write to a custom log location. + // so if we need a clean log, use stdout for that, and use 0 for the + // normal, non-clean log. + if (GetUnityInstance().GetCleanLog()) + return 0; + #endif + + gConsoleFile = stdout; + + #elif UNITY_OSX + gConsolePath = GetHomedirLogfile ("Library/Logs/Unity"); + if (!gConsolePath.empty()) + OpenConsoleFile (); + + #elif UNITY_LINUX + gConsolePath = GetHomedirLogfile (".config/unity3d"); + if (!gConsolePath.empty()) + OpenConsoleFile (); + + #elif UNITY_WIN + + OpenConsoleWin(); + + #elif UNITY_PS3 + gConsoleFile = stdout; + // When running from a read only file system, stdout is a valid file pointer, but it crashes later + // if trying to write into it. So we check if valid _fileno exists for it. + if( gConsoleFile && fileno(gConsoleFile) < 0 ) + gConsoleFile = NULL; + + #else + + #error "Unknown platform" + + #endif + + return gConsoleFile; +} + +#if UNITY_WIN +void CloseConsoleFile() +{ + CloseConsoleWin(); +} +#endif + +#if UNITY_EDITOR +string GetEditorConsoleLogPath () +{ + #if UNITY_OSX + + string home = getenv ("HOME"); + return AppendPathName (home, "Library/Logs/Unity/Editor.log"); + + #elif UNITY_WIN + + return gConsolePath.c_str(); + + #elif UNITY_LINUX + + string home = getenv ("HOME"); + return AppendPathName (home, ".config/Unity/Editor/Editor.log"); + + #else + #error "Unknown platform" + #endif +} + +string GetMonoDevelopLogPath () +{ + #if UNITY_OSX + + string result = getenv ("HOME"); + return AppendPathName( result, "Library/Logs/MonoDevelop/MonoDevelop.log"); + + #elif UNITY_WIN + + wchar_t widePath[MAX_PATH]; + if( SUCCEEDED(SHGetFolderPathW( NULL, CSIDL_APPDATA, NULL, SHGFP_TYPE_CURRENT, widePath )) ) + { + std::string folder; + ConvertWindowsPathName( widePath, folder ); + folder = AppendPathName( folder, "MonoDevelop-Unity" ); + return AppendPathName( folder, "log.txt" ); + } + + #elif UNITY_LINUX + + string result = getenv ("HOME"); + return AppendPathName( result, ".config/MonoDevelop-Unity/log.txt"); + + #else + #error "Unknown platform" + #endif + + return ""; +} + +#if UNITY_OSX +string GetPlayerConsoleLogPath () +{ + string home = getenv ("HOME"); + return AppendPathName (home, "Library/Logs/Unity/Player.log"); +} +#endif + +#if UNITY_LINUX +string GetPlayerConsoleLogPath () +{ + string home = getenv ("HOME"); + return AppendPathName (home, ".config/unity3d/Editor/Player.log"); +} +#endif +#endif + +string GetConsoleLogPath () +{ + return gConsolePath.c_str(); +} + +#if (UNITY_XENON || UNITY_PS3) && !MASTER_BUILD +static Mutex s_mutex; +#endif + +void printf_consolev (LogType logType, const char* log, va_list alist) +{ + va_list list; + va_copy (list, alist); + + if (gCurrentLogEntryHandler && !gCurrentLogEntryHandler (logType, log, list)) + return; + +#if UNITY_FLASH + char buffer[1024 * 10]; + vsnprintf (buffer, 1024 * 10, log, list); + Ext_Trace(buffer); + + va_end (list); + return; +#endif + +#if UNITY_ANDROID + if (gReproductionLogFile == NULL) // gReproductionLogFile / 'cleanedLogFile' should remove all other logging + { + if (ENABLE_PROFILER /* == development player */ || logType < LogType_Debug) + __android_log_vprint(CsToAndroid[logType], "Unity", log, list); + } + + va_end (list); + return; +#endif + +#if UNITY_TIZEN + if (gReproductionLogFile == NULL) + { + static char buffer[1024 * 10]; + memset(buffer, 0, 1024*10); + vsnprintf (buffer, 1024 * 10, log, list); + AppLogTagInternal("Unity", "", 0, buffer); + } +#endif + +#if UNITY_XENON +#if !MASTER_BUILD + Mutex::AutoLock lock(s_mutex); + + char buffer[1024 * 8] = { 0 }; + vsnprintf(buffer, 1024 * 8, log, list); + + gConsoleFile = fopen(gConsolePath.c_str(), "a"); + if(gConsoleFile) + { + fprintf(gConsoleFile, buffer); + fflush(gConsoleFile); + fclose(gConsoleFile); + } + + OutputDebugString(buffer); + +#endif + va_end (list); + return; +#endif + +#if UNITY_PS3 +#if !MASTER_BUILD + Mutex::AutoLock lock(s_mutex); + + gConsoleFile = fopen(gConsolePath.c_str(), "a"); + if(gConsoleFile) + { + vfprintf (gConsoleFile, log, list); + fflush( gConsoleFile ); + fclose( gConsoleFile ); + vfprintf (stdout, log, list); + } + else + { + vfprintf(stdout, log, list); + fflush(stdout); + } + +#endif + va_end (list); + return; +#endif + +#if UNITY_WII + #if !MASTER_BUILD + vfprintf (stdout, log, list); + fflush (stdout); + va_end (list); + #endif + return; +#endif + + if (gConsoleFile == NULL) + { + if (OpenConsole() == NULL) { + va_end (list); + return; + } + } + + if (gConsoleFile) + { + vfprintf (gConsoleFile, log, list); + fflush( gConsoleFile ); + + // Clamp the size of the file to 100kb with a rolling buffer. + // copy last 50kb to beginning of file. + #if CAP_LOG_OUTPUT_SIZE + gConsoleSizeCheck++; + if (gConsoleSizeCheck > 20) + { + gConsoleSizeCheck = 0; + struct stat statbuffer; + if( ::stat(gConsolePath.c_str(), &statbuffer) == 0 && statbuffer.st_size > kMaxLogSize) + { + FILE* file = fopen(gConsolePath.c_str(), "r"); + if (file) + { + fseek(file, statbuffer.st_size - kMaxLogSize / 2, SEEK_SET); + UInt8* buffer = new UInt8[kMaxLogSize / 2]; + if (fread(buffer, 1, kMaxLogSize / 2, file) == kMaxLogSize / 2) + { + fclose(file); + fclose(gConsoleFile); + gConsoleFile = fopen(gConsolePath.c_str(), "w"); + fwrite(buffer, 1, kMaxLogSize / 2, gConsoleFile); + } + else + { + fclose(file); + } + delete[] buffer; + } + } + } + #endif + } + else + { +#ifndef DISABLE_TTY + vfprintf (stdout, log, list); + fflush( stdout ); +#endif + } + + LOG_TO_WINDOWS_DEBUGGER; + va_end (list); +} + +extern "C" void printf_console_log(const char* log, va_list list) +{ + printf_consolev(LogType_Log, log, list); +} + +extern "C" void printf_console (const char* log, ...) +{ + va_list vl; + va_start(vl, log); + printf_consolev(LogType_Debug, log, vl); + va_end(vl); +} + +static void InternalLogConsole (const char* log, ...) +{ + va_list vl; + va_start(vl, log); + printf_consolev(LogType_Log, log, vl); + va_end(vl); +} + +static void InternalWarningConsole (const char* log, ...) +{ + va_list vl; + va_start(vl, log); + printf_consolev(LogType_Warning, log, vl); + va_end(vl); +} + +static void InternalAssertConsole (const char* log, ...) +{ + va_list vl; + va_start(vl, log); + printf_consolev(LogType_Assert, log, vl); + va_end(vl); +} + +static void InternalErrorConsole (const char* log, ...) +{ + va_list vl; + va_start(vl, log); + printf_consolev(LogType_Error, log, vl); + va_end(vl); +} + +static void InternalIgnoreConsole (const char* log, ...) +{ +} + +static LogToConsoleImpl* gLogToConsoleFunc = NULL; +static LogCallback* gLogCallbackFunc = NULL; +static bool gLogCallbackFuncThreadSafe = false; +static PreprocessCondition* gPreprocessor = NULL; +static RemoveLogFunction* gRemoveLog = NULL; +static RemoveLogFunction* gShowLogWithMode = NULL; +extern "C" void __msl_assertion_failed(char const *condition, char const *filename, char const *funcname, int lineno); + +/* +extern "C" +{ +void __eprintf(const char* log, ...) +{ +printf_console (log, va_list(&log + 1)); +} + +void malloc_printf (const char* log, ...) +{ + printf_console (log, va_list(&log + 1)); +} +} +*/ +void RegisterLogToConsole (LogToConsoleImpl* func) +{ + gLogToConsoleFunc = func; +} + +void RegisterLogCallback (LogCallback* callback, bool threadsafe) +{ + gLogCallbackFunc = callback; + gLogCallbackFuncThreadSafe = threadsafe; +} + +void RegisterLogPreprocessor (PreprocessCondition* func) +{ + gPreprocessor = func; +} + +void RegisterRemoveImportErrorFromConsole (RemoveLogFunction* func) +{ + gRemoveLog = func; +} + +void RegisterShowErrorWithMode (RemoveLogFunction* func) +{ + gShowLogWithMode = func; +} + + +extern "C" void __msl_assertion_failed(char const *condition, char const *filename, char const *funcname, int lineno) +{ + DebugStringToFile (condition, 0, filename, lineno, kAssert, 0); +} + +inline bool ContainsNewLine (const char* c) +{ + while (*c != '\0') + { + if (*c == '\n') + return true; + c++; + } + return false; +} + + +static void CompilerErrorAnalytics (int mode, const char *condition) +{ +#if UNITY_EDITOR + if ( mode & kScriptCompileError ) + { + std::string message = condition; + + // The compiler error message is formatted like this: + // filename: error/warning number: description + int n1 = message.find(": "); + if ( n1 != string::npos ) + { + int n2 = message.find(": ", n1+2); + if ( n2 != string::npos ) + { + string error = message.substr(n1+2, n2-n1-2); + string description = message.substr(n2+2); + AnalyticsTrackEvent("Compiler", error, description, 1); + return; + } + } + AnalyticsTrackEvent("Compiler", "Unknown", message, 1); + } +#endif +} + +bool DefaultCleanLogHandlerv (LogType logType, const char* log, va_list alist) +{ + va_list list; + va_copy (list, alist); + +#if UNITY_ANDROID // On Android we don't use a separate clean log-file, but instead we clean up the actual logcat +#define LOG_PRINTF(x, ...) __android_log_vprint(ANDROID_LOG_INFO, "Unity", __VA_ARGS__) +#define LOG_FLUSH(x) +#else +#define LOG_PRINTF vfprintf +#define LOG_FLUSH fflush +#endif + +LOG_PRINTF (gReproductionLogFile, log, list); +LOG_FLUSH( gReproductionLogFile ); + +#undef LOG_PRINTF +#undef LOG_FLUSH + + va_end (list); + + return true; +} + +void CleanLogHandler(LogType logType, const char* log, ...) +{ + if (gCleanLogEntryHandlers != NULL) + { + for (std::list<LogEntryHandler>::iterator it = gCleanLogEntryHandlers->begin(); + it != gCleanLogEntryHandlers->end(); + it++) + { + va_list vl; + va_start(vl, log); + (**it)(logType, log, vl); + } + } +} + + +typedef void PrintConsole (const char* log, ...); + +// convert the log mode to the cs LogType enum +// LogType.Error = 0, LogType.Assert = 1, LogType.Warning = 2, LogType.Log = 3, LogType.Exception = 4 +inline LogType LogModeToLogType(int mode) +{ + LogType logType; + if ( mode & (kScriptingException) ) logType = LogType_Exception; + else if ( mode & (kError | kFatal | kScriptingError | kScriptCompileError | kStickyError | kAssetImportError | kGraphCompileError) ) logType = LogType_Error; + else if ( mode & kAssert ) logType = LogType_Assert; + else if ( mode & (kScriptingWarning | kScriptCompileWarning | kAssetImportWarning) ) logType = LogType_Warning; + else logType = LogType_Log; + + return logType; +} + +void DebugStringToFilePostprocessedStacktrace (const char* condition, const char* strippedStacktrace, const char* stacktrace, int errorNum, const char* file, int line, int mode, int objectInstanceID, int identifier) +{ + LogType logType = LogModeToLogType(mode); + + #if !UNITY_EXTERNAL_TOOL + int depth = gRecursionLock; + if (depth == 1) + return; + gRecursionLock = 1; + + if ( gLogCallbackFunc) + { +#if SUPPORT_THREADS + if (gLogCallbackFuncThreadSafe || Thread::CurrentThreadIsMainThread()) +#endif + gLogCallbackFunc (condition, strippedStacktrace, (int)logType); + } + + #endif +#if UNITY_WII + if (mode & kFatal) + { + wii::DbgFatalError ("%s\n", condition); + } + else if (mode & (kAssert | kError)) + { + wii::DbgOutput ("%s\n", condition); + } +#elif UNITY_XENON && MASTER_BUILD + return; +#endif + CompilerErrorAnalytics (mode, condition); + + string conditionAndStacktrace = condition; + if ( stacktrace ) + { + conditionAndStacktrace += "\n"; + conditionAndStacktrace += stacktrace; + } + + string conditionAndStrippedStacktrace = condition; + if ( stacktrace ) + { + conditionAndStrippedStacktrace += "\n"; + conditionAndStrippedStacktrace += strippedStacktrace; + } + + if (errorNum) + CleanLogHandler (logType, "%s (Error: %d)\n\n", condition, errorNum); + else + CleanLogHandler (logType, "%s\n\n", condition); + + + PrintConsole* printConsole = InternalIgnoreConsole; + // Logs + if (mode & (kLog | kScriptingLog)) + printConsole = InternalLogConsole; + else if (mode & (kScriptingWarning | kAssetImportWarning)) + printConsole = InternalWarningConsole; + else if (mode & kAssert) + printConsole = InternalAssertConsole; + // Real errors ---- YOU WANT TO BREAKPOINT THIS LINE! + else + printConsole = InternalErrorConsole; + + + if (errorNum) + { + if (ContainsNewLine (conditionAndStacktrace.c_str())) + printConsole ("%s \n(Error: %li Filename: %s Line: %li)\n\n", conditionAndStacktrace.c_str(), errorNum, file, line); + else + printConsole ("%s (Error: %li Filename: %s Line: %li)\n", conditionAndStacktrace.c_str(), errorNum, file, line); + } + else + { + if (ContainsNewLine (conditionAndStacktrace.c_str())) + printConsole ("%s \n(Filename: %s Line: %li)\n\n", conditionAndStacktrace.c_str(), file, line); + else + printConsole ("%s (Filename: %s Line: %li)\n", conditionAndStacktrace.c_str(), file, line); + } + + if (gLogToConsoleFunc) + gLogToConsoleFunc (conditionAndStrippedStacktrace, errorNum, file, line, mode, objectInstanceID, identifier); + + #if WEBPLUG + #if DEBUGMODE && 0 + if (mode & kAssert) + { + DebugBreak(); + } + #endif + if (mode & kFatal) + { + ExitWithErrorCode(kErrorFatalException); + } + #elif UNITY_WINRT + if (mode & kAssert) + { + // Used to set a breakpoint + int s = 5; + } + if (mode & kFatal) + __debugbreak(); + #endif + + #if !UNITY_EXTERNAL_TOOL + gRecursionLock = 0; + #endif +} + +PROFILER_INFORMATION (gProfilerLogString, "LogStringToConsole", kProfilerOther); + +void DebugStringToFile (const char* condition, int errorNum, const char* file, int line, int mode, int objectInstanceID, int identifier) +{ + PROFILER_AUTO(gProfilerLogString, NULL); + SET_ALLOC_OWNER(NULL); +#if UNITY_ANDROID && i386 + printf_console("%s: %d at %s:%d (%d, %d, %d)\n", condition, errorNum, file, line, mode, objectInstanceID, identifier); +#endif + +#if UNITY_FLASH || UNITY_WEBGL + printf_console(condition); + if (!(mode&(kScriptingWarning | kScriptingLog ))){ +#if UNITY_FLASH + Ext_Flash_LogCallstack();//Let's only switch this on for internal debugging, it sucks having to go through callstacks because of a log. +#elif UNITY_WEBGL + // __asm __volatile__("console.log(new Error().stack)"); +#endif + }else{ + return; + } +#endif + + string stackTrace; + string strippedStackTrace; + string preprocessedFile; + if (gPreprocessor) + { + preprocessedFile = file; + string conditionStr = condition; + + gPreprocessor (conditionStr, strippedStackTrace, stackTrace, errorNum, preprocessedFile, &line, mode, objectInstanceID); + + file = preprocessedFile.c_str (); + } + + DebugStringToFilePostprocessedStacktrace (condition, strippedStackTrace.c_str(), stackTrace.c_str(), errorNum, file, line, mode, objectInstanceID, identifier); + +} + +void RemoveErrorWithIdentifierFromConsole (int identifier) +{ + if (gRemoveLog) + gRemoveLog (identifier); +} + +void ShowErrorWithMode (int identifier) +{ + if (gShowLogWithMode) + gShowLogWithMode (identifier); +} + + +void DebugTextLineByLine(const char* text, int maxLineLen) +{ + if(maxLineLen == -1) + maxLineLen = 1023; + + // on android we can output maximum 1023 chars (1024 including \0) +#if UNITY_ANDROID + if(maxLineLen > 1023) + maxLineLen = 1023; +#endif + + #define SKIP_CRLF(ptr) \ + do{ \ + while( (*ptr == '\r' || *ptr == '\n') && *ptr != 0 ) \ + ++ptr; \ + } while(0) + + #define SKIP_UNTIL_CRLF(ptr) \ + do{ \ + while( *ptr != '\r' && *ptr != '\n' && *ptr != 0 ) \ + ++ptr; \ + } while(0) + + + const char* lineStart = text; + SKIP_CRLF(lineStart); + + std::string out; + while(*lineStart != 0) + { + const char* lineEnd = lineStart; + SKIP_UNTIL_CRLF(lineEnd); + + if(lineEnd - lineStart > maxLineLen) + lineEnd = lineStart + maxLineLen; + + bool needSkipCRLF = *lineEnd == '\r' || *lineEnd == '\n'; + + out.assign(lineStart, lineEnd-lineStart); + #if UNITY_ANDROID + __android_log_print( ANDROID_LOG_DEBUG, "Unity", "%s", out.c_str()); + #else + printf_console("%s\n", out.c_str()); + #endif + + lineStart = lineEnd; + if(needSkipCRLF) + SKIP_CRLF(lineStart); + } + + #undef SKIP_UNTIL_CRLF + #undef SKIP_CRLF +} + + +#if UNITY_IPHONE || UNITY_ANDROID + +#if UNITY_IPHONE +#include <execinfo.h> +#elif UNITY_ANDROID +#include "PlatformDependent/AndroidPlayer/utils/backtrace_impl.h" +#endif + +void DumpCallstackConsole( const char* prefix, const char* file, int line ) +{ + const size_t kMaxDepth = 100; + + size_t stackDepth; + void* stackAddr[kMaxDepth]; + char** stackSymbol; + + stackDepth = backtrace(stackAddr, kMaxDepth); + stackSymbol = backtrace_symbols(stackAddr, stackDepth); + + printf_console("%s%s:%d\n", prefix, file, line); + + // TODO: demangle? use unwind to get more info? + // start from 1 to bypass self + for( unsigned stackI = 1 ; stackI < stackDepth ; ++stackI ) + { + #if UNITY_IPHONE + printf_console(" #%02d %s\n", stackI-1, stackSymbol[stackI]); + #elif UNITY_ANDROID // just for now + __android_log_print( ANDROID_LOG_DEBUG, "DEBUG", " #%02d %s\n", stackI-1, stackSymbol[stackI]); + #endif + } + + ::free(stackSymbol); +} + +#else + +void DumpCallstackConsole( const char* /*prefix*/, const char* /*file*/, int /*line*/ ) +{ +} + +#endif + +#if UNITY_OSX || UNITY_IPHONE +#include <sys/sysctl.h> +// taken from apple technical note QA1361 +bool EXPORT_COREMODULE IsDebuggerPresent() +{ + static bool debuggerPresent = false; + static bool inited = false; + + if(!inited) + { + kinfo_proc info; + ::memset(&info, 0x00, sizeof(info)); + + int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, ::getpid()}; + + size_t size = sizeof(info); + sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, NULL, 0); + + debuggerPresent = (info.kp_proc.p_flag & P_TRACED) != 0; + inited = true; + } + + return debuggerPresent; +} +#endif + +#if UNITY_LINUX || UNITY_TIZEN +#include <sys/ptrace.h> +#include <sys/prctl.h> +#include <sys/wait.h> +#include <errno.h> + +bool IsDebuggerPresent () +{ + int status = 0, + pid = -1, + result = 0; + +#ifdef PR_SET_PTRACER +// Guard ancient versions (&*^$%@*&#^%$ build agents) + // Enable tracing by self and children + if (0 != prctl (PR_SET_PTRACER, getpid (), 0, 0, 0)) + { + ErrorString (Format ("Unable to enable tracing: %s", strerror (errno))); + return false; + } +#endif + + pid = fork (); + if (0 > pid) + { + ErrorString ("Error creating child process"); + return false; + } + + if (0 == pid) + { + // Child + int parent = getppid(); + + // Attempt to attach to parent + if (ptrace (PTRACE_ATTACH, parent, NULL, NULL) == 0) + { + // Debugger is not attached; continue parent once it stops + waitpid (parent, NULL, WUNTRACED | WCONTINUED); + ptrace (PTRACE_DETACH, getppid (), NULL, NULL); + result = 0; + } + else + { + // Debugger is already tracing parent + result = 1; + } + exit(result); + } + + // Parent + waitpid (pid, &status, 0); + result = WEXITSTATUS (status); +#ifdef PR_SET_PTRACER + // Clear tracing + prctl (PR_SET_PTRACER, 0, 0, 0, 0); +#endif + + return (0 != result); +} +#endif + + |