Skip to main content
Skip table of contents

Error Monitoring for Salesforce Cloud (Configure)

Features

Guide to configuring the analysis of impactful errors and filtering out the insignificant ones to Salesforce Users or Business Processes. Ignore the noise and focus on the issues that are critical to your business! Germain has the ability to help you understand the impact, at scale, of any errors on the end-user or business operations while operating with Salesforce Cloud. It helps separate critical errors from benign ones and also identifies the new ones.

salesforce error.png

Salesforce Error detected and analyzed by Germain UX

Enable User Error Monitoring for Salesforce

Here is how to configure Germain UX’s monitoring to best detect and analyze errors that affect Salerforce users.

  • Configure UX Monitoring Profile
    Germain Workspace > Left Menu > Analytics > UX Monitoring Profiles > Salesforce

    salesforce ux profile.png

    UX Monitoring Profile for Salesforce Cloud - Germain UX

    Sample Code:

    CODE
    // Work-around for detached documents (https://github.com/germainsoftware/germain-apm-customers/issues/2176)
    germainApm.native.XmlHttpRequest = XMLHttpRequest;
    germainApm.dev.compressRequests = false;
    installCustomSfdcIntegration();
    var settings = germainApm.getDefaultSettings();
    settings.constants.logLevelToEmitAsFacts = 'WARN';
    settings.constants.sessionMarkerEnd = ['Log Out', 'Logout'];
    settings.application.eventSourceElementExtractor = customEventSourceElementExtractor;
    settings.application.metadataProviders['sessionId'] = getSessionId;
    settings.application.metadataProviders['user.name'] = getUsername;
    settings.application.metadataProviders['application.component'] = getAppComponent;
    settings.plugins.network.excludedUrls.push(/cometd\/replay/, /analytics\/wave\/ui/);
    settings.plugins.network.eventProcessor = networkEventProcessor;
    settings.plugins.network.factProcessor = networkFactProcessor;
    settings.plugins.network.requestAndResponseBodyProcessor = networkRequestAndResponseBodyProcessor;
    settings.plugins.network.monitorServerSideTiminigs = false; // disable Server-Side http extraction as we get this data from perfSummary from response body
    console.log(settings.plugins.network);
    settings.plugins.network.monitorDuplicates = {
    timeThresholdMillis: 100,
    treshold: 5
    };
    settings.application.factProcessor = factProcessor;
    settings.plugins.console = {
        methods: ['error', 'warn'],
        eventProcessor: consoleFactProcessor,
        maxLogStringLength: settings.plugins.console.maxLogStringLength
    };
    settings.plugins.click.factProcessor = clickFactProcessor;
    settings.plugins.performance.measureMonitoringEnabled = true;
    settings.plugins.performance.measureMonitoringEntryNames = ['PageView EPT'];
    settings.plugins.pageLoad.factProcessor = pageLoadFactProcessor;
    germainApm.eventGenerators.customLogout = {
        emits: ['session end'],
        installer: function (window, settings, fire, services) {
            services.onMonitoringEvent('click', function (event) {
                if (event.target && germainApm.utils.instanceOf(event.target, HTMLAnchorElement)) {
                    var innerText = (event.target.innerText || '').trim();
                    if (innerText && settings.constants.sessionMarkerEnd && ~settings.constants.sessionMarkerEnd.indexOf(innerText)
                        && event.target.className && event.target.className.indexOf('logout') > -1) {
                        fire({ type: 'session end', reason: 'logout' });
                        germainApm.uninstall(true, true);
                    }
                }
            });
            return function () { };
        }
    };
    
    germainApm.dataGenerators.notFoundSearch = function (on, submit) {
    
        on('text appeared', function (event) {
            if (event.textType === 'visibleText') {
        var title = window.document.title;
                for (var i = 0; i < event.blocks.length; ++i) {
                    if (event.blocks[i] === 'Don\'t give up yet!')
                        germainApm.api.createEvent("Salesforce Search Not Found", {
                            userError: true,
                            success: false,
                            businessObject: title && title.indexOf('Search') > -1 ? title.replace(' - Search | Salesforce', '') : null
                        });
                }
            }
        });
    };
    
    germainApm.dataGenerators.opportunityBP = (on, submit, settings, services, replay) => {
        new germainApm.utils.StaticBusinessProcess('Salesforce:Opportunity Interval', on, function (lastKnown) {
            var isOpportunity = window.location && window.location.pathname && window.location.pathname.indexOf('Opportunity') > -1
                && window.location.pathname.indexOf('/view') > -1;
            var oppId = isOpportunity ? window.location.pathname.replace('/lightning/r/Opportunity/', '').replace('/view', '').replace('/', '') : null;
            var activeViewEl = germainApm.native.documentQuerySelector(window.document, '.windowViewMode-maximized.active')
                || germainApm.native.documentQuerySelector(window.document, '.windowViewMode-normal.active');
            var pathNavEl = activeViewEl ? germainApm.native.elementQuerySelector(activeViewEl, '.slds-path__nav') : null;
            var allStagesEl = pathNavEl ? germainApm.native.elementQuerySelectorAll(pathNavEl, '.slds-path__item') : null;
            var stageActiveEl = pathNavEl ? germainApm.native.elementQuerySelector(pathNavEl, '.slds-is-current') : null;
            var stageName = stageActiveEl ? stageActiveEl.innerText : null;
            var stageActiveIndex = undefined;
            if (allStagesEl && stageActiveEl) {
                for (var i = 0; i < allStagesEl.length; i++) {
                    if (allStagesEl[i] === stageActiveEl) {
                        stageActiveIndex = i;
                        break;
                    }
                }
            }
            var status = 'IN_PROGRESS';
            if (oppId && allStagesEl && stageActiveIndex && stageActiveIndex === allStagesEl.length - 1) {
                status = 'COMPLETED';
            }
            return {
                stepName: stageName,
                stepOrdinal: stageActiveIndex,
                oppId: oppId,
                label: stageName,
                status: status
            };
        }, function (fact, metadata) {
            // fact.hierarchy.level1 = metadata.srType as string | null || undefined;
            // fact.hierarchy.level2 = metadata.srArea as string | null || undefined;
            // fact.hierarchy.level3 = metadata.srSubarea as string | null || undefined;
            fact.businessObject = metadata.oppId;
            if (metadata.status)
                fact.status = metadata.status;
            if (fact.duration === 0)
                fact.duration = .2; // eventDebounce in seconds
            submit({ time: fact.timestamp, type: 'fact', fact: fact });
        }, {
            idleThresholdMillis: 5 * 60 * 1000,
            eventDebounceMillis: 2 * 1000, // 2 seconds
        });
    }
    
    germainApm.start(settings);
    function installCustomSfdcIntegration() {
        germainApm.eventGenerators.customLogger = {
            emits: ['log'],
            installer: function (window, settings, fire) {
                var subscribers = {
                    ASSERT: function assertCallback(level, message) {
                        fire({
                            type: 'log',
                            level: 'assert',
                            message: message || '',
                        });
                    },
                    ERROR: function errorCallback(level, message, e) {
                        fire({
                            type: 'log',
                            level: 'error',
                            message: e ? (e.stack ? e.stack : e.message) : '',
                            isError: true
                        });
                    }
                };
                germainApm.utils.waitForProperty(window, '$A', function () {
                    Object.keys(subscribers).forEach(function (subscriberKey) { return window.$A.logger.subscribe(subscriberKey, subscribers[subscriberKey]); });
                });
                return function () {
                    if (window.$A && window.$A.logger) {
                        Object.keys(subscribers).forEach(function (subscriberKey) { return window.$A.logger.unsubscribe(subscriberKey, subscribers[subscriberKey]); });
                    }
                };
            },
        };
        germainApm.eventGenerators.customAlert = {
            emits: ['DOM popup opening', 'DOM popup closed'],
            installer: function (window, settings, fire) {
                var message;
                var createComponent;
                var getEvt;
                var get;
                germainApm.utils.waitForProperty(window, '$A', function () {
                    germainApm.utils.waitForProperty(window.$A, 'message', function () {
                        message = germainApm.utils.hookCall(window.$A, 'message', {
                            callBefore: function (_a) {
                                var msg = _a[0], error = _a[1];
                                fire({
                                    type: 'DOM popup opening',
                                    name: 'Message Error',
                                    title: msg,
                                    message: String(error),
                                    isError: true
                                });
                            }
                        });
                    });
                    germainApm.utils.waitForProperty(window.$A, 'createComponent', function () {
                        createComponent = germainApm.utils.hookCall(window.$A, 'createComponent', {
                            callBefore: function (_a) {
                                var type = _a[0], attributes = _a[1];
                                if (type === 'markup://one:applicationError') {
                                    if (attributes.error && attributes.error.id) {
                                        fire({
                                            type: 'DOM popup opening',
                                            name: 'App Error',
                                            title: attributes.error ? attributes.error.message : '',
                                            message: attributes.error ? String(attributes.error.stackTrace || attributes.error.stack || '') : '',
                                            isError: true,
                                            processedAttributes: {
                                                businessObject: attributes.error.id
                                            }
                                        });
                                    }
                                }
                                else if (type === 'ltng:developerError') {
                                    fire({
                                        type: 'DOM popup opening',
                                        name: 'Developer Error',
                                        title: attributes.messageTitle || '',
                                        message: attributes.stackTrace || attributes.stack || attributes.messageText || '',
                                        isError: true
                                    });
                                }
                            }
                        });
                    });
                    germainApm.utils.waitForProperty(window.$A, 'getEvt', function () {
                        getEvt = germainApm.utils.hookCall(window.$A, 'getEvt', {
                            callAfter: function (event, _a) {
                                var eventDef = _a[0];
                                // If the event to creating a toast, hook the events fire method to check when it gets fires
                                if (eventDef === 'markup://force:showToast') {
                                    germainApm.utils.hookCall(event, 'fire', {
                                        callBefore: function () {
                                            var type = this.getParam('type') || this.getParam('key');
                                            var title = this.getParam('title');
                                            var message = getToastMessage(this.getParams());
                                            var error = germainApm.utils.cleanStack(new Error(), '.*'); // Current stack
                                            fire({
                                                type: 'DOM popup opening',
                                                name: 'Toast ' + (typeof type === 'string' ? type : 'other'),
                                                title: message,
                                                message: error.cleanedStack ? error.cleanedStack : error.stack || '',
                                                isError: type === 'error',
                                                processedAttributes: {
                                                    businessObject: title
                                                }
                                            });
                                        }
                                    });
                                }
                                else if (eventDef === 'markup://ui:panelTransitionEnd' && event.source) {
                                    try {
                                        if (event.getParam('action') === 'show' || event.source._showing) {
                                            var elementEl = event.source.getElement();
                                            if (elementEl && elementEl.className && ~elementEl.className.indexOf('forceFormPageError')) {
                                                var isElement = germainApm.utils.isElement(elementEl);
                                                var headerEl = isElement
                                                    ? germainApm.native.elementGetElementsByTagName(elementEl, 'header')
                                                    : germainApm.native.documentGetElementsByTagName(elementEl, 'header');
                                                if (headerEl.length && ~headerEl[0].innerText.indexOf('hit a snag')) {
                                                    var recordsEld = isElement
                                                        ? germainApm.native.elementGetElementsByTagName(elementEl, 'records-record-edit-error')
                                                        : germainApm.native.documentGetElementsByTagName(elementEl, 'records-record-edit-error');
                                                    fire({
                                                        type: 'DOM popup opening',
                                                        name: 'Hit a Snag',
                                                        title: 'We hit a snag',
                                                        message: recordsEld.length ? recordsEld[0].innerText : null,
                                                        isError: true
                                                    });
                                                }
                                            }
                                        }
                                    }
                                    catch (e) { }
                                }
                                else if (eventDef === 'markup://force:recordPageError') {
                                    germainApm.utils.hookCall(event, 'fire', {
                                        callAfter: function () {
                                            var errors = event.getParam('pageErrors');
                                            if (errors && errors.length > 0) {
                                                fire({
                                                    type: 'DOM popup opening',
                                                    name: 'Page Error',
                                                    title: 'Page Error',
                                                    message: germainApm.utils.safeStringify(errors, 10000),
                                                    isError: true
                                                });
                                            }
                                        }
                                    });
                                }
                            }
                        });
                    });
                    germainApm.utils.waitForProperty(window.$A, 'get', function () {
                        get = germainApm.utils.hookCall(window.$A, 'get', {
                            callAfter: function (event, _a) {
                                var eventDef = _a[0];
                                // If the event to creating a toast, hook the events fire method to check when it gets fires
                                if (eventDef === 'e.force:showToast') {
                                    germainApm.utils.hookCall(event, 'fire', {
                                        callBefore: function () {
                                            try {
                                                var type = this.getParam('type') || this.getParam('key');
                                                var title = this.getParam('title');
                                                var message_1 = getToastMessage(this.getParams());
                                                var error = germainApm.utils.cleanStack(new Error(), '.*'); // Current stack
                                                fire({
                                                    type: 'DOM popup opening',
                                                    name: 'Toast ' + (typeof type === 'string' ? type : 'other'),
                                                    title: message_1,
                                                    message: error.cleanedStack ? error.cleanedStack : error.stack || '',
                                                    isError: type === 'error',
                                                    processedAttributes: {
                                                        businessObject: title
                                                    }
                                                });
                                            }
                                            catch (e) { }
                                        }
                                    });
                                }
                            }
                        });
                    });
                });
                return function () {
                    if (message)
                        message.unhook();
                    if (createComponent)
                        createComponent.unhook();
                    if (getEvt)
                        getEvt.unhook();
                    if (get)
                        get.unhook();
                };
            },
        };
    }
    function getToastMessage(params) {
        if (!params)
            return '';
        // If there's a template, we need to serialize the message, for example:
        // {
        //      messageTemplate: 'Validation {0} at {1}!',
        //      messageTemplateData: ['Some Value', { label: 'Some other label', action: {...} }]
        // }
        // Expected Result: 'Validation Some Value at Some other label!'
        if (params.messageTemplateData && params.messageTemplate) {
            try {
                return params.messageTemplate.replace(/{(\d+)}/g, function (match) {
                    var index = parseInt(match.substring(1, match.length - 1)); // Strip off {}
                    var value = params.messageTemplateData[index];
                    if (!value)
                        return '';
                    return typeof value === 'string' ? value : value.label;
                });
            }
            catch (ex) {
                germainApm.log('ERROR', 'Failed to get Toast Message', ex);
            }
        }
        return params.message;
    }
    var HHHSFMessageTypes = [
        'helper.createOpty() called',
        'Publishing bookmark JSON',
        'CallTransfer',
        'getBookmark about to invoke class Method associateTransferRec',
        'getBookmark',
        'Response from associateTransferRec',
        'Received Lightning channel message',
        'VS_RESPONSE',
        'VSEFTPayload',
        'ServiceClientAPI: receiveMessage',
        'WWE Listener - LOCALSTORAGE_EVENT',
        'WWE_INTERACTION Value',
        'sessionLock Value'
    ];
    var HHHSFCriticalMsgTypes = [
        'Browser Case Record',
        'IVR Returns',
        'AuxListActions',
        'EFT SDC OUTPUT',
        'mskNum',
        'PaymentForMedSup',
        'Before setting maskedCCNum field',
        'Masked CC Number',
        'Masked EFT Number',
        'Set bnkaccNumEncrypted field',
        'Token length',
        'Valid token returned',
        'Inside SDC_RESPONSE',
        'handleChanged'
    ];
    var SFDCMsgTypes = [
        'callTracking - Checking Duna',
        'callTracking - formatDynamicPickPayload',
        'callTracking -  Type Handled',
        'initFormatDynamicPickPayload'
    ];
    var HHHSFCriticalMsgTypeRegex = /Lightning Message Type is - (.*?)\s/;
    function consoleFactProcessor(event, fireEvent, settings) {
        var stack = event.stack;
        if (stack && typeof stack === 'string') {
            if (!~stack.indexOf('InstrumentationResult') && (~stack.toLowerCase().indexOf('error') || ~stack.toLowerCase().indexOf('exception'))) {
                event.level = 'error';
                event.isError = true;
            }
        }
        // collect only CTI HHHSF console log events; otherwise skip log events but always collect error
        if (event.level === 'log' || event.level === 'error') {
            if (!event.message)
                return event.level === 'error';
            if (event.message.indexOf('HHHSF') === -1) {
                return event.level === 'error';
            }
            else {
                if (~event.message.indexOf('HHHSF ---------------'))
                    return false;
                event.message = event.message.replace('HHHSF , ', '').replace('HHHSF - ', '').replace('HHHSF ', '');
                var name_1 = null;
                if (~event.message.indexOf('LocalStorage handler called for key') || ~event.message.indexOf('reviveCallVars Final state') || ~event.message.indexOf('reviveCallVars Initial state')) {
                    var items = event.message.split(',');
                    if (items.length > 1) {
                        name_1 = items[0].trim();
                    }
                }
                else if (~event.message.indexOf('SDC_CRITICAL')) {
                    var match = event.message.match(HHHSFCriticalMsgTypeRegex);
                    if (match && match.length > 1) {
                        // e.g. HHHSF SDC_CRITICAL, Lightning Message Type is - BOOKMARK , Component reference is SecureComponent: markup://c:ShowMemberDetails {10965:0}{ key: {"namespace":"c"} }
                        name_1 = 'Lightning Message Type - ' + match[1];
                    }
                    else {
                        var typesFound = HHHSFCriticalMsgTypes.filter(function (type) { return ~event.message.indexOf(type); });
                        name_1 = typesFound.length > 0 ? typesFound[0] : 'SDC_CRITICAL';
                    }
                }
                else {
                    var typesFound = HHHSFMessageTypes.filter(function (type) { return ~event.message.indexOf(type); });
                    if (typesFound.length > 0) {
                        name_1 = typesFound[0];
                    }
                }
                // post process
                event.processedAttributes = {
                    businessObject: 'HHHSF'
                };
                if (name_1) {
                    event.processedAttributes.details = event.message;
                    event.message = name_1;
                }
            }
        }
        return true;
    }
    function networkEventProcessor(event) {
        var request = event.request;
        var response = event.response;
        if (response && event.url.pathname === '/aura') {
            try {
                var object = JSON.parse(response);
                if (object && object.actions && object.actions.length > 0) {
                    for (var i = 0; i < object.actions.length; i++) {
                        var action = object.actions[i];
                        if (action) {
                            if (action.state === 'ERROR') {
                                event.success = false;
                                if (Array.isArray(action.error) && action.error.length > 0) {
                                    var firstError = action.error[0];
                                    if (firstError.message) {
                                        var httpErrorMatch = firstError.message.replaceAll('\n', '').match(/(\d+):(.*)/);
                                        if (httpErrorMatch && httpErrorMatch.length > 2) { // contains special error message: "HTTP Status:{"message": "Error message"}"
                                            event.status = httpErrorMatch[1];
                                            try {
                                                var errorContent = JSON.parse(httpErrorMatch[2]);
                                                if (errorContent && errorContent.message) {
                                                    event.businessObject = errorContent.message;
                                                }
                                            }
                                            catch (e) {
                                                event.businessObject = httpErrorMatch[2];
                                            }
                                        }
                                        else {
                                            event.businessObject = firstError.message.replaceAll(':', ' ');
                                        }
                                    }
                                    else if (firstError.event && firstError.event.attributes && firstError.event.attributes.values && firstError.event.attributes.values.error && firstError.event.attributes.values.error.message) {
                                        event.businessObject = firstError.event.attributes.values.error.message;
                                        if (firstError.event.attributes.values.error.data && firstError.event.attributes.values.error.data.statusCode) {
                                            event.status = firstError.event.attributes.values.error.data.statusCode;
                                        }
                                    }
                                }
                                break;
                            }
                            else if (action.returnValue && action.returnValue.returnValue && action.returnValue.returnValue.ErrorMsg) {
                                event.success = false;
                                event.businessObject = action.returnValue.returnValue.ErrorMsg;
                                break;
                            }
                        }
                    }
                }
            }
            catch (e) { }
        }
        if (request)
            event.request = decodeURIComponent(request);
    }
    function networkFactProcessor(fact, event, settings, fireEvent, submit) {
        // headers
        if (event.requestHeaders) {
            var requestId = window.Headers && event.requestHeaders instanceof Headers ? event.requestHeaders.get('X-SFDC-Request-Id') : event.requestHeaders['X-SFDC-Request-Id'];
            if (requestId) {
                fact.backendSequence = 'TID:' + requestId;
            }
        }
        if (fact.myClassName === 'OutboundFetchRequest') {
            try {
                var resp_1 = JSON.parse(fact.responseBody || '');
                var subFactIndex = 0;
                // backend performance metrics 
                if (resp_1.perfSummary && resp_1.perfSummary.actions && resp_1.perfSummary.actionsTotal) {
                    var dbTime = Object.keys(resp_1.perfSummary.actions).reduce(function (sum, key) { return sum + resp_1.perfSummary.actions[key].db; }, 0);
                    var serviceTime = (resp_1.perfSummary.actionsTotal || 0) - (dbTime || 0);
                    var serviceSequence = fact.sequence + '.' + subFactIndex++;
                    // Service txn
                    var serviceFact = {
                        myClassName: 'GenericTransaction',
                        type: 'Salesforce:Execution',
                        timestamp: fact.timestamp,
                        name: 'Service Transaction',
                        duration: serviceTime / 1000,
                        success: true,
                        sequence: serviceSequence
                    };
                    submit({ time: serviceFact.timestamp, type: 'fact', fact: serviceFact });
                    if (typeof dbTime === 'number' && !isNaN(dbTime)) {
                        // DB txn
                        var dbFact = {
                            myClassName: 'GenericTransaction',
                            type: 'Salesforce:Database Execution',
                            timestamp: fact.timestamp,
                            name: 'Database Transaction',
                            duration: dbTime / 1000,
                            success: true,
                            sequence: serviceSequence + '.' + 0
                        };
                        submit({ time: dbFact.timestamp, type: 'fact', fact: dbFact });
                    }
                }
                // errors extraction from response
                if (resp_1 && resp_1.actions && resp_1.actions.length > 0) {
                    for (var i = 0; i < resp_1.actions.length; i++) {
                        var action = resp_1.actions[i];
                        if (action) {
                            if (action.state === 'ERROR') {
                                if (Array.isArray(action.error) && action.error.length > 0) {
                                    for (var j = 0; j < action.error.length; j++) {
                                        var error = action.error[j];
                                        var message = '';
                                        if (error.message) {
                                            var httpErrorMatch = error.message.replaceAll('\n', '').match(/(\d+):(.*)/);
                                            if (httpErrorMatch && httpErrorMatch.length > 2) { // contains special error message: "HTTP Status:{"message": "Error message"}"
                                                try {
                                                    var errorContent = JSON.parse(httpErrorMatch[2]);
                                                    if (errorContent && errorContent.message) {
                                                        message = errorContent.message;
                                                    }
                                                }
                                                catch (e) {
                                                    message = httpErrorMatch[2];
                                                }
                                            }
                                            else {
                                                message = error.message;
                                            }
                                        }
                                        else if (error.event && error.event.attributes && error.event.attributes.values && error.event.attributes.values.error
                                            && error.event.attributes.values.error.message) {
                                            message = error.event.attributes.values.error.message;
                                        }
                                        var details = null;
                                        try {
                                            details = JSON.stringify(error);
                                        }
                                        catch (ex) {
                                            details = germainApm.utils.safeStringify(error, 10000);
                                        }
                                        germainApm.api.createEvent("Salesforce Error", {
                                            success: false,
                                            displayedName: message,
                                            details: details,
                                            sequence: fact.sequence + '.' + subFactIndex++
                                        });
                                    }
                                }
                            }
                            else if (action.returnValue && action.returnValue.returnValue && action.returnValue.returnValue.ErrorMsg) {
                                var details = null;
                                try {
                                    details = JSON.stringify(action.returnValue.returnValue);
                                }
                                catch (ex) {
                                    details = germainApm.utils.safeStringify(action.returnValue.returnValue, 10000);
                                }
                                germainApm.api.createEvent("Salesforce Error", {
                                    success: false,
                                    displayedName: action.returnValue.returnValue.ErrorMsg,
                                    details: details,
                                    sequence: fact.sequence + '.' + subFactIndex++
                                });
                            }
                            else if (action.returnValue && action.returnValue.response && action.returnValue.response.errors) {
                                if (typeof action.returnValue.response.errors === 'string') {
                                    var msg = action.returnValue.response.errors.replaceAll('<b>', '').replaceAll('</b>', '').replaceAll('<br>', ' ');
                                    germainApm.api.createEvent("Salesforce Error", {
                                        success: false,
                                        displayedName: msg.substr(0, 200),
                                        details: msg,
                                        sequence: fact.sequence + '.' + subFactIndex++
                                    });
                                }
                                else if (Array.isArray(action.returnValue.response.errors) && action.returnValue.response.errors.length > 0) {
                                    for (var j = 0; j < action.returnValue.response.errors.length; j++) {
                                        var error = action.returnValue.response.errors[j];
                                        if (error) {
                                            var msg = (error.message && error.message.replaceAll('<b>', '').replaceAll('</b>', '').replaceAll('<br>', ' ')) || '';
                                            var displayedName = msg.indexOf('Limit Exceeded') > -1 ? 'Limit Exceeded' : error.code;
                                            germainApm.api.createEvent("Salesforce Error", {
                                                success: false,
                                                displayedName: displayedName || msg.substr(0, 200),
                                                details: msg,
                                                sequence: fact.sequence + '.' + subFactIndex++
                                            });
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
            catch (ex) {
                germainApm.log('ERROR', 'Salesforce networkFactProcessor failed', ex);
            }
        }
    }
    function networkRequestAndResponseBodyProcessor(fact, settings, event) {
        if (fact.myClassName === 'OutboundDocumentRequest' && fact.responseBody) {
            if (fact.http.path && (~fact.http.path.indexOf('RemoteAccessErrorPage') || ~fact.http.path.indexOf('SamlError'))) {
                var errorMsg = 'Internal Server Error';
                if (fact.http.query) {
                    var values = germainApm.utils.parseQueryStringToMap(fact.http.query);
                    if (values['error'])
                        errorMsg = values['error'].replace('_', ' ');
                }
                else {
                    var header = germainApm.native.getElementById(window.document, 'header');
                    if (header)
                        errorMsg = header.innerText;
                }
                fact.success = false;
                fact.http.status = '500';
                fact.http.message = '500 - ' + errorMsg;
            }
            else if (~fact.responseBody.indexOf('Unable to Process Request') && ~fact.responseBody.indexOf('temporarily unable to respond to your request')) {
                fact.success = false;
                fact.http.status = '503';
                fact.http.message = '503 - Unable to Process Request';
            }
        }
    }
    function customEventSourceElementExtractor(event) {
        var srcElement = event.target || event.srcElement;
        var path = event.composedPath !== undefined ? event.composedPath() : null;
        if (path && path.length > 0) {
            var parent_1 = path.length > 1 ? path[1] : null;
            if (parent_1 && ~['click', 'dblclick', 'touchstart'].indexOf(event.type)) {
                var parentNodeName = parent_1.nodeName;
                var currentNodeName = path[0].nodeName;
                if (currentNodeName === 'SPAN' && ~['H1', 'H2', 'H3', 'H4', 'H5', 'H6'].indexOf(parentNodeName))
                    return path[1];
            }
            return path[0];
        }
        return srcElement;
    }
    function getSessionId(window) {
        return new germainApm.native.Promise(function (resolve, reject) {
            var sid = null;
            var cookiearray = window.document.cookie.split(';');
            for (var i = 0; i < cookiearray.length; i++) {
                var index = cookiearray[i].indexOf('sid=');
                if (index > -1) {
                    sid = cookiearray[i].substr(index + 4);
                    break;
                }
            }
            if (sid) {
                resolve(sid);
            }
            else {
                germainApm.utils.waitForProperty(window, '$A', function () {
                    // Ec is not the same as "sid" value but for SFDC Lightning "sid" can't be extracted due to HttpOnly=true flag for cookie
                    // This value is unique for a session
                    resolve(window.$A.clientService ? window.$A.clientService.Ec : null);
                }, function () { return reject(null); }, 20, 500);
            }
        });
    }
    function getAppComponent(window) {
        return new germainApm.native.Promise(function (resolve, reject) {
            germainApm.utils.waitForProperty(window, '$A', function () {
                var counter = 0;
                // I have seen appDefinition being available 5-7sec after pageReady=true therefore lets wait max 10sec for it
                var maxRetry = 20, retryTimeoutInMillis = 500;
                var intervalId = germainApm.native.setInterval(function () {
                    counter++;
                    var app = window.$A;
                    if (app['getRoot'] && app.getRoot() && app.getRoot().find("host") && app.getRoot().find("host").get("v.appMetadata.appDefinition")) {
                        germainApm.native.clearInterval(intervalId);
                        try {
                            var appDef = app.getRoot().find("host").get("v.appMetadata.appDefinition");
                            germainApm.api.createActivityHint('App changed', { name: appDef.label || appDef.developerName });
                            resolve(appDef.label || appDef.developerName);
                        }
                        catch (e) {
                            reject(null);
                        }
                    }
                    else if (counter >= maxRetry) {
                        germainApm.native.clearInterval(intervalId);
                        reject(null);
                    }
                }, retryTimeoutInMillis);
            }, function () { return reject(null); });
        });
    }
    function getUsername(window) {
        return new germainApm.native.Promise(function (resolve, reject) {
            // Salesforce Lightening has $A, whereas Classic has UserContext
            germainApm.utils.waitForProperty(window, '$A', sfLightning, function () { return done(null, false); });
            germainApm.utils.waitForProperty(window, 'UserContext', sfClassic, function () { return done(null, false); });
            function sfLightning() {
                var counter = 0;
                var maxRetry = 20, retryTimeoutInMillis = 500;
                var intervalId = germainApm.native.setInterval(function () {
                    counter++;
                    if (window.$A.get('$SObjectType.CurrentUser') || counter >= maxRetry) {
                        germainApm.native.clearInterval(intervalId);
                        callback();
                    }
                }, retryTimeoutInMillis);
                function callback() {
                    // First try to get email and if empty use user ID. Look for this data in the following components in order:
                    // 1) SFDC API
                    // 2) germain SFDC component
                    try {
                        // email
                        var username = window.$A.get('$SObjectType.CurrentUser.Email');
                        if (!username) {
                            var email = germainApm.native.getElementById(window.document, 'germain-apm-profile-email');
                            if (email) {
                                username = email.textContent || email.innerText;
                            }
                            // id
                            if (!username) {
                                username = window.$A.get('$SObjectType.CurrentUser.Id');
                                if (!username) {
                                    var id = germainApm.native.getElementById(window.document, 'germain-apm-profile-id');
                                    if (id) {
                                        username = id.textContent || id.innerText;
                                    }
                                }
                            }
                        }
                        germainApm.log('INFO', 'Username ID found: ' + username);
                        done(username || null, true);
                    }
                    catch (e) {
                        germainApm.log('INFO', 'Username not found. Exception in the extractor.', { error: e });
                        done(null, false);
                    }
                }
            }
            function sfClassic() {
                if (window.UserContext) {
                    done(window.UserContext.userName || window.UserContext.userId, true);
                }
                else {
                    done(null, false);
                }
            }
            var resolved = false;
            function done(value, success) {
                if (!resolved) {
                    resolved = true;
                    if (success) {
                        resolve(value);
                    }
                    else {
                        reject(value);
                    }
                }
            }
        });
    }
    function getCurrentPage(w) {
        if (w.sfdcPage) {
            var tab = germainApm.native.documentQuerySelector(w.document, '.zen .zen-tabMenu .zen-active a');
            var pageType = w.document.getElementsByClassName('pageType')[0]; // e.g. Account Edit, Task, Opportunity, ...
            // const pageDescription = w.document.getElementsByClassName('pageDescription')[0];
            return {
                app: 'Classic',
                comp: undefined,
                tab: tab ? tab.innerText : undefined,
                view: pageType ? pageType.innerText : undefined
            };
        }
        else if (w.FORCE && w.FORCE.location && w.FORCE.location.getCurrentPage) {
            var currentPage = w.FORCE.location.getCurrentPage();
            if (currentPage) {
                var oneAppnav = germainApm.native.documentQuerySelector(w.document, 'one-appnav');
                var oneAppnavBar = oneAppnav && oneAppnav.shadowRoot ? germainApm.native.shadowRootQuerySelector(oneAppnav.shadowRoot, 'one-app-nav-bar') : null;
                var activeTab = oneAppnavBar && oneAppnavBar.shadowRoot ? germainApm.native.shadowRootQuerySelector(oneAppnavBar.shadowRoot, 'nav .slds-is-active') : null;
                return {
                    app: 'Lightning',
                    comp: currentPage.app.appName,
                    tab: activeTab && activeTab.innerText ? activeTab.innerText.trim() : undefined,
                    view: currentPage.sobjectType
                };
            }
        }
        return null;
    }
    function clickFactProcessor(fact, event, fireEvent, settings, exclusion) {
        var target = event.target;
        if (target instanceof HTMLElement) {
            switch (target.nodeName) {
                case 'DIV':
                    fact.name = 'div';
                    if (target.className) {
                        switch (target.className.trim()) {
                            case 'homeHeroChartOverlay':
                            case 'reportsEclairChart':
                            case 'chartWrapper':
                            case 'chartBody':
                                fact.name = 'chart';
                                break;
                            default:
                                break;
                        }
                    }
                    fact.details = germainApm.utils.applyExclusion(exclusion, target.textContent || target.innerText || '');
                    break;
                case 'TD':
                    fact.name = 'cell';
                    fact.details = germainApm.utils.applyExclusion(exclusion, target.textContent || target.innerText || '');
                    break;
                case 'SPAN':
                    fact.name = 'span';
                    fact.details = germainApm.utils.applyExclusion(exclusion, target.textContent || target.innerText || '');
                    break;
                case 'OL':
                case 'UL':
                    fact.name = 'list';
                    break;
                case 'LI':
                    fact.name = 'list item';
                    fact.details = germainApm.utils.applyExclusion(exclusion, target.textContent || target.innerText || '');
                    break;
                case 'LIGHTNING-PRIMITIVE-ICON':
                case 'LIGHTNING-ICON':
                    fact.name = 'icon';
                    if (target.shadowRoot && target.shadowRoot.firstChild && target.shadowRoot.firstChild.nodeName === 'svg') {
                        var svg = target.shadowRoot.firstChild;
                        if (svg instanceof SVGElement) {
                            if (svg.dataset && svg.dataset.key) {
                                fact.details = svg.dataset.key;
                            }
                        }
                    }
                    break;
                case 'FORCE-LIST-VIEW-MANAGER-SEARCH-BAR':
                    fact.name = 'search bar';
                    break;
            }
            if (!fact.domId && target.dataset && target.dataset.auraRenderedBy) {
                fact.domId = target.dataset.auraRenderedBy; // SFDC internal DOM id
            }
            if (target.title)
                fact.domLabel = germainApm.utils.applyExclusion(exclusion, target.title.replace(/\s/g, ' ').trim());
            if (fact.details) {
                // No need to exclude, these values will have already been excluded if needed
                fact.details = fact.details.trim();
                fact.domValue = String(fact.details).substring(0, settings.plugins.click.maxInnerTextLength || 200);
            }
            // integrate DOM popup close event
            // check if any parent of specific clicked element contains aura attribute which indicates it has been an error
            if (event.composedPath && (fact.details === "OK" || fact.details === "Close this window")) {
                var errorContainers = event.composedPath.filter(function (evtTarget, index) {
                    return evtTarget instanceof HTMLElement && evtTarget.dataset && evtTarget.dataset.auraClass &&
                        (evtTarget.dataset.auraClass === "oneApplicationError" || evtTarget.dataset.auraClass === "uiModal--app-error uiModal");
                }).filter(Boolean);
                if (errorContainers.length > 0) {
                    fireEvent({ type: 'DOM popup closed', name: fact.details === "OK" ? 'App Error' : 'Developer Error' });
                }
            }
            if (fact.details === "x" && event.target instanceof HTMLElement) {
                if (event.target.id === "dismissError") {
                    fireEvent({ type: 'DOM popup closed', name: 'Message Error' });
                }
            }
            if (target.nodeName === 'A') {
                if (target.className) {
                    if (~target.className.indexOf('slds-context-bar__label-action')) {
                        fact.name = 'view';
                        germainApm.api.createActivityHint('View navigation', { name: target.title });
                    }
                    else if (~target.className.indexOf('tabHeader') || ~target.className.indexOf('slds-tabs_default__link')) {
                        fact.name = 'tab';
                        germainApm.api.createActivityHint('Tab navigation', { name: target.title || target.dataset && target.dataset.label || target.innerText });
                    }
                    else if (~target.className.indexOf('test-drillin')) {
                        germainApm.api.createActivityHint('Drill to', { name: target.title });
                    }
                    else if (~target.className.indexOf('forceActionLink')) {
                        fact.name = 'action button';
                        germainApm.api.createActivityHint('Action button clicked', { name: target.title });
                    }
                }
                else if (target.dataset && target.dataset.feedtype) {
                    fact.name = 'feed';
                    germainApm.api.createActivityHint('Feed navigation', { name: target.title || target.dataset.feedtype });
                }
            }
            else if (target.nodeName === 'BUTTON') {
                if (target.className) {
                    if (~target.className.indexOf('slds-button slds-button_neutral search-button')) {
                        fact.name = 'search input';
                        germainApm.api.createActivityHint('Search input clicked');
                    }
                }
            }
        }
    }
    var entityTypeRegex = /(recordType|entityName|entityApiName|objectApiName)\\?":\\?"(.*?)\\?"/;
    var recordIdRegex = /recordId\\?":\\?"(.*?)\\?"/;
    function pageLoadFactProcessor(userClick, events) {
        console.log("pageLoadFactProcessor", events, events.length);
        var result = null, success = userClick.success;
        var all = [];
        var containsDocumentRequest = false;
        for (var i = 0; i < events.length; i++) {
            var event_1 = events[i].event;
            var page = events[i].page;
            if (event_1.type === 'request') {
                var searchContent = event_1.request || germainApm.utils.getQueryParams(event_1.url) || "";
                // mark user click as failed when one of its http request has failed
                if (event_1.success !== undefined)
                    success = event_1.success;
                // propagate businessObject from http request to user click
                // it might be overriden by next request or below
                if (event_1.businessObject) {
                    userClick.businessObject = event_1.businessObject;
                }
                if (searchContent && typeof searchContent === "string") {
                    if (~searchContent.indexOf("RecordUiController/ACTION$getObjectInfo") || ~searchContent.indexOf("RelatedListContainerController/ACTION$getRelatedListInfos")) {
                        result = { name: "View Record", event: event_1, content: searchContent, page: page };
                        break;
                    }
                    else if (~searchContent.indexOf("RecordUiController/ACTION$updateRecord") || ~searchContent.indexOf("RecordGvpController/ACTION$saveRecord")) {
                        result = { name: "Update Record", event: event_1, content: searchContent, page: page };
                        break;
                    }
                    else if ((~searchContent.indexOf("ActionsManagerController/ACTION$handleAction") && (~searchContent.indexOf("MRU_LIST") || ~searchContent.indexOf("LIST_VIEW")))
                        || (~searchContent.indexOf("ListViewManagerGridController/ACTION$getRecordLayoutComponent") && ~searchContent.indexOf("ListViewDataManagerController/ACTION$getItems"))) {
                        result = { name: "List Records", event: event_1, content: searchContent, page: page };
                        break;
                    }
                    else if (~searchContent.indexOf("GlobalNavDropdownController/ACTION$getModelForEntity")) {
                        result = { name: "Menu Opened", event: event_1, content: searchContent, page: page };
                        break;
                    }
                    else if ((~searchContent.indexOf("DetailController/ACTION$getRecord") && ~searchContent.indexOf("CREATE")) ||
                        (~searchContent.indexOf("RecordGvpController/ACTION$createRecord") && ~searchContent.indexOf("CREATE"))) {
                        result = { name: "Create New", event: event_1, content: searchContent, page: page };
                        break;
                    }
                    else if (~searchContent.indexOf("DetailController/ACTION$getRecord") && ~searchContent.indexOf("RecordEditActionsController/ACTION$getEditActions")
                        && ~searchContent.indexOf("force:detailPanel")) {
                        result = { name: "Open Edit Window", event: event_1, content: searchContent, page: page };
                        break;
                    }
                    else if (~searchContent.indexOf("FolderHomeController/ACTION$getRecords") && ~searchContent.indexOf('"entityApiName":"Report"')) {
                        result = { name: "List Reports", event: event_1, content: searchContent, page: page };
                        break;
                    }
                    else if (~searchContent.indexOf("ReportPageController/ACTION$runReport")) {
                        result = { name: "Run Report", event: event_1, content: searchContent, page: page };
                        break;
                    }
                    else if (~searchContent.indexOf("FingerprintController/ACTION$logFingerprint")) {
                        result = { name: "Logging", event: event_1, content: searchContent, page: page };
                        // no break on purpose as we want to see next requests if there is something better in http requests
                    }
                    else {
                        all.push(searchContent);
                    }
                }
            }
            else if (event_1.type === 'window load') {
                containsDocumentRequest = true;
            }
        }
        if (result) {
            if (result.name === 'View Record' || result.name === 'Update Record') {
                var matchedRecordId = result.content.match(recordIdRegex);
                if (matchedRecordId) {
                    userClick.businessObject = matchedRecordId[1];
                }
            }
            // name
            userClick.name = result.name;
            // add entity type to the name
            var entityType = result.content.match(entityTypeRegex);
            if (entityType) {
                userClick.name = entityType[2] + " " + userClick.name;
            }
            // page
            if (result.page) {
                userClick.page = result.page;
            }
        }
        else {
            userClick.name = 'Scripting and Rendering';
            for (var i = 0; i < events.length; i++) {
                var event_2 = events[i].event;
                if (event_2.type === 'click') {
                    var name_2 = getNameForClickEvent(event_2);
                    if (name_2 !== undefined) {
                        userClick.name = name_2;
                        break;
                    }
                }
            }
        }
        // SFDC Classic with hard navigations
        if (containsDocumentRequest && userClick.name === 'Scripting and Rendering' && userClick.page) {
            var name_3 = userClick.page.title || userClick.page.path || '';
            if (name_3) {
                userClick.name = name_3.split(' ~ Salesforce')[0].trim(); // remove SFDC License name from the name
            }
        }
        // success
        userClick.success = success;
        
        
        // TODO
        var recentRequest = { event: undefined, counter: 0, firstTimestamp: 0 };
        // Person Account Search
        // Duplicate Requests
        events.forEach(function (event) {
            if (event.event) {
                if (event.event.type === 'request' && event.event.url) {
                    if (!recentRequest.event) { // first request
                        recentRequest.event = event.event;
                        recentRequest.firstTimestamp = event.event.startTimestamp;
                        recentRequest.counter = 1;
                    }
                    else if (event.event.startTimestamp - recentRequest.event.startTimestamp <= 700) { // new request within time threshold
                        // console.log("check", event.event, recentRequest.event);
                        if (recentRequest.event.url.protocol === event.event.url.protocol && recentRequest.event.url.hostname === event.event.url.hostname && recentRequest.event.url.pathname === event.event.url.pathname &&
                             recentRequest.event.method === event.event.method) { // new request is equal to previous one
                            recentRequest.counter++;
                            recentRequest.event = event.event;
                        }
                        else { // reset state as new request is not equal
                            recentRequest.event = event.event;
                            recentRequest.counter = 1;
                            recentRequest.firstTimestamp = event.event.startTimestamp;
                        }
                    }
                    else {
                        // check if current state contains duplicates
                        if (recentRequest.counter >= 5 && recentRequest.event) {
                            console.log("DUP", recentRequest);
                            var queryAndHash = [germainApm.utils.getQueryParams(recentRequest.event.url), germainApm.utils.getHash(recentRequest.event.url)].filter(Boolean).join('#') || null;
                            var httpDim = { path: recentRequest.event.url.pathname, query: queryAndHash };
                            germainApm.api.createMetric('Duplicated Network Request', recentRequest.counter, {
                                http: httpDim
                            });
                        }
                        // reset state
                        recentRequest.event = event.event;
                        recentRequest.counter = 1;
                        recentRequest.firstTimestamp = event.event.startTimestamp;
                    }
                }
            }
        });
        // last check for recent requests state
        console.log("Last Check", recentRequest);
        if (recentRequest.counter >= 5 && recentRequest.event) {
            console.log("DUP", recentRequest);
            var queryAndHash = [germainApm.utils.getQueryParams(recentRequest.event.url), germainApm.utils.getHash(recentRequest.event.url)].filter(Boolean).join('#') || null;
            var httpDim = { path: recentRequest.event.url.pathname, query: queryAndHash };
            germainApm.api.createMetric('Duplicated Network Request', recentRequest.counter, {
                http: httpDim
            });
        }
        
        function getNameForClickEvent(event) {
            if (germainApm.utils.instanceOf(event.target, HTMLElement)) {
                var target = event.target;
                var dataLabel = target.getAttribute('data-label');
                if (dataLabel) {
                    return 'Click on ' + dataLabel;
                }
                switch (target.nodeName) {
                    case "A":
                    case "BUTTON":
                    case "LI": {
                        return getClickName(target);
                    }
                    case "SPAN": {
                        if (target.className && ~target.className.indexOf('title')) {
                            return getClickName(target);
                        }
                        else if (target.parentElement && target.parentElement.nodeName === 'A') {
                            return getClickName(target.parentElement);
                        }
                    }
                }
            }
            return undefined;
        }
        function getClickName(target) {
            var content = target.innerText || target.textContent;
            return content ? 'Click on ' + content : 'Click';
        }
    }
    function factProcessor(fact) {
        switch (fact.type) {
            case 'Browser:User Click': {
                fact.type = fact.type + ':Salesforce';
                break;
            }
            case 'Browser:User Click Cluster': {
                fact.type = 'Browser:User Click:Salesforce Cluster';
                break;
            }
            case 'HTTP:Request': {
                fact.type = fact.type + ':Salesforce';
                if (fact.http && fact.http.path === '/aura' && fact.name === 'xmlhttprequest') {
                    fact.name = 'Aura HTTP Request';
                }
                break;
            }
        }
    }
    

Automate Identification of “new” errors

To be able to ignore the million of errors that occur every day and know when a new error occur, configure Germain UX’s Categorizationfeature.

Enable Alert and/or Report

Be alerted immediately when a new error occurs. Receive automatic report to help understand error volume trends.

Visualize & Analyze Errors

Add a portlet and set it up as following:

  • KPI = “JavaScript Popup Dialog” or “Javascript Console Event” KPI
    (or any other KPI you have created in connection with the above error monitoring).

  • Pivot = “Message”

  • Filters = “userError == true”

image-20240227-182617.png

Add “JavaScript Popup Dialog” KPI for Salesforce Error analysis - Germain UX

This is how the portlet would look like:

image-20240227-182512.png

Salesforce Error portlet - Germain UX

  • Exclude Errors
    And once your portlet is created, you can always exclude/include additional errors, as following:

    image-20240227-165033.png

    Filter in or out specific Salesforce errors - Germain UX

Component: Engine

Feature Availability: 2014.1 or later

JavaScript errors detected

Please note, these errors can depend on your browser setup.

If this problem persists, please contact our support.