Prototype: Logging without Commit in AL

Update:
There’s probably a better solution so solve this, especially in newer versions.

Just use background sessions.

Recently i had an idea to create a persistent error logging in Business Central 365 without having to commit the transaction. I think every (add-on-) developer has this problem from time to time. How to handle and especially log error(?) messages in BC? But, for sure, without committing and this way probably breaking rollback of the transaction in case of errors. And this way, of course… rolling back your log entries. For sure, hard errors might be a little bit tricky to catch, but this is just an idea for a new approach.

So, I had the idea to use internal webservices to call the logging methods inside a logging extension.

Here’s a dirty litte example AL project to download and play around. Create and deploy it to your docker container. After setting up your API user you could start logging to yourself 😛

Automatically span up the webservice

Since BC we can automatically publish webservices via xml definition, we span up our logging API page we predefined:

<?xml version="1.0" encoding="utf-8"?>
<ExportedData>
    <TenantWebServiceCollection>
        <TenantWebService>
            <ObjectType>Page</ObjectType>
            <ObjectID>50102</ObjectID>
            <ServiceName>ComsolLogging</ServiceName>
            <Published>true</Published>
        </TenantWebService>
    </TenantWebServiceCollection>
</ExportedData>

For sure, this would also work using an classic install codeunit and creating the webservice record manually:

codeunit 50101 "Logging Installer"
{
    Subtype = Install;
    trigger OnInstallAppPerCompany()
    var
        TenentWebservice: Record "Tenant Web Service";
    begin
        with TenentWebservice do begin
            Init();
            "Object Type" := "Object Type"::Page;
            "Object ID" := 50102;
            "Service Name" := 'ComsolLogging';
            Published := true;
            Insert(true);
        end;
    end;
}

This webservice simply publishes a page of type API with our defined logging fields. This could be, for sure, easily extended to your own needs.

page 50102 "Logging API"
{
    PageType = API;
    Caption = 'LogEntry';
    APIPublisher = 'Comsol';
    APIGroup = 'ComsolLogging';
    APIVersion = 'beta';
    EntityName = 'LogEntry';
    EntitySetName = 'ComsolLogging';
    SourceTable = "Logging Entries";
    DelayedInsert = true;
    ODataKeyFields = Id;

    layout
    {
        area(Content)
        {
            repeater(GroupName)
            {
                field(id; Id)
                {
                    ApplicationArea = All;
                }
                field(No; "No.")
                {
                    ApplicationArea = All;
                }
                field(LogType; "Log Type")
                {
                    ApplicationArea = All;
                }
                field(LogMessage; "Log Message")
                {
                    ApplicationArea = All;
                }
                field(LogUser; "Log User")
                {
                    ApplicationArea = All;
                }
                field(LogPayload; "Log Payload")
                {
                    ApplicationArea = All;
                }
            }
        }
    }

    trigger OnInsertRecord(BelowxRec: Boolean): Boolean
    begin
        Insert(true);

        Modify(true);
        exit(false);
    end;
}

At that moment, the webservice will automatically listen on something like https://localhost:7048/BC/api/publisher/entity/beta/… after installing the extension… giving us the opportunity to use the published methods directly via BC REST API.

Calling the logging method

After we have created the webservice we will write a codeunit serving the procedures to create the log message and directly calling the webservice. It’s an amazingly simple codeunit using the HttpClient with Basic Authentication.

You might use this method wherever you would like to log stuff in BC… Create extensions, events…

//This is the method we would probably use withing an event to log messages
procedure CreateLogEntry(LogType: Option error,info; LogMessage: Text[250]): Boolean
var
    LoggingSetup: Record "Logging Setup";
    WebClient: HttpClient;
    RequestHeader: HttpHeaders;
    RequestContentHeader: HttpHeaders;
    RequestContent: HttpContent;
    ResponseMessage: HttpResponseMessage;
    RequestMessage: HttpRequestMessage;
    ResponseText: Text;
    JObject: JsonObject;
    JToken: JsonToken;
begin
    LoggingSetup.GetSetup();

    CreateWebRequest(RequestHeader, RequestMessage, 'POST');
    RequestMessage.SetRequestUri(StrSubstNo('%1/companies(%2)/ComsolLogging', LoggingSetup.GetApiEndpoint(), LoggingSetup."Company Id"));

    //Add Content
    RequestContent.WriteFrom(CreateLogPayload(LogType, LogMessage));
    RequestContent.GetHeaders(RequestContentHeader);
    RequestContentHeader.Remove('Content-Type');
    RequestContentHeader.Add('Content-Type', 'application/json; charset=utf-8');
    RequestMessage.Content(RequestContent);

    WebClient.Send(RequestMessage, ResponseMessage);

    //Debug
    if ResponseMessage.IsSuccessStatusCode then begin
        ResponseMessage.Content.ReadAs(ResponseText);
        JObject.ReadFrom(ResponseText);

        if JObject.Get('id', JToken) then
            exit(ResponseMessage.IsSuccessStatusCode and (JToken.AsValue().AsText() <> ''));
    end;

    exit(ResponseMessage.IsSuccessStatusCode);
end;

After this method is called, the log message is instantly stored in our logging record… without committing the transaction and interfering with the parent transactions. Additionally, you might store complex json structures in the “LogPayload” field of the record. So, you’re not limited by the field length or table structure.

Page showing log entries

Once again… this is just an idea and i’ve not yet used it in a productive extension.

So… feel free to download and discuss it.

... is a technical consultant and developer at Comsol Unternehmenslösungen AG in Kronberg/Taunus. Major tasks are the architecture and implementation of complex, usually cross-system applications in and around Microsoft Dynamics 365 Business Central.

Leave a Reply

Your email address will not be published.