Skip to content

Commit

Permalink
chore: add a sf demo project
Browse files Browse the repository at this point in the history
  • Loading branch information
tomcarman committed May 7, 2024
1 parent 9163b7f commit 007e542
Show file tree
Hide file tree
Showing 44 changed files with 1,306 additions and 0 deletions.
16 changes: 16 additions & 0 deletions force-app/main/default/applications/Logger_Demo.app-meta.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<CustomApplication xmlns="http://soap.sforce.com/2006/04/metadata">
<brand>
<headerColor>#0070D2</headerColor>
<shouldOverrideOrgTheme>false</shouldOverrideOrgTheme>
</brand>
<formFactors>Small</formFactors>
<formFactors>Large</formFactors>
<isNavAutoTempTabsDisabled>false</isNavAutoTempTabsDisabled>
<isNavPersonalizationDisabled>false</isNavPersonalizationDisabled>
<label>Logger Demo</label>
<navType>Standard</navType>
<tabs>Log_Event__c</tabs>
<uiType>Lightning</uiType>
<utilityBar>Logger_Demo_UtilityBar</utilityBar>
</CustomApplication>
34 changes: 34 additions & 0 deletions force-app/main/default/classes/FlowException.cls
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/**
* @author elganellis
* @description Custom exception for errors executed in flows.
*/

public with sharing class FlowException extends Exception {

public String flowName;
public String failingActionName;
/**
* @description Constructor
* @param faultMessage the fault message produced by the flow when the error was encountere
* @param flowName the name of the flow that reported the error
* @param failingActionName the name of the action that triggered the error
*/
public FlowException(String faultMessage, String flowName, String failingActionName) {

this(faultMessage,flowName);
this.failingActionName = failingActionName;

}
/**
* @description Constructor
* Used for creating flow exceptions without the name of the causing action
* @param faultMessage the fault message produced by the flow when the error was encountere
* @param flowName the name of the flow that reported the error
*/
public FlowException(String faultMessage, String flowName) {

this.flowName = flowName;
setMessage(faultMessage);

}
}
5 changes: 5 additions & 0 deletions force-app/main/default/classes/FlowException.cls-meta.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>54.0</apiVersion>
<status>Active</status>
</ApexClass>
44 changes: 44 additions & 0 deletions force-app/main/default/classes/InvocableFlowExceptions.cls
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@

/**
* @author elganellis
* @description class used to contain an invocable method 'log flow exceptions' to log errors in flows
*/
public with sharing class InvocableFlowExceptions {
/**
* @description Invocable input class
* @param faultMessage the fault message produced by the flow when the error was encountere
* @param flowName the name of the flow that reported the error
* @param failingActionName the name of the action that triggered the error
*/
public class MethodInput {

@InvocableVariable(required=true)
public String faultMessage;

@InvocableVariable(required=true)
public String flowName;

@InvocableVariable(required=false)
public String failing_action_name;

}
/**
* @description invocable method that takes detail of a flow error and logs it as a log event record
*/
@InvocableMethod( label = 'Log flow exception')
public static void LogExceptions(List<MethodInput> methodInputs) {

Logger exceptionLogger = Logger.get();

for (MethodInput input : methodInputs) {

FlowException newFlowException = new FlowException( input.faultMessage,input.flowName, input.failing_action_name);
exceptionLogger.add(newFlowException);

}

exceptionLogger.publish();


}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>54.0</apiVersion>
<status>Active</status>
</ApexClass>
198 changes: 198 additions & 0 deletions force-app/main/default/classes/Log.cls
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
/**
* @author tomcarman
* @description Base class for all Log types. Common methods/atrributes should get added here, whilst
specific implementations of different types of logs should extend this class - eg.
LogExceptn, LogDMLExceptn.
*/

public virtual class Log {

/**
* @description String that describes the type of Log
*/
protected String logType = 'Standard Log';


/**
* @description String of arbitrary data that forms the log. Recommended format is JSON, but can be anything.
* Limited to 131,072 characters
*/
protected String data;


/**
* @description Enum to represent the severity of the log - DEBUG, INFO, WARN, ERROR
*/
protected LogSeverity severity;


/**
* @description Used to store any single recordId, eg. the Id of the record that initiated the transaction
*/
protected Id recordId;


/**
* @description The internal Id of the Salesforce request (often called transaction) that is executing when this
* log is being created. Useful to correlate multiple logs created in a single execution.
*/
protected String requestId {
get {
if(requestId == null) {
requestId = Request.getCurrent()?.getRequestId();
}
return requestId;
}
protected set;
}


/**
* description The quddity value of the request that is executing when this log is being created.
*/
protected Quiddity quiddity {
get {
if(quiddity == null) {
quiddity = Request.getCurrent()?.getQuiddity();
}
return quiddity;
}
protected set;
}

/**
* description Logic to determine the "location" of where the log was raised - eg. MyClass.myMethod Line 1 Column 1
* There is currently no native apex method to do this, so instead an Exception is constructed and then
* the stacktrace is traversed with regex to extract the class and method.
* Note: changes to the implementation of the Logger/Log classes could require a change to the line noted
* below with an inline comment, as the stack trace could get deeper/shallower.
*/


protected String location {

get {

if(location == null) {

Map<String, String> locationMap = new Map<String, String>();

try {

List<String> stackTraceLines = new DmlException().getStackTraceString().split('\n');

String relevantLine = '';

if(stackTraceLines.size() >= 6) {
relevantLine = stackTraceLines[5];
} else if (stackTraceLines.size() >= 3) {
relevantLine = stackTraceLines[stackTraceLines.size()-2];
} else {
relevantLine = stackTraceLines[stackTraceLines.size()-1];
}

Matcher m = generateMatcher(relevantLine);

if (m.find()) {

if (String.isBlank(m.group(3))) {

locationMap.put('className', m.group(1));
locationMap.put('methodName',prettifyMethodName(m.group(2)));

} else {

locationMap.put('className', m.group(1) + '.' + m.group(2));
locationMap.put('methodName', prettifyMethodName(m.group(3)));

}

locationMap.put('line', String.valueOf(m.group(4)));
locationMap.put('column', String.valueOf(m.group(5)));
}

location = JSON.serializePretty(locationMap);

} catch (Exception e) {
// Allow this to silently fail and print a debug, as this can be a little finicky, and we
// dont want block the creation of logs.
System.debug('Failed to identify location for Log: ' + e);
}
}

return location;
}

protected set;
}



/**
* @description Default constructor - required to allow subclassing.
*/
public Log(){}


/**
* @description Constructor
* @param data an arbitrary string of data
*/
public Log(String data){
this(data, LogSeverity.INFO);
}

/**
* @description Constructor
* @param data an arbitrary string of data
* @param severity LogSeverity enum
*/
public Log(String data, LogSeverity severity){
this.data = data;
this.severity = severity;
}


/**
* @description Method to convert this log into a Log__e Platform Event ready for publishing
* Overridable to allow different implementations of Log.cls to create different types of
* Platform Event.
*/
public virtual SObject toEvent() {
return new Log__e(
Type__c = this.logType,
Severity__c = this.severity?.name(),
Record_Id__c = this.recordId,
Transaction_Id__c = this.requestId,
Quiddity__c = this.quiddity?.name(),
Location__c = this.location,
Data__c = this.data,
Running_User__c = UserInfo.getUserId()
);

}




/**
* @description Tidy up method names for constructors and getter/setters when using the location property
* @param name Raw method name from stack trace
*/
private String prettifyMethodName(String name) {
return (name == null) ? null :
name.replace('<init>', '(constructor) ')
.replace('__sfdc_', '(getter/setter) ');
}

/**
* @description Regex expression to extract class/method/line/column from a stack trace.
* @param firstLine Raw line of stack trace
*/
private Matcher generateMatcher(String firstLine) {
return Pattern.compile(
'(?i)^(?:class\\.)?([^.]+)\\.?([^\\.\\:]+)?[\\.\\:]?([^\\.\\:]*): line (\\d+), column (\\d+)$'
).matcher(firstLine);
}

}
5 changes: 5 additions & 0 deletions force-app/main/default/classes/Log.cls-meta.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>50.0</apiVersion>
<status>Active</status>
</ApexClass>
57 changes: 57 additions & 0 deletions force-app/main/default/classes/LogDMLExceptn.cls
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/**
* @author tomcarman
* @description Subclass of Log.cls to create Logs specific to DMLExceptions
*/
public class LogDMLExceptn extends Log {

/**
* @description An instance of a DML Exception
*/
private DMLException dmlEx;


/**
* @description Constructor
* @param dmlEx the DML exception to create the log from
*/
public LogDMLExceptn(DMLException dmlEx) {

this.dmlEx = dmlEx;
this.recordId = dmlEx.getDmlId(0);
this.logType = 'DML Exception Log';
this.data = buildData();
this.severity = LogSeverity.ERROR;

}


/**
* @description Helper method to extract details from the DML exception and format as JSON.
*/
private String buildData() {

Map<String, Map<String, String>> exceptionMap = new Map<String, Map<String, String>>();

Map<String, String> exceptionDetails = new Map<String, String>();

exceptionDetails.put('ExceptionType', dmlEx.getTypeName());
exceptionDetails.put('LineNumber', String.valueOf(dmlEx.getLineNumber()));
exceptionDetails.put('Message', dmlEx.getMessage());
exceptionDetails.put('StackTrace', dmlEx.getStackTraceString());

exceptionMap.put('Exception Details', exceptionDetails);

for (Integer i = 0; i < dmlEx.getNumDml(); i++) {
Map<String, String> dmlExceptionMap = new Map<String, String>();
dmlExceptionMap.put('Id', dmlEx.getDmlId(i));
dmlExceptionMap.put('Message', dmlEx.getDmlMessage(i));
dmlExceptionMap.put('StatusCode', dmlEx.getDmlStatusCode(i));
dmlExceptionMap.put('FieldNames', String.join(dmlEx.getDmlFieldNames(i), ', '));
exceptionMap.put('Row ' + String.valueOf(i), dmlExceptionMap);
}

return JSON.serializePretty(exceptionMap);

}

}
5 changes: 5 additions & 0 deletions force-app/main/default/classes/LogDMLExceptn.cls-meta.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>50.0</apiVersion>
<status>Active</status>
</ApexClass>
Loading

0 comments on commit 007e542

Please sign in to comment.