Blog
All Blog Posts | Next Post | Previous PostLog exceptions in your Delphi app in the cloud with StellarDS.io
Wednesday, December 4, 2024
Introduction
If you want a way to effortlessly log exceptions in the cloud generated by applications you build and deploy to customers and remotely inspect these exceptions, this article is for you. You will learn how you can do this with just 2 units to add to your application, no additional components to install and the free tier of our StellarDS.io data in the cloud service (or the paid tier when you also want to log screenshots at the time of exception). Or you can of course also start from the free code provided and add more functionality according to your needs.
Exception handling
To generally handle exceptions in a Delphi applications, we can do this by adding an event handler for Application.OnException. This event is triggered whenever an unhandled exception handles in the application. From there, we get the exception object and we can get its information. As it is always useful to add some system information along with the exception information, we've added to the unit also a basic function for this. Further, this can be extended with a full stacktrace. Delphi offers the capability to get this full stack trace by binding a stack trace routine. A couple of free ones exist, among which the one in the JEDI JCL library.
Setting up your StellarDS.io account
These are the only few steps needed to setup your StellarDS.io account:
- Sign-in or sign-up for your (free) StellarDS.io account
- Go under Tables and create the new table named "Exceptions"
- Manage the table to add fields (case is important):
-Exception : NVarChar(2048)
-SysInfo : NVarChar(20248)
-Timestamp : DateTime
-Screenshot : Blob (optional for paid StellarDS.io accounts)
(the id field is auto-created and always available automatically) - Create an access token (with admin rights) under Application, Access Tokens and copy this token to the clipboard and save it for use in your Delphi code.
Using Delphi's TRESTClient to work with StellarDS.io
StellarDS.io has an easy to use REST API and as such, it is also easy to use with the Delphi standard built-in TRESTClient class. We have implemented 4 basic functions to access StellarDS.io. A function that uses the StellarDS.io API endpoint to get the project ID, to get the table ID, to log the exception, system information and timestamp to the Exceptions table on StellarDS.io and finally, also a function to submit a screenshot as binary data for a blob field in the StellarDS.io Exceptions table.
To make its use easy, we have wrapped these 4 functions in the class TStellarDSLogger in unit TMS.StellarDSLogger.pas. We added a property FreeTier: boolean to this class that will control whether the exception logger will take advantage of the availability of blob fields in a paid StellarDS.io subscription or not (and thus you can use the free tier).
Here is the actual implementation of the function that posts the exception information, system information and timestamp to StellarDS.io. In its response, when this is not a free tier of StellarDS.io, it will return the unique URL to access the blob field and this URL can be used to upload the screenshot image.
function TStellarDSLogger.Log(AException, ASysInfo: string): boolean; var t: TJSONObject; ja: TJSONArray; b: boolean; RestClient: TRESTClient; RestRequest: TRESTRequest; RestResponse: TRESTResponse; JsonData: TJSONObject; JsonMain: TJSONObject; JsonArray: TJSONArray; begin Result := false; // some checks if project ID, table ID and access token exists if FProjectID = '' then raise Exception.Create('No project ID specified'); if FTableID = -1 then raise Exception.Create('No table ID specified'); if AccessToken = '' then raise Exception.Create('No access token specified'); // create the REST client, request & response objects RestClient := TRESTClient.Create('https://api.stellards.io/v1/data/table?project='+ FProjectID + '&table=' + FTableID.ToString); RestRequest := TRESTRequest.Create(nil); RestResponse := TRESTResponse.Create(nil); RestRequest.Client := RestClient; // set the access token in the header RestRequest.Params.AddItem('Authorization','Bearer ' + AccessToken, TRESTRequestParameterKind.pkHTTPHEADER, [poDoNotEncode]); RestRequest.Response := RestResponse; RestRequest.Method := rmPOST; JsonMain := TJSONObject.Create; try // Create JSON payload to submit exception information as new record in StellarDS.io Exceptions table JsonData := TJSONObject.Create; JsonData.AddPair('Exception', AException); JsonData.AddPair('SysInfo', ASysInfo); JsonData.AddPair('Timestamp', FormatDateTime('yyyy-mm-dd"T"hh:nn:ss"Z"',Now)); JsonArray := TJSONArray.Create; JsonArray.AddElement(JsonData); JsonMain.AddPair('records', JsonArray); // Assign JSON payload to request body RestRequest.AddBody(JsonMain); RestRequest.Execute; // when a valid response returned, parse JSON and extract Screenshot blob URL (in case no free tier is used) if (RestResponse.StatusCode div 100 = 2) then begin Result := true; t := TJSONObject.ParseJSONValue(RestResponse.Content) as TJSONObject; try if Assigned(t) then begin b := t.GetValue<boolean>('isSuccess'); if b and not FFreeTier then begin ja := t.GetValue<TJSONArray>('data'); if Assigned(ja) and (ja.Count > 0) then begin FScreenShotURL := TJSONObject(ja.Items[0]).GetValue<string>('Screenshot'); end; end; end; finally t.Free; end; end; finally JsonMain.Free; RestRequest.Free; RestResponse.Free; RestClient.Free; end; end;
function TStellarDSLogger.LogScreenShot(AURL, AFileName: string): boolean; var RestClient: TRESTClient; RestRequest: TRESTRequest; RestResponse: TRESTResponse; begin Result := false; if FProjectID = '' then raise Exception.Create('No project ID specified'); if FTableID = -1 then raise Exception.Create('No table ID specified'); if AccessToken = '' then raise Exception.Create('No access token specified'); if FFreeTier then raise Exception.Create('Cannot log image to blob field in free tier'); RestClient := TRESTClient.Create(AURL); RestRequest := TRESTRequest.Create(nil); RestResponse := TRESTResponse.Create(nil); RestRequest.Client := RestClient; RestRequest.Params.AddItem('Authorization','Bearer ' + AccessToken, TRESTRequestParameterKind.pkHTTPHEADER, [poDoNotEncode]); RestRequest.AddFile('data', AFileName, TRESTContentType.ctAPPLICATION_OCTET_STREAM); RestRequest.Response := RestResponse; RestRequest.Method := rmPOST; try RestRequest.Execute; if (RestResponse.StatusCode div 100 = 2) then begin Result := true; end; finally RestRequest.Free; RestResponse.Free; RestClient.Free; end; end;
Binding the exception handling to logging it to StellarDS.io
Now, to bind the logging on StellarDS.io with capturing the exception information, we first create an instance of the StellarDSLogger class and install the application level exception handler by assigning it to Application.OnException.
procedure TForm1.FormCreate(Sender: TObject); begin StellarDSLogger := TStellarDSLogger.Create; StellarDSLogger.AccessToken := token; StellarDSLogger.GetProjectID; StellarDSLogger.GetTableID; Application.OnException := ExceptionHandler; end;
Given the installed application exception handling and the methods to work with StellarDS.io REST API, it is only a small step to bring these 2 pieces of the puzzle together and use the TRESTClient to log the exception information to the data in the cloud at StellarDS.io:
procedure TForm1.ExceptionHandler(Sender: TObject; E: Exception); var fn: string; begin // create an application screenshot if not StellarDSLogger.FreeTier then begin fn := GetTempPNGFileName; // method for capturing the active form in the app as screenshot is in TMS.ExceptionInfo.pas CaptureFullActiveFormScreenshot(fn); end; // The functions ExceptionInformation & SysInformation respectively return // exception information and system information as text if StellarDSLogger.Log(ExceptionInformation(E), SysInformation) and not StellarDSLogger.FreeTier then begin StellarDSLogger.LogScreenShot(StellarDSLogger.ScreenShotURL, fn); end; end;
Bring everything into action
procedure TForm1.Button1Click(Sender: TObject); var sl: TStringList; s: string; begin sl := TStringList.Create; sl.Add('1'); sl.Add('2'); // this will invoke an exception s := sl.Strings[3]; sl.Free; end;
Download project
You can download the full source code of this project here
What's next?
Of course, you can add additional exception information to the log or store the data in a different way on StellarDS.io. With this demo, we have also not touched the subject of inspecting the exception log. We could for example very easily write a web client application that allows to see & filter the exceptions logged by applications. The same StellarDS.io table is easily accessible in read-only mode from a public facing web application from where a developer could for example follow & investigate exceptions logged in his application. Let us know if you manage to create such a TMS WEB Core web client application yourself or if you prefer we bring a follow-up blog that covers the web client part to give inights in the exceptions logged.
Bruno Fierens
This blog post has not received any comments yet.
All Blog Posts | Next Post | Previous Post