2023-08-28 15:03:29 -04:00
|
|
|
package funkin.util.logging;
|
|
|
|
|
|
|
|
import openfl.Lib;
|
|
|
|
import openfl.events.UncaughtErrorEvent;
|
2023-11-21 13:31:02 -05:00
|
|
|
import flixel.util.FlxSignal.FlxTypedSignal;
|
2024-01-20 14:07:31 -05:00
|
|
|
import flixel.FlxG.FlxRenderMethod;
|
2023-08-28 15:03:29 -04:00
|
|
|
|
|
|
|
/**
|
|
|
|
* A custom crash handler that writes to a log file and displays a message box.
|
|
|
|
*/
|
|
|
|
@:nullSafety
|
|
|
|
class CrashHandler
|
|
|
|
{
|
2023-11-21 01:37:49 -05:00
|
|
|
public static final LOG_FOLDER = 'logs';
|
2023-08-28 15:03:29 -04:00
|
|
|
|
2023-11-21 13:31:02 -05:00
|
|
|
/**
|
|
|
|
* Called before exiting the game when a standard error occurs, like a thrown exception.
|
|
|
|
* @param message The error message.
|
|
|
|
*/
|
|
|
|
public static var errorSignal(default, null):FlxTypedSignal<String->Void> = new FlxTypedSignal<String->Void>();
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Called before exiting the game when a critical error occurs, like a stack overflow or null object reference.
|
|
|
|
* CAREFUL: The game may be in an unstable state when this is called.
|
|
|
|
* @param message The error message.
|
|
|
|
*/
|
|
|
|
public static var criticalErrorSignal(default, null):FlxTypedSignal<String->Void> = new FlxTypedSignal<String->Void>();
|
|
|
|
|
2023-08-28 15:03:29 -04:00
|
|
|
/**
|
|
|
|
* Initializes
|
|
|
|
*/
|
|
|
|
public static function initialize():Void
|
|
|
|
{
|
|
|
|
trace('[LOG] Enabling standard uncaught error handler...');
|
|
|
|
Lib.current.loaderInfo.uncaughtErrorEvents.addEventListener(UncaughtErrorEvent.UNCAUGHT_ERROR, onUncaughtError);
|
|
|
|
|
|
|
|
#if cpp
|
|
|
|
trace('[LOG] Enabling C++ critical error handler...');
|
|
|
|
untyped __global__.__hxcpp_set_critical_error_handler(onCriticalError);
|
|
|
|
#end
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Called when an uncaught error occurs.
|
|
|
|
* This handles most thrown errors, and is sufficient to handle everything alone on HTML5.
|
|
|
|
* @param error Information on the error that was thrown.
|
|
|
|
*/
|
|
|
|
static function onUncaughtError(error:UncaughtErrorEvent):Void
|
|
|
|
{
|
|
|
|
try
|
|
|
|
{
|
2023-11-21 13:31:02 -05:00
|
|
|
errorSignal.dispatch(generateErrorMessage(error));
|
|
|
|
|
2023-08-28 15:03:29 -04:00
|
|
|
#if sys
|
|
|
|
logError(error);
|
|
|
|
#end
|
|
|
|
|
|
|
|
displayError(error);
|
|
|
|
}
|
|
|
|
catch (e:Dynamic)
|
|
|
|
{
|
|
|
|
trace('Error while handling crash: ' + e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static function onCriticalError(message:String):Void
|
|
|
|
{
|
|
|
|
try
|
|
|
|
{
|
2023-11-21 13:31:02 -05:00
|
|
|
criticalErrorSignal.dispatch(message);
|
|
|
|
|
2023-08-28 15:03:29 -04:00
|
|
|
#if sys
|
|
|
|
logErrorMessage(message, true);
|
|
|
|
#end
|
|
|
|
|
|
|
|
displayErrorMessage(message);
|
|
|
|
}
|
|
|
|
catch (e:Dynamic)
|
|
|
|
{
|
|
|
|
trace('Error while handling crash: $e');
|
|
|
|
|
|
|
|
trace('Message: $message');
|
|
|
|
}
|
2024-05-01 11:40:51 -04:00
|
|
|
|
|
|
|
#if sys
|
|
|
|
// Exit the game. Since it threw an error, we use a non-zero exit code.
|
|
|
|
Sys.exit(1);
|
|
|
|
#end
|
2023-08-28 15:03:29 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
static function displayError(error:UncaughtErrorEvent):Void
|
|
|
|
{
|
|
|
|
displayErrorMessage(generateErrorMessage(error));
|
|
|
|
}
|
|
|
|
|
|
|
|
static function displayErrorMessage(message:String):Void
|
|
|
|
{
|
|
|
|
lime.app.Application.current.window.alert(message, "Fatal Uncaught Exception");
|
|
|
|
}
|
|
|
|
|
|
|
|
#if sys
|
|
|
|
static function logError(error:UncaughtErrorEvent):Void
|
|
|
|
{
|
|
|
|
logErrorMessage(generateErrorMessage(error));
|
|
|
|
}
|
|
|
|
|
|
|
|
static function logErrorMessage(message:String, critical:Bool = false):Void
|
|
|
|
{
|
|
|
|
FileUtil.createDirIfNotExists(LOG_FOLDER);
|
|
|
|
|
2023-11-20 11:26:58 -05:00
|
|
|
sys.io.File.saveContent('$LOG_FOLDER/crash${critical ? '-critical' : ''}-${DateUtil.generateTimestamp()}.log', buildCrashReport(message));
|
|
|
|
}
|
|
|
|
|
|
|
|
static function buildCrashReport(message:String):String
|
|
|
|
{
|
|
|
|
var fullContents:String = '=====================\n';
|
|
|
|
fullContents += ' Funkin Crash Report\n';
|
|
|
|
fullContents += '=====================\n';
|
|
|
|
|
|
|
|
fullContents += '\n';
|
|
|
|
|
|
|
|
fullContents += 'Generated by: ${Constants.GENERATED_BY}\n';
|
2024-05-01 13:29:54 -04:00
|
|
|
fullContents += ' Git hash: ${Constants.GIT_HASH} (${Constants.GIT_HAS_LOCAL_CHANGES ? 'MODIFIED' : 'CLEAN'})\n';
|
2023-11-20 11:26:58 -05:00
|
|
|
fullContents += 'System timestamp: ${DateUtil.generateTimestamp()}\n';
|
|
|
|
var driverInfo = FlxG?.stage?.context3D?.driverInfo ?? 'N/A';
|
|
|
|
fullContents += 'Driver info: ${driverInfo}\n';
|
|
|
|
fullContents += 'Platform: ${Sys.systemName()}\n';
|
2024-01-20 14:07:31 -05:00
|
|
|
fullContents += 'Render method: ${renderMethod()}\n';
|
2023-11-20 11:26:58 -05:00
|
|
|
|
|
|
|
fullContents += '\n';
|
|
|
|
|
|
|
|
fullContents += '=====================\n';
|
|
|
|
|
2024-02-21 17:10:18 -05:00
|
|
|
fullContents += '\n';
|
|
|
|
|
|
|
|
fullContents += MemoryUtil.buildGCInfo();
|
|
|
|
|
|
|
|
fullContents += '\n\n';
|
|
|
|
|
|
|
|
fullContents += '=====================\n';
|
|
|
|
|
2024-02-22 01:47:35 -05:00
|
|
|
fullContents += '\n';
|
|
|
|
|
2024-05-08 00:08:44 -04:00
|
|
|
var currentState = FlxG.state != null ? Type.getClassName(Type.getClass(FlxG.state)) : 'No state loaded';
|
|
|
|
|
|
|
|
fullContents += 'Flixel Current State: ${currentState}\n';
|
2024-02-22 01:47:35 -05:00
|
|
|
|
|
|
|
fullContents += '\n';
|
|
|
|
|
|
|
|
fullContents += '=====================\n';
|
|
|
|
|
|
|
|
fullContents += '\n';
|
|
|
|
|
2024-01-09 14:48:20 -05:00
|
|
|
fullContents += 'Haxelibs: \n';
|
|
|
|
|
|
|
|
for (lib in Constants.LIBRARY_VERSIONS)
|
|
|
|
{
|
|
|
|
fullContents += '- ${lib}\n';
|
|
|
|
}
|
|
|
|
|
|
|
|
fullContents += '\n';
|
|
|
|
|
|
|
|
fullContents += '=====================\n';
|
|
|
|
|
2023-11-20 11:26:58 -05:00
|
|
|
fullContents += '\n';
|
|
|
|
|
2024-05-02 04:07:56 -04:00
|
|
|
fullContents += 'Loaded mods: \n';
|
|
|
|
|
|
|
|
if (funkin.modding.PolymodHandler.loadedModIds.length == 0)
|
|
|
|
{
|
|
|
|
fullContents += 'No mods loaded.\n';
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
for (mod in funkin.modding.PolymodHandler.loadedModIds)
|
|
|
|
{
|
|
|
|
fullContents += '- ${mod}\n';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fullContents += '\n';
|
|
|
|
|
|
|
|
fullContents += '=====================\n';
|
|
|
|
|
|
|
|
fullContents += '\n';
|
|
|
|
|
2023-11-20 11:26:58 -05:00
|
|
|
fullContents += message;
|
|
|
|
|
|
|
|
fullContents += '\n';
|
|
|
|
|
|
|
|
return fullContents;
|
2023-08-28 15:03:29 -04:00
|
|
|
}
|
|
|
|
#end
|
|
|
|
|
|
|
|
static function generateErrorMessage(error:UncaughtErrorEvent):String
|
|
|
|
{
|
|
|
|
var errorMessage:String = "";
|
|
|
|
var callStack:Array<haxe.CallStack.StackItem> = haxe.CallStack.exceptionStack(true);
|
|
|
|
|
|
|
|
errorMessage += '${error.error}\n';
|
|
|
|
|
|
|
|
for (stackItem in callStack)
|
|
|
|
{
|
|
|
|
switch (stackItem)
|
|
|
|
{
|
|
|
|
case FilePos(innerStackItem, file, line, column):
|
|
|
|
errorMessage += ' in ${file}#${line}';
|
|
|
|
if (column != null) errorMessage += ':${column}';
|
|
|
|
case CFunction:
|
|
|
|
errorMessage += '[Function] ';
|
|
|
|
case Module(m):
|
|
|
|
errorMessage += '[Module(${m})] ';
|
|
|
|
case Method(classname, method):
|
|
|
|
errorMessage += '[Function(${classname}.${method})] ';
|
|
|
|
case LocalFunction(v):
|
|
|
|
errorMessage += '[LocalFunction(${v})] ';
|
|
|
|
}
|
|
|
|
errorMessage += '\n';
|
|
|
|
}
|
|
|
|
|
|
|
|
return errorMessage;
|
|
|
|
}
|
|
|
|
|
|
|
|
public static function queryStatus():Void
|
|
|
|
{
|
|
|
|
@:privateAccess
|
|
|
|
var currentStatus = Lib.current.stage.__uncaughtErrorEvents.__enabled;
|
|
|
|
trace('ERROR HANDLER STATUS: ' + currentStatus);
|
|
|
|
|
|
|
|
#if openfl_enable_handle_error
|
|
|
|
trace('Define: openfl_enable_handle_error is enabled');
|
|
|
|
#else
|
|
|
|
trace('Define: openfl_enable_handle_error is disabled');
|
|
|
|
#end
|
|
|
|
|
|
|
|
#if openfl_disable_handle_error
|
|
|
|
trace('Define: openfl_disable_handle_error is enabled');
|
|
|
|
#else
|
|
|
|
trace('Define: openfl_disable_handle_error is disabled');
|
|
|
|
#end
|
|
|
|
}
|
|
|
|
|
|
|
|
public static function induceBasicCrash():Void
|
|
|
|
{
|
|
|
|
throw "This is an example of an uncaught exception.";
|
|
|
|
}
|
2024-01-20 14:07:31 -05:00
|
|
|
|
2024-01-27 03:24:49 -05:00
|
|
|
public static function induceNullObjectReference():Void
|
|
|
|
{
|
|
|
|
var obj:Dynamic = null;
|
|
|
|
var value = obj.test;
|
|
|
|
}
|
|
|
|
|
|
|
|
public static function induceNullObjectReference2():Void
|
|
|
|
{
|
|
|
|
var obj:Dynamic = null;
|
|
|
|
var value = obj.test();
|
|
|
|
}
|
|
|
|
|
|
|
|
public static function induceNullObjectReference3():Void
|
|
|
|
{
|
|
|
|
var obj:Dynamic = null;
|
|
|
|
var value = obj();
|
|
|
|
}
|
|
|
|
|
2024-01-20 14:07:31 -05:00
|
|
|
static function renderMethod():String
|
|
|
|
{
|
2024-05-01 11:41:15 -04:00
|
|
|
try
|
|
|
|
{
|
|
|
|
return switch (FlxG.renderMethod)
|
|
|
|
{
|
|
|
|
case FlxRenderMethod.DRAW_TILES: 'DRAW_TILES';
|
|
|
|
case FlxRenderMethod.BLITTING: 'BLITTING';
|
|
|
|
default: 'UNKNOWN';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
catch (e)
|
2024-01-20 14:07:31 -05:00
|
|
|
{
|
2024-05-01 11:41:15 -04:00
|
|
|
return 'ERROR ON QUERY RENDER METHOD: ${e}';
|
2024-01-20 14:07:31 -05:00
|
|
|
}
|
|
|
|
}
|
2023-08-28 15:03:29 -04:00
|
|
|
}
|