8

As I am otherwise unable to debug a long running batch process in a customer org (logs are lost when I am not logged in all the time via subscriber support) of our managed ISV package, I tried to send the exception via email.

The code looks like this:

try {
   Integer a =  3/ 0;
}
catch(Exception e) {
    notifyDevelopersOf(e, 'MyClass');
    throw e; // Email is only sent when this line is commented out!
}

...

public static void notifyDevelopersOf(Exception e, String locationName) {
    String orgId = UserInfo.getOrganizationId();
    String orgName = UserInfo.getOrganizationName();
    String user = UserInfo.getUserName();
    String message = e.getMessage();
    String stacktrace = e.getStackTraceString();
    String exType = '' + e.getTypeName();
    String line = '' + e.getLineNumber();
    String theTime = '' + System.now();

    String subject = String.format('Exception thrown at {0} by user {1} in org {2} (Id: {3})', new List<String>{ locationName, user, orgName, orgId });
    String body = String.format('Time: {0}\nMessage: {1}\nStacktrace: {2}\nLine: {3}', new List<String>{ theTime, message, stacktrace, line });

    Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
    String[] toAddresses = new String[] {'developers@acme.com'};
    mail.setToAddresses(toAddresses);
    mail.setSubject(subject);
    mail.setUseSignature(false);
    mail.setPlainTextBody(body);

    Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail });
}

The email is only sent out when I DO NOT rethrow the exception in the catch. It even does not work when I send and rethrow in a finally clause.

I need to rethrow the exception because I want the batch to fail and have error messages in the common places as well.

EDIT: Even making the method that sends the email @future or calling it from finally doesn't work.

8

As per the documentation of Outbound Email:

The email is not sent until the Apex transaction is committed.

An unhandled exception will cause the Apex transaction to roll back, cancelling the e-mail. A solution for you would be to mark the notifyDevelopersOf() function with @future. This will create a new request and transaction for the execution function. Note that you cannot make more than 10 calls to a future method per request.

EDIT; After some fiddling about I've found out that future methods won't be executed too if the transaction fails. So using future methods will not help you. As far as I can tell, there is really no way to get around the fact that e-mails will not be sent if the transaction isn't commit. Is there any way you can make it work without rethrowing the exception? Maybe using addError(), or, if you are using VF ApexPages.addMessage(myMsg); I'm not 100% sure that will work, but it could be worth trying.

Another edit; You can make an HTTP callout from the catch that will not be cancelled if the exception is rethrown, something like this:

try {
    Integer a = 3/0;
} catch(Exception e){
    //set the url
    String url = 'http://www.mydomain.net/SendEmail?param1=value1';
    HttpRequest req = new HttpRequest();
    req.setEndpoint(url);
    req.setMethod('GET');

    Http http = new Http();
    HTTPResponse res = http.send(req);

    throw e;
}

A major drawback of this solution is that the client needs to add your domain name in the list of trusted remote site in their environment.

|improve this answer|||||
  • As I am passing a non primitive type I made the email sending future but not the creation of the message strings. And it did not work even with future... – Robert Sösemann Jul 8 '13 at 11:58
  • I also change the code so the notifgication is done in a finally clause. Should the finally be called in any cases EVEN if the transaction is commited?! – Robert Sösemann Jul 8 '13 at 12:00
  • 1
    Finally will always be executed. I'm gonna try it with a future method. – rael_kid Jul 8 '13 at 12:03
  • 1
    Hm, it seems that the context of future methods is not as seperate as I thought. – rael_kid Jul 8 '13 at 12:25
  • No problem, I've updated my answer... Couldn't get it to work :( – rael_kid Jul 8 '13 at 12:51
2

When an exception is thrown, none of the work you did during that try{} block is kept, which includes sending an email.

One way to get around this is to use custom Exceptions and different catch() blocks on your main try/catch to handle different exceptions differently:

    class ContactUpdateException extends Exception {}
    class OpportunityUpdateException extends Exception {}

    public PageReference tryExample()
    {
        ...
        try
        {
            try
            {
                update listOfContacts;
            }
            catch (Exception e)
            {
                throw new ContactUpdateException('Contact Update Exception: ' + e.getMessage());
            }

            try
            {
                update listOfOpportunities;
            }
            catch (Exception e)
            {
                throw new OpportunityUpdateException('Opportunity Update Exception: ' + e.getMessage());
            }

        }
        catch (ContactUpdateException e)
        {
            //send email or do whatever for this kind of exception
        }
        catch (OpportunityUpdateException e)
        {
            //send email or do whatever for this kind of exception
        }
        catch (Exception e)
        {
            //handle all other kinds of exceptions
        }
    }
|improve this answer|||||
  • My question was never about work in a try but in a catch or finally and those were solely made for that purpose. – Robert Sösemann Jul 8 '13 at 17:22
  • 1
    I am suggesting that you wrap that catch inside an outer try/catch, so that you can handle your exceptions within your code. This will allow you to complete your processing. The point is that everything within a transaction in which an unhandled exception appears is discarded. The only way to preserve information like that is through the exception itself, as in my code above. – Jeremy Nottingham Jul 8 '13 at 19:25
2

If your DML is working and you get an exception on something else (null pointer) then

  1. you could set a Savepoint at the start of each execute(),
  2. wrap the entire execute code block with a try-catch
  3. in the catch, send the email, set a global variable to 'failed' and then do a rollback
  4. Use global variables in the class to communicate status between each execute invocation
  5. In your finish(), examine the global variables and output a batch status message to your favorite place (email, special SObject, ...)
|improve this answer|||||
  • Is finish always called? Even when errors occur or jobs get auto aborted? – Robert Sösemann Jul 13 '13 at 11:56
  • good question, i don't know -- you could try an easy experiment in your execute() by doing something that try{} catch{} won't catch - like exceeding a limit (e.g do a for loop of 200,000 statements). This would let you see if finish() is called. – cropredy Jul 15 '13 at 15:58
2

Note that this wrapping approach does not work if you are using addError() to tell the user about something. The addError() or setMessage() still leaves the transaction uncommitted, even if you've put that in the wrapped area, so the email message won't go out.

|improve this answer|||||
  • 1
    This looks more like a comment, but with only a rep of 1 you couldn't post it there. We will move it - meanwhile - go get some points :) Welcome to SFSE! – Kelly J Andrews Dec 3 '14 at 0:53
  • 1
    Modified it so it wasn't just a comment, added info from subsequent experiments. – David Taber Dec 3 '14 at 2:08

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.