Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions nebula-logger/core.package.xml
Original file line number Diff line number Diff line change
Expand Up @@ -735,6 +735,7 @@
<members>LoggerParameter.EnableLoggerSystemMessages</members>
<members>LoggerParameter.EnableStackTraceParsing</members>
<members>LoggerParameter.EnableTagging</members>
<members>LoggerParameter.IgnoredJavaScriptOrigins</members>
<members>LoggerParameter.LogBatchPurgerDefaultBatchSize</members>
<members>LoggerParameter.LogEntryEventStreamDisplayFields</members>
<members>LoggerParameter.NormalizeScenarioData</members>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,22 @@ public class LoggerParameter {
private set;
}

/**
* @description A list of JavaScript file name patterns that should be ignored when parsing stack traces.
* Any stack trace lines containing file names that match these patterns will be removed from the parsed stack trace.
* This is useful for filtering out utility or framework files from stack traces.
* Controlled by the custom metadata record `LoggerParameter.IgnoredJavaScriptOrigins`, or an empty list as the default
*/
public static final List<String> IGNORED_JAVASCRIPT_ORIGINS {
get {
if (IGNORED_JAVASCRIPT_ORIGINS == null) {
IGNORED_JAVASCRIPT_ORIGINS = getStringList('IgnoredJavaScriptOrigins', new List<String>());
}
return IGNORED_JAVASCRIPT_ORIGINS;
}
private set;
}

/**
* @description Controls the default batch size used by the batchable class `LogBatchPurger` when purging old logging data.
* Controlled by the custom metadata record `LoggerParameter.LogBatchPurgerDefaultBatchSize`, or `500` as the default
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<CustomMetadata xmlns="http://soap.sforce.com/2006/04/metadata" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<label>Ignored JavaScript Origins</label>
<protected>false</protected>
<values>
<field>Comments__c</field>
<value xsi:nil="true"/>
</values>
<values>
<field>Description__c</field>
<value xsi:type="xsd:string">A comma-separated list of JavaScript file name patterns to ignore when parsing stack traces. For example: &apos;jquery,analytics.js,third-party-lib&apos;

These patterns will be used to filter out utility libraries, third-party scripts, or other files that should not appear in stack traces. This helps clean up JavaScript stack traces for better readability and debugging.</value>
</values>
<values>
<field>Value__c</field>
<value xsi:type="xsd:string"></value>
</values>
</CustomMetadata>
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,13 @@ public inherited sharing class ComponentLogger {
@AuraEnabled
public ComponentLoggingLevel userLoggingLevel { get; set; }

/**
* @description A list of JavaScript file name patterns that should be ignored when parsing stack traces,
* based on `LoggerParameter.IgnoredJavaScriptOrigins`
*/
@AuraEnabled
public List<String> ignoredJavaScriptOrigins { get; set; }

private ComponentLoggerSettings() {
LoggerSettings__c userSettings = Logger.getUserSettings();

Expand All @@ -258,6 +265,7 @@ public inherited sharing class ComponentLogger {
this.isLightningLoggerEnabled = userSettings.IsJavaScriptLightningLoggerEnabled__c;
this.supportedLoggingLevels = getSupportedLoggingLevels();
this.userLoggingLevel = getUserLoggingLevel();
this.ignoredJavaScriptOrigins = LoggerParameter.IGNORED_JAVASCRIPT_ORIGINS;
}

private Map<String, Integer> getSupportedLoggingLevels() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,4 +168,45 @@ describe('logger stack trace parsing tests', () => {
expect(originStackTrace.functionName).toEqual('logInfoWithoutDebugExample');
expect(originStackTrace.metadataType).toEqual('LightningComponentBundle');
});

it('correctly applies instance-level ignored origins', async () => {
const loggerStackTrace = new LoggerStackTrace();
loggerStackTrace.setIgnoredOrigins(['jquery', 'analytics']);

// Create a mock error with a stack trace that includes ignored origins
const mockError = new Error('Test error');
mockError.stack = `Error: Test error
at someFunction (https://example.com/jquery.min.js:1:1)
at anotherFunction (https://example.com/analytics.js:2:2)
at validFunction (https://example.com/myComponent.js:3:3)`;

const originStackTrace = loggerStackTrace.parse(mockError);

// Should skip the ignored files and use the valid one
expect(originStackTrace.fileName).toContain('myComponent.js');
expect(originStackTrace.fileName).not.toContain('jquery');
expect(originStackTrace.fileName).not.toContain('analytics');
});

it('correctly applies global ignored origins', async () => {
// Set global ignored origins
LoggerStackTrace.setGlobalIgnoredOrigins(['global-ignored']);

const loggerStackTrace = new LoggerStackTrace();

// Create a mock error with a stack trace that includes globally ignored origins
const mockError = new Error('Test error');
mockError.stack = `Error: Test error
at someFunction (https://example.com/global-ignored.js:1:1)
at validFunction (https://example.com/myComponent.js:3:3)`;

const originStackTrace = loggerStackTrace.parse(mockError);

// Should skip the globally ignored file and use the valid one
expect(originStackTrace.fileName).toContain('myComponent.js');
expect(originStackTrace.fileName).not.toContain('global-ignored');

// Clean up global state
LoggerStackTrace.setGlobalIgnoredOrigins([]);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import FORM_FACTOR from '@salesforce/client/formFactor';
import { log as lightningLog } from 'lightning/logger';
import LogEntryEventBuilder from './logEntryBuilder';
import LoggerServiceTaskQueue from './loggerServiceTaskQueue';
import LoggerStackTrace from './loggerStackTrace';
import getSettings from '@salesforce/apex/ComponentLogger.getSettings';
import saveComponentLogEntries from '@salesforce/apex/ComponentLogger.saveComponentLogEntries';

Expand Down Expand Up @@ -70,6 +71,14 @@ export default class LoggerService {
this.#scenario = scenario;
}

/**
* @description Sets the list of JavaScript file name patterns to ignore when parsing stack traces
* @param {String[]} ignoredOrigins - Array of file name patterns to ignore (e.g., ['jquery', 'analytics.js'])
*/
setIgnoredOrigins(ignoredOrigins) {
LoggerStackTrace.setGlobalIgnoredOrigins(ignoredOrigins);
}

exception(message, exception, originStackTraceError) {
this.error(message, originStackTraceError).setExceptionDetails(exception);
this.saveLog();
Expand Down Expand Up @@ -156,6 +165,9 @@ export default class LoggerService {
userLoggingLevel: Object.freeze(retrievedSettings.userLoggingLevel)
});

// Configure LoggerStackTrace with ignored origins from settings
LoggerStackTrace.setGlobalIgnoredOrigins(this.#settings.ignoredJavaScriptOrigins);

if (!LoggerService.hasInitialized && LoggerService.areSystemMessagesEnabled && this.#settings.isConsoleLoggingEnabled) {
this._logToConsole('INFO', 'logger component initialized\n' + JSON.stringify(new BrowserContext(), null, 2));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,31 @@ The code below is specific to Nebula Logger - it leverages stacktrace.js plus so
additional parsing logic to handle Salesforce-specific stack traces in LWC & Aura components
*/
export default class LoggerStackTrace {
static #globalIgnoredOrigins = [];

/**
* @description Sets the global list of file name patterns to ignore when parsing stack traces
* @param {String[]} ignoredOrigins - Array of file name patterns to ignore globally
*/
static setGlobalIgnoredOrigins(ignoredOrigins) {
LoggerStackTrace.#globalIgnoredOrigins = ignoredOrigins || [];
}

#ignoredOrigins = [];

constructor() {
// Initialize with global ignored origins
this.#ignoredOrigins = [...LoggerStackTrace.#globalIgnoredOrigins];
}

/**
* @description Sets the list of file name patterns to ignore when parsing stack traces
* @param {String[]} ignoredOrigins - Array of file name patterns to ignore
*/
setIgnoredOrigins(ignoredOrigins) {
this.#ignoredOrigins = ignoredOrigins || [];
}

parse(originStackTraceError) {
if (!originStackTraceError) {
return this;
Expand All @@ -158,12 +183,8 @@ export default class LoggerStackTrace {
let originStackTraceParticle;
const parsedStackTraceLines = [];
originStackTraceParticles.forEach(currentStackTraceParticle => {
if (!originStackTraceParticle && currentStackTraceParticle.fileName?.endsWith('/logger.js')) {
return;
}

const ignoredAuraFilenamesRegEx = /aura_prod(?:\.js|debug(?:\.js)?)$/;
if (!originStackTraceParticle && ignoredAuraFilenamesRegEx.test(currentStackTraceParticle.fileName)) {
// Check if this particle should be ignored
if (this._shouldIgnoreStackTraceParticle(currentStackTraceParticle, !originStackTraceParticle)) {
return;
}

Expand All @@ -178,6 +199,28 @@ export default class LoggerStackTrace {
return { ...originStackTraceParticle, parsedStackTraceString };
}

_shouldIgnoreStackTraceParticle(stackTraceParticle, isFirstParticle) {
// Always ignore logger.js files
if (isFirstParticle && stackTraceParticle.fileName?.endsWith('/logger.js')) {
return true;
}

// Check configurable ignored origins
for (const ignoredOrigin of this.#ignoredOrigins) {
if (stackTraceParticle.fileName?.includes(ignoredOrigin)) {
return true;
}
}

// Check hardcoded patterns for backward compatibility
const ignoredAuraFilenamesRegEx = /aura_prod(?:\.js|debug(?:\.js)?)$/;
if (isFirstParticle && ignoredAuraFilenamesRegEx.test(stackTraceParticle.fileName)) {
return true;
}

return false;
}

_cleanStackTraceParticle(stackTraceParticle) {
const lwcModulesFileNamePrefix = 'modules/';
if (stackTraceParticle.fileName?.includes(lwcModulesFileNamePrefix)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,25 @@ private class LoggerParameter_Tests {
System.Assert.areEqual(mockValue, returnedValue);
}

@IsTest
static void it_should_return_constant_value_for_ignored_javascript_origins() {
List<String> mockValue = new List<String>{ 'c/someUtilityComponent', 'c/anotherWrapper' };
LoggerParameter__mdt mockParameter = new LoggerParameter__mdt(DeveloperName = 'IgnoredJavaScriptOrigins', Value__c = System.JSON.serialize(mockValue));
LoggerParameter.setMock(mockParameter);

List<String> returnedValue = LoggerParameter.IGNORED_JAVASCRIPT_ORIGINS;

System.Assert.areEqual(mockValue, returnedValue, 'Returned value does not match expected parameter value');
}

@IsTest
static void it_should_return_empty_list_as_default_for_ignored_javascript_origins() {
List<String> returnedValue = LoggerParameter.IGNORED_JAVASCRIPT_ORIGINS;

System.Assert.isNotNull(returnedValue);
System.Assert.areEqual(0, returnedValue.size(), 'Default value should be an empty list');
}

@IsTest
static void it_should_return_constant_value_for_log_batch_purger_default_batch_size() {
Integer mockValue = 1234;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,35 @@ private class ComponentLogger_Tests {
Integer returnedOrdinal = componentLoggerSettings.supportedLoggingLevels.get(currentLoggingLevel.name());
System.Assert.areEqual(currentLoggingLevel.ordinal(), returnedOrdinal);
}
System.Assert.isNotNull(componentLoggerSettings.ignoredJavaScriptOrigins, 'ignoredJavaScriptOrigins should never be null but empty list by default');
}

@IsTest
static void it_should_return_configured_ignored_javascript_origins_in_settings() {
List<String> mockIgnoredOrigins = new List<String>{ 'c/someUtilityComponent', 'c/anotherWrapper' };
LoggerParameter__mdt mockParameter = new LoggerParameter__mdt(
DeveloperName = 'IgnoredJavaScriptOrigins',
Value__c = System.JSON.serialize(mockIgnoredOrigins)
);
LoggerParameter.setMock(mockParameter);

ComponentLogger.ComponentLoggerSettings componentLoggerSettings = ComponentLogger.getSettings();

System.Assert.isNotNull(componentLoggerSettings.ignoredJavaScriptOrigins, 'ignoredJavaScriptOrigins wrongly returned as null when a value was configured');
System.Assert.areEqual(
mockIgnoredOrigins.size(),
componentLoggerSettings.ignoredJavaScriptOrigins.size(),
'Size of ignored JavaScript origins list did not match expected size'
);
System.Assert.areEqual(mockIgnoredOrigins, componentLoggerSettings.ignoredJavaScriptOrigins, 'Ignored JavaScript origins did not match expected values');
}

@IsTest
static void it_should_return_empty_ignored_javascript_origins_by_default_in_settings() {
ComponentLogger.ComponentLoggerSettings componentLoggerSettings = ComponentLogger.getSettings();

System.Assert.isNotNull(componentLoggerSettings.ignoredJavaScriptOrigins, 'ignoredJavaScriptOrigins should never be null but empty list by default');
System.Assert.areEqual(0, componentLoggerSettings.ignoredJavaScriptOrigins.size(), 'Default should be an empty list');
}

@IsTest
Expand Down