Save Error Messages In D365

Share this:

When something goes wrong while running a complex process, it can be challenging to understand how to correct it. Understanding how to save error messages in D365 will help give you the information needed to take further action.

Example Scenario

Oftentimes, a custom process will call base Microsoft code that shows info, warnings, and error messages. When the process is run in a batch job, the messages are saved and can be viewed later in the batch form. However, other times, the code does not run as part of a batch job. There are times when it is helpful to save error messages in D365 to a custom log table so they can be viewed more easily in a workbench form. In this article, learn to scrape the info log and save those messages.

Logging Tutorials

Before proceeding, I recommend reviewing these other tutorials I wrote on logging. This article will build on some of that information.

Save Messages

Every time the system sends an info(), warning(), or error() message to the user, the message is logged to the info log. Additionally, the info log has a counter that gets incremented for each message. By keeping track of the counter number before calling a process, we can go back and read all of the messages that have occurred since. Then, save error messages in D365 to a table.

First, add the following method to your custom log table:

    static SysInfoLogStr logMessages(int _sinceLineNum, RefRecId _logRecId)
    {
        int             i;
        int             curLineNum;
        int             lastLineNum;
        int             infoMsgNum;
        int             warningMsgNum;
        int             errorMsgNum;
        SysInfoLogStr   message;
        Exception       exception;
        SysInfoLogStr   allMessages;

        lastLineNum = infologLine();

        for (i = 1; i <= lastLineNum - _sinceLineNum; i++)
        {
            // This will capture the oldest message first
            curLineNum = _sinceLineNum + i;

            // To capture the 'Most recent message first', comment out the above line, and uncomment this line.
            //curLineNum = lastLineNum + 1 - i;

            message = SysInfologMessageStruct::construct(infolog.text(curLineNum)).message();
            exception = infolog.level(curLineNum);

            switch (exception)
            {
                case Exception::Info:
                    infoMsgNum++;
                    message = 'Info: ' + message+ '\n';
                    break;

                case Exception::Warning:
                    warningMsgNum++;
                    message = 'Warning: ' + message+ '\n';
                    break;

                // Assume all other exceptions are worthy of type 'error'.
                default:
                    errorMsgNum++;
                    msg = 'Error: ' + msg + '\n';

            }
            CustomLog::log(msg, _logRecId);
            allMessages += msg;
        }

        return allMessages;
    }

}

Notice, this code calls “lastLineNum = infologLine();” to get the most current info log counter value.

After that, a for loop iterates through each message in the info log by calling this code to get the message: “message = SysInfologMessageStruct::construct(infolog.text(curLineNum)).message();

Additionally, it calls “exception = infolog.level(curLineNum);” to get whether the message was of type info, warning, or error.

How To Use This Code

So far, you have learned how to write the code to save error messages in D365. Next, learn how to call this code within your own custom code.

First, at the start of your process, add this code to store the current info log line number.

int startingLineNumber = infologLine();

Second, call whatever process you wish to monitor. For example, this could be processing a staging record from a website into a sales order. Or, it could be posting a journal. The code you call could be entirely custom code or base Microsoft code. Either way, the method written above will help save error messages in D365 for reviewing later.

Third, call the logMessages method, passing in the initial line number. Any messages sent to the info log in the process will then be scraped and recorded.

To demonstrate, see this example.

    int startingLineNumber = infologLine();

    //run some base process
    //We will pretend the base method called these infolog messages.
    info("Example info message");
    Warning("Example warning message");
    Error("Example error message");

    CustomLog::logMessages(startingLineNumber, 111);

Finally, check the records inserted into the CustomLog table.

Save Thrown Error Messages

In addition to simply saving info, warning, and error messages, sometimes error messages are thrown by the underlying base code.

For example, if you have code that tries to insert a record that already exists, the system will throw an error. There is no explicit call to the error() method in the code that you can see. But an error is thrown anyway.

Similarly, there are other scenarios where a runtime error may be thrown. For example, if the code tries to divide by zero, the underlying D365 code will throw an error. The ability to save error messages in D365 like these is incredibly helpful for users trying to figure out what happened.

See this example:

int startingLineNumber = infologLine();

//Because there already exists a customer with this account
//this should cause a duplicate key error to be thrown
try
{
    custTable.AccountNum = "004003";
    custTable.insert(); 
}
catch
{
    error("Some error was thrown when inserting a customer");
}

CustomLog::logMessages(startingLineNumber, 0);

Different Ways To Handle Errors

Now, in many use cases, there isn’t a need to save error messages in D365. For example, a user might click a button on a form, and if an error message is shown on the screen it is very clear which record the error message is referring to. However, there are many scenarios where the system is asked to process many records simultaneously. In these cases, there are usually two ways the system should handle error messages.

First, if an error occurs, the system could stop and roll back all the work it has done so far. For example, if the system is invoicing one sales order, and it encounters a problem with one sales line in the order, it should stop and go back to the previous state. It would not usually make sense to invoice some of the sales lines, because the records are dependent and need to be processed at the same time.

Second, if an error occurs with one record, the system could catch the record and then continue processing any additional records in a queue. For example, perhaps hundreds of sales orders have come into a D365 staging table from an e-commerce website. Then, a batch job processes each record and turns it into a sales order. If an error occurs, the system should continue processing the other records in the staging table, since they are not dependent on each other.

To support the second scenario, developers need to write code that will catch and save error messages in D365, linking them to the record that resulted in the error. Then, a user can later review these messages and make any needed corrections before reprocessing the record.

Example

In order to test how to save error messages in D365, try out this runnable class (job) for yourself.

internal final class testLogErrorMessages
{
    public static void main(Args _args)
    {
        CustTable custTable;
        int startingLineNumber = infologLine();
        str allMessages;

        custTable = testLogErrorMessages::someProcess();

        allMessages = CustomLog::logMessages(startingLineNumber, custTable.RecId);
    }

    static CustTable someProcess()
    {
        CustTable custTable;
        int numerator, denominator, total;

        //create some messages
        info("Some info message");
        warning("Some warning message");
        error("Some error message");

        //Run a process with warning messages in the code.
        custTable.validateWrite();
        
        //Because there already exists a customer with this account
        //this should cause a duplicate key error to be thrown
        try
        {
            custTable.AccountNum = "004003";
            custTable.insert(); 
        }
        catch
        {
            error("Some error was thrown when inserting a customer");
        }

        //Try a Divide By Zero Error
        try
        {
            numerator = 5;
            denominator = 0;
            total = numerator / denominator;
        }
        catch
        {
            error("Some error was thrown diving by zero");
        }

        return custTable;
    }

}

After running the code, review the CustomLog table to see the error messages that were record.

Microsoft Documentation

For more information on the InfologLine, see Microsoft’s documentation here.

Also note that Microsoft has developed a new messaging system. See the documentation here. The info(), warning(), and error() methods are still supported.

Conclusion

When you write a process that is entirely custom code, you can print messages to the screen and save them to a log table. When base Microsoft code runs and there are info, warning, and error messages, you can still save error messages in D365. The messages can be scraped from the info log table and saved to a custom log associated with the processed record for easier processing later. Furthermore, when the system throws runtime errors, these can also be saved. This makes it much easier to have processes that continue working on other records, even after experiencing an issue with one record.

Peter Ramer
Peter Ramer is a part of the Managed Application Services team at RSM working on Microsoft Dynamics 365. He focuses on the Retail and Commerce industries. When he is not solving problems and finding ways to accelerate his clients' business, he enjoys time with his three kids and amazing wife.

Share this:

Leave a Reply

Your email address will not be published. Required fields are marked *

Proudly powered by WordPress | Theme: Baskerville 2 by Anders Noren.

Up ↑