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; 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(Func: TFunc; const DefaultValue: T; const ErrorMsg: string = ''): T; overload; procedure SafeCall(Func: TProc; const ErrorMsg: string = ''); overload; property OnException: TProc 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(Func: TFunc; 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.