Tag Archives: Apex

Salesforce User Logout Event Trigger

  • Salesforce has introduced LogoutEventStream in Summer’18 release, to get the User logout events.
  • It is a beta feature and to make it available in your org, you need to contact Salesforce Support.
  • After it will available in your Org, you can enable LogoutEventStream.
  • After enabled LogoutEventStream, Salesforce publishes logout events when users log out from the UI.
  • You can add an Apex trigger to subscribe to those events, where you can implement our custom logic during logout.

Enable Logout Events Stream :
Go to Setup | Session Setting (Enter Session Setting in Quick Find) | In Logout Events section Check Enable Logout Events Stream Checkbox.

After enable Logout Events Stream, I have created an Apex trigger on LogoutEventStream object, where I’m saving the User Logout Event information in a Custom Object.

Custom Object:

Apex Trigger:

trigger LogoutEventTrigger on LogoutEventStream (after insert) {
    
    List<LogoutEventInfo__c> leInfoList = new List<LogoutEventInfo__c>();
    
    for(LogoutEventStream les : Trigger.new){
        LogoutEventInfo__c leInfo = new LogoutEventInfo__c();
        leInfo.EventIdentifier__c = les.EventIdentifier;
        leInfo.UserId__c = les.UserId;
        leInfo.Username__c = les.Username;
        leInfo.EventDate__c = les.EventDate;
        leInfo.RelatedEventIdentifier__c = les.RelatedEventIdentifier;
        leInfo.SessionKey__c = les.SessionKey;
        leInfo.LoginKey__c = les.LoginKey;
        leInfo.ReplayId__c = les.ReplayId;
        leInfo.SessionLevel__c = les.SessionLevel;
        leInfo.SourceIp__c = les.SourceIp;
        leInfoList.add(leInfo);
    }
    Insert leInfoList;
}

Now, you can Logout and Login again to check the Logout Event information in Custom Object.

Salesforce Custom Object to Save Logs

As a Salesforce developer sometimes we face different scenarios, where we need to track production error logs or failed records. To save custom logs, here I have created an object Log__c and a helper class LogHandler, which will help to save custom logs for future references.

Object (Log__c) Fields:

Field Label Field API Name Field Type
Class Class__c Text(255)
Method Method__c Text(255)
Description Description__c Long Text Area(32768)
Line Number Line_Number__c Number(8, 0)
HTTP Response HTTP_Response__c Long Text Area(32768)
HTTP Status Code HTTP_Status_code__c Number(6, 0)
Object Object__c Text(255)
Record Id Record_Id__c Text(255)
Type Type__c Picklist(Success, Error, Information)

Apex Class:

public class LogHandler
{
    //Save information log
    public static void logInformation(String className, String methodName, String description){
        Log__c log = new Log__c();
        log.Class__c = className;
        log.Method__c = methodName;
        log.Description__c = description;
        log.Type__c = 'Information';
        Insert log;
    }
    
    //Save success log
    public static void logSuccessData(String className, String methodName, String description){
        Log__c log = new Log__c();
        log.Class__c = className;
        log.Method__c = methodName;
        log.Description__c = description;
        log.Type__c = 'Success';
        Insert log;
    }
    
    //Save exception log
    public static void logException(String className, String methodName, String description, Integer lineNumber){
        Log__c log = new Log__c();
        log.Class__c = className;
        log.Method__c = methodName;
        log.Description__c = description;
        log.Type__c = 'Error';
        log.Line_Number__c = lineNumber;
        Insert log;
    }
    
    //Save HTTP response log
    public static void logHTTPResponse(String className, String methodName, String description, HttpResponse response){
        Log__c log = new Log__c();
        log.Class__c = className;
        log.Method__c = methodName;
        log.Description__c = description;
        log.Type__c = 'Information';
        if(response != null){
            log.HTTP_Response__c = response.getBody();
            log.HTTP_Status_code__c = response.getStatusCode();			
        }
        Insert log;
    }
    
    //Save result log
    public static void logSaveResult(String className, String methodName, List<Database.SaveResult> saveResultList){
        List<Log__c> logList = new List<Log__c>();
        for (Database.SaveResult sr: saveResultList) {        
            if (sr.isSuccess()) {                    
                Log__c log = new Log__c();
                log.Class__c = className;
                log.Method__c = methodName;
                log.Type__c = 'Success';
                if(sr.getId() != null){
                    log.Object__c = sr.getId().getSObjectType().getDescribe().getName();
                    log.Record_Id__c = sr.getId();
                }
                logList.add(log);                    
            }else{
                Log__c log = new Log__c();
                log.Class__c = className;
                log.Method__c = methodName;
                log.Type__c = 'Error';
                log.Description__c = String.valueOf(sr.getErrors()[0].getMessage());
                logList.add(log);  
            }
        }
        if(!logList.isEmpty()){
            Insert logList;
        }	
    }
    
    //Upsert result log
    public static void logUpsertResult(String className, String methodName, List<Database.UpsertResult> upsertResultList){
        List<Log__c> logList = new List<Log__c>();
        for (Database.UpsertResult ur: upsertResultList) {        
            if (ur.isSuccess()) {                    
                Log__c log = new Log__c();
                log.Class__c = className;
                log.Method__c = methodName;
                log.Type__c = 'Success';
                if(ur.getId() != null){
                    log.Object__c = ur.getId().getSObjectType().getDescribe().getName();
                    log.Record_Id__c = ur.getId();
                }
                logList.add(log);                    
            }else{
                Log__c log = new Log__c();
                log.Class__c = className;
                log.Method__c = methodName;
                log.Type__c = 'Error';
                log.Description__c = String.valueOf(ur.getErrors()[0].getMessage());
                if(ur.getId() != null){
                    log.Object__c = ur.getId().getSObjectType().getDescribe().getName();
                    log.Record_Id__c = ur.getId();
                }
                logList.add(log);  
            }
        }
        if(!logList.isEmpty()){
            Insert logList;
        }	
    }
}

Apex Callable Interface

In Winter’19 release Salesforce introduced Callable interface in apex. The interface enables developers to use a common interface to build loosely coupled integrations between Apex classes or triggers, even for code in separate packages.

This interface has one method call(String action, Map args). It helps developer to call a method dynamically with the help of “Type” class.

Example:

public class SampleCallable implements Callable {
    
    //Method 1
    public String method1(String stringValue) {
        return 'Welcome ' + stringValue;
    }
    
    //Method 2
    public Decimal method2(Integer integerValue) {
        return integerValue + 100;
    }
    
    //Callable interface method
    public Object call(String action, Map<String, Object> args) {
        switch on action {
            when 'method1' {
                return this.method1((String)args.get('stringValue'));
            }
            when 'method2' {
                return this.method2((Integer)args.get('integerValue'));
            }
            when else {
                return null;
            }
        }
    }
}

Execute:

Callable c = (Callable)Type.forName('SampleCallable').newInstance();
system.debug(c.call('method1', new map<String,Object>{'stringValue' => 'Biswajeet'}));
system.debug(c.call('method2', new map<String,Object>{'integerValue' => 5}));

Cacheable Apex Method

To improve the performance of the lightning component cache Salesforce introduced storable (cacheable) in Winter’19 release. In API version 44.0 and later, you can mark an Apex method as storable (cacheable) instead of using setStorable() on every JavaScript action that calls the Apex method to centralize your caching notation for a method in the Apex class.

Here is an example to cache data returned from an Apex method for lightning component, annotate the Apex method with @AuraEnabled(cacheable=true).

Apex Class:

public class SampleAuraController {
    
    @AuraEnabled(cacheable = true)
    public static List<Account> getDirectCustomerAccount() {
        List<Account> accList = new List<Account>();
        accList = [SELECT Id, Name, Type, Industry, Phone
                   FROM Account WHERE Type = 'Customer - Direct'];
        return accList;
    }
}

Lightning Component:

<aura:component controller="SampleAuraController" implements="flexipage:availableForAllPageTypes,force:appHostable">
    
    <!--Declare Event Handlers-->
    <aura:handler name="init" value="{!this}" action="{!c.doInit}" />
    
    <!--Declare Attributes-->
    <aura:attribute name="accList" type="Account[]"/>
    
    <!--Component Start-->
    <div class="slds-m-around_xx-large">
        <table class="slds-table slds-table_cell-buffer slds-table_bordered slds-table_col-bordered">
            <thead>
                <tr class="slds-line-height_reset">
                    <th class="slds-text-title_caps" scope="col">
                        <div class="slds-truncate" title="Account Name">Account Name</div>
                    </th>
                    <th class="slds-text-title_caps" scope="col">
                        <div class="slds-truncate" title="Account Type">Account Type</div>
                    </th>
                    <th class="slds-text-title_caps" scope="col">
                        <div class="slds-truncate" title="Industry">Industry</div>
                    </th>
                    <th class="slds-text-title_caps" scope="col">
                        <div class="slds-truncate" title="Phone">Phone</div>
                    </th>
                </tr>
            </thead>
            <tbody>
                <aura:iteration items="{!v.accList}" var="acc">
                    <tr class="slds-hint-parent">
                        <th data-label="Account Name" scope="row">
                            <div class="slds-truncate" title="{!acc.Name}"><a href="javascript:void(0);" tabindex="-1">{!acc.Name}</a></div>
                        </th>
                        <td data-label="Account Type">
                            <div class="slds-truncate" title="{!acc.Type}">{!acc.Type}</div>
                        </td>
                        <td data-label="Industry">
                            <div class="slds-truncate" title="{!acc.Industry}">{!acc.Industry}</div>
                        </td>
                        <td data-label="Phone">
                            <div class="slds-truncate" title="{!acc.Phone}">{!acc.Phone}</div>
                        </td>
                    </tr>
                </aura:iteration>
            </tbody>
        </table>
    </div>
    <!--Component End-->
</aura:component>

Lightning JS Controller:

({
    doInit : function(component, event) {
        var action = component.get("c.getDirectCustomerAccount");
        action.setCallback(this, function(response) {
            var state = response.getState();
            if(state === "SUCCESS"){
                var result = response.getReturnValue();
                component.set("v.accList", result);
            }
        });
        $A.enqueueAction(action);
    }
})

Output: