In my current project, I have a requirement where I have to implement a custom logging framework. Salesforce put the debug/exception log with System.debug() statement, but going through that debug log to identify the issue is always a challenge. So the better approach is to store the exception as soon as it occurred somewhere so that later point we can always refer that to identify what happened.

In order to achieve that below is what I did-

First I have created an object(API Name: Custom_Log__c) to store the exception logs-
Below are the custom field inside the object-

  • Class__c(Text Area(255))   From which class the exception is coming
  • Method__c(Text Area(255))  From which method the exception is coming
  • Message__c(Long Text Area(32768))  Stack trace of the exception
  • Priority__c(Picklist) – Possible values are DEBUG, ERROR, FATAL, INFO, WARNING
Now I need to create the below custom settings –
EnableCustomLogging with possible values true/false. 
The reason behind is that with EnableCustomLogging = true, exceptions will be stored in the custom object i.e. Custom_Log__c and with EnableCustomLogging = false, exception will be stored in the debug log with System.debug() 
Now with this setup, I have created the visual page.
Source Code:

<apex:page controller="CustomLogController">
<apex:form >
<apex:commandButton value="Divide By Zero Exception!" action="{!divideByZeroException}" />
<br/>
<apex:commandButton value="Null Pointer Exception!" action="{!nullPointerException}" />
</apex:form>
</apex:page>

Now the controller – CustomLogController:

public class CustomLogController {
public void divideByZeroException(){
Integer number1, number2;
try{
number1 = 10;
number2 = 0;
Integer number3 = number1/number2;
}catch(Exception ex){
String message = 'Error: ' + ex.getMessage() + '; Stack Trace:' + ex.getStackTraceString();
CustomLogging.logMessage('CustomLogController', 'divideByZeroException', message, CustomLogging.WARNING);
}
}

public void nullPointerException(){
Contact aContact = null;
try{
String email = aContact.Email;
}catch(Exception ex){
String message = 'Error: ' + ex.getMessage() + '; Stack Trace:' + ex.getStackTraceString();
CustomLogging.logMessage('CustomLogController', 'nullPointerException', message, CustomLogging.ERROR);
}
}
}

Now the CustomLogging class-

public class CustomLogging {

public static String INFO = 'INFO';
public static String DEBUG = 'DEBUG';
public static String WARNING = 'WARNING';
public static String ERROR = 'ERROR';
public static String FATAL = 'FATAL';

private static Boolean isEnabled(){
Decision_Object__c enableCustomLogging = Decision_Object__c.getInstance('EnableCustomLogging');
System.debug('IsEnabled: ' + enableCustomLogging.Value__c);
return enableCustomLogging.Value__c;
}

public static void logMessage(String className, String methodName, String message, String Priority){
if(isEnabled()){
Custom_Log__c newLogMessage = new Custom_Log__c(
Class__c = className,
Method__c = methodName,
Message__c = message,
Priority__c = Priority);
try{
Database.insert(newLogMessage);
}catch(Exception ex){
System.debug(
'Failed to INSERT the [Apex Debug Log] ADL record. ' +
'Error: ' + ex.getMessage()
);
}
}else{
String completeErrorMessage = 'Error occured at class: ' + className + ' method: ' + methodName +
' and the error is: ' + message;
System.debug(completeErrorMessage);
}

}
}

And finally the test class – This is very important

@isTest
private class UnitTestCustomLogController {

private static void setEnableCustomLoggingFlag(Boolean value){
insert(new Decision_Object__c(Name='EnableCustomLogging', Value__c=value));
}

static testMethod void testWhetherCustomLoggingIsGettingCreatedWhileDisabled() {
setEnableCustomLoggingFlag(false);
CustomLogController customLogController = new CustomLogController();
customLogController.divideByZeroException();

Integer numberOfCustomLogs= [Select count() From Custom_Log__c];
System.assertEquals(0, numberOfCustomLogs);
}

static testMethod void testDivideByZeroException(){
setEnableCustomLoggingFlag(true);
CustomLogController customLogController = new CustomLogController();
customLogController.divideByZeroException();

Integer numberOfCustomLogs= [Select count() From Custom_Log__c];
Custom_Log__c customLog= [Select Class__c, Priority__c, Method__c, Message__c From Custom_Log__c LIMIT 1];
System.assertEquals('CustomLogController', customLog.Class__c);
System.assertEquals('divideByZeroException', customLog.Method__c);
System.assertEquals(CustomLogging.WARNING, customLog.Priority__c);
}

static testMethod void testNullPointerException(){
setEnableCustomLoggingFlag(true);
CustomLogController customLogController = new CustomLogController();
customLogController.nullPointerException();

Integer numberOfCustomLogs= [Select count() From Custom_Log__c];
Custom_Log__c customLog= [Select Class__c, Priority__c, Method__c, Message__c From Custom_Log__c LIMIT 1];
System.assertEquals('CustomLogController', customLog.Class__c);
System.assertEquals('nullPointerException', customLog.Method__c);
System.assertEquals(CustomLogging.ERROR, customLog.Priority__c);
}
}

Done. That’s all.
So now if I click on any of the buttons in VisualForce page and EnableCustomLogging = true, I can see one record is getting inserted into the Custom_Log__c object like below-

If you have any feedback, please share. Thanks.