342 lines
9.9 KiB
ObjectPascal
342 lines
9.9 KiB
ObjectPascal
unit uExceptionHandler;
|
|
|
|
interface
|
|
|
|
uses
|
|
SysUtils, Classes, System.JSON, System.Threading, FMX.Forms;
|
|
|
|
type
|
|
EBaseException = class(Exception)
|
|
private
|
|
FErrorCode: Integer;
|
|
FErrorType: string;
|
|
FCause: string;
|
|
FContext: TStrings;
|
|
FTimestamp: TDateTime;
|
|
public
|
|
constructor Create(const AMessage: string; AErrorCode: Integer = 0; AErrorType: string = ''); overload;
|
|
constructor Create(const AMessage: string; const AArgs: array of const; AErrorCode: Integer = 0; AErrorType: string = ''); overload;
|
|
destructor Destroy; override;
|
|
function ToJSON: TJSONObject;
|
|
function ToDetailString: string;
|
|
property ErrorCode: Integer read FErrorCode write FErrorCode;
|
|
property ErrorType: string read FErrorType write FErrorType;
|
|
property Cause: string read FCause write FCause;
|
|
property Context: TStrings read FContext;
|
|
property Timestamp: TDateTime read FTimestamp;
|
|
end;
|
|
|
|
ENetworkException = class(EBaseException)
|
|
public
|
|
constructor Create(const AMessage: string; AErrorCode: Integer = 0); overload;
|
|
constructor Create(const AMessage: string; const AArgs: array of const; AErrorCode: Integer = 0); overload;
|
|
end;
|
|
|
|
EDataException = class(EBaseException)
|
|
public
|
|
constructor Create(const AMessage: string; AErrorCode: Integer = 0); overload;
|
|
constructor Create(const AMessage: string; const AArgs: array of const; AErrorCode: Integer = 0); overload;
|
|
end;
|
|
|
|
EBusinessException = class(EBaseException)
|
|
public
|
|
constructor Create(const AMessage: string; AErrorCode: Integer = 0); overload;
|
|
constructor Create(const AMessage: string; const AArgs: array of const; AErrorCode: Integer = 0); overload;
|
|
end;
|
|
|
|
EDeviceException = class(EBaseException)
|
|
public
|
|
constructor Create(const AMessage: string; AErrorCode: Integer = 0); overload;
|
|
constructor Create(const AMessage: string; const AArgs: array of const; AErrorCode: Integer = 0); overload;
|
|
end;
|
|
|
|
EValidationException = class(EBaseException)
|
|
private
|
|
FFieldName: string;
|
|
FFieldValue: string;
|
|
public
|
|
constructor Create(const AFieldName, AFieldValue, AMessage: string);
|
|
property FieldName: string read FFieldName write FFieldName;
|
|
property FieldValue: string read FFieldValue write FFieldValue;
|
|
end;
|
|
|
|
TExceptionHandler = class
|
|
private
|
|
class var
|
|
FInstance: TExceptionHandler;
|
|
var
|
|
FOnException: TProc<Exception, TObject>;
|
|
FLogEnabled: Boolean;
|
|
FShowDialogEnabled: Boolean;
|
|
FErrorPrefix: string;
|
|
class function GetInstance: TExceptionHandler; static;
|
|
public
|
|
constructor Create;
|
|
destructor Destroy; override;
|
|
procedure HandleException(E: Exception; Context: TObject = nil);
|
|
procedure HandleAndRethrow(E: Exception; const AdditionalInfo: string = '');
|
|
function SafeCall<T>(Func: TFunc<T>; const DefaultValue: T; const ErrorMsg: string = ''): T; overload;
|
|
procedure SafeCall(Func: TProc; const ErrorMsg: string = ''); overload;
|
|
property OnException: TProc<Exception, TObject> read FOnException write FOnException;
|
|
property LogEnabled: Boolean read FLogEnabled write FLogEnabled;
|
|
property ShowDialogEnabled: Boolean read FShowDialogEnabled write FShowDialogEnabled;
|
|
property ErrorPrefix: string read FErrorPrefix write FErrorPrefix;
|
|
class property Instance: TExceptionHandler read GetInstance;
|
|
class procedure SetAsDefaultInstance;
|
|
class procedure LogException(E: Exception; const Context: string = '');
|
|
class function GetExceptionDetail(E: Exception): string;
|
|
end;
|
|
|
|
procedure RaiseNetworkException(const AMsg: string; AErrorCode: Integer = 0);
|
|
procedure RaiseDataException(const AMsg: string; AErrorCode: Integer = 0);
|
|
procedure RaiseBusinessException(const AMsg: string; AErrorCode: Integer = 0);
|
|
procedure RaiseDeviceException(const AMsg: string; AErrorCode: Integer = 0);
|
|
procedure RaiseValidationException(const AFieldName, AFieldValue, AMsg: string);
|
|
|
|
implementation
|
|
|
|
uses
|
|
uSafeLog;
|
|
|
|
{ EBaseException }
|
|
|
|
constructor EBaseException.Create(const AMessage: string; AErrorCode: Integer; AErrorType: string);
|
|
begin
|
|
inherited Create(AMessage);
|
|
FErrorCode := AErrorCode;
|
|
FErrorType := AErrorType;
|
|
FCause := '';
|
|
FContext := TStringList.Create;
|
|
FTimestamp := Now;
|
|
end;
|
|
|
|
constructor EBaseException.Create(const AMessage: string; const AArgs: array of const; AErrorCode: Integer; AErrorType: string);
|
|
begin
|
|
Create(Format(AMessage, AArgs), AErrorCode, AErrorType);
|
|
end;
|
|
|
|
destructor EBaseException.Destroy;
|
|
begin
|
|
FContext.Free;
|
|
inherited;
|
|
end;
|
|
|
|
function EBaseException.ToJSON: TJSONObject;
|
|
begin
|
|
Result := TJSONObject.Create;
|
|
Result.AddPair('message', Message);
|
|
Result.AddPair('errorCode', TJSONNumber.Create(FErrorCode));
|
|
Result.AddPair('errorType', FErrorType);
|
|
Result.AddPair('cause', FCause);
|
|
Result.AddPair('timestamp', FormatDateTime('yyyy-mm-dd hh:nn:ss.zzz', FTimestamp));
|
|
end;
|
|
|
|
function EBaseException.ToDetailString: string;
|
|
var
|
|
I: Integer;
|
|
begin
|
|
Result := Format('[%s] %s (Code: %d, Type: %s)',
|
|
[FormatDateTime('yyyy-mm-dd hh:nn:ss', FTimestamp), Message, FErrorCode, FErrorType]);
|
|
if FCause <> '' then
|
|
Result := Result + sLineBreak + 'Cause: ' + FCause;
|
|
if FContext.Count > 0 then
|
|
begin
|
|
Result := Result + sLineBreak + 'Context:';
|
|
for I := 0 to FContext.Count - 1 do
|
|
Result := Result + sLineBreak + ' ' + FContext.Names[I] + ' = ' + FContext.ValueFromIndex[I];
|
|
end;
|
|
end;
|
|
|
|
{ ENetworkException }
|
|
|
|
constructor ENetworkException.Create(const AMessage: string; AErrorCode: Integer);
|
|
begin
|
|
inherited Create(AMessage, AErrorCode, 'Network');
|
|
end;
|
|
|
|
constructor ENetworkException.Create(const AMessage: string; const AArgs: array of const; AErrorCode: Integer);
|
|
begin
|
|
inherited Create(Format(AMessage, AArgs), AErrorCode, 'Network');
|
|
end;
|
|
|
|
{ EDataException }
|
|
|
|
constructor EDataException.Create(const AMessage: string; AErrorCode: Integer);
|
|
begin
|
|
inherited Create(AMessage, AErrorCode, 'Data');
|
|
end;
|
|
|
|
constructor EDataException.Create(const AMessage: string; const AArgs: array of const; AErrorCode: Integer);
|
|
begin
|
|
inherited Create(Format(AMessage, AArgs), AErrorCode, 'Data');
|
|
end;
|
|
|
|
{ EBusinessException }
|
|
|
|
constructor EBusinessException.Create(const AMessage: string; AErrorCode: Integer);
|
|
begin
|
|
inherited Create(AMessage, AErrorCode, 'Business');
|
|
end;
|
|
|
|
constructor EBusinessException.Create(const AMessage: string; const AArgs: array of const; AErrorCode: Integer);
|
|
begin
|
|
inherited Create(Format(AMessage, AArgs), AErrorCode, 'Business');
|
|
end;
|
|
|
|
{ EDeviceException }
|
|
|
|
constructor EDeviceException.Create(const AMessage: string; AErrorCode: Integer);
|
|
begin
|
|
inherited Create(AMessage, AErrorCode, 'Device');
|
|
end;
|
|
|
|
constructor EDeviceException.Create(const AMessage: string; const AArgs: array of const; AErrorCode: Integer);
|
|
begin
|
|
inherited Create(Format(AMessage, AArgs), AErrorCode, 'Device');
|
|
end;
|
|
|
|
{ EValidationException }
|
|
|
|
constructor EValidationException.Create(const AFieldName, AFieldValue, AMessage: string);
|
|
begin
|
|
inherited Create(AMessage, -1, 'Validation');
|
|
FFieldName := AFieldName;
|
|
FFieldValue := AFieldValue;
|
|
end;
|
|
|
|
{ TExceptionHandler }
|
|
|
|
constructor TExceptionHandler.Create;
|
|
begin
|
|
inherited;
|
|
FLogEnabled := True;
|
|
FShowDialogEnabled := True;
|
|
FErrorPrefix := '[Error]';
|
|
end;
|
|
|
|
destructor TExceptionHandler.Destroy;
|
|
begin
|
|
inherited;
|
|
end;
|
|
|
|
class function TExceptionHandler.GetInstance: TExceptionHandler;
|
|
begin
|
|
if FInstance = nil then
|
|
FInstance := TExceptionHandler.Create;
|
|
Result := FInstance;
|
|
end;
|
|
|
|
procedure TExceptionHandler.HandleException(E: Exception; Context: TObject);
|
|
begin
|
|
if FLogEnabled then
|
|
LogException(E);
|
|
|
|
if FShowDialogEnabled and Assigned(FOnException) then
|
|
FOnException(E, Context);
|
|
end;
|
|
|
|
procedure TExceptionHandler.HandleAndRethrow(E: Exception; const AdditionalInfo: string);
|
|
var
|
|
Ex: EBaseException;
|
|
begin
|
|
if E is EBaseException then
|
|
begin
|
|
Ex := E as EBaseException;
|
|
if AdditionalInfo <> '' then
|
|
Ex.Cause := AdditionalInfo;
|
|
raise Ex;
|
|
end
|
|
else
|
|
begin
|
|
Ex := EBusinessException.Create(E.Message);
|
|
if AdditionalInfo <> '' then
|
|
Ex.Cause := AdditionalInfo;
|
|
raise Ex;
|
|
end;
|
|
end;
|
|
|
|
function TExceptionHandler.SafeCall<T>(Func: TFunc<T>; const DefaultValue: T; const ErrorMsg: string): T;
|
|
begin
|
|
try
|
|
Result := Func;
|
|
except
|
|
on E: Exception do
|
|
begin
|
|
if ErrorMsg <> '' then
|
|
LogException(E, ErrorMsg)
|
|
else
|
|
LogException(E);
|
|
Result := DefaultValue;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TExceptionHandler.SafeCall(Func: TProc; const ErrorMsg: string);
|
|
begin
|
|
try
|
|
Func;
|
|
except
|
|
on E: Exception do
|
|
begin
|
|
if ErrorMsg <> '' then
|
|
LogException(E, ErrorMsg)
|
|
else
|
|
LogException(E);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
class procedure TExceptionHandler.SetAsDefaultInstance;
|
|
begin
|
|
if FInstance = nil then
|
|
FInstance := TExceptionHandler.Create;
|
|
end;
|
|
|
|
class procedure TExceptionHandler.LogException(E: Exception; const Context: string);
|
|
begin
|
|
if not Assigned(FInstance) then
|
|
Exit;
|
|
|
|
try
|
|
if Context <> '' then
|
|
WorkLog.Error('[Context: %s] %s: %s', [Context, E.ClassName, E.Message])
|
|
else
|
|
WorkLog.Error('%s: %s', [E.ClassName, E.Message]);
|
|
except
|
|
end;
|
|
end;
|
|
|
|
class function TExceptionHandler.GetExceptionDetail(E: Exception): string;
|
|
begin
|
|
if E is EBaseException then
|
|
Result := (E as EBaseException).ToDetailString
|
|
else
|
|
Result := Format('[%s] %s', [E.ClassName, E.Message]);
|
|
end;
|
|
|
|
procedure RaiseNetworkException(const AMsg: string; AErrorCode: Integer);
|
|
begin
|
|
raise ENetworkException.Create(AMsg, AErrorCode);
|
|
end;
|
|
|
|
procedure RaiseDataException(const AMsg: string; AErrorCode: Integer);
|
|
begin
|
|
raise EDataException.Create(AMsg, AErrorCode);
|
|
end;
|
|
|
|
procedure RaiseBusinessException(const AMsg: string; AErrorCode: Integer);
|
|
begin
|
|
raise EBusinessException.Create(AMsg, AErrorCode);
|
|
end;
|
|
|
|
procedure RaiseDeviceException(const AMsg: string; AErrorCode: Integer);
|
|
begin
|
|
raise EDeviceException.Create(AMsg, AErrorCode);
|
|
end;
|
|
|
|
procedure RaiseValidationException(const AFieldName, AFieldValue, AMsg: string);
|
|
begin
|
|
raise EValidationException.Create(AFieldName, AFieldValue, AMsg);
|
|
end;
|
|
|
|
end. |