Question
· May 12

Error Handling Server to Client Side - Best Practices

There have been some really good, helpful posts on this already:

https://community.intersystems.com/post/robust-error-handling-and-cleanu...

https://community.intersystems.com/post/registering-new-error-code-and-e...

https://community.intersystems.com/post/objectscript-error-handling-snip...

https://community.intersystems.com/post/can-you-have-custom-status-objec...

I'm trying to put this all together and determine what is the best convention for handling errors, logging them and reporting them to the user as they navigate a web application or command line program.  

In my own evolution as a ObjectScript developer, I've tried to be more consistent for class methods that return %Status by 

- Starting each method with setting st=$$$OK

- Using a try/catch block

- Check if {somethingsWrongOrNotRight} $$$ThrowStatus($$$ERROR($$$GeneralError,"My custom error message.")) to throw the error handling into the catch block

- Log the error to our error table in the catch block using $System.Status.GetErrorText(thrownError.AsStatus()) to get the error message as defined in the bullet above and then s st=thrownError.AsStatus()

- End the method with q st

I don't know if I modeled it quite right, but I took this from studying method implications of InterSystems library methods in various classes. It works quite well especially using backend processes.  However, I am continually challenged on the best ways to get the correct error message to the client.  A few thoughts:

- Often the error message I am reporting in the audit/error log is not the one I want to display to the client 

- Sometimes, especially if implementing an API, I want to handle or consider the status code of the HTTP response.

- We work with CSP pages, REST services (building our own and implementing others), and command line applications.

- Considering the articles and advice above, what's the best way to generate custom errors that we can log to the audit log but then also format 'happy text' that we may need to show the user but also not go through the process of creating a whole new library of custom errors for tons of little validation things along the way.

A use case:

We are implementing an API that tends to have intermittent availability errors and we also call it using a session token that expires.  So if we call the API and it's a 502, we just want to stop the presses and try again later.  If we call it and it's a 403, we want to refresh the token and try again.  In the implementation method I will $$$ThrowStatus($$$ERROR($$$GeneralError,HTTPSRequest.HttpResponse.StatusCode_" "_HTTPSRequest.HttpResponse.Data.Read())) if the code is not 200 or whatever we expect as successful. 

This is good, robust info to add to the audit log so we do that in the catch block, but I still have to return a status.  502 Gateway error is not useful to the client or even to my logic that called it so I find myself doing something like if $$$ISERR(st) { if $SYSTEM.Status.GetErrorText(st)[502 {//handle that} elseif $SYSTEM.Status.GetErrorText(st)[403 {//handle this}}

I suppose this works, but it feels hacky to me.  I would prefer setting a custom error code so I didn't have to rely on the string analysis to determine what to do.  But I don't feel it's necessary to create a whole new custom error code in a library for this one off reason.  Further, if this were a method that was returning something to a client now I have an additional step of checking the string and handling the audit but also now crafting a display message to the user "The service is currently unavailable.  Please try again later or call technical support."

That $$$ThrowStatus() macro compiles to ##safeexpression($$macroERROR^%occMsgXML(%literalargs)) and I don't know from looking at that if my message could be an object so that I have something like {"errorCode": "API502", "errorAudit": "Gateway error", "errorDisplay": "The service is currently unavailable.  Please try again later or call technical support."}.  I can view the %occStatus routine but I cannot find the %occMsgXML routine to examine what I can or can't pass into this.  That said, I haven't seen this as a recommended solution anywhere, so I'm sure that's wrong.  Does anyone have a good example of how they use a %Status to report an error to a client gracefully?

At the end of the day I think I'm trying to fit square pegs into round holes, so maybe someone can set me straight on some best practices here.

Discussion (1)1
Log in or sign up to continue

I'm not sure there's any getting around having to check to the HTTP status entirely, but you could log an exception without throwing it, then throw a more user-friendly exception to your catch block to be returned. It would also get logged, but that's probably okay as long as whoever is looking at the system logs knows that will happen.

//Log this one
do ##class(%Exception.StatusException).CreateFromStatus($$$ERROR($$$GeneralError,HTTPSRequest.HttpResponse.StatusCode_" "_HTTPSRequest.HttpResponse.Data.Read())).Log()
//Throw this one
if HTTPSRequest.HttpResponse.StatusCode = 502{
    throw ##class(%Exception.StatusException).CreateFromStatus($$$ERROR($$$GeneralError,"The service is currently unavailable. Please call tech support or try again later."))
}

Then some elseif blocks for whatever other HTTP statuses you want to handle, then a final "else" at the bottom to throw something generic to catch anything else.

That at least gets the custom handling out of the catch block so it isn't processed for every exception.