Blog

All Blog Posts  |  Next Post  |  Previous Post

Log exceptions in your Delphi app in the cloud with StellarDS.io

Wednesday, December 4, 2024

TMS Software Delphi  Components

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

TMS Software Delphi  Components

These are the only few steps needed to setup your StellarDS.io account:

  1. Sign-in or sign-up for your (free) StellarDS.io account

  2. Go under Tables and create the new table named "Exceptions"

  3. 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)

  4. 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;
If you use a paid subscription of StellarDS.io, you can also submit the application screenshot to the blob field in the Exceptions table. This is thanks to the TRESTClient also very simple in Delphi:

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

While usually we want to avoid exceptions at all cost, for once, let's force an exception to see our setup in action and see the first exception information logged at StellarDS.io. We can easily invoke an exception by accessing an non-existing string in a TStringList for example:

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;
When this piece of code is executed, it generates a nice exception that we can subsequently see on StellarDS.io:

TMS Software Delphi  Components

Download project

You can download the full source code of this project here

TMS Software Delphi  Components


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.



Add a new comment

You will receive a confirmation mail with a link to validate your comment, please use a valid email address.
All fields are required.



All Blog Posts  |  Next Post  |  Previous Post