Recipes by Category

App Distribution (2) Bundle logic, interface and services for distribution. App Logic (37) The Apex programming language, workflow and formulas for logic. Collaboration (6) The Salesforce Chatter collaboration platform. Database (29) Data persistence, reporting and analytics. Integration (33) Web Service APIs and toolkits for integration. Security (9) Platform, application and data security. Tools (4) Force.com tooling User Interface (36) Visualforce MVC and metadata-drive user interfaces. Web Sites (12) Public web sites and apps with optional user registration and login.
Beta Feedback
Cookbook Home » Using Inbound Email Services and Regex to Monitor Apex Error Emails

Using Inbound Email Services and Regex to Monitor Apex Error Emails

Post by kbromer  (2011-03-17)

Status: Unverified
Level: intermediate

Problem

You want to accept, parse, store and analyze unhandled Apex exception emails as error messages in the context of your environment, perhaps even integrating License Management Organization (LMO) information.

Solution

The general solution has 3 steps:

  • You need to create an email service class that will parse incoming exception emails.
  • You need to hook that class up to a new inbound email service, and make sure that this new email address is used for all exceptions.
  • You need to create a simple object to store the data you parse from the exception emails, which lets you create dashboards etc.

This recipe looks at the second step in detail. Read An Introduction to Email Services on Force.com to learn how to create an email service. Setup forwarding from the email address currently receiving unhandled Apex exception emails to the unique address of the Inbound Email Service you create.

You'll also need to create an object with fields to store the exception data you parse. The code below uses one called Error_Message__c.

You then need to create a class implementing Messaging.InboundEmailHandler, mapping this class to the email address. Now, whenever an error email is sent, the handleInboundEmail method on that class will be invoked.

Your class will need to implement the standard interface:

global class NPSPErrorProcessor implements Messaging.InboundEmailHandler {
      
    global Messaging.InboundEmailResult handleInboundEmail(Messaging.InboundEmail email, Messaging.InboundEnvelope envelope) {
 

Using the InboundEmailResult object, you can parse and store the body of the email using regular expressions (regex) and Apex string methods to create and fill our custom object. First though, we use Apex string methods to find basic information about our message

        Messaging.InboundEmailResult result = new Messaging.InboundEmailresult();
      
        //create a new instance of our custom object to hold the error data
        Error_Message__c em = new Error_Message__c(Message__c = email.plainTextBody);
        
        //convert email body to lowercase to avoid case mismatches
        string lcEmailBody = email.plainTextBody.toLowerCase().trim();
               
        //First, determine the error type
        string emErrorContext = '';        
        if (lcEmailBody.contains('custom_validation'))        
            emErrorContext = emErrorContext + 'Validation Error;';
        if (lcEmailBody.contains('apex script unhandled trigger exception'))
            emErrorContext = emErrorContext + 'Apex Trigger;';
        if (lcEmailBody.contains('batch'))
            emErrorContext = emErrorContext + 'Batch Apex;';
        if (lcEmailBody.contains('visualforce'))
            emErrorContext = emErrorContext + 'Visualforce;';
        if (lcEmailBody.contains('apex script unhandled exception'))
            emErrorContext = emErrorContext + 'Apex Class;';
        //Finally, if we haven't found anything, dump it in 'other'
        if (emErrorContext.length() < 2)
            emErrorContext = 'Other;'; 
        
        //Place the Error Context text into our custom object em
        em.Error_Context__c = emErrorContext;

You can now define a regular expression string that identifies the patterns in the email you want to match. Once you've created the string, create a new Pattern object and call the static Pattern compile method, passing in our regex string. Finally, create a new Matcher object to hold the results, and call the Pattern instance method matcher, passing the body of the email as the parameter.

  string regex = '005[A-Za-z0-9]{12}+/(00D[A-Za-z0-9]{12}+)[\\r\\n]+(.+?)\\r\\n]+caused by:[\\s]*System\\.([^:]*)[\\s]*:(.+)[\\r\\n]+(?:(?:Class)|(?:Trigger))\\.([\\w]*)\\.([\\w]*)\\.?([\\w]*):';
  Pattern emailPattern = Pattern.compile(regex);
  Matcher emailMatcher = emailPattern.matcher(email.plainTextBody);

This regular expression will parse the body of the exception email and capture the following items from the error message:

  • Start: "005[A-Za-z0-9]{12}+/" Start by finding 005, a user record, then the first 12 alphanumeric characters and a slash. We won’t capture this in a group, since our UserID doesn’t help us. This allows us to ignore any header info in the email.
  • Group 1: "(00D[A-Za-z0-9]{12}+)" Starting with 00D, an orgID, grab the next 12 alphanumerics and make that our first grouping. In other words, the 15 character OrgID.
  • Group 2: "(.+?)" Traps for periods, this is a throwaway grouping to make sure we’re grabbing the right stuff.
  • Group 3: "([^:]*)" after finding the ‘System\\.’ text with a period, we want to grab the text right after that. In other words, the exception type, excluding the colon. In the error message above, that would mean: "DMLException".
  • Group 4: "(.+)" We want to grab everything between the first colon after the exception type, and a new line character. In other words, the ‘short message’. In the example above we’d start with ‘Update Failed’ and grab everything up to and including ‘[npe01__PreferredPhone__c]’.
  • Group 5: "(?:(?:Class)|(?:Trigger))" Starting with either ‘Class’ or ‘Trigger’, grab the items between that word and the next period. In this case, our namespace, or ‘npo02’ from the above example.
  • Group 6: "([\\w]*)" Now get everything after the period, but before the next one, in other words, our class name: ‘OpportunityRollups’ from above.
  • Group 7: "([\\w]*)" Same as above, after the next period. In other words, our method name: ‘rollupContacts’.
  • Finally, we have group 8, which we don’t capture in the error processor class below, but you could use in your implementation. This is the final "([\\w]*)", which would capture the line information: ‘line 667, column 42’.

Note how the expression stores each parsed set of information in a regular expression group (given by the number next to item above). You could expand this to include other available information, such as User ID.

The regex string above is the actual production string used for the Nonprofit Starter Pack. You may need or want to adjust the values if you're not using namespaces or a managed package.

With the regular expression in had, you can check the results from the Matcher object, and using the group method, assign results to the custom object. In this case, we use SOQL to identify associated License and Account objects in the LMA (optional), and store the rest of the data in our error_message__c object.

if (emailMatcher.find()){ 
           
  sfLma__License__c errorLicense = [select id,  
            sfLma__Package_Version__r.sfLma__Package__r.id, 
            sfLma__Package_Version__r.id from sflma__License__c where 
            sfLma__Subscriber_Org_ID__c = :emailMatcher.group(1).trim() and 
            sflma__Status__c = 'Active' and 
            sfLma__Package_Version__r.sfLma__Package__r.Namespace__c = 
            :emailMatcher.group(5).trim()];
  em.License__c = errorLicense.id;       
  em.Package__c = errorLicense.sfLma__Package_Version__r.sfLma__Package__r.id;
  em.Package_Version__c = errorLicense.sfLma__Package_Version__r.id;
  em.Account__c = [select id from Account where Organization_ID__c = 
            :emailMatcher.group(1).trim()].id;
  em.Exception_Type__c = emailMatcher.group(3).trim();        
  em.Short_Message__c = emailMatcher.group(4).trim();    
  em.Class_Name__c = emailMatcher.group(6).trim();       
  em.Method_Name__c = emailMatcher.group(7).trim();
  
  insert em;
}      

} //close class

The group method takes an integer corresponding to the regex pattern from our regex string to identify which data we want from the Apex error email. When complete, we insert our new error message object.

Once you have all your exceptions recorded, you can use the data to create dashboards and detail views to monitor an instance or package eco-system.

Discussion

  • The regex above will parse most Apex exception emails, including namespaces
  • When configuring your Inbound Email Services, it's recommended that you not bounce error message back to the sender. If your class doesn't handle a message cleanly (for example: not having a valid parent-child lookup), it should be invisible to your end user
  • Make sure you do not add workflow or validation rules to your LMA License object as this can cause License creation to fail
  • This can also be implemented without regex using the available static and instance string methods in Apex, but is more verbose and inefficient

References

Share

Recipe Activity - Please Log in to write a comment

Thats nice idea for tracking exceptions via email and logging them back.

by Jitendra Zaa  (2011-11-18)

X

Vote to Verify a Recipe

Verifying a recipe is a way to give feedback to others and broaden your own understanding of the capabilities on Force.com. When you verify a recipe, please make sure the code runs, and the functionality solves the articulated problem as expected.

Please make sure:
  • All the necessary pieces are mentioned
  • You have tested the recipe in practice
  • Have sent any suggestions for improvements to the author

Please Log in to verify a recipe

You have voted to verify this recipe.