Blog

All Blog Posts  |  Next Post  |  Previous Post

Web application error logging made easy

Tuesday, September 3, 2024

TMS Software Delphi  Components
As software developer, don't you always wish to collect as much information as possible when an error occurs in your application?
Just like with native Windows applications, where exception loggers exist for a long time, also for web applications this is a desirable tool to have. And for sure, in the web world, such solutions also exist for quite some time. And it doesn't stop with logging errors, it might be useful to log all kinds of other information on a central server database. 

Cook your own logger!

With this example, we show you how unbelievable easy it became to log errors in a TMS WEB Core application or other things on a central server. For this case, we take advantage of our newest offering: StellarDS.io. StellarDS.io offers you central data access as a service. It saves you from learning how to write, deploy and secure a backend server. It is up & running already and in a matter of minutes, you define the data you need and start using it! In this example, we'll guide you step by step how you can do this.

Setup your StellarDS.io account

TMS Software Delphi  Components

So, signup for the StellarDS.io service and after doing this, you can start setting up your project. Your default project has a project ID you will use later. For this logging example, we need just one table, so go ahead and under Tables, create the table Log. In this table, we will create 5 fields, but nothing prevents you from adcding more if you want to log more information. Note the ID of the table, as we'll need it later in the logger class.

The fields we will use are:

App : string field
the name of the application for which we want to log
User : string field
the name of the user using this app
TimeStamp : DateTime field
the time at which something is logged
Message : Blob field
a blob field used to store a large amount of text. This will hold the exception information
Navigator : Blob field
also a blob field to store details about the browser in which the error happened
In the StellarDS.io admin panel this looks like:

TMS Software Delphi  Components

Next, we'll need to create a means of access to the StellarDS.io service from our web app. Two methods exist:

OAuth based authentication & authorization for access
This means, a user needs to login first with credentials before obtaining an access token with which the StellarDS.io API can be access. 
Fixed access token 
This is preferably domain locked to prevent this token can be used for other purposes. 
In this example, for reasons of simplicity, we will use a fixed access token. So, go ahead and create an access token. For debugging purposes, lock this to the domain http://localhost but remember that when you're about to deploy your app, to change this to a token locked to your domain on which you deploy the app.

There isn't more to it. Our StellarDS.io account is ready to go.

Create the logger class

We are going to create a class that is fast & easy to use to log information on the StellarDS.io service. The class has the following public interface:

  TStellarDSLogger = class(TPersistent)
  public
    constructor Create(ProjectID: string; TableID: integer; AccessToken: string);
    procedure Log(App, User, Msg: string); {$IFDEF PAS2JS} async; {$ENDIF}
  end;

So, you create the an instance of this logger class, pass via the constructor the access token, the project ID and the table ID. Other than this, there is the Log() method that will send the logging data, consisting of App name, User name and Message to the service.

Class implementation

It will be interesting to have a look at the actual implementation of this class. We are taking advantage here of the classes already created to access StellarDS.io that are included in TMS WEB Core. This includes the classes TStellarDataStore, TStellarDataStoreTable, TStellarDataStoreEntity. By means of these classes, you do not need to bother about figuring out the StellarDS.io REST API but you can use these logically architected classes.  So, we will create the TStellarDataStore class mapping on our StellarDS.io project and one TStellarDataStoreTable mapping on our StellarDS.io table.

The constructor of our logger class therefore looks like:
constructor TStellarDSLogger.Create(ProjectID: string; TableID: integer;
  AccessToken: string);
begin
  FDS := TStellarDataStore.Create(nil);
  FDS.AccessToken := AccessToken;
  FDS.ProjectID := ProjectID;
  FDS.Tables.Add;
  FDT := FDS.Tables[0];
  FDT.TableID := TableID.ToString;
end;
Then there is the actual log method that looks like:
procedure TStellarDSLogger.Log(App, User, Msg: string);
var
  de: TStellarDataStoreEntity;
  res: string;
begin
  if FDT.Fields.Count = 0 then
  begin
    TAwait.ExecP<boolean>(FDT.GetFields);
  end;

  de := FDT.Entities.Add;
  de.Value['App'] := App;
  de.Value['User'] := User;
  de.Value['TimeStamp'] := DateTimeToRFC3339(Now);

  res := TAwait.ExecP<string>(de.Insert);
  if (res<> '') then
  begin
    TAwait.ExecP<boolean>(de.WriteBlob('Message', Msg, res));
    TAwait.ExecP<boolean>(de.WriteBlob('Navigator', TJSJSON.stringify(GetNavigatorInfo), res));
  end;
end;
What we see here is:

1) When our table was first created, it doesn't get the field information from the service by default, so, when we log the first time and this field information was not retrieved yet, we call FDT.GetFields. Notice that this uses a TAwait construct as, like with all HTTP requests in a web application, it is executed asynchronously. 

2) We add an entity (i.e. a record) to the table and set its values. As eventually the information from the entity is sent as JSON text object to the server, we set the values as strings for each of the fields we use. Note here the use of the method DateTimeToRFC3339(). This method is used to convert to the current time to a string adhering the expected JSON date/time standard format

Blob fields are for performance reasons never stored in one call with regular fields on the server. Intead, when a new record is created in the table, the blob fields get a unique URL value and by means of a HTTP GET or POST, blob data is retrieved or written to the field. This is what you see after the asynchronous insert of the new record, where two blob field values are written. One for the log message and one for the browser information. To collect the browser information, we wrote a function GetNavigatorInfo that returns a whole lot of browser info as JSON object. In order to send it to the StellarDS.io service via a HTTP request, we convert this JSON object to a string with the function  TJSJSON.stringify(). 

Start using the logger

In our application, we create an instance of the logger class with:
  LLogger := TStellarDSLogger.Create(cProjectID, cTableID, ACCESS_TOKEN);
Then, we attach an event handler to the Application.OnError event:
  Application.OnError := AppError;
with the implementation:
procedure TForm1.AppError(Sender: TObject;  AError: TApplicationError; var Handled: boolean);
begin
  LLogger.Log('My app', 'My user', TJSJSON.stringify(AError));
end;
The Application.OnError event handler is called with the error information stored in an object AError: TApplicationError and also this object is writting as string to the Message blob field.

Viewing logs

From any other machine or app, we can easily make a log viewer. To make our lives easier, for the viewer, we will use the TWebStellarDataStoreClientDataSet. We connect this dataset to our Log table and this dataset, we can easily show in a TWebDBGrid by connecting it to the dataset via a TWebDataSource (all identical to the way we do databinding in a VCL application).

  WebDataSource1.DataSet := WebStellarDataStoreClientDataset1;
  WebDBGrid1.DataSource := WebDataSource1;
  WebStellarDataStoreClientDataset1.AccessToken := ACCESS_TOKEN;
  WebStellarDataStoreClientDataset1.TableID := cTableId;
  WebStellarDataStoreClientDataset1.ProjectID := cProjectID;
  WebStellarDataStoreClientDataset1.Active := true;
This will fill the TWebDBGrid with the information that is logged. To show the log for a specific user, app or date range, we can use WebStellarDataStoreClientDataset1.TableWhereQuery to set the filter.

For the attentive reader, you'll realize that we won't see the message & navigator information stored in the blob right-away. Indeed, for performance reasons, it needs to be retriebed on demand. To visualize the exception message for example in a TWebMemo control, following code can be used:
var
  s: string;
begin
  s := TAwait.ExecP<string>(WebStellarDataStoreClientDataset1.ReadBlobAsText('Navigator'));
  WebMemo1.Lines.Text := s;
end;

The result in the browser looks like this:

TMS Software Delphi  Components

Get started!
Make sure you are on the latest TMS WEB Core v2.6.0.0 version (beta is available at this moment for all active TMS WEB Core or TMS ALL-ACCESS users) and download the project here. Create the account at StellarDS.io and a few minutes you'll have a web application that has loggin on board! 













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