14

I'm really wrestling with the best error handling strategy. My goal is to handle all exceptions that I can identify, log them to a table in the database, and surface a helpful message about what happened to the user.

The problem

I am using an auth provider for SSO OAuth from one Salesforce instance to another. It's generally working fine but the registration handler has some complex logic, including a callout to another org for verification of some data. Sometimes the callout fails for a few different reasons and there are a handful of other infrequent but notable registration errors that we wish to log. The problem is that without throwing a custom exception, there is no way to communicate to the error handler page for the auth provider what happened, not that I see at least. Handled errors fail silently. When I throw an exception in my reg handler, whatever logging I had attempted gets rolled back.

What I've tried

I tried logging and then throwing the exception in the catch block in a bunch of places in my registration handler class. As I mentioned, this was a no go since the logging got rolled back.

I've also tried not throwing the exception but then some of my logic in the reg handler gets subverted/ignored. I have yet to try rolling back to a savepoint in my catch blocks following known and handled errors, and this may solve this issue, although I wouldn't be able to surface anything helpful to the user at this point.

Questions

When would I use the Finally block? Can it be used for any purpose when throwing my own exception or would whatever logging I did get rolled back.

Is there any way to force custom logging and save to the database while also throwing an exception?

What other strategy should I try here?

Finally, how do you think this should work? It seems like there is some logging path that you should be able to continue down in apex, even after an exception and a rollback.

4 Answers 4

9

First please take a look at this answer for a partial solution / approach, Can I prevent an insert to be rolled back even if my class (later) runs into an exception?.

Basically as per the Apex Developers guide the final commit transaction management is baked into Apex, meaning it will always rollback if you let the platform handle your exceptions, if you don't you have to manage the rollback scope yourself or accept any DML that has been issued be committed.

If the entire request completes successfully, all changes are committed to the database. For example, suppose a Visualforce page called an Apex controller, which in turn called an additional Apex class. Only when all the Apex code has finished running and the Visualforce page has finished running, are the changes committed to the database. If the request does not complete successfully, all database changes are rolled back

The only way to get control of this is to use the try/catch/finally semantics, along with Savepoints, and critically not throwing any exception (note that as per the answer above some system exceptions cannot be caught sadly). Since your implementing the Auth.RegistrationHandler interface your stuck with its method signature and thus cannot code any alternative way to pass errors back, not does there appear to be any support for this on the interface itself.

Passing a null user back from createUser. What happens if you using try/catch/finally with a logging approach as above and then null back? I expect the system will handle this and present some general error message. Of course in this case your more detail errors will have been logged, since technically the call completed successfully. You would of course have to educate your users / admins to check your own log in this case. Given the frequency of the issues, perhaps this is tolerable? As regards the updateUser method, i don't see any way to infer errors with a successful execution of the method.

Summary and System.debug? Sorry this is not the answer you wanted, but i believe this is the situation. Your not the first to want to implement such a logging approach as you can see. Ultimately its a matter of how far your prepared to go against the transaction management semantics of the platform vs resorting to having your Admins enable Debug Logs and adding System.debug output to your code to output messages to the standard platform log that can then be reviewed for your errors.

2
  • Thanks for the comprehensive response. I think my only option is going to be to throw exceptions in the updateUser method as nulling out the user does not appear to work there, which leaves me back where I started.
    – greenstork
    Commented Nov 2, 2013 at 13:49
  • I dug into this and discovered quite a bit about this interface and came up with a creative work-around, which I'll post an answer.
    – greenstork
    Commented Nov 6, 2013 at 0:16
8

As a follow up to this thread and to answer this question should anyone happen up this, here is what I discovered:

  • The Auth.RegistrationHandler interface at the heart of this question has two main methods, as Andy noted, createUser() and updateUser(). The former returns a user and the latter is a void method.
  • I tried Andy's suggestion and pattern of try/catch/finally to log errors. This actually works fine for the updateUser method, since it is a void method.
  • For the createUser method however, I tried to rollback in the catch block and log in the finally block but the rollback of createUser() passes a null or empty user back to the Auth.RegistrationHandler interface, which then causes an unhandled exception in the "parent" class to occur. The exception is Argument 1 cannot be null. Since this is an unhandled exception, the entire transaction including my logging gets rolled back.

The solution that I've come up with is to catch all exceptions, then construct the stack trace and other important debug information into a new custom exception adding all of that info to the exception message, which I then throw. In my Auth Provider error page, I take the custom exception that I've thrown, which includes the stack trace and other useful info off the querystring and parse and log it there, as well as displaying what I want of that message (not including the stack trace) to the end user on that page.

1
5

Usually, I use the finally after catching several different exception types, to reset variables used in a processing loop. As far as your logging concept, it is a good idea. There are two ways you can create records in the context of an exception. One would be to use partial processing dml instead of "all or nothing". This is done by using the "database" version of the dml. For example database.insert(my account list, false); The false allows partial processing. Then you can iterate over the results from the dml looking for exceptions. When you use partial processing you can still create records in your catch block. The other option, which may be better for your with scenario, would be to create a separate logger class and method, configured as a future method. Pass the error or list of errors to the future method and the logging records can be created out of the transaction context of the exception.

Hope that helps you out. Jim

2
  • 3
    Future methods also roll back in the event of an unhandled exception.
    – sfdcfox
    Commented Nov 2, 2013 at 2:44
  • My solution considers catching all exception types, which would allow the logging records to be saved.
    – JimRae
    Commented Nov 2, 2013 at 18:30
2

I encountered this exact issue recently and figured I would post my solution for anyone who finds this thread in future.

The solution I came up with was to publish a Platform Event before throwing an exception from the createuser method. The Platform Event needs to be set to a Publish Behavior of 'Publish Immediately' so that it publishes even when the apex transaction encounters an exception. Then you can use a Platform Event Triggered flow or trigger to actually do the logging.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .