From 2d5be9e90801b0683987d0c18c501db0b8982f52 Mon Sep 17 00:00:00 2001 From: Jason Siders <41714875+jasonsiders@users.noreply.github.com> Date: Wed, 6 Aug 2025 14:25:02 -0500 Subject: [PATCH 01/38] temporary - adding package aliases to outer project file --- sfdx-project.json | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/sfdx-project.json b/sfdx-project.json index 1227b85b..3d02aee8 100644 --- a/sfdx-project.json +++ b/sfdx-project.json @@ -3,8 +3,17 @@ { "path": "source", "default": true, + "package": "Apex Database Layer", "versionName": "v3.1.0", "versionNumber": "3.1.0.NEXT" + }, + { + "versionName": "v1.0.0", + "versionNumber": "1.0.0.NEXT", + "path": "packages/nebula-logger/source", + "default": false, + "package": "Apex Database Layer: Nebula Logger Plugin", + "versionDescription": "Use Nebula Logger to automatically log details of DML & SOQL operations" } ], "name": "apex-database-layer", @@ -13,6 +22,8 @@ "sourceApiVersion": "64.0", "packageAliases": { "apex-database-layer-managed": "0HoDn0000010wCnKAI", - "apex-database-layer-unlocked": "0HoDn000000kAONKA2" + "apex-database-layer-unlocked": "0HoDn000000kAONKA2", + "apex-database-layer-nebula-logger-plugin-unlocked": "0HoDn0000010wCxKAI", + "apex-database-layer-nebula-logger-plugin-managed": "0HoDn0000010wD2KAI" } -} +} \ No newline at end of file From 3ea78ee1b71a7174a4e1c563fa7360d6c7b92f6d Mon Sep 17 00:00:00 2001 From: Jason Siders <41714875+jasonsiders@users.noreply.github.com> Date: Wed, 6 Aug 2025 15:49:52 -0500 Subject: [PATCH 02/38] promoting DatabaseLayerTestUtils to global, adding method to inject specific method, deepening module --- source/classes/DatabaseLayerTestUtils.cls | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/source/classes/DatabaseLayerTestUtils.cls b/source/classes/DatabaseLayerTestUtils.cls index f7bf83b7..9e6d4fcd 100644 --- a/source/classes/DatabaseLayerTestUtils.cls +++ b/source/classes/DatabaseLayerTestUtils.cls @@ -1,8 +1,9 @@ /** * @description Test utility class providing common functionality and spy implementations for framework testing. */ +@SuppressWarnings('PMD.AvoidGlobalModifier') @IsTest -public class DatabaseLayerTestUtils { +global class DatabaseLayerTestUtils { // Note: These properties do not use traditional naming conventions to support a 'namespace-like' API @SuppressWarnings('PMD.FieldNamingConventions') public static final PreAndPostProcessorSpy DmlPluginSpy = new PreAndPostProcessorSpy(); @@ -11,14 +12,24 @@ public class DatabaseLayerTestUtils { /** * @description Creates a mock DatabaseLayerSetting__mdt for testing and injects it into the metadata selector. + * @param setting The custom metadata record being injected. * @return The created test settings object */ - public static DatabaseLayerSetting__mdt initSettings() { - DatabaseLayerSetting__mdt setting = new DatabaseLayerSetting__mdt(); + global static DatabaseLayerSetting__mdt initSettings(DatabaseLayerSetting__mdt setting) { DatabaseLayer.Utils.MetadataSelector.settings = new List{ setting }; + DatabaseLayer.Dml.initPlugins(); + DatabaseLayer.Soql.initPlugins(); return setting; } + /** + * @description Creates an empty DatabaseLayerSetting__mdt for testing and injects it into the metadata selector. + * @return The created test settings object + */ + global static DatabaseLayerSetting__mdt initSettings() { + return DatabaseLayerTestUtils.initSettings(new DatabaseLayerSetting__mdt()); + } + // **** INNER **** // /** * @description Spy implementation for tracking plugin method invocations during testing. From 89038067b70b23f295558ae135eda113785fc831 Mon Sep 17 00:00:00 2001 From: Jason Siders <41714875+jasonsiders@users.noreply.github.com> Date: Wed, 6 Aug 2025 15:55:04 -0500 Subject: [PATCH 03/38] checkpoint - refactoring DmlTest to use new plugin utility method --- source/classes/DmlTest.cls | 31 +++++++++---------------------- 1 file changed, 9 insertions(+), 22 deletions(-) diff --git a/source/classes/DmlTest.cls b/source/classes/DmlTest.cls index baeb23a9..c2f2c580 100644 --- a/source/classes/DmlTest.cls +++ b/source/classes/DmlTest.cls @@ -13,9 +13,7 @@ private class DmlTest { // Custom metadata actually defined in the org should not interfere with this test: DatabaseLayer.Utils.MetadataSelector.settings = new List{}; // Initialize plugins - DatabaseLayerSetting__mdt settings = DatabaseLayerTestUtils.initSettings(); - settings.DmlPreAndPostProcessor__c = DatabaseLayerTestUtils.SamplePlugin.class.getName(); - DatabaseLayer.Dml.initPlugins(); + DmlTest.initPlugins(DatabaseLayerTestUtils.SamplePlugin.class.getName()); } // **** TESTS **** // @@ -874,10 +872,7 @@ private class DmlTest { @IsTest static void shouldRunPreAndPostProcessorPluginLogic() { - DatabaseLayerSetting__mdt settings = DatabaseLayerTestUtils.initSettings(); - settings.DmlPreAndPostProcessor__c = DatabaseLayerTestUtils.SamplePlugin.class.getName(); - // Re-initialize the plugins, now that the field has been set: - DatabaseLayer.Dml.initPlugins(); + DmlTest.initPlugins(DatabaseLayerTestUtils.SamplePlugin.class.getName()); Account account = DmlTest.initAccount(); Test.startTest(); @@ -896,10 +891,7 @@ private class DmlTest { @IsTest static void shouldRunPluginErrorLogic() { - DatabaseLayerSetting__mdt settings = DatabaseLayerTestUtils.initSettings(); - settings.DmlPreAndPostProcessor__c = DatabaseLayerTestUtils.SamplePlugin.class.getName(); - // Re-initialize the plugins, now that the field has been set: - DatabaseLayer.Dml.initPlugins(); + DmlTest.initPlugins(DatabaseLayerTestUtils.SamplePlugin.class.getName()); Account account = DmlTest.initAccount(); Test.startTest(); @@ -925,10 +917,7 @@ private class DmlTest { @IsTest static void shouldNotRunProcessorLogicIfInvalidApexClassDefined() { - DatabaseLayerSetting__mdt settings = DatabaseLayerTestUtils.initSettings(); - settings.DmlPreAndPostProcessor__c = 'an obviously invalid apex class name'; - // Re-initialize the plugins, now that the field has been set: - DatabaseLayer.Dml.initPlugins(); + DmlTest.initPlugins('An obviously invalid apex class name'); Account account = DmlTest.initAccount(); Test.startTest(); @@ -947,9 +936,7 @@ private class DmlTest { @IsTest static void shouldNotRunProcessorLogicIfNoValueDefined() { - DatabaseLayerSetting__mdt settings = DatabaseLayerTestUtils.initSettings(); - settings.DmlPreAndPostProcessor__c = null; - DatabaseLayer.Dml.initPlugins(); + DmlTest.initPlugins(null); Account account = DmlTest.initAccount(); Test.startTest(); @@ -1044,10 +1031,10 @@ private class DmlTest { return leadToConvert; } - private static DatabaseLayerSetting__mdt initSettings() { - DatabaseLayerSetting__mdt settings = new DatabaseLayerSetting__mdt(); - DatabaseLayer.Utils.MetadataSelector.settings?.add(settings); - return settings; + private static void initPlugins(String pluginName) { + DatabaseLayerSetting__mdt setting = new DatabaseLayerSetting__mdt(); + setting.DmlPreAndPostProcessor__c = pluginName; + DatabaseLayerTestUtils.initSettings(setting); } // **** INNER **** // From 79edb6a9953aea91cc5f21860a2d0af5d8ceacb5 Mon Sep 17 00:00:00 2001 From: Jason Siders <41714875+jasonsiders@users.noreply.github.com> Date: Wed, 6 Aug 2025 15:58:19 -0500 Subject: [PATCH 04/38] adding new plugin method to DmlTest --- source/classes/DmlTest.cls | 4 ++-- source/classes/MockDmlTest.cls | 29 +++++++++++------------------ 2 files changed, 13 insertions(+), 20 deletions(-) diff --git a/source/classes/DmlTest.cls b/source/classes/DmlTest.cls index c2f2c580..d1bdeda7 100644 --- a/source/classes/DmlTest.cls +++ b/source/classes/DmlTest.cls @@ -1031,9 +1031,9 @@ private class DmlTest { return leadToConvert; } - private static void initPlugins(String pluginName) { + private static void initPlugins(String dmlPluginName) { DatabaseLayerSetting__mdt setting = new DatabaseLayerSetting__mdt(); - setting.DmlPreAndPostProcessor__c = pluginName; + setting.DmlPreAndPostProcessor__c = dmlPluginName; DatabaseLayerTestUtils.initSettings(setting); } diff --git a/source/classes/MockDmlTest.cls b/source/classes/MockDmlTest.cls index 7bf6c6a7..c30d00db 100644 --- a/source/classes/MockDmlTest.cls +++ b/source/classes/MockDmlTest.cls @@ -11,9 +11,7 @@ private class MockDmlTest { // Use Mock DML: DatabaseLayer.useMockDml(); // Initialize plugins: - DatabaseLayerSetting__mdt settings = DatabaseLayerTestUtils.initSettings(); - settings.DmlPreAndPostProcessor__c = DatabaseLayerTestUtils.SamplePlugin.class.getName(); - DatabaseLayer.Dml.initPlugins(); + MockDmlTest.initPlugins(DatabaseLayerTestUtils.SamplePlugin.class.getName()); } // **** TESTS **** // @@ -905,10 +903,7 @@ private class MockDmlTest { @IsTest static void shouldRunPreAndPostProcessorPluginLogic() { - DatabaseLayerSetting__mdt settings = DatabaseLayerTestUtils.initSettings(); - settings.DmlPreAndPostProcessor__c = DatabaseLayerTestUtils.SamplePlugin.class.getName(); - // Re-initialize the plugins, now that the field has been set: - DatabaseLayer.Dml.initPlugins(); + MockDmlTest.initPlugins(DatabaseLayerTestUtils.SamplePlugin.class.getName()); List accounts = MockDmlTest.initRecords(Account.SObjectType, false); Test.startTest(); @@ -927,10 +922,7 @@ private class MockDmlTest { @IsTest static void shouldRunPluginErrorLogic() { - DatabaseLayerSetting__mdt settings = DatabaseLayerTestUtils.initSettings(); - settings.DmlPreAndPostProcessor__c = DatabaseLayerTestUtils.SamplePlugin.class.getName(); - // Re-initialize the plugins, now that the field has been set: - DatabaseLayer.Dml.initPlugins(); + MockDmlTest.initPlugins(DatabaseLayerTestUtils.SamplePlugin.class.getName()); List accounts = MockDmlTest.initRecords(Account.SObjectType, false); MockDml.shouldFail(); @@ -957,10 +949,7 @@ private class MockDmlTest { @IsTest static void shouldNotRunProcessorLogicIfInvalidApexClassDefined() { - DatabaseLayerSetting__mdt settings = DatabaseLayerTestUtils.initSettings(); - settings.DmlPreAndPostProcessor__c = 'an obviously invalid apex class name'; - // Re-initialize the plugins, now that the field has been set: - DatabaseLayer.Dml.initPlugins(); + MockDmlTest.initPlugins('An obviously invalid apex class name'); List accounts = MockDmlTest.initRecords(Account.SObjectType, false); Test.startTest(); @@ -979,9 +968,7 @@ private class MockDmlTest { @IsTest static void shouldNotRunProcessorLogicIfNoValueDefined() { - DatabaseLayerSetting__mdt settings = DatabaseLayerTestUtils.initSettings(); - settings.DmlPreAndPostProcessor__c = null; - DatabaseLayer.Dml.initPlugins(); + MockDmlTest.initPlugins(null); List accounts = MockDmlTest.initRecords(Account.SObjectType, false); Test.startTest(); @@ -1019,6 +1006,12 @@ private class MockDmlTest { } // **** HELPER **** // + private static void initPlugins(String dmlPluginName) { + DatabaseLayerSetting__mdt settings = DatabaseLayerTestUtils.initSettings(); + settings.DmlPreAndPostProcessor__c = dmlPluginName; + DatabaseLayerTestUtils.initSettings(settings); + } + private static List initRecords(SObjectType objectType, Boolean withId) { List records = new List(); for (Integer i = 0; i < TEST_SIZE; i++) { From 724af929dadf6601fcd12fa75ac6788cdfef417e Mon Sep 17 00:00:00 2001 From: Jason Siders <41714875+jasonsiders@users.noreply.github.com> Date: Wed, 6 Aug 2025 15:58:40 -0500 Subject: [PATCH 05/38] checkpoint - creating failing tests, still missing way of adding the plugins --- .../managed/unlocked/sfdx-project.json | 30 +++++++ .../DatabaseLayerNebulaLoggerAdapter.cls | 58 +++++++++++++ ...abaseLayerNebulaLoggerAdapter.cls-meta.xml | 5 ++ .../DatabaseLayerNebulaLoggerAdapterTest.cls | 84 +++++++++++++++++++ ...eLayerNebulaLoggerAdapterTest.cls-meta.xml | 5 ++ sfdx-project.json | 6 +- 6 files changed, 185 insertions(+), 3 deletions(-) create mode 100644 plugins/nebula-logger/packages/managed/unlocked/sfdx-project.json create mode 100644 plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapter.cls create mode 100644 plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapter.cls-meta.xml create mode 100644 plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapterTest.cls create mode 100644 plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapterTest.cls-meta.xml diff --git a/plugins/nebula-logger/packages/managed/unlocked/sfdx-project.json b/plugins/nebula-logger/packages/managed/unlocked/sfdx-project.json new file mode 100644 index 00000000..9bd984ba --- /dev/null +++ b/plugins/nebula-logger/packages/managed/unlocked/sfdx-project.json @@ -0,0 +1,30 @@ +{ + "packageDirectories": [ + { + "path": "plugins/nebula/logger/source", + "default": true, + "package": "apex-database-layer-plugin-nebula-logger-unlocked", + "versionName": "v1.0.0", + "versionNumber": "1.0.0.NEXT", + "versionDescription": "Use Nebula Logger to automatically log details of DML & SOQL operations", + "dependencies": [ + { + "package": "apex-database-layer-unlocked", + "versionNumber": "3.0.0.LATEST" + }, + { + "package": "nebula-logger-unlocked@4.16.1" + } + ] + } + ], + "name": "Apex Database Layer: Nebula Logger Plugin (Unlocked)", + "namespace": "", + "sfdcLoginUrl": "https://login.salesforce.com", + "sourceApiVersion": "64.0", + "packageAliases": { + "apex-database-layer-unlocked": "0HoDn000000kAONKA2", + "nebula-logger-unlocked@4.16.1": "04tKe0000011MXEIA2", + "apex-database-layer-plugin-nebula-logger-unlocked": "0HoDn0000010wCxKAI" + } +} \ No newline at end of file diff --git a/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapter.cls b/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapter.cls new file mode 100644 index 00000000..e3b38473 --- /dev/null +++ b/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapter.cls @@ -0,0 +1,58 @@ +@SuppressWarnings('PMD.AvoidGlobalModifier') +global class DatabaseLayerNebulaLoggerAdapter implements Dml.PreAndPostProcessor, Soql.PreAndPostProcessor { + public static final String SCENARIO_NAME = 'apex-database-layer'; + + /** + * @description Called before executing a DML operation. + * @param request The DML request to be processed + */ + global void processPreDml(Dml.Request request) { + // TODO + } + + /** + * @description Called after successfully executing a DML operation. + * @param request The DML request that was processed + * @param databaseResults The results returned from the DML operation + */ + global void processPostDml(Dml.Request request, List databaseResults) { + // TODO + } + + /** + * @description Called when a DML operation encounters an error. + * @param request The DML request that encountered an error + * @param error The exception that was thrown + */ + global void processDmlError(Dml.Request request, Exception error) { + // TODO + } + + + /** + * @description Processes SOQL requests before execution. + * @param request The SOQL request being processed + */ + global void processPreSoql(Soql.Request request) { + // TODO + } + + /** + * @description Processes SOQL requests after execution. + * @param request The SOQL request that was processed + * @param results The results from the SOQL operation + */ + global void processPostSoql(Soql.Request request, Object results) { + // TODO + } + + /** + * @description Processes SOQL errors. + * @param request The SOQL request that failed + * @param error The exception that occurred + */ + global void processSoqlError(Soql.Request request, Exception error) { + // TODO + } + +} diff --git a/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapter.cls-meta.xml b/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapter.cls-meta.xml new file mode 100644 index 00000000..1e7de940 --- /dev/null +++ b/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapter.cls-meta.xml @@ -0,0 +1,5 @@ + + + 64.0 + Active + diff --git a/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapterTest.cls b/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapterTest.cls new file mode 100644 index 00000000..a38134f0 --- /dev/null +++ b/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapterTest.cls @@ -0,0 +1,84 @@ +@SuppressWarnings('PMD.ApexUnitTestClassShouldHaveRunAs') +@IsTest +private class DatabaseLayerNebulaLoggerAdapterTest { + private static final Account MOCK_RECORD; + + static { + DatabaseLayer.useMocks(); + MOCK_RECORD = (Account) new MockRecord(Account.SObjectType)?.withId()?.toSObject(); + MockSoql.setGlobalMock().withResults(new List{ MOCK_RECORD }); + // TODO: Figure out how to enable nebula logger plugins + } + + @IsTest + static void shouldLogSuccessfulDmlOperations() { + Test.startTest(); + DatabaseLayer.Dml.doUpdate(MOCK_RECORD); + Logger.saveLog(); + Test.stopTest(); + + List entries = DatabaseLayerNebulaLoggerAdapterTest.getLogs(); + Assert.isFalse(entries?.isEmpty(), 'Did not log via nebula logger'); + } + + @IsTest + static void shouldLogFailedDmlOperations() { + MockDml.shouldFail(); + + Test.startTest(); + Exception caughtError; + try { + DatabaseLayer.Dml.doUpdate(MOCK_RECORD); + Assert.fail('A System.DmlException was not thrown'); + } catch (System.DmlException error) { + caughtError = error; + } finally { + Logger.saveLog(); + } + Test.stopTest(); + + List entries = DatabaseLayerNebulaLoggerAdapterTest.getLogs(); + Assert.isFalse(entries?.isEmpty(), 'Did not log via nebula logger'); + } + + @IsTest + static void shouldLogSuccessfulSoqlOperations() { + Test.startTest(); + DatabaseLayer.Soql.newQuery(Account.SObjectType)?.query(); + Logger.saveLog(); + Test.stopTest(); + + List entries = DatabaseLayerNebulaLoggerAdapterTest.getLogs(); + Assert.isFalse(entries?.isEmpty(), 'Did not log via nebula logger'); + } + + @IsTest + static void shouldLogFailedSoqlOperations() { + MockSoql.setGlobalMock()?.withError(); + + Test.startTest(); + Exception caughtError; + try { + DatabaseLayer.Soql.newQuery(Account.SObjectType)?.query(); + Assert.fail('Did not throw a System.QueryException'); + } catch (System.QueryException error) { + caughtError = error; + } finally { + Logger.saveLog(); + } + Test.stopTest(); + + List entries = DatabaseLayerNebulaLoggerAdapterTest.getLogs(); + Assert.isFalse(entries?.isEmpty(), 'Did not log via nebula logger'); + } + + // **** HELPER **** // + private static List getLogs() { + // Note: Nebula Logger DML is not encompassed by apex-database-layer; this will always create real log entries + return [ + SELECT Id + FROM LogEntry__c + WHERE EntryScenario__r.Name = :DatabaseLayerNebulaLoggerAdapter.SCENARIO_NAME + ]; + } +} \ No newline at end of file diff --git a/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapterTest.cls-meta.xml b/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapterTest.cls-meta.xml new file mode 100644 index 00000000..1e7de940 --- /dev/null +++ b/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapterTest.cls-meta.xml @@ -0,0 +1,5 @@ + + + 64.0 + Active + diff --git a/sfdx-project.json b/sfdx-project.json index 3d02aee8..5e26d488 100644 --- a/sfdx-project.json +++ b/sfdx-project.json @@ -10,7 +10,7 @@ { "versionName": "v1.0.0", "versionNumber": "1.0.0.NEXT", - "path": "packages/nebula-logger/source", + "path": "plugins/nebula-logger/source", "default": false, "package": "Apex Database Layer: Nebula Logger Plugin", "versionDescription": "Use Nebula Logger to automatically log details of DML & SOQL operations" @@ -23,7 +23,7 @@ "packageAliases": { "apex-database-layer-managed": "0HoDn0000010wCnKAI", "apex-database-layer-unlocked": "0HoDn000000kAONKA2", - "apex-database-layer-nebula-logger-plugin-unlocked": "0HoDn0000010wCxKAI", - "apex-database-layer-nebula-logger-plugin-managed": "0HoDn0000010wD2KAI" + "apex-database-layer-nebula-logger-plugin-managed": "0HoDn0000010wD2KAI", + "apex-database-layer-nebula-logger-plugin-unlocked": "0HoDn0000010wCxKAI" } } \ No newline at end of file From 4547899d51a36519ad58b149f19a1d48452b6334 Mon Sep 17 00:00:00 2001 From: Jason Siders <41714875+jasonsiders@users.noreply.github.com> Date: Wed, 6 Aug 2025 16:00:43 -0500 Subject: [PATCH 06/38] linking frameworks --- .../classes/DatabaseLayerNebulaLoggerAdapterTest.cls | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapterTest.cls b/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapterTest.cls index a38134f0..288d4290 100644 --- a/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapterTest.cls +++ b/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapterTest.cls @@ -2,12 +2,13 @@ @IsTest private class DatabaseLayerNebulaLoggerAdapterTest { private static final Account MOCK_RECORD; + private static final String PLUGIN_NAME = DatabaseLayerNebulaLoggerAdapter.class.getName(); static { DatabaseLayer.useMocks(); MOCK_RECORD = (Account) new MockRecord(Account.SObjectType)?.withId()?.toSObject(); MockSoql.setGlobalMock().withResults(new List{ MOCK_RECORD }); - // TODO: Figure out how to enable nebula logger plugins + DatabaseLayerNebulaLoggerAdapterTest.initPlugins(); } @IsTest @@ -81,4 +82,12 @@ private class DatabaseLayerNebulaLoggerAdapterTest { WHERE EntryScenario__r.Name = :DatabaseLayerNebulaLoggerAdapter.SCENARIO_NAME ]; } + + private static void initPlugins() { + // Inject the plugin class to be used by the database layer framework: + DatabaseLayerSetting__mdt settings = new DatabaseLayerSetting__mdt(); + settings.DmlPreAndPostProcessor__c = PLUGIN_NAME; + settings.SoqlPreAndPostProcessor__c = PLUGIN_NAME; + DatabaseLayerTestUtils.initSettings(settings); + } } \ No newline at end of file From ff36030440ebd1730d1e8112de4b386909233760 Mon Sep 17 00:00:00 2001 From: Jason Siders <41714875+jasonsiders@users.noreply.github.com> Date: Thu, 7 Aug 2025 10:11:46 -0500 Subject: [PATCH 07/38] adding utility method to Soql tests --- source/classes/MockSoqlTest.cls | 86 +++++++++------------------------ source/classes/SoqlTest.cls | 26 ++++------ 2 files changed, 31 insertions(+), 81 deletions(-) diff --git a/source/classes/MockSoqlTest.cls b/source/classes/MockSoqlTest.cls index 631b06fb..8ea68f1d 100644 --- a/source/classes/MockSoqlTest.cls +++ b/source/classes/MockSoqlTest.cls @@ -5,19 +5,16 @@ private class MockSoqlTest { private static final Integer NUM_ACCS; static { + DatabaseLayer.useMocks(); NUM_ACCS = 10; - DatabaseLayerSetting__mdt settings = DatabaseLayerTestUtils.initSettings(); - settings.SoqlPreAndPostProcessor__c = DatabaseLayerTestUtils.SamplePlugin.class.getName(); - DatabaseLayer.Soql.initPlugins(); + MockSoqlTest.initPlugins(DatabaseLayerTestUtils.SamplePlugin.class.getName()); } // **** TESTS **** // @IsTest static void shouldMockCountQuery() { - // Build a query Soql.Aggregation count = new Soql.Aggregation(Soql.Function.COUNT); - MockSoql soql = (MockSoql) DatabaseLayer.useMockSoql()?.newQuery(Account.SObjectType)?.addSelect(count); - // Inject the query w/mock results + MockSoql soql = (MockSoql) DatabaseLayer.Soql?.newQuery(Account.SObjectType)?.addSelect(count); List accounts = MockSoqlTest.initAccounts(NUM_ACCS); soql?.setMock()?.withResults(accounts); @@ -31,9 +28,7 @@ private class MockSoqlTest { @IsTest static void shouldMockCountQueryErrors() { - // Build a query - MockSoql soql = (MockSoql) DatabaseLayer.useMockSoql()?.newQuery(Account.SObjectType)?.addSelect(Account.Name); - // Inject the query w/a mock error + MockSoql soql = (MockSoql) DatabaseLayer.Soql.newQuery(Account.SObjectType)?.addSelect(Account.Name); soql?.setMock()?.withError(); Test.startTest(); @@ -49,8 +44,7 @@ private class MockSoqlTest { @IsTest static void shouldMockCursor() { - MockSoql soql = (MockSoql) DatabaseLayer.useMockSoql()?.newQuery(Account.SObjectType)?.addSelect(Account.Name); - // Inject the query w/mock results + MockSoql soql = (MockSoql) DatabaseLayer.Soql.newQuery(Account.SObjectType)?.addSelect(Account.Name); List accounts = MockSoqlTest.initAccounts(NUM_ACCS); soql?.setMock()?.withResults(accounts); Integer numToFetch = 3; @@ -68,8 +62,7 @@ private class MockSoqlTest { @IsTest static void shouldHandleCursorFetchCallWithNegativePositionParameter() { - MockSoql soql = (MockSoql) DatabaseLayer.useMockSoql()?.newQuery(Account.SObjectType)?.addSelect(Account.Name); - // Inject the query w/mock results + MockSoql soql = (MockSoql) DatabaseLayer.Soql.newQuery(Account.SObjectType)?.addSelect(Account.Name); List accounts = MockSoqlTest.initAccounts(NUM_ACCS); soql?.setMock()?.withResults(accounts); Soql.Cursor cursor = soql?.getCursor(); @@ -88,8 +81,7 @@ private class MockSoqlTest { @IsTest static void shouldHandleCursorFetchCallWithNegativeCountParameter() { - MockSoql soql = (MockSoql) DatabaseLayer.useMockSoql()?.newQuery(Account.SObjectType)?.addSelect(Account.Name); - // Inject the query w/mock results + MockSoql soql = (MockSoql) DatabaseLayer.Soql.newQuery(Account.SObjectType)?.addSelect(Account.Name); List accounts = MockSoqlTest.initAccounts(NUM_ACCS); soql?.setMock()?.withResults(accounts); Soql.Cursor cursor = soql?.getCursor(); @@ -108,8 +100,7 @@ private class MockSoqlTest { @IsTest static void shouldHandleCursorFetchCallThatExceedsNumberOfRecords() { - MockSoql soql = (MockSoql) DatabaseLayer.useMockSoql()?.newQuery(Account.SObjectType)?.addSelect(Account.Name); - // Inject the query w/mock results + MockSoql soql = (MockSoql) DatabaseLayer.Soql.newQuery(Account.SObjectType)?.addSelect(Account.Name); List accounts = MockSoqlTest.initAccounts(NUM_ACCS); soql?.setMock()?.withResults(accounts); Soql.Cursor cursor = soql?.getCursor(); @@ -128,8 +119,7 @@ private class MockSoqlTest { @IsTest static void shouldHandleCursorFetchCallWithNullParameters() { - MockSoql soql = (MockSoql) DatabaseLayer.useMockSoql()?.newQuery(Account.SObjectType)?.addSelect(Account.Name); - // Inject the query w/mock results + MockSoql soql = (MockSoql) DatabaseLayer.Soql.newQuery(Account.SObjectType)?.addSelect(Account.Name); List accounts = MockSoqlTest.initAccounts(NUM_ACCS); soql?.setMock()?.withResults(accounts); Soql.Cursor cursor = soql?.getCursor(); @@ -148,9 +138,7 @@ private class MockSoqlTest { @IsTest static void shouldMockQueryLocator() { - // Build a query - MockSoql soql = (MockSoql) DatabaseLayer.useMockSoql()?.newQuery(Account.SObjectType)?.addSelect(Account.Name); - // Inject the query w/mock results + MockSoql soql = (MockSoql) DatabaseLayer.Soql.newQuery(Account.SObjectType)?.addSelect(Account.Name); List accounts = MockSoqlTest.initAccounts(NUM_ACCS); soql?.setMock()?.withResults(accounts); @@ -182,9 +170,7 @@ private class MockSoqlTest { @IsTest static void shouldMockQueryLocatorErrors() { - // Build a query - MockSoql soql = (MockSoql) DatabaseLayer.useMockSoql()?.newQuery(Account.SObjectType)?.addSelect(Account.Name); - // Inject the query w/a mock error + MockSoql soql = (MockSoql) DatabaseLayer.Soql.newQuery(Account.SObjectType)?.addSelect(Account.Name); soql?.setMock()?.withError(); Test.startTest(); @@ -200,9 +186,7 @@ private class MockSoqlTest { @IsTest static void shouldMockQuery() { - // Build query - MockSoql soql = (MockSoql) DatabaseLayer.useMockSoql()?.newQuery(Account.SObjectType)?.addSelect(Account.Name); - // Inject the query w/mock results + MockSoql soql = (MockSoql) DatabaseLayer.Soql.newQuery(Account.SObjectType)?.addSelect(Account.Name); List accounts = MockSoqlTest.initAccounts(NUM_ACCS); soql?.setMock()?.withResults(accounts); @@ -216,9 +200,7 @@ private class MockSoqlTest { @IsTest static void shouldMockQueryErrors() { - // Build a query - MockSoql soql = (MockSoql) DatabaseLayer.useMockSoql()?.newQuery(Account.SObjectType)?.addSelect(Account.Name); - // Inject the query w/a mock error + MockSoql soql = (MockSoql) DatabaseLayer.Soql.newQuery(Account.SObjectType)?.addSelect(Account.Name); soql?.setMock()?.withError(); Test.startTest(); @@ -234,14 +216,12 @@ private class MockSoqlTest { @IsTest static void shouldMockAggregateQuery() { - // Build an aggregate query Soql.Aggregation count = new Soql.Aggregation(Soql.Function.COUNT, User.Id); MockSoql soql = (MockSoql) DatabaseLayer.useMockSoql() ?.newQuery(User.SObjectType) ?.addSelect(new Soql.ParentField(User.ProfileId, Profile.Name)) ?.addSelect(count) ?.addGroupBy(new Soql.ParentField(User.ProfileId, Profile.Name)); - // Inject results into the query MockSoql.AggregateResult mockResult1 = new MockSoql.AggregateResult() ?.addParameter('Foo Profile') ?.addParameter(123); @@ -270,14 +250,12 @@ private class MockSoqlTest { @IsTest static void shouldMockAggregateQueryErrors() { - // Build an aggregate query Soql.Aggregation count = new Soql.Aggregation(Soql.Function.COUNT, User.Id); MockSoql soql = (MockSoql) DatabaseLayer.useMockSoql() ?.newQuery(User.SObjectType) ?.addSelect(new Soql.ParentField(User.ProfileId, Profile.Name)) ?.addSelect(count) ?.addGroupBy(new Soql.ParentField(User.ProfileId, Profile.Name)); - // Inject an error soql?.setMock()?.withError(); Test.startTest(); @@ -293,14 +271,12 @@ private class MockSoqlTest { @IsTest static void shouldMockQueryAndCastToType() { - // Build an aggregate query Soql.Aggregation count = new Soql.Aggregation(Soql.Function.COUNT, User.Id); MockSoql soql = (MockSoql) DatabaseLayer.useMockSoql() ?.newQuery(User.SObjectType) ?.addSelect(new Soql.ParentField(User.ProfileId, Profile.Name)) ?.addSelect(count) ?.addGroupBy(new Soql.ParentField(User.ProfileId, Profile.Name)); - // Inject results MockSoqlTest.SampleWrapper mockResult1 = new MockSoqlTest.SampleWrapper()?.wrap('Foo Profile', 123); MockSoqlTest.SampleWrapper mockResult2 = new MockSoqlTest.SampleWrapper()?.wrap('Bar Profile', 45); List mockResults = new List{ mockResult1, mockResult2 }; @@ -324,14 +300,12 @@ private class MockSoqlTest { @IsTest static void shouldHandleErrorsWithMockAndCast() { - // Build an aggregate query Soql.Aggregation count = new Soql.Aggregation(Soql.Function.COUNT, User.Id); MockSoql soql = (MockSoql) DatabaseLayer.useMockSoql() ?.newQuery(User.SObjectType) ?.addSelect(new Soql.ParentField(User.ProfileId, Profile.Name)) ?.addSelect(count) ?.addGroupBy(new Soql.ParentField(User.ProfileId, Profile.Name)); - // Inject an error soql?.setMock()?.withError(new System.TypeException()); Test.startTest(); @@ -347,14 +321,11 @@ private class MockSoqlTest { @IsTest static void shouldUseCustomSimulatorMocks() { - // Establish Dml & Soql objects to be used - DatabaseLayer.useMocks(); MockSoql soql = (MockSoql) DatabaseLayer.Soql.newQuery(Task.SObjectType) ?.addSelect(Task.WhatId) ?.addSelect(Task.WhoId); // Mock with a custom class that leverages MockDml.INSERTED to generate results soql?.setMock(new MockSoqlTest.CustomTaskQueryLogic()); - // Mock insert an account + related contact Account mockAccount = (Account) new MockRecord(Account.SObjectType)?.toSObject(); DatabaseLayer.Dml.doInsert(mockAccount); Contact mockContact = (Contact) new MockRecord(Contact.SObjectType) @@ -375,7 +346,6 @@ private class MockSoqlTest { @IsTest static void shouldSetGlobalMock() { - DatabaseLayer.useMocks(); MockSoql theQuery = (MockSoql) DatabaseLayer.Soql.newQuery(Account.SObjectType); // Set a global mock - all SOQL objects will return these results by default now: Account mockAccount = (Account) new MockRecord(Account.SObjectType)?.withId()?.toSObject(); @@ -391,7 +361,6 @@ private class MockSoqlTest { @IsTest static void shouldIgnoreGlobalMockIfSpecificSimulatorAssignedToQuery() { - DatabaseLayer.useMocks(); MockSoql theQuery = (MockSoql) DatabaseLayer.Soql.newQuery(Account.SObjectType); // Set a global mock - all SOQL objects will return these results by default now: Account mockAccount1 = (Account) new MockRecord(Account.SObjectType)?.withId()?.toSObject(); @@ -410,7 +379,6 @@ private class MockSoqlTest { @IsTest static void shouldIntelligentlyReturnCorrectQueryResults() { - DatabaseLayer.useMocks(); MockSoql accountQuery = (MockSoql) DatabaseLayer.Soql.newQuery(Account.SObjectType); MockSoql caseQuery = (MockSoql) DatabaseLayer.Soql.newQuery(Case.SObjectType); MockSoql.Simulator simulator = new MockSoqlTest.DynamicSimulator(); @@ -430,7 +398,7 @@ private class MockSoqlTest { @IsTest static void shouldReturnEmptyListIfNoMocks() { // If a MockSoql is ran, and no Simulator is injected, the methods will automatically return an empty List - MockSoql soql = (MockSoql) DatabaseLayer.useMockSoql()?.newQuery(Account.SObjectType); + MockSoql soql = (MockSoql) DatabaseLayer.Soql.newQuery(Account.SObjectType); Test.startTest(); List aggregateResults = soql?.aggregateQuery(); @@ -452,11 +420,6 @@ private class MockSoqlTest { @IsTest static void shouldRunPreAndPostProcessorPluginLogic() { - DatabaseLayer.useMocks(); - DatabaseLayerSetting__mdt settings = DatabaseLayerTestUtils.initSettings(); - settings.SoqlPreAndPostProcessor__c = DatabaseLayerTestUtils.SamplePlugin.class.getName(); - DatabaseLayer.Soql.initPlugins(); - Test.startTest(); DatabaseLayer.Soql.newQuery(User.SObjectType)?.setRowLimit(1)?.toSoql()?.query(); Test.stopTest(); @@ -472,10 +435,6 @@ private class MockSoqlTest { @IsTest static void shouldRunPluginErrorLogic() { - DatabaseLayer.useMocks(); - DatabaseLayerSetting__mdt settings = DatabaseLayerTestUtils.initSettings(); - settings.SoqlPreAndPostProcessor__c = DatabaseLayerTestUtils.SamplePlugin.class.getName(); - DatabaseLayer.Soql.initPlugins(); MockSoql.setGlobalMock()?.withError(); Test.startTest(); @@ -500,10 +459,7 @@ private class MockSoqlTest { @IsTest static void shouldNotRunProcessorLogicIfInvalidApexClassDefined() { - DatabaseLayer.useMocks(); - DatabaseLayerSetting__mdt settings = DatabaseLayerTestUtils.initSettings(); - settings.SoqlPreAndPostProcessor__c = 'an obviously invalid apex class name'; - DatabaseLayer.Soql.initPlugins(); + MockSoqlTest.initPlugins('an obviously invalid apex class name'); Test.startTest(); DatabaseLayer.Soql.newQuery(User.SObjectType)?.setRowLimit(1)?.toSoql()?.query(); @@ -520,10 +476,7 @@ private class MockSoqlTest { @IsTest static void shouldNotRunProcessorLogicIfNoValueDefined() { - DatabaseLayer.useMocks(); - DatabaseLayerSetting__mdt settings = DatabaseLayerTestUtils.initSettings(); - settings.SoqlPreAndPostProcessor__c = null; - DatabaseLayer.Soql.initPlugins(); + MockSoqlTest.initPlugins(null); Test.startTest(); DatabaseLayer.Soql.newQuery(User.SObjectType)?.setRowLimit(1)?.toSoql()?.query(); @@ -541,7 +494,6 @@ private class MockSoqlTest { @IsTest static void shouldNotRunProcessorLogicIfNoSettingsDefined() { DatabaseLayer.Utils.MetadataSelector.settings = new List{}; - DatabaseLayer.useMocks(); DatabaseLayer.Soql.initPlugins(); Test.startTest(); @@ -568,6 +520,12 @@ private class MockSoqlTest { return accounts; } + private static void initPlugins(String soqlPluginName) { + DatabaseLayerSetting__mdt settings = DatabaseLayerTestUtils.initSettings(); + settings.SoqlPreAndPostProcessor__c = soqlPluginName; + DatabaseLayerTestUtils.initSettings(settings); + } + // **** INNER **** // private class CustomTaskQueryLogic implements MockSoql.Simulator { /** diff --git a/source/classes/SoqlTest.cls b/source/classes/SoqlTest.cls index 476a0f37..76c1e05a 100644 --- a/source/classes/SoqlTest.cls +++ b/source/classes/SoqlTest.cls @@ -4,9 +4,7 @@ @IsTest private class SoqlTest { static { - DatabaseLayerSetting__mdt settings = DatabaseLayerTestUtils.initSettings(); - settings.SoqlPreAndPostProcessor__c = DatabaseLayerTestUtils.SamplePlugin.class.getName(); - DatabaseLayer.Soql.initPlugins(); + SoqlTest.initPlugins(DatabaseLayerTestUtils.SamplePlugin.class.getName()); } // **** TESTS **** // @@ -1518,10 +1516,6 @@ private class SoqlTest { @IsTest static void shouldRunPreAndPostProcessorPluginLogic() { - DatabaseLayerSetting__mdt settings = DatabaseLayerTestUtils.initSettings(); - settings.SoqlPreAndPostProcessor__c = DatabaseLayerTestUtils.SamplePlugin.class.getName(); - DatabaseLayer.Soql.initPlugins(); - Test.startTest(); DatabaseLayer.Soql.newQuery(User.SObjectType)?.setRowLimit(1)?.toSoql()?.query(); Test.stopTest(); @@ -1541,10 +1535,6 @@ private class SoqlTest { @IsTest static void shouldRunPluginErrorLogic() { - DatabaseLayerSetting__mdt settings = DatabaseLayerTestUtils.initSettings(); - settings.SoqlPreAndPostProcessor__c = DatabaseLayerTestUtils.SamplePlugin.class.getName(); - DatabaseLayer.Soql.initPlugins(); - Test.startTest(); Exception caughtError; try { @@ -1572,9 +1562,7 @@ private class SoqlTest { @IsTest static void shouldNotRunProcessorLogicIfInvalidApexClassDefined() { - DatabaseLayerSetting__mdt settings = DatabaseLayerTestUtils.initSettings(); - settings.SoqlPreAndPostProcessor__c = 'an obviously invalid apex class name'; - DatabaseLayer.Soql.initPlugins(); + SoqlTest.initPlugins('an obviously invalid apex class name'); Test.startTest(); DatabaseLayer.Soql.newQuery(User.SObjectType)?.setRowLimit(1)?.toSoql()?.query(); @@ -1595,9 +1583,7 @@ private class SoqlTest { @IsTest static void shouldNotRunProcessorLogicIfNoValueDefined() { - DatabaseLayerSetting__mdt settings = DatabaseLayerTestUtils.initSettings(); - settings.SoqlPreAndPostProcessor__c = null; - DatabaseLayer.Soql.initPlugins(); + SoqlTest.initPlugins(null); Test.startTest(); DatabaseLayer.Soql.newQuery(User.SObjectType)?.setRowLimit(1)?.toSoql()?.query(); @@ -1639,6 +1625,12 @@ private class SoqlTest { } // **** HELPER **** // + private static void initPlugins(String soqlPluginName) { + DatabaseLayerSetting__mdt settings = DatabaseLayerTestUtils.initSettings(); + settings.SoqlPreAndPostProcessor__c = soqlPluginName; + DatabaseLayerTestUtils.initSettings(settings); + } + private static DatabaseLayerSetting__mdt initSettings() { DatabaseLayerSetting__mdt settings = new DatabaseLayerSetting__mdt(); DatabaseLayer.Utils.MetadataSelector.settings?.add(settings); From 41fc18ab5b9ca22dc5cb152eaa809a5981de3338 Mon Sep 17 00:00:00 2001 From: Jason Siders <41714875+jasonsiders@users.noreply.github.com> Date: Thu, 7 Aug 2025 10:13:29 -0500 Subject: [PATCH 08/38] cleanup --- source/classes/DmlTest.cls | 2 -- source/classes/MockDmlTest.cls | 2 -- 2 files changed, 4 deletions(-) diff --git a/source/classes/DmlTest.cls b/source/classes/DmlTest.cls index d1bdeda7..d71f248e 100644 --- a/source/classes/DmlTest.cls +++ b/source/classes/DmlTest.cls @@ -872,7 +872,6 @@ private class DmlTest { @IsTest static void shouldRunPreAndPostProcessorPluginLogic() { - DmlTest.initPlugins(DatabaseLayerTestUtils.SamplePlugin.class.getName()); Account account = DmlTest.initAccount(); Test.startTest(); @@ -891,7 +890,6 @@ private class DmlTest { @IsTest static void shouldRunPluginErrorLogic() { - DmlTest.initPlugins(DatabaseLayerTestUtils.SamplePlugin.class.getName()); Account account = DmlTest.initAccount(); Test.startTest(); diff --git a/source/classes/MockDmlTest.cls b/source/classes/MockDmlTest.cls index c30d00db..00278ec0 100644 --- a/source/classes/MockDmlTest.cls +++ b/source/classes/MockDmlTest.cls @@ -903,7 +903,6 @@ private class MockDmlTest { @IsTest static void shouldRunPreAndPostProcessorPluginLogic() { - MockDmlTest.initPlugins(DatabaseLayerTestUtils.SamplePlugin.class.getName()); List accounts = MockDmlTest.initRecords(Account.SObjectType, false); Test.startTest(); @@ -922,7 +921,6 @@ private class MockDmlTest { @IsTest static void shouldRunPluginErrorLogic() { - MockDmlTest.initPlugins(DatabaseLayerTestUtils.SamplePlugin.class.getName()); List accounts = MockDmlTest.initRecords(Account.SObjectType, false); MockDml.shouldFail(); From a302900619c755179fa8a5fe73b991f474bb214d Mon Sep 17 00:00:00 2001 From: Jason Siders <41714875+jasonsiders@users.noreply.github.com> Date: Thu, 7 Aug 2025 10:23:23 -0500 Subject: [PATCH 09/38] adding new method to make it easier to instantiate plugins --- source/classes/DatabaseLayerTestUtils.cls | 13 +++++++++++++ source/classes/DmlTest.cls | 12 +++--------- source/classes/MockDmlTest.cls | 12 +++--------- source/classes/MockSoqlTest.cls | 12 +++--------- source/classes/SoqlTest.cls | 12 +++--------- 5 files changed, 25 insertions(+), 36 deletions(-) diff --git a/source/classes/DatabaseLayerTestUtils.cls b/source/classes/DatabaseLayerTestUtils.cls index 9e6d4fcd..58fd69d3 100644 --- a/source/classes/DatabaseLayerTestUtils.cls +++ b/source/classes/DatabaseLayerTestUtils.cls @@ -30,6 +30,19 @@ global class DatabaseLayerTestUtils { return DatabaseLayerTestUtils.initSettings(new DatabaseLayerSetting__mdt()); } + /** + * @description Creates a DatabaseLayerSetting__mdt for testing with both DML and SOQL plugins configured to use the same class. + * @param className The name of the class to use for both DML and SOQL pre/post processing + * @return The created test settings object with plugin configuration + */ + global static DatabaseLayerSetting__mdt initDmlAndSoqlPlugins(String className) { + DatabaseLayerSetting__mdt setting = new DatabaseLayerSetting__mdt( + DmlPreAndPostProcessor__c = className, + SoqlPreAndPostProcessor__c = className + ); + return DatabaseLayerTestUtils.initSettings(setting); + } + // **** INNER **** // /** * @description Spy implementation for tracking plugin method invocations during testing. diff --git a/source/classes/DmlTest.cls b/source/classes/DmlTest.cls index d71f248e..c714b745 100644 --- a/source/classes/DmlTest.cls +++ b/source/classes/DmlTest.cls @@ -13,7 +13,7 @@ private class DmlTest { // Custom metadata actually defined in the org should not interfere with this test: DatabaseLayer.Utils.MetadataSelector.settings = new List{}; // Initialize plugins - DmlTest.initPlugins(DatabaseLayerTestUtils.SamplePlugin.class.getName()); + DatabaseLayerTestUtils.initDmlAndSoqlPlugins(DatabaseLayerTestUtils.SamplePlugin.class.getName()); } // **** TESTS **** // @@ -915,7 +915,7 @@ private class DmlTest { @IsTest static void shouldNotRunProcessorLogicIfInvalidApexClassDefined() { - DmlTest.initPlugins('An obviously invalid apex class name'); + DatabaseLayerTestUtils.initDmlAndSoqlPlugins('An obviously invalid apex class name'); Account account = DmlTest.initAccount(); Test.startTest(); @@ -934,7 +934,7 @@ private class DmlTest { @IsTest static void shouldNotRunProcessorLogicIfNoValueDefined() { - DmlTest.initPlugins(null); + DatabaseLayerTestUtils.initDmlAndSoqlPlugins(null); Account account = DmlTest.initAccount(); Test.startTest(); @@ -1029,12 +1029,6 @@ private class DmlTest { return leadToConvert; } - private static void initPlugins(String dmlPluginName) { - DatabaseLayerSetting__mdt setting = new DatabaseLayerSetting__mdt(); - setting.DmlPreAndPostProcessor__c = dmlPluginName; - DatabaseLayerTestUtils.initSettings(setting); - } - // **** INNER **** // private class DeleteCallback extends DataSource.AsyncDeleteCallback { public override void processDelete(Database.DeleteResult result) { diff --git a/source/classes/MockDmlTest.cls b/source/classes/MockDmlTest.cls index 00278ec0..f71f6cb6 100644 --- a/source/classes/MockDmlTest.cls +++ b/source/classes/MockDmlTest.cls @@ -11,7 +11,7 @@ private class MockDmlTest { // Use Mock DML: DatabaseLayer.useMockDml(); // Initialize plugins: - MockDmlTest.initPlugins(DatabaseLayerTestUtils.SamplePlugin.class.getName()); + DatabaseLayerTestUtils.initDmlAndSoqlPlugins(DatabaseLayerTestUtils.SamplePlugin.class.getName()); } // **** TESTS **** // @@ -947,7 +947,7 @@ private class MockDmlTest { @IsTest static void shouldNotRunProcessorLogicIfInvalidApexClassDefined() { - MockDmlTest.initPlugins('An obviously invalid apex class name'); + DatabaseLayerTestUtils.initDmlAndSoqlPlugins('An obviously invalid apex class name'); List accounts = MockDmlTest.initRecords(Account.SObjectType, false); Test.startTest(); @@ -966,7 +966,7 @@ private class MockDmlTest { @IsTest static void shouldNotRunProcessorLogicIfNoValueDefined() { - MockDmlTest.initPlugins(null); + DatabaseLayerTestUtils.initDmlAndSoqlPlugins(null); List accounts = MockDmlTest.initRecords(Account.SObjectType, false); Test.startTest(); @@ -1004,12 +1004,6 @@ private class MockDmlTest { } // **** HELPER **** // - private static void initPlugins(String dmlPluginName) { - DatabaseLayerSetting__mdt settings = DatabaseLayerTestUtils.initSettings(); - settings.DmlPreAndPostProcessor__c = dmlPluginName; - DatabaseLayerTestUtils.initSettings(settings); - } - private static List initRecords(SObjectType objectType, Boolean withId) { List records = new List(); for (Integer i = 0; i < TEST_SIZE; i++) { diff --git a/source/classes/MockSoqlTest.cls b/source/classes/MockSoqlTest.cls index 8ea68f1d..be55a780 100644 --- a/source/classes/MockSoqlTest.cls +++ b/source/classes/MockSoqlTest.cls @@ -7,7 +7,7 @@ private class MockSoqlTest { static { DatabaseLayer.useMocks(); NUM_ACCS = 10; - MockSoqlTest.initPlugins(DatabaseLayerTestUtils.SamplePlugin.class.getName()); + DatabaseLayerTestUtils.initDmlAndSoqlPlugins(DatabaseLayerTestUtils.SamplePlugin.class.getName()); } // **** TESTS **** // @@ -459,7 +459,7 @@ private class MockSoqlTest { @IsTest static void shouldNotRunProcessorLogicIfInvalidApexClassDefined() { - MockSoqlTest.initPlugins('an obviously invalid apex class name'); + DatabaseLayerTestUtils.initDmlAndSoqlPlugins('an obviously invalid apex class name'); Test.startTest(); DatabaseLayer.Soql.newQuery(User.SObjectType)?.setRowLimit(1)?.toSoql()?.query(); @@ -476,7 +476,7 @@ private class MockSoqlTest { @IsTest static void shouldNotRunProcessorLogicIfNoValueDefined() { - MockSoqlTest.initPlugins(null); + DatabaseLayerTestUtils.initDmlAndSoqlPlugins(null); Test.startTest(); DatabaseLayer.Soql.newQuery(User.SObjectType)?.setRowLimit(1)?.toSoql()?.query(); @@ -520,12 +520,6 @@ private class MockSoqlTest { return accounts; } - private static void initPlugins(String soqlPluginName) { - DatabaseLayerSetting__mdt settings = DatabaseLayerTestUtils.initSettings(); - settings.SoqlPreAndPostProcessor__c = soqlPluginName; - DatabaseLayerTestUtils.initSettings(settings); - } - // **** INNER **** // private class CustomTaskQueryLogic implements MockSoql.Simulator { /** diff --git a/source/classes/SoqlTest.cls b/source/classes/SoqlTest.cls index 76c1e05a..1284b373 100644 --- a/source/classes/SoqlTest.cls +++ b/source/classes/SoqlTest.cls @@ -4,7 +4,7 @@ @IsTest private class SoqlTest { static { - SoqlTest.initPlugins(DatabaseLayerTestUtils.SamplePlugin.class.getName()); + DatabaseLayerTestUtils.initDmlAndSoqlPlugins(DatabaseLayerTestUtils.SamplePlugin.class.getName()); } // **** TESTS **** // @@ -1562,7 +1562,7 @@ private class SoqlTest { @IsTest static void shouldNotRunProcessorLogicIfInvalidApexClassDefined() { - SoqlTest.initPlugins('an obviously invalid apex class name'); + DatabaseLayerTestUtils.initDmlAndSoqlPlugins('an obviously invalid apex class name'); Test.startTest(); DatabaseLayer.Soql.newQuery(User.SObjectType)?.setRowLimit(1)?.toSoql()?.query(); @@ -1583,7 +1583,7 @@ private class SoqlTest { @IsTest static void shouldNotRunProcessorLogicIfNoValueDefined() { - SoqlTest.initPlugins(null); + DatabaseLayerTestUtils.initDmlAndSoqlPlugins(null); Test.startTest(); DatabaseLayer.Soql.newQuery(User.SObjectType)?.setRowLimit(1)?.toSoql()?.query(); @@ -1625,12 +1625,6 @@ private class SoqlTest { } // **** HELPER **** // - private static void initPlugins(String soqlPluginName) { - DatabaseLayerSetting__mdt settings = DatabaseLayerTestUtils.initSettings(); - settings.SoqlPreAndPostProcessor__c = soqlPluginName; - DatabaseLayerTestUtils.initSettings(settings); - } - private static DatabaseLayerSetting__mdt initSettings() { DatabaseLayerSetting__mdt settings = new DatabaseLayerSetting__mdt(); DatabaseLayer.Utils.MetadataSelector.settings?.add(settings); From db10d5563e78899bc094e22bc3dd93bb0e8fcee7 Mon Sep 17 00:00:00 2001 From: Jason Siders <41714875+jasonsiders@users.noreply.github.com> Date: Thu, 7 Aug 2025 10:52:19 -0500 Subject: [PATCH 10/38] checkpoint - reworked tests --- .../DatabaseLayerNebulaLoggerAdapter.cls | 16 +++- .../DatabaseLayerNebulaLoggerAdapterTest.cls | 83 ++++++++++++------- 2 files changed, 65 insertions(+), 34 deletions(-) diff --git a/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapter.cls b/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapter.cls index e3b38473..9b623586 100644 --- a/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapter.cls +++ b/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapter.cls @@ -1,13 +1,16 @@ @SuppressWarnings('PMD.AvoidGlobalModifier') global class DatabaseLayerNebulaLoggerAdapter implements Dml.PreAndPostProcessor, Soql.PreAndPostProcessor { - public static final String SCENARIO_NAME = 'apex-database-layer'; + public static final String TAG_NAME = 'apex-database-layer'; /** * @description Called before executing a DML operation. * @param request The DML request to be processed */ global void processPreDml(Dml.Request request) { - // TODO + List records = request?.records ?? new List{}; + String op = this.getOperationName(request?.operation); + String msg = 'Processing ' + op + ' on ' + request?.numRecords + ' ' + request?.sObjectType + ' records'; + Logger.finest(msg)?.addTag(TAG_NAME)?.setRecord(records); } /** @@ -16,7 +19,10 @@ global class DatabaseLayerNebulaLoggerAdapter implements Dml.PreAndPostProcessor * @param databaseResults The results returned from the DML operation */ global void processPostDml(Dml.Request request, List databaseResults) { - // TODO + List records = request?.records ?? new List{}; + String op = this.getOperationName(request?.operation); + String msg = 'Processed ' + op + ' on ' + request?.numRecords + ' ' + request?.sObjectType + ' records\nResults: ' + JSON.serialize(databaseResults); + Logger.finest(msg)?.addTag(TAG_NAME)?.setRecord(records); } /** @@ -28,7 +34,6 @@ global class DatabaseLayerNebulaLoggerAdapter implements Dml.PreAndPostProcessor // TODO } - /** * @description Processes SOQL requests before execution. * @param request The SOQL request being processed @@ -55,4 +60,7 @@ global class DatabaseLayerNebulaLoggerAdapter implements Dml.PreAndPostProcessor // TODO } + private String getOperationName(Dml.Operation operation) { + return operation?.name()?.removeStart('DO_'); + } } diff --git a/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapterTest.cls b/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapterTest.cls index 288d4290..193e319b 100644 --- a/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapterTest.cls +++ b/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapterTest.cls @@ -8,18 +8,18 @@ private class DatabaseLayerNebulaLoggerAdapterTest { DatabaseLayer.useMocks(); MOCK_RECORD = (Account) new MockRecord(Account.SObjectType)?.withId()?.toSObject(); MockSoql.setGlobalMock().withResults(new List{ MOCK_RECORD }); - DatabaseLayerNebulaLoggerAdapterTest.initPlugins(); + DatabaseLayerTestUtils.initDmlAndSoqlPlugins(PLUGIN_NAME); } @IsTest static void shouldLogSuccessfulDmlOperations() { Test.startTest(); DatabaseLayer.Dml.doUpdate(MOCK_RECORD); - Logger.saveLog(); Test.stopTest(); - List entries = DatabaseLayerNebulaLoggerAdapterTest.getLogs(); - Assert.isFalse(entries?.isEmpty(), 'Did not log via nebula logger'); + LogSummary summary = new LogSummary(); + Assert.areEqual(2, summary?.numTotalEntries(), 'Wrong # of log entries'); + Assert.areEqual(2, summary?.numEntriesWithLevel(System.LoggingLevel.FINEST), 'Wrong # of FINEST logs'); } @IsTest @@ -33,24 +33,24 @@ private class DatabaseLayerNebulaLoggerAdapterTest { Assert.fail('A System.DmlException was not thrown'); } catch (System.DmlException error) { caughtError = error; - } finally { - Logger.saveLog(); } Test.stopTest(); - List entries = DatabaseLayerNebulaLoggerAdapterTest.getLogs(); - Assert.isFalse(entries?.isEmpty(), 'Did not log via nebula logger'); + LogSummary summary = new LogSummary(); + Assert.areEqual(2, summary?.numTotalEntries(), 'Wrong # of log entries'); + Assert.areEqual(1, summary?.numEntriesWithLevel(System.LoggingLevel.FINEST), 'Wrong # of FINEST logs'); + Assert.areEqual(1, summary?.numEntriesWithLevel(System.LoggingLevel.ERROR), 'Wrong # of ERROR logs'); } @IsTest static void shouldLogSuccessfulSoqlOperations() { Test.startTest(); DatabaseLayer.Soql.newQuery(Account.SObjectType)?.query(); - Logger.saveLog(); Test.stopTest(); - List entries = DatabaseLayerNebulaLoggerAdapterTest.getLogs(); - Assert.isFalse(entries?.isEmpty(), 'Did not log via nebula logger'); + LogSummary summary = new LogSummary(); + Assert.areEqual(2, summary?.numTotalEntries(), 'Wrong # of log entries'); + Assert.areEqual(2, summary?.numEntriesWithLevel(System.LoggingLevel.FINEST), 'Wrong # of FINEST logs'); } @IsTest @@ -64,30 +64,53 @@ private class DatabaseLayerNebulaLoggerAdapterTest { Assert.fail('Did not throw a System.QueryException'); } catch (System.QueryException error) { caughtError = error; - } finally { - Logger.saveLog(); } Test.stopTest(); - List entries = DatabaseLayerNebulaLoggerAdapterTest.getLogs(); - Assert.isFalse(entries?.isEmpty(), 'Did not log via nebula logger'); + LogSummary summary = new LogSummary(); + Assert.areEqual(2, summary?.numTotalEntries(), 'Wrong # of log entries'); + Assert.areEqual(1, summary?.numEntriesWithLevel(System.LoggingLevel.FINEST), 'Wrong # of FINEST logs'); + Assert.areEqual(1, summary?.numEntriesWithLevel(System.LoggingLevel.ERROR), 'Wrong # of ERROR logs'); } - // **** HELPER **** // - private static List getLogs() { - // Note: Nebula Logger DML is not encompassed by apex-database-layer; this will always create real log entries - return [ - SELECT Id - FROM LogEntry__c - WHERE EntryScenario__r.Name = :DatabaseLayerNebulaLoggerAdapter.SCENARIO_NAME - ]; - } + // **** INNER **** // + private class LogSummary { + private List taggedEntries; + private Map> entryIdsByLevel; - private static void initPlugins() { - // Inject the plugin class to be used by the database layer framework: - DatabaseLayerSetting__mdt settings = new DatabaseLayerSetting__mdt(); - settings.DmlPreAndPostProcessor__c = PLUGIN_NAME; - settings.SoqlPreAndPostProcessor__c = PLUGIN_NAME; - DatabaseLayerTestUtils.initSettings(settings); + public LogSummary() { + Logger.saveLog(); + Test.getEventBus().deliver(); + this.queryTaggedEntries(); + this.mapTaggedEntriesByLevel(); + } + + public Integer numEntriesWithLevel(System.LoggingLevel level) { + return this.entryIdsByLevel?.get(level?.name())?.size() ?? 0; + } + + public Integer numTotalEntries() { + return this.taggedEntries?.size() ?? 0; + } + + private void mapTaggedEntriesByLevel() { + // Group LogEntry Ids by their common Logging Level name + this.entryIdsByLevel = new Map>{}; + for (LogEntryTag__c taggedEntry : this.taggedEntries) { + String level = taggedEntry?.LogEntry__r?.LoggingLevel__c; + Set matching = this.entryIdsByLevel?.get(level) ?? new Set{}; + matching?.add(taggedEntry?.LogEntry__c); + this.entryIdsByLevel?.put(level, matching); + } + } + + private void queryTaggedEntries() { + // Query for all log entries that were tagged with 'apex-database-layer' + this.taggedEntries = [ + SELECT Id, LogEntry__c, LogEntry__r.LoggingLevel__c, LogEntry__r.Message__c, Tag__r.Name + FROM LogEntryTag__c + WHERE Tag__r.Name = :DatabaseLayerNebulaLoggerAdapter.TAG_NAME + ]; + } } } \ No newline at end of file From d99352a0bc3e97bf416434de73c6c781627bbb5c Mon Sep 17 00:00:00 2001 From: Jason Siders <41714875+jasonsiders@users.noreply.github.com> Date: Thu, 7 Aug 2025 10:58:22 -0500 Subject: [PATCH 11/38] adding comments --- .../classes/DatabaseLayerNebulaLoggerAdapterTest.cls | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapterTest.cls b/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapterTest.cls index 193e319b..ed1fbec3 100644 --- a/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapterTest.cls +++ b/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapterTest.cls @@ -78,6 +78,9 @@ private class DatabaseLayerNebulaLoggerAdapterTest { private List taggedEntries; private Map> entryIdsByLevel; + /** + * @description Creates a new LogSummary by saving logs, delivering events, and querying tagged entries + */ public LogSummary() { Logger.saveLog(); Test.getEventBus().deliver(); @@ -85,10 +88,19 @@ private class DatabaseLayerNebulaLoggerAdapterTest { this.mapTaggedEntriesByLevel(); } + /** + * @description Returns the number of log entries with the specified logging level + * @param level The logging level to count entries for + * @return The number of entries with the specified level, or 0 if none exist + */ public Integer numEntriesWithLevel(System.LoggingLevel level) { return this.entryIdsByLevel?.get(level?.name())?.size() ?? 0; } + /** + * @description Returns the total number of tagged log entries + * @return The total number of tagged entries, or 0 if none exist + */ public Integer numTotalEntries() { return this.taggedEntries?.size() ?? 0; } From e8b58bdb9e4c1406c4e67f4ea0ab7c74e5970edc Mon Sep 17 00:00:00 2001 From: Jason Siders <41714875+jasonsiders@users.noreply.github.com> Date: Thu, 7 Aug 2025 11:14:10 -0500 Subject: [PATCH 12/38] all tests passing --- .../DatabaseLayerNebulaLoggerAdapter.cls | 24 ++++++++++++------- source/classes/Soql.cls | 4 ++-- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapter.cls b/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapter.cls index 9b623586..85b09906 100644 --- a/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapter.cls +++ b/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapter.cls @@ -8,8 +8,8 @@ global class DatabaseLayerNebulaLoggerAdapter implements Dml.PreAndPostProcessor */ global void processPreDml(Dml.Request request) { List records = request?.records ?? new List{}; - String op = this.getOperationName(request?.operation); - String msg = 'Processing ' + op + ' on ' + request?.numRecords + ' ' + request?.sObjectType + ' records'; + String op = this.getDmlOperationName(request?.operation); + String msg = 'Processing DML ' + op + ' on ' + request?.numRecords + ' ' + request?.sObjectType + ' records'; Logger.finest(msg)?.addTag(TAG_NAME)?.setRecord(records); } @@ -20,8 +20,8 @@ global class DatabaseLayerNebulaLoggerAdapter implements Dml.PreAndPostProcessor */ global void processPostDml(Dml.Request request, List databaseResults) { List records = request?.records ?? new List{}; - String op = this.getOperationName(request?.operation); - String msg = 'Processed ' + op + ' on ' + request?.numRecords + ' ' + request?.sObjectType + ' records\nResults: ' + JSON.serialize(databaseResults); + String op = this.getDmlOperationName(request?.operation); + String msg = 'Processed DML ' + op + ' on ' + request?.numRecords + ' ' + request?.sObjectType + ' records\n\nRequest: ' + JSON.serialize(request) + '\n\nResults:\n' + JSON.serialize(databaseResults); Logger.finest(msg)?.addTag(TAG_NAME)?.setRecord(records); } @@ -31,7 +31,10 @@ global class DatabaseLayerNebulaLoggerAdapter implements Dml.PreAndPostProcessor * @param error The exception that was thrown */ global void processDmlError(Dml.Request request, Exception error) { - // TODO + List records = request?.records ?? new List{}; + String op = this.getDmlOperationName(request?.operation); + String msg = 'Error processing DML ' + op + ':\n' + error + '\n' + error?.getStackTraceString() + '\n\nRequest: ' + JSON.serialize(request); + Logger.error(msg)?.addTag(TAG_NAME)?.setRecord(records); } /** @@ -39,7 +42,8 @@ global class DatabaseLayerNebulaLoggerAdapter implements Dml.PreAndPostProcessor * @param request The SOQL request being processed */ global void processPreSoql(Soql.Request request) { - // TODO + String msg = 'Processing SOQL ' + request?.operation + ':\n' + request?.queryString; + Logger.finest(msg)?.addTag(TAG_NAME); } /** @@ -48,7 +52,8 @@ global class DatabaseLayerNebulaLoggerAdapter implements Dml.PreAndPostProcessor * @param results The results from the SOQL operation */ global void processPostSoql(Soql.Request request, Object results) { - // TODO + String msg = 'Processed SOQL ' + request?.operation + ':\n' + request?.queryString + '\nResults: ' + results; + Logger.finest(msg)?.addTag(TAG_NAME); } /** @@ -57,10 +62,11 @@ global class DatabaseLayerNebulaLoggerAdapter implements Dml.PreAndPostProcessor * @param error The exception that occurred */ global void processSoqlError(Soql.Request request, Exception error) { - // TODO + String msg = 'Error processing SOQL ' + request?.operation + ':\n' + error + '\n' + error?.getStackTraceString() + '\n\nRequest: ' + JSON.serialize(request); + Logger.error(msg)?.addTag(TAG_NAME); } - private String getOperationName(Dml.Operation operation) { + private String getDmlOperationName(Dml.Operation operation) { return operation?.name()?.removeStart('DO_'); } } diff --git a/source/classes/Soql.cls b/source/classes/Soql.cls index c82e481d..e4199b4f 100644 --- a/source/classes/Soql.cls +++ b/source/classes/Soql.cls @@ -1722,8 +1722,6 @@ global inherited sharing virtual class Soql extends Soql.Builder { * @description The Soql query instance being executed. */ global Soql query { get; private set; } - protected transient System.AccessLevel accessLevel { get; private set; } - protected transient Map binds { get; private set; } /** * @description The SOQL query string representation. */ @@ -1732,6 +1730,8 @@ global inherited sharing virtual class Soql extends Soql.Builder { return this.query?.toString(); } } + protected transient System.AccessLevel accessLevel { get; private set; } + protected transient Map binds { get; private set; } protected Request(Soql query, Soql.Operation operation) { this.accessLevel = query?.accessLevel; From b5bcc6a0feaa3aa3e5e75ce7f854cbf974d426da Mon Sep 17 00:00:00 2001 From: Jason Siders <41714875+jasonsiders@users.noreply.github.com> Date: Thu, 7 Aug 2025 11:25:56 -0500 Subject: [PATCH 13/38] adding constructor to ignore database layer's internal framework --- .../DatabaseLayerNebulaLoggerAdapter.cls | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapter.cls b/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapter.cls index 85b09906..5c37ee3d 100644 --- a/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapter.cls +++ b/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapter.cls @@ -1,7 +1,18 @@ +/** + * @description Adapter class that integrates Nebula Logger with the Apex Database Layer framework. + * Implements both DML and SOQL pre/post processors to log database operations. + */ @SuppressWarnings('PMD.AvoidGlobalModifier') global class DatabaseLayerNebulaLoggerAdapter implements Dml.PreAndPostProcessor, Soql.PreAndPostProcessor { public static final String TAG_NAME = 'apex-database-layer'; + /** + * @description Configure Nebula Logger to ignore database layer framework classes in log stack traces + */ + global DatabaseLayerNebulaLoggerAdapter() { + this.omitFrameworkFromLogStackTrace(); + } + /** * @description Called before executing a DML operation. * @param request The DML request to be processed @@ -69,4 +80,17 @@ global class DatabaseLayerNebulaLoggerAdapter implements Dml.PreAndPostProcessor private String getDmlOperationName(Dml.Operation operation) { return operation?.name()?.removeStart('DO_'); } + + private void omitFrameworkFromLogStackTrace() { + // Prevent framework-specific method calls from polluting Nebula Logger's stack trace + Logger.ignoreOrigin(DatabaseLayerNebulaLoggerAdapter.class); + Logger.ignoreOrigin(DatabaseLayer.class); + Logger.ignoreOrigin(DatabaseLayerUtils.class); + Logger.ignoreOrigin(DatabaseLayerTestUtils.class); + Logger.ignoreOrigin(Dml.class); + Logger.ignoreOrigin(MockDml.class); + Logger.ignoreOrigin(MockRecord.class); + Logger.ignoreOrigin(MockSoql.class); + Logger.ignoreOrigin(Soql.class); + } } From 95030aca1d5229db6fca9b226b6009f4306c75fd Mon Sep 17 00:00:00 2001 From: Jason Siders <41714875+jasonsiders@users.noreply.github.com> Date: Thu, 7 Aug 2025 13:48:06 -0500 Subject: [PATCH 14/38] fixed issue with plugin initialization --- source/classes/DatabaseLayer.cls | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/source/classes/DatabaseLayer.cls b/source/classes/DatabaseLayer.cls index 7b049d54..5fa275c1 100644 --- a/source/classes/DatabaseLayer.cls +++ b/source/classes/DatabaseLayer.cls @@ -3,17 +3,11 @@ */ @SuppressWarnings('PMD.AvoidGlobalModifier') global class DatabaseLayer { - public static final DatabaseLayerUtils UTILS; - private static final DatabaseLayer INSTANCE; + private static final DatabaseLayer INSTANCE = new DatabaseLayer(); private Dml currentDml; private SoqlProvider currentSoql; - static { - INSTANCE = new DatabaseLayer(); - UTILS = new DatabaseLayerUtils(INSTANCE); - } - private DatabaseLayer() { this.currentDml = new Dml(this); this.currentSoql = new SoqlProvider(this); @@ -41,6 +35,19 @@ global class DatabaseLayer { } } + /** + * @description Provides access to utility methods for database operations and testing. + * @return Singleton instance of the DatabaseLayerUtils class + */ + @SuppressWarnings('PMD.PropertyNamingConventions') + public static final DatabaseLayerUtils Utils { + get { + DatabaseLayer.Utils = DatabaseLayer.Utils ?? new DatabaseLayerUtils(INSTANCE); + return DatabaseLayer.Utils; + } + private set; + } + /** * @description Switches the DML provider to use mock implementation for testing. * @return The mock DML instance that was configured From 2a830a883f63193effe1d7f308a045f709e6edf1 Mon Sep 17 00:00:00 2001 From: Jason Siders <41714875+jasonsiders@users.noreply.github.com> Date: Thu, 7 Aug 2025 14:12:29 -0500 Subject: [PATCH 15/38] adjusted log messages; ran prettier --- .../managed/unlocked/sfdx-project.json | 24 +- .../DatabaseLayerNebulaLoggerAdapter.cls | 196 ++++++++------ ...abaseLayerNebulaLoggerAdapter.cls-meta.xml | 2 +- .../DatabaseLayerNebulaLoggerAdapterTest.cls | 252 +++++++++--------- ...eLayerNebulaLoggerAdapterTest.cls-meta.xml | 2 +- sfdx-project.json | 2 +- 6 files changed, 257 insertions(+), 221 deletions(-) diff --git a/plugins/nebula-logger/packages/managed/unlocked/sfdx-project.json b/plugins/nebula-logger/packages/managed/unlocked/sfdx-project.json index 9bd984ba..af373159 100644 --- a/plugins/nebula-logger/packages/managed/unlocked/sfdx-project.json +++ b/plugins/nebula-logger/packages/managed/unlocked/sfdx-project.json @@ -7,15 +7,15 @@ "versionName": "v1.0.0", "versionNumber": "1.0.0.NEXT", "versionDescription": "Use Nebula Logger to automatically log details of DML & SOQL operations", - "dependencies": [ - { - "package": "apex-database-layer-unlocked", - "versionNumber": "3.0.0.LATEST" - }, - { - "package": "nebula-logger-unlocked@4.16.1" - } - ] + "dependencies": [ + { + "package": "apex-database-layer-unlocked", + "versionNumber": "3.0.0.LATEST" + }, + { + "package": "nebula-logger-unlocked@4.16.1" + } + ] } ], "name": "Apex Database Layer: Nebula Logger Plugin (Unlocked)", @@ -24,7 +24,7 @@ "sourceApiVersion": "64.0", "packageAliases": { "apex-database-layer-unlocked": "0HoDn000000kAONKA2", - "nebula-logger-unlocked@4.16.1": "04tKe0000011MXEIA2", - "apex-database-layer-plugin-nebula-logger-unlocked": "0HoDn0000010wCxKAI" + "nebula-logger-unlocked@4.16.1": "04tKe0000011MXEIA2", + "apex-database-layer-plugin-nebula-logger-unlocked": "0HoDn0000010wCxKAI" } -} \ No newline at end of file +} diff --git a/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapter.cls b/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapter.cls index 5c37ee3d..f9e426de 100644 --- a/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapter.cls +++ b/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapter.cls @@ -4,93 +4,129 @@ */ @SuppressWarnings('PMD.AvoidGlobalModifier') global class DatabaseLayerNebulaLoggerAdapter implements Dml.PreAndPostProcessor, Soql.PreAndPostProcessor { - public static final String TAG_NAME = 'apex-database-layer'; + public static final String TAG_NAME = 'apex-database-layer'; - /** - * @description Configure Nebula Logger to ignore database layer framework classes in log stack traces - */ - global DatabaseLayerNebulaLoggerAdapter() { - this.omitFrameworkFromLogStackTrace(); - } + /** + * @description Configure Nebula Logger to ignore database layer framework classes in log stack traces + */ + global DatabaseLayerNebulaLoggerAdapter() { + this.omitFrameworkFromLogStackTrace(); + } - /** - * @description Called before executing a DML operation. - * @param request The DML request to be processed - */ - global void processPreDml(Dml.Request request) { - List records = request?.records ?? new List{}; - String op = this.getDmlOperationName(request?.operation); - String msg = 'Processing DML ' + op + ' on ' + request?.numRecords + ' ' + request?.sObjectType + ' records'; - Logger.finest(msg)?.addTag(TAG_NAME)?.setRecord(records); - } + /** + * @description Called before executing a DML operation. + * @param request The DML request to be processed + */ + global void processPreDml(Dml.Request request) { + List lines = new List{ + '⏳ Processing DML ' + + this.getDmlOperationName(request?.operation) + + '...', + 'Request:\n' + JSON.serialize(request) + }; + String msg = String.join(lines, '\n\n'); + Logger.finest(msg)?.addTag(TAG_NAME)?.setRecord(request?.records); + } - /** - * @description Called after successfully executing a DML operation. - * @param request The DML request that was processed - * @param databaseResults The results returned from the DML operation - */ - global void processPostDml(Dml.Request request, List databaseResults) { - List records = request?.records ?? new List{}; - String op = this.getDmlOperationName(request?.operation); - String msg = 'Processed DML ' + op + ' on ' + request?.numRecords + ' ' + request?.sObjectType + ' records\n\nRequest: ' + JSON.serialize(request) + '\n\nResults:\n' + JSON.serialize(databaseResults); - Logger.finest(msg)?.addTag(TAG_NAME)?.setRecord(records); - } + /** + * @description Called after successfully executing a DML operation. + * @param request The DML request that was processed + * @param results The Database.*Results returned from the DML operation + */ + global void processPostDml(Dml.Request request, List results) { + List lines = new List{ + '✅ Processed DML ' + this.getDmlOperationName(request?.operation), + 'Request:\n' + JSON.serialize(request), + 'Results:\n' + JSON.serialize(results) + }; + String msg = String.join(lines, '\n\n'); + Logger.finest(msg)?.addTag(TAG_NAME)?.setRecord(request?.records); + } - /** - * @description Called when a DML operation encounters an error. - * @param request The DML request that encountered an error - * @param error The exception that was thrown - */ - global void processDmlError(Dml.Request request, Exception error) { - List records = request?.records ?? new List{}; - String op = this.getDmlOperationName(request?.operation); - String msg = 'Error processing DML ' + op + ':\n' + error + '\n' + error?.getStackTraceString() + '\n\nRequest: ' + JSON.serialize(request); - Logger.error(msg)?.addTag(TAG_NAME)?.setRecord(records); - } + /** + * @description Called when a DML operation encounters an error. + * @param request The DML request that encountered an error + * @param error The exception that was thrown + */ + global void processDmlError(Dml.Request request, Exception error) { + List lines = new List{ + '🚨 Error processing DML ' + this.getDmlOperationName(request?.operation), + 'Error:\n' + + error + + '\n' + + error?.getStackTraceString(), + 'Request:\n' + JSON.serialize(request) + }; + String msg = String.join(lines, '\n\n'); + Logger.error(msg)?.addTag(TAG_NAME)?.setRecord(request?.records); + } - /** - * @description Processes SOQL requests before execution. - * @param request The SOQL request being processed - */ - global void processPreSoql(Soql.Request request) { - String msg = 'Processing SOQL ' + request?.operation + ':\n' + request?.queryString; - Logger.finest(msg)?.addTag(TAG_NAME); - } + /** + * @description Processes SOQL requests before execution. + * @param request The SOQL request being processed + */ + global void processPreSoql(Soql.Request request) { + List lines = new List{ + '⏳ Processing SOQL ' + + request?.operation + + '...', + 'Query:\n' + request?.queryString + }; + String msg = String.join(lines, '\n\n'); + Logger.finest(msg)?.addTag(TAG_NAME); + } - /** - * @description Processes SOQL requests after execution. - * @param request The SOQL request that was processed - * @param results The results from the SOQL operation - */ - global void processPostSoql(Soql.Request request, Object results) { - String msg = 'Processed SOQL ' + request?.operation + ':\n' + request?.queryString + '\nResults: ' + results; - Logger.finest(msg)?.addTag(TAG_NAME); - } + /** + * @description Processes SOQL requests after execution. + * @param request The SOQL request that was processed + * @param results The results from the SOQL operation + */ + global void processPostSoql(Soql.Request request, Object results) { + List lines = new List{ + '✅ Processed SOQL ' + + request?.operation + + ':', + 'Query:\n' + request?.queryString, + 'Results:\n' + results + }; + String msg = String.join(lines, '\n\n'); + Logger.finest(msg)?.addTag(TAG_NAME); + } - /** - * @description Processes SOQL errors. - * @param request The SOQL request that failed - * @param error The exception that occurred - */ - global void processSoqlError(Soql.Request request, Exception error) { - String msg = 'Error processing SOQL ' + request?.operation + ':\n' + error + '\n' + error?.getStackTraceString() + '\n\nRequest: ' + JSON.serialize(request); - Logger.error(msg)?.addTag(TAG_NAME); - } + /** + * @description Processes SOQL errors. + * @param request The SOQL request that failed + * @param error The exception that occurred + */ + global void processSoqlError(Soql.Request request, Exception error) { + List lines = new List{ + '🚨 Error processing SOQL ' + + request?.operation + + ':', + 'Error:\n' + + error + + '\n' + + error?.getStackTraceString(), + 'Query:\n' + request?.queryString + }; + String msg = String.join(lines, '\n\n'); + Logger.error(msg)?.addTag(TAG_NAME); + } - private String getDmlOperationName(Dml.Operation operation) { - return operation?.name()?.removeStart('DO_'); - } + private String getDmlOperationName(Dml.Operation operation) { + return operation?.name()?.removeStart('DO_'); + } - private void omitFrameworkFromLogStackTrace() { - // Prevent framework-specific method calls from polluting Nebula Logger's stack trace - Logger.ignoreOrigin(DatabaseLayerNebulaLoggerAdapter.class); - Logger.ignoreOrigin(DatabaseLayer.class); - Logger.ignoreOrigin(DatabaseLayerUtils.class); - Logger.ignoreOrigin(DatabaseLayerTestUtils.class); - Logger.ignoreOrigin(Dml.class); - Logger.ignoreOrigin(MockDml.class); - Logger.ignoreOrigin(MockRecord.class); - Logger.ignoreOrigin(MockSoql.class); - Logger.ignoreOrigin(Soql.class); - } + private void omitFrameworkFromLogStackTrace() { + // Prevent framework-specific method calls from polluting Nebula Logger's stack trace + Logger.ignoreOrigin(DatabaseLayerNebulaLoggerAdapter.class); + Logger.ignoreOrigin(DatabaseLayer.class); + Logger.ignoreOrigin(DatabaseLayerUtils.class); + Logger.ignoreOrigin(DatabaseLayerTestUtils.class); + Logger.ignoreOrigin(Dml.class); + Logger.ignoreOrigin(MockDml.class); + Logger.ignoreOrigin(MockRecord.class); + Logger.ignoreOrigin(MockSoql.class); + Logger.ignoreOrigin(Soql.class); + } } diff --git a/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapter.cls-meta.xml b/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapter.cls-meta.xml index 1e7de940..88ae4ade 100644 --- a/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapter.cls-meta.xml +++ b/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapter.cls-meta.xml @@ -1,4 +1,4 @@ - + 64.0 Active diff --git a/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapterTest.cls b/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapterTest.cls index ed1fbec3..4aafa2b1 100644 --- a/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapterTest.cls +++ b/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapterTest.cls @@ -1,128 +1,128 @@ @SuppressWarnings('PMD.ApexUnitTestClassShouldHaveRunAs') -@IsTest +@IsTest private class DatabaseLayerNebulaLoggerAdapterTest { - private static final Account MOCK_RECORD; - private static final String PLUGIN_NAME = DatabaseLayerNebulaLoggerAdapter.class.getName(); - - static { - DatabaseLayer.useMocks(); - MOCK_RECORD = (Account) new MockRecord(Account.SObjectType)?.withId()?.toSObject(); - MockSoql.setGlobalMock().withResults(new List{ MOCK_RECORD }); - DatabaseLayerTestUtils.initDmlAndSoqlPlugins(PLUGIN_NAME); - } - - @IsTest - static void shouldLogSuccessfulDmlOperations() { - Test.startTest(); - DatabaseLayer.Dml.doUpdate(MOCK_RECORD); - Test.stopTest(); - - LogSummary summary = new LogSummary(); - Assert.areEqual(2, summary?.numTotalEntries(), 'Wrong # of log entries'); - Assert.areEqual(2, summary?.numEntriesWithLevel(System.LoggingLevel.FINEST), 'Wrong # of FINEST logs'); - } - - @IsTest - static void shouldLogFailedDmlOperations() { - MockDml.shouldFail(); - - Test.startTest(); - Exception caughtError; - try { - DatabaseLayer.Dml.doUpdate(MOCK_RECORD); - Assert.fail('A System.DmlException was not thrown'); - } catch (System.DmlException error) { - caughtError = error; - } - Test.stopTest(); - - LogSummary summary = new LogSummary(); - Assert.areEqual(2, summary?.numTotalEntries(), 'Wrong # of log entries'); - Assert.areEqual(1, summary?.numEntriesWithLevel(System.LoggingLevel.FINEST), 'Wrong # of FINEST logs'); - Assert.areEqual(1, summary?.numEntriesWithLevel(System.LoggingLevel.ERROR), 'Wrong # of ERROR logs'); - } - - @IsTest - static void shouldLogSuccessfulSoqlOperations() { - Test.startTest(); - DatabaseLayer.Soql.newQuery(Account.SObjectType)?.query(); - Test.stopTest(); - - LogSummary summary = new LogSummary(); - Assert.areEqual(2, summary?.numTotalEntries(), 'Wrong # of log entries'); - Assert.areEqual(2, summary?.numEntriesWithLevel(System.LoggingLevel.FINEST), 'Wrong # of FINEST logs'); - } - - @IsTest - static void shouldLogFailedSoqlOperations() { - MockSoql.setGlobalMock()?.withError(); - - Test.startTest(); - Exception caughtError; - try { - DatabaseLayer.Soql.newQuery(Account.SObjectType)?.query(); - Assert.fail('Did not throw a System.QueryException'); - } catch (System.QueryException error) { - caughtError = error; - } - Test.stopTest(); - - LogSummary summary = new LogSummary(); - Assert.areEqual(2, summary?.numTotalEntries(), 'Wrong # of log entries'); - Assert.areEqual(1, summary?.numEntriesWithLevel(System.LoggingLevel.FINEST), 'Wrong # of FINEST logs'); - Assert.areEqual(1, summary?.numEntriesWithLevel(System.LoggingLevel.ERROR), 'Wrong # of ERROR logs'); - } - - // **** INNER **** // - private class LogSummary { - private List taggedEntries; - private Map> entryIdsByLevel; - - /** - * @description Creates a new LogSummary by saving logs, delivering events, and querying tagged entries - */ - public LogSummary() { - Logger.saveLog(); - Test.getEventBus().deliver(); - this.queryTaggedEntries(); - this.mapTaggedEntriesByLevel(); - } - - /** - * @description Returns the number of log entries with the specified logging level - * @param level The logging level to count entries for - * @return The number of entries with the specified level, or 0 if none exist - */ - public Integer numEntriesWithLevel(System.LoggingLevel level) { - return this.entryIdsByLevel?.get(level?.name())?.size() ?? 0; - } - - /** - * @description Returns the total number of tagged log entries - * @return The total number of tagged entries, or 0 if none exist - */ - public Integer numTotalEntries() { - return this.taggedEntries?.size() ?? 0; - } - - private void mapTaggedEntriesByLevel() { - // Group LogEntry Ids by their common Logging Level name - this.entryIdsByLevel = new Map>{}; - for (LogEntryTag__c taggedEntry : this.taggedEntries) { - String level = taggedEntry?.LogEntry__r?.LoggingLevel__c; - Set matching = this.entryIdsByLevel?.get(level) ?? new Set{}; - matching?.add(taggedEntry?.LogEntry__c); - this.entryIdsByLevel?.put(level, matching); - } - } - - private void queryTaggedEntries() { - // Query for all log entries that were tagged with 'apex-database-layer' - this.taggedEntries = [ - SELECT Id, LogEntry__c, LogEntry__r.LoggingLevel__c, LogEntry__r.Message__c, Tag__r.Name - FROM LogEntryTag__c - WHERE Tag__r.Name = :DatabaseLayerNebulaLoggerAdapter.TAG_NAME - ]; - } - } -} \ No newline at end of file + private static final Account MOCK_RECORD; + private static final String PLUGIN_NAME = DatabaseLayerNebulaLoggerAdapter.class.getName(); + + static { + DatabaseLayer.useMocks(); + MOCK_RECORD = (Account) new MockRecord(Account.SObjectType)?.withId()?.toSObject(); + MockSoql.setGlobalMock().withResults(new List{ MOCK_RECORD }); + DatabaseLayerTestUtils.initDmlAndSoqlPlugins(PLUGIN_NAME); + } + + @IsTest + static void shouldLogSuccessfulDmlOperations() { + Test.startTest(); + DatabaseLayer.Dml.doUpdate(MOCK_RECORD); + Test.stopTest(); + + LogSummary summary = new LogSummary(); + Assert.areEqual(2, summary?.numTotalEntries(), 'Wrong # of log entries'); + Assert.areEqual(2, summary?.numEntriesWithLevel(System.LoggingLevel.FINEST), 'Wrong # of FINEST logs'); + } + + @IsTest + static void shouldLogFailedDmlOperations() { + MockDml.shouldFail(); + + Test.startTest(); + Exception caughtError; + try { + DatabaseLayer.Dml.doUpdate(MOCK_RECORD); + Assert.fail('A System.DmlException was not thrown'); + } catch (System.DmlException error) { + caughtError = error; + } + Test.stopTest(); + + LogSummary summary = new LogSummary(); + Assert.areEqual(2, summary?.numTotalEntries(), 'Wrong # of log entries'); + Assert.areEqual(1, summary?.numEntriesWithLevel(System.LoggingLevel.FINEST), 'Wrong # of FINEST logs'); + Assert.areEqual(1, summary?.numEntriesWithLevel(System.LoggingLevel.ERROR), 'Wrong # of ERROR logs'); + } + + @IsTest + static void shouldLogSuccessfulSoqlOperations() { + Test.startTest(); + DatabaseLayer.Soql.newQuery(Account.SObjectType)?.query(); + Test.stopTest(); + + LogSummary summary = new LogSummary(); + Assert.areEqual(2, summary?.numTotalEntries(), 'Wrong # of log entries'); + Assert.areEqual(2, summary?.numEntriesWithLevel(System.LoggingLevel.FINEST), 'Wrong # of FINEST logs'); + } + + @IsTest + static void shouldLogFailedSoqlOperations() { + MockSoql.setGlobalMock()?.withError(); + + Test.startTest(); + Exception caughtError; + try { + DatabaseLayer.Soql.newQuery(Account.SObjectType)?.query(); + Assert.fail('Did not throw a System.QueryException'); + } catch (System.QueryException error) { + caughtError = error; + } + Test.stopTest(); + + LogSummary summary = new LogSummary(); + Assert.areEqual(2, summary?.numTotalEntries(), 'Wrong # of log entries'); + Assert.areEqual(1, summary?.numEntriesWithLevel(System.LoggingLevel.FINEST), 'Wrong # of FINEST logs'); + Assert.areEqual(1, summary?.numEntriesWithLevel(System.LoggingLevel.ERROR), 'Wrong # of ERROR logs'); + } + + // **** INNER **** // + private class LogSummary { + private List taggedEntries; + private Map> entryIdsByLevel; + + /** + * @description Creates a new LogSummary by saving logs, delivering events, and querying tagged entries + */ + public LogSummary() { + Logger.saveLog(); + Test.getEventBus().deliver(); + this.queryTaggedEntries(); + this.mapTaggedEntriesByLevel(); + } + + /** + * @description Returns the number of log entries with the specified logging level + * @param level The logging level to count entries for + * @return The number of entries with the specified level, or 0 if none exist + */ + public Integer numEntriesWithLevel(System.LoggingLevel level) { + return this.entryIdsByLevel?.get(level?.name())?.size() ?? 0; + } + + /** + * @description Returns the total number of tagged log entries + * @return The total number of tagged entries, or 0 if none exist + */ + public Integer numTotalEntries() { + return this.taggedEntries?.size() ?? 0; + } + + private void mapTaggedEntriesByLevel() { + // Group LogEntry Ids by their common Logging Level name + this.entryIdsByLevel = new Map>{}; + for (LogEntryTag__c taggedEntry : this.taggedEntries) { + String level = taggedEntry?.LogEntry__r?.LoggingLevel__c; + Set matching = this.entryIdsByLevel?.get(level) ?? new Set{}; + matching?.add(taggedEntry?.LogEntry__c); + this.entryIdsByLevel?.put(level, matching); + } + } + + private void queryTaggedEntries() { + // Query for all log entries that were tagged with 'apex-database-layer' + this.taggedEntries = [ + SELECT Id, LogEntry__c, LogEntry__r.LoggingLevel__c, LogEntry__r.Message__c, Tag__r.Name + FROM LogEntryTag__c + WHERE Tag__r.Name = :DatabaseLayerNebulaLoggerAdapter.TAG_NAME + ]; + } + } +} diff --git a/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapterTest.cls-meta.xml b/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapterTest.cls-meta.xml index 1e7de940..88ae4ade 100644 --- a/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapterTest.cls-meta.xml +++ b/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapterTest.cls-meta.xml @@ -1,4 +1,4 @@ - + 64.0 Active diff --git a/sfdx-project.json b/sfdx-project.json index 5e26d488..2505d902 100644 --- a/sfdx-project.json +++ b/sfdx-project.json @@ -26,4 +26,4 @@ "apex-database-layer-nebula-logger-plugin-managed": "0HoDn0000010wD2KAI", "apex-database-layer-nebula-logger-plugin-unlocked": "0HoDn0000010wCxKAI" } -} \ No newline at end of file +} From 59b111c400b2a6e1525a95664e63bb78bba6fe13 Mon Sep 17 00:00:00 2001 From: Jason Siders <41714875+jasonsiders@users.noreply.github.com> Date: Fri, 8 Aug 2025 10:59:10 -0500 Subject: [PATCH 16/38] cleaning up log messages --- .../DatabaseLayerNebulaLoggerAdapter.cls | 70 ++++++++++--------- source/classes/Soql.cls | 9 ++- 2 files changed, 45 insertions(+), 34 deletions(-) diff --git a/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapter.cls b/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapter.cls index f9e426de..621a622b 100644 --- a/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapter.cls +++ b/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapter.cls @@ -4,7 +4,9 @@ */ @SuppressWarnings('PMD.AvoidGlobalModifier') global class DatabaseLayerNebulaLoggerAdapter implements Dml.PreAndPostProcessor, Soql.PreAndPostProcessor { - public static final String TAG_NAME = 'apex-database-layer'; + @TestVisible + private static final String TAG_NAME = 'apex-database-layer'; + private static final String DELIMITER = '\n---\n'; /** * @description Configure Nebula Logger to ignore database layer framework classes in log stack traces @@ -18,13 +20,12 @@ global class DatabaseLayerNebulaLoggerAdapter implements Dml.PreAndPostProcessor * @param request The DML request to be processed */ global void processPreDml(Dml.Request request) { + // prettier-ignore List lines = new List{ - '⏳ Processing DML ' + - this.getDmlOperationName(request?.operation) + - '...', + '⏳ DML: Processing ' + this.getDmlOperationName(request?.operation) + '...', 'Request:\n' + JSON.serialize(request) }; - String msg = String.join(lines, '\n\n'); + String msg = String.join(lines, DELIMITER); Logger.finest(msg)?.addTag(TAG_NAME)?.setRecord(request?.records); } @@ -34,12 +35,13 @@ global class DatabaseLayerNebulaLoggerAdapter implements Dml.PreAndPostProcessor * @param results The Database.*Results returned from the DML operation */ global void processPostDml(Dml.Request request, List results) { + // prettier-ignore List lines = new List{ - '✅ Processed DML ' + this.getDmlOperationName(request?.operation), + '✅ DML: Processed ' + this.getDmlOperationName(request?.operation), 'Request:\n' + JSON.serialize(request), 'Results:\n' + JSON.serialize(results) }; - String msg = String.join(lines, '\n\n'); + String msg = String.join(lines, DELIMITER); Logger.finest(msg)?.addTag(TAG_NAME)?.setRecord(request?.records); } @@ -49,15 +51,13 @@ global class DatabaseLayerNebulaLoggerAdapter implements Dml.PreAndPostProcessor * @param error The exception that was thrown */ global void processDmlError(Dml.Request request, Exception error) { + // prettier-ignore List lines = new List{ - '🚨 Error processing DML ' + this.getDmlOperationName(request?.operation), - 'Error:\n' + - error + - '\n' + - error?.getStackTraceString(), + '🚨 DML: Error processing ' + this.getDmlOperationName(request?.operation), + error + '\n' + error?.getStackTraceString(), 'Request:\n' + JSON.serialize(request) }; - String msg = String.join(lines, '\n\n'); + String msg = String.join(lines, DELIMITER); Logger.error(msg)?.addTag(TAG_NAME)?.setRecord(request?.records); } @@ -66,13 +66,12 @@ global class DatabaseLayerNebulaLoggerAdapter implements Dml.PreAndPostProcessor * @param request The SOQL request being processed */ global void processPreSoql(Soql.Request request) { + // prettier-ignore List lines = new List{ - '⏳ Processing SOQL ' + - request?.operation + - '...', + '⏳ SOQL: Processing ' + request?.operation + '...', 'Query:\n' + request?.queryString }; - String msg = String.join(lines, '\n\n'); + String msg = String.join(lines, DELIMITER); Logger.finest(msg)?.addTag(TAG_NAME); } @@ -82,14 +81,13 @@ global class DatabaseLayerNebulaLoggerAdapter implements Dml.PreAndPostProcessor * @param results The results from the SOQL operation */ global void processPostSoql(Soql.Request request, Object results) { + // prettier-ignore List lines = new List{ - '✅ Processed SOQL ' + - request?.operation + - ':', - 'Query:\n' + request?.queryString, - 'Results:\n' + results + '✅ SOQL: Processed ' + request?.operation, + request?.queryString, + this.summarizeSoqlResults(results) }; - String msg = String.join(lines, '\n\n'); + String msg = String.join(lines, DELIMITER); Logger.finest(msg)?.addTag(TAG_NAME); } @@ -99,20 +97,17 @@ global class DatabaseLayerNebulaLoggerAdapter implements Dml.PreAndPostProcessor * @param error The exception that occurred */ global void processSoqlError(Soql.Request request, Exception error) { + // prettier-ignore List lines = new List{ - '🚨 Error processing SOQL ' + - request?.operation + - ':', - 'Error:\n' + - error + - '\n' + - error?.getStackTraceString(), - 'Query:\n' + request?.queryString + '🚨 SOQL: Error processing ' + request?.operation, + request?.queryString, + error + '\n' + error?.getStackTraceString() }; - String msg = String.join(lines, '\n\n'); + String msg = String.join(lines, DELIMITER); Logger.error(msg)?.addTag(TAG_NAME); } + // **** PRIVATE **** // private String getDmlOperationName(Dml.Operation operation) { return operation?.name()?.removeStart('DO_'); } @@ -129,4 +124,15 @@ global class DatabaseLayerNebulaLoggerAdapter implements Dml.PreAndPostProcessor Logger.ignoreOrigin(MockSoql.class); Logger.ignoreOrigin(Soql.class); } + + private String summarizeSoqlResults(Object resultsObj) { + if (resultsObj instanceof Soql.QueryLocator) { + // Note: QueryLocators aren't serializable, and there isn't a performant way to determine the number of results: + Soql.QueryLocator locator = (Soql.QueryLocator) resultsObj; + return 'Results:\n' + locator?.getLocator(); + } else { + // All other possible query operations return serializable results: + return 'Results:\n' + JSON.serialize(resultsObj); + } + } } diff --git a/source/classes/Soql.cls b/source/classes/Soql.cls index e4199b4f..fa2f6a31 100644 --- a/source/classes/Soql.cls +++ b/source/classes/Soql.cls @@ -1430,6 +1430,11 @@ global inherited sharing virtual class Soql extends Soql.Builder { */ global virtual class Cursor { protected Database.Cursor cursor; + protected Integer numRecords { + get { + return this.cursor?.getNumRecords(); + } + } protected Cursor(Database.Cursor cursor) { this.cursor = cursor; @@ -1466,7 +1471,7 @@ global inherited sharing virtual class Soql extends Soql.Builder { * @return The total number of records in the cursor */ global virtual Integer getNumRecords() { - return this.cursor?.getNumRecords(); + return this.numRecords; } } @@ -1675,7 +1680,7 @@ global inherited sharing virtual class Soql extends Soql.Builder { * @description Mockable wrapper for Database.QueryLocator that enables testing of batch processing queries. */ global virtual class QueryLocator { - private Database.QueryLocator locator; + protected transient Database.QueryLocator locator; protected QueryLocator(Database.QueryLocator locator) { this.locator = locator; From 1989b8b0a2f20c4548dc49f1a6b2fed0897db9c3 Mon Sep 17 00:00:00 2001 From: Jason Siders <41714875+jasonsiders@users.noreply.github.com> Date: Fri, 8 Aug 2025 12:00:33 -0500 Subject: [PATCH 17/38] continuing refactoring messages --- .../DatabaseLayerNebulaLoggerAdapter.cls | 67 +++++++++++++------ 1 file changed, 45 insertions(+), 22 deletions(-) diff --git a/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapter.cls b/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapter.cls index 621a622b..970d6a15 100644 --- a/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapter.cls +++ b/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapter.cls @@ -12,7 +12,15 @@ global class DatabaseLayerNebulaLoggerAdapter implements Dml.PreAndPostProcessor * @description Configure Nebula Logger to ignore database layer framework classes in log stack traces */ global DatabaseLayerNebulaLoggerAdapter() { - this.omitFrameworkFromLogStackTrace(); + Logger.ignoreOrigin(DatabaseLayerNebulaLoggerAdapter.class); + Logger.ignoreOrigin(DatabaseLayer.class); + Logger.ignoreOrigin(DatabaseLayerUtils.class); + Logger.ignoreOrigin(DatabaseLayerTestUtils.class); + Logger.ignoreOrigin(Dml.class); + Logger.ignoreOrigin(MockDml.class); + Logger.ignoreOrigin(MockRecord.class); + Logger.ignoreOrigin(MockSoql.class); + Logger.ignoreOrigin(Soql.class); } /** @@ -38,11 +46,11 @@ global class DatabaseLayerNebulaLoggerAdapter implements Dml.PreAndPostProcessor // prettier-ignore List lines = new List{ '✅ DML: Processed ' + this.getDmlOperationName(request?.operation), - 'Request:\n' + JSON.serialize(request), - 'Results:\n' + JSON.serialize(results) + 'Request:\n' + JSON.serialize(request) }; String msg = String.join(lines, DELIMITER); - Logger.finest(msg)?.addTag(TAG_NAME)?.setRecord(request?.records); + LogEntryEventBuilder log = Logger.finest(msg)?.addTag(TAG_NAME)?.setRecord(request?.records); + this.logDmlResults(log, results); } /** @@ -84,11 +92,19 @@ global class DatabaseLayerNebulaLoggerAdapter implements Dml.PreAndPostProcessor // prettier-ignore List lines = new List{ '✅ SOQL: Processed ' + request?.operation, - request?.queryString, - this.summarizeSoqlResults(results) + request?.queryString }; + List records = new List(); + // Log the query results, depending on the result type: + if (results instanceof List) { + // Where possible, add the SOQL query results to the log using the `setRecord` method: + records = (List) results; + } else { + // `setRecord` only accepts a List. Print other types of results in the message body: + lines?.add(this.summarizeSoqlResults(results)); + } String msg = String.join(lines, DELIMITER); - Logger.finest(msg)?.addTag(TAG_NAME); + Logger.finest(msg)?.addTag(TAG_NAME)?.setRecord(records); } /** @@ -112,27 +128,34 @@ global class DatabaseLayerNebulaLoggerAdapter implements Dml.PreAndPostProcessor return operation?.name()?.removeStart('DO_'); } - private void omitFrameworkFromLogStackTrace() { - // Prevent framework-specific method calls from polluting Nebula Logger's stack trace - Logger.ignoreOrigin(DatabaseLayerNebulaLoggerAdapter.class); - Logger.ignoreOrigin(DatabaseLayer.class); - Logger.ignoreOrigin(DatabaseLayerUtils.class); - Logger.ignoreOrigin(DatabaseLayerTestUtils.class); - Logger.ignoreOrigin(Dml.class); - Logger.ignoreOrigin(MockDml.class); - Logger.ignoreOrigin(MockRecord.class); - Logger.ignoreOrigin(MockSoql.class); - Logger.ignoreOrigin(Soql.class); + private void logDmlResults(LogEntryEventBuilder log, List objList) { + // Note: Nebula Logger doesn't accept an untyped List in `setDatabaseResult`: + if (objList instanceof List) { + List results = (List) objList; + log?.setDatabaseResult(results); + } else if (objList instanceof List) { + List results = (List) objList; + log?.setDatabaseResult(results); + } else if (objList instanceof List) { + List results = (List) objList; + log?.setDatabaseResult(results); + } else if (objList instanceof List) { + List results = (List) objList; + log?.setDatabaseResult(results); + } else if (objList instanceof List) { + List results = (List) objList; + log?.setDatabaseResult(results); + } } - private String summarizeSoqlResults(Object resultsObj) { - if (resultsObj instanceof Soql.QueryLocator) { + private String summarizeSoqlResults(Object results) { + if (results instanceof Soql.QueryLocator) { // Note: QueryLocators aren't serializable, and there isn't a performant way to determine the number of results: - Soql.QueryLocator locator = (Soql.QueryLocator) resultsObj; + Soql.QueryLocator locator = (Soql.QueryLocator) results; return 'Results:\n' + locator?.getLocator(); } else { // All other possible query operations return serializable results: - return 'Results:\n' + JSON.serialize(resultsObj); + return 'Results:\n' + JSON.serialize(results); } } } From eddc9e58aedd9076bf9880261bdb186efec2ef77 Mon Sep 17 00:00:00 2001 From: Jason Siders <41714875+jasonsiders@users.noreply.github.com> Date: Fri, 8 Aug 2025 13:08:23 -0500 Subject: [PATCH 18/38] contd. --- .../DatabaseLayerNebulaLoggerAdapter.cls | 23 +++++++------------ 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapter.cls b/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapter.cls index 970d6a15..703006f0 100644 --- a/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapter.cls +++ b/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapter.cls @@ -99,10 +99,14 @@ global class DatabaseLayerNebulaLoggerAdapter implements Dml.PreAndPostProcessor if (results instanceof List) { // Where possible, add the SOQL query results to the log using the `setRecord` method: records = (List) results; - } else { - // `setRecord` only accepts a List. Print other types of results in the message body: - lines?.add(this.summarizeSoqlResults(results)); - } + } else if (results instanceOf Soql.QueryLocator) { + // Note: QueryLocators aren't serializable, and there isn't a performant way to determine the number of results: + Soql.QueryLocator locator = (Soql.QueryLocator) results; + lines?.add('Results:\n' + locator?.getLocator()); + } else { + // All other possible query operations return serializable results: + lines?.add('Results:\n' + JSON.serialize(results)); + } String msg = String.join(lines, DELIMITER); Logger.finest(msg)?.addTag(TAG_NAME)?.setRecord(records); } @@ -147,15 +151,4 @@ global class DatabaseLayerNebulaLoggerAdapter implements Dml.PreAndPostProcessor log?.setDatabaseResult(results); } } - - private String summarizeSoqlResults(Object results) { - if (results instanceof Soql.QueryLocator) { - // Note: QueryLocators aren't serializable, and there isn't a performant way to determine the number of results: - Soql.QueryLocator locator = (Soql.QueryLocator) results; - return 'Results:\n' + locator?.getLocator(); - } else { - // All other possible query operations return serializable results: - return 'Results:\n' + JSON.serialize(results); - } - } } From f2c80588979331a890cd6f74e78b37131391c2df Mon Sep 17 00:00:00 2001 From: Jason Siders <41714875+jasonsiders@users.noreply.github.com> Date: Fri, 8 Aug 2025 14:21:07 -0500 Subject: [PATCH 19/38] refactoring --- .../DatabaseLayerNebulaLoggerAdapter.cls | 29 ++++++++++--------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapter.cls b/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapter.cls index 703006f0..4878676a 100644 --- a/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapter.cls +++ b/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapter.cls @@ -24,7 +24,7 @@ global class DatabaseLayerNebulaLoggerAdapter implements Dml.PreAndPostProcessor } /** - * @description Called before executing a DML operation. + * @description Logs DML requests before execution * @param request The DML request to be processed */ global void processPreDml(Dml.Request request) { @@ -38,7 +38,7 @@ global class DatabaseLayerNebulaLoggerAdapter implements Dml.PreAndPostProcessor } /** - * @description Called after successfully executing a DML operation. + * @description Logs a successful DML operation * @param request The DML request that was processed * @param results The Database.*Results returned from the DML operation */ @@ -54,7 +54,7 @@ global class DatabaseLayerNebulaLoggerAdapter implements Dml.PreAndPostProcessor } /** - * @description Called when a DML operation encounters an error. + * @description Logs DML errors * @param request The DML request that encountered an error * @param error The exception that was thrown */ @@ -70,7 +70,7 @@ global class DatabaseLayerNebulaLoggerAdapter implements Dml.PreAndPostProcessor } /** - * @description Processes SOQL requests before execution. + * @description Logs SOQL requests before execution. * @param request The SOQL request being processed */ global void processPreSoql(Soql.Request request) { @@ -84,35 +84,36 @@ global class DatabaseLayerNebulaLoggerAdapter implements Dml.PreAndPostProcessor } /** - * @description Processes SOQL requests after execution. + * @description Logs SOQL requests after execution. * @param request The SOQL request that was processed * @param results The results from the SOQL operation */ global void processPostSoql(Soql.Request request, Object results) { + LogEntryEventBuilder log = Logger.finest('')?.addTag(TAG_NAME); // prettier-ignore List lines = new List{ '✅ SOQL: Processed ' + request?.operation, request?.queryString }; - List records = new List(); - // Log the query results, depending on the result type: - if (results instanceof List) { - // Where possible, add the SOQL query results to the log using the `setRecord` method: - records = (List) results; + // Add the query results to the log, depending on their type + if (results instanceOf List) { + // SObjects can be added to the log's related records, via the `setRecord` method + // All other types will be appended to the log body itself + List records = (List) results; + log?.setRecord(records); } else if (results instanceOf Soql.QueryLocator) { - // Note: QueryLocators aren't serializable, and there isn't a performant way to determine the number of results: + // QueryLocators aren't serializable, and there isn't a performant way to determine the number of results: Soql.QueryLocator locator = (Soql.QueryLocator) results; lines?.add('Results:\n' + locator?.getLocator()); } else { // All other possible query operations return serializable results: lines?.add('Results:\n' + JSON.serialize(results)); } - String msg = String.join(lines, DELIMITER); - Logger.finest(msg)?.addTag(TAG_NAME)?.setRecord(records); + log?.setMessage(String.join(lines, DELIMITER)); } /** - * @description Processes SOQL errors. + * @description Logs SOQL errors. * @param request The SOQL request that failed * @param error The exception that occurred */ From aafa99893ad640affece84b52b5cb5337e0d6420 Mon Sep 17 00:00:00 2001 From: Jason Siders <41714875+jasonsiders@users.noreply.github.com> Date: Fri, 8 Aug 2025 15:15:20 -0500 Subject: [PATCH 20/38] Finalizing logging behavior --- .../DatabaseLayerNebulaLoggerAdapter.cls | 211 +++++++++++------- 1 file changed, 135 insertions(+), 76 deletions(-) diff --git a/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapter.cls b/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapter.cls index 4878676a..4a2cd40c 100644 --- a/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapter.cls +++ b/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapter.cls @@ -28,13 +28,12 @@ global class DatabaseLayerNebulaLoggerAdapter implements Dml.PreAndPostProcessor * @param request The DML request to be processed */ global void processPreDml(Dml.Request request) { - // prettier-ignore - List lines = new List{ - '⏳ DML: Processing ' + this.getDmlOperationName(request?.operation) + '...', - 'Request:\n' + JSON.serialize(request) - }; - String msg = String.join(lines, DELIMITER); - Logger.finest(msg)?.addTag(TAG_NAME)?.setRecord(request?.records); + String op = this.getDmlOperationName(request?.operation); + this.log(System.LoggingLevel.FINEST) + ?.addLine('⏳ DML: Processing {0}', op) + ?.addLine('Request:\n{0}', JSON.serialize(request)) + ?.setRecords(request?.records) + ?.build(); } /** @@ -43,14 +42,13 @@ global class DatabaseLayerNebulaLoggerAdapter implements Dml.PreAndPostProcessor * @param results The Database.*Results returned from the DML operation */ global void processPostDml(Dml.Request request, List results) { - // prettier-ignore - List lines = new List{ - '✅ DML: Processed ' + this.getDmlOperationName(request?.operation), - 'Request:\n' + JSON.serialize(request) - }; - String msg = String.join(lines, DELIMITER); - LogEntryEventBuilder log = Logger.finest(msg)?.addTag(TAG_NAME)?.setRecord(request?.records); - this.logDmlResults(log, results); + String op = this.getDmlOperationName(request?.operation); + this.log(System.LoggingLevel.FINEST) + ?.addLine('✅ DML: Processed {0}', op) + ?.addLine('Request: {0}', JSON.serialize(request)) + ?.setDatabaseResults(results) + ?.setRecords(request?.records) + ?.build(); } /** @@ -59,14 +57,13 @@ global class DatabaseLayerNebulaLoggerAdapter implements Dml.PreAndPostProcessor * @param error The exception that was thrown */ global void processDmlError(Dml.Request request, Exception error) { - // prettier-ignore - List lines = new List{ - '🚨 DML: Error processing ' + this.getDmlOperationName(request?.operation), - error + '\n' + error?.getStackTraceString(), - 'Request:\n' + JSON.serialize(request) - }; - String msg = String.join(lines, DELIMITER); - Logger.error(msg)?.addTag(TAG_NAME)?.setRecord(request?.records); + String op = this.getDmlOperationName(request?.operation); + this.log(System.LoggingLevel.ERROR) + ?.addLine('🚨 DML: Error processing {0}', op) + ?.addLine('{0}\n{1}', error, error?.getStackTraceString()) + ?.addLine('Request:\n{0}', JSON.serialize(request)) + ?.setRecords(request?.records) + ?.build(); } /** @@ -74,13 +71,10 @@ global class DatabaseLayerNebulaLoggerAdapter implements Dml.PreAndPostProcessor * @param request The SOQL request being processed */ global void processPreSoql(Soql.Request request) { - // prettier-ignore - List lines = new List{ - '⏳ SOQL: Processing ' + request?.operation + '...', - 'Query:\n' + request?.queryString - }; - String msg = String.join(lines, DELIMITER); - Logger.finest(msg)?.addTag(TAG_NAME); + this.log(System.LoggingLevel.FINEST) + ?.addLine('⏳ SOQL: Processing {0}...', request?.operation) + ?.addLine(request?.queryString) + ?.build(); } /** @@ -89,27 +83,11 @@ global class DatabaseLayerNebulaLoggerAdapter implements Dml.PreAndPostProcessor * @param results The results from the SOQL operation */ global void processPostSoql(Soql.Request request, Object results) { - LogEntryEventBuilder log = Logger.finest('')?.addTag(TAG_NAME); - // prettier-ignore - List lines = new List{ - '✅ SOQL: Processed ' + request?.operation, - request?.queryString - }; - // Add the query results to the log, depending on their type - if (results instanceOf List) { - // SObjects can be added to the log's related records, via the `setRecord` method - // All other types will be appended to the log body itself - List records = (List) results; - log?.setRecord(records); - } else if (results instanceOf Soql.QueryLocator) { - // QueryLocators aren't serializable, and there isn't a performant way to determine the number of results: - Soql.QueryLocator locator = (Soql.QueryLocator) results; - lines?.add('Results:\n' + locator?.getLocator()); - } else { - // All other possible query operations return serializable results: - lines?.add('Results:\n' + JSON.serialize(results)); - } - log?.setMessage(String.join(lines, DELIMITER)); + this.log(System.LoggingLevel.FINEST) + ?.addLine('✅ SOQL: Processed {0}', request?.operation) + ?.addLine(request?.queryString) + ?.setRecords(results) + ?.build(); } /** @@ -118,14 +96,11 @@ global class DatabaseLayerNebulaLoggerAdapter implements Dml.PreAndPostProcessor * @param error The exception that occurred */ global void processSoqlError(Soql.Request request, Exception error) { - // prettier-ignore - List lines = new List{ - '🚨 SOQL: Error processing ' + request?.operation, - request?.queryString, - error + '\n' + error?.getStackTraceString() - }; - String msg = String.join(lines, DELIMITER); - Logger.error(msg)?.addTag(TAG_NAME); + this.log(System.LoggingLevel.ERROR) + ?.addLine('🚨 SOQL: Error processing {0}', request?.operation) + ?.addLine(request?.queryString) + ?.addLine('{0}\n{1}', error, error?.getStackTraceString()) + ?.build(); } // **** PRIVATE **** // @@ -133,23 +108,107 @@ global class DatabaseLayerNebulaLoggerAdapter implements Dml.PreAndPostProcessor return operation?.name()?.removeStart('DO_'); } - private void logDmlResults(LogEntryEventBuilder log, List objList) { - // Note: Nebula Logger doesn't accept an untyped List in `setDatabaseResult`: - if (objList instanceof List) { - List results = (List) objList; - log?.setDatabaseResult(results); - } else if (objList instanceof List) { - List results = (List) objList; - log?.setDatabaseResult(results); - } else if (objList instanceof List) { - List results = (List) objList; - log?.setDatabaseResult(results); - } else if (objList instanceof List) { - List results = (List) objList; - log?.setDatabaseResult(results); - } else if (objList instanceof List) { - List results = (List) objList; - log?.setDatabaseResult(results); + private LogBuilder log(System.LoggingLevel level) { + return new DatabaseLayerNebulaLoggerAdapter.LogBuilder(level); + } + + // **** INNER **** // + private class LogBuilder { + private LogEntryEventBuilder entry; + private List lines; + + /** + * @description Creates a new LogBuilder instance with the specified logging level + * @param level The logging level for this log entry + */ + public LogBuilder(System.LoggingLevel level) { + String temporaryMsg = null; + this.entry = Logger.newEntry(level, temporaryMsg)?.addTag(TAG_NAME); + this.lines = new List{}; + } + + /** + * @description Adds a line to the log message + * @param line The line to add to the log message + * @return This LogBuilder instance for method chaining + */ + public LogBuilder addLine(String line) { + this.lines?.add(line); + return this; + } + + /** + * @description Adds a formatted line to the log message with two arguments + * @param tmp The string template with placeholders + * @param arg1 The first argument to substitute in the template + * @param arg2 The second argument to substitute in the template + * @return This LogBuilder instance for method chaining + */ + public LogBuilder addLine(String tmp, Object arg1, Object arg2) { + String line = String.format(tmp, new List{ arg1?.toString(), arg2?.toString() }); + return this.addLine(line); + } + + /** + * @description Adds a formatted line to the log message with one argument + * @param tmp The string template with placeholders + * @param arg1 The argument to substitute in the template + * @return This LogBuilder instance for method chaining + */ + public LogBuilder addLine(String tmp, Object arg1) { + return this.addLine(tmp, arg1, ''); + } + + /** + * @description Builds the final log entry with all accumulated lines + * @return The LogEntryEventBuilder with the complete log message + */ + public LogEntryEventBuilder build() { + String msg = String.join(this.lines, DELIMITER); + return this.entry?.setMessage(msg); + } + + /** + * @description Sets database operation results on the log entry + * @param obj List of database results (DeleteResult, SaveResult, etc.) + * @return This LogBuilder instance for method chaining + */ + public LogBuilder setDatabaseResults(List obj) { + if (obj instanceof List) { + List databaseResults = (List) obj; + this.entry?.setDatabaseResult(databaseResults); + } else if (obj instanceof List) { + List databaseResults = (List) obj; + this.entry?.setDatabaseResult(databaseResults); + } else if (obj instanceof List) { + List databaseResults = (List) obj; + this.entry?.setDatabaseResult(databaseResults); + } else if (obj instanceof List) { + List databaseResults = (List) obj; + this.entry?.setDatabaseResult(databaseResults); + } else if (obj instanceof List) { + List databaseResults = (List) obj; + this.entry?.setDatabaseResult(databaseResults); + } + return this; + } + + /** + * @description Sets records or query results on the log entry + * @param obj Records to log (SObject list, QueryLocator, or other results) + * @return This LogBuilder instance for method chaining + */ + public LogBuilder setRecords(Object obj) { + if (obj instanceof List) { + List records = (List) obj; + this.entry?.setRecord(records); + } else if (obj instanceof Soql.QueryLocator) { + Soql.QueryLocator locator = (Soql.QueryLocator) obj; + this.lines?.add('Results:\n' + locator); + } else { + this.lines?.add('Results:\n' + JSON.serialize(obj)); + } + return this; } } } From 2131c65dc6ef59cc906ab7bd6bd23e5fcaed106d Mon Sep 17 00:00:00 2001 From: Jason Siders <41714875+jasonsiders@users.noreply.github.com> Date: Fri, 8 Aug 2025 15:25:47 -0500 Subject: [PATCH 21/38] patching test coverage gaps --- .../DatabaseLayerNebulaLoggerAdapterTest.cls | 91 +++++++++++++++++-- 1 file changed, 84 insertions(+), 7 deletions(-) diff --git a/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapterTest.cls b/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapterTest.cls index 4aafa2b1..78f9253e 100644 --- a/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapterTest.cls +++ b/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapterTest.cls @@ -1,18 +1,18 @@ @SuppressWarnings('PMD.ApexUnitTestClassShouldHaveRunAs') @IsTest private class DatabaseLayerNebulaLoggerAdapterTest { - private static final Account MOCK_RECORD; + private static final Lead MOCK_RECORD; private static final String PLUGIN_NAME = DatabaseLayerNebulaLoggerAdapter.class.getName(); static { DatabaseLayer.useMocks(); - MOCK_RECORD = (Account) new MockRecord(Account.SObjectType)?.withId()?.toSObject(); - MockSoql.setGlobalMock().withResults(new List{ MOCK_RECORD }); + MOCK_RECORD = (Lead) new MockRecord(Lead.SObjectType)?.withId()?.toSObject(); + MockSoql.setGlobalMock().withResults(new List{ MOCK_RECORD }); DatabaseLayerTestUtils.initDmlAndSoqlPlugins(PLUGIN_NAME); } @IsTest - static void shouldLogSuccessfulDmlOperations() { + static void shouldLogDmlSaveOperations() { Test.startTest(); DatabaseLayer.Dml.doUpdate(MOCK_RECORD); Test.stopTest(); @@ -22,6 +22,57 @@ private class DatabaseLayerNebulaLoggerAdapterTest { Assert.areEqual(2, summary?.numEntriesWithLevel(System.LoggingLevel.FINEST), 'Wrong # of FINEST logs'); } + @IsTest + static void shouldLogDmlConvertOperations() { + // Mostly for coverage -- necessary since Nebula Logger doesn't support using generic Objects for its `setDatabaseResult` method + Database.LeadConvert leadToConvert = new Database.LeadConvert(); + leadToConvert.leadId = MOCK_RECORD?.Id; + + Test.startTest(); + DatabaseLayer.Dml.doConvert(leadToConvert); + Test.stopTest(); + + LogSummary summary = new LogSummary(); + Assert.areEqual(2, summary?.numTotalEntries(), 'Wrong # of log entries'); + Assert.areEqual(2, summary?.numEntriesWithLevel(System.LoggingLevel.FINEST), 'Wrong # of FINEST logs'); + } + + @IsTest + static void shouldLogDmlDeleteOperations() { + // Mostly for coverage -- necessary since Nebula Logger doesn't support using generic Objects for its `setDatabaseResult` method + Test.startTest(); + DatabaseLayer.Dml.doDelete(MOCK_RECORD); + Test.stopTest(); + + LogSummary summary = new LogSummary(); + Assert.areEqual(2, summary?.numTotalEntries(), 'Wrong # of log entries'); + Assert.areEqual(2, summary?.numEntriesWithLevel(System.LoggingLevel.FINEST), 'Wrong # of FINEST logs'); + } + + @IsTest + static void shouldLogDmlUndeleteOperations() { + // Mostly for coverage -- necessary since Nebula Logger doesn't support using generic Objects for its `setDatabaseResult` method + Test.startTest(); + DatabaseLayer.Dml.doUndelete(MOCK_RECORD); + Test.stopTest(); + + LogSummary summary = new LogSummary(); + Assert.areEqual(2, summary?.numTotalEntries(), 'Wrong # of log entries'); + Assert.areEqual(2, summary?.numEntriesWithLevel(System.LoggingLevel.FINEST), 'Wrong # of FINEST logs'); + } + + @IsTest + static void shouldLogDmlUpsertOperations() { + // Mostly for coverage -- necessary since Nebula Logger doesn't support using generic Objects for its `setDatabaseResult` method + Test.startTest(); + DatabaseLayer.Dml.doUpsert(MOCK_RECORD); + Test.stopTest(); + + LogSummary summary = new LogSummary(); + Assert.areEqual(2, summary?.numTotalEntries(), 'Wrong # of log entries'); + Assert.areEqual(2, summary?.numEntriesWithLevel(System.LoggingLevel.FINEST), 'Wrong # of FINEST logs'); + } + @IsTest static void shouldLogFailedDmlOperations() { MockDml.shouldFail(); @@ -43,9 +94,35 @@ private class DatabaseLayerNebulaLoggerAdapterTest { } @IsTest - static void shouldLogSuccessfulSoqlOperations() { + static void shouldLogSoqlOperations() { + Test.startTest(); + DatabaseLayer.Soql.newQuery(Lead.SObjectType)?.query(); + Test.stopTest(); + + LogSummary summary = new LogSummary(); + Assert.areEqual(2, summary?.numTotalEntries(), 'Wrong # of log entries'); + Assert.areEqual(2, summary?.numEntriesWithLevel(System.LoggingLevel.FINEST), 'Wrong # of FINEST logs'); + } + + @IsTest + static void shouldLogSoqlQueryLocatorOperations() { + // Mostly for coverage -- Database.QueryLocators are not serializable, + // and therefore have special log handling to prevent errors: + Test.startTest(); + DatabaseLayer.Soql.newQuery(Lead.SObjectType)?.getQueryLocator(); + Test.stopTest(); + + LogSummary summary = new LogSummary(); + Assert.areEqual(2, summary?.numTotalEntries(), 'Wrong # of log entries'); + Assert.areEqual(2, summary?.numEntriesWithLevel(System.LoggingLevel.FINEST), 'Wrong # of FINEST logs'); + } + + @IsTest + static void shouldLogOtherSoqlOperations() { + // This is mostly for coverage; results from query operations + // that return something other than a List are serialized Test.startTest(); - DatabaseLayer.Soql.newQuery(Account.SObjectType)?.query(); + DatabaseLayer.Soql.newQuery(Lead.SObjectType)?.countQuery(); Test.stopTest(); LogSummary summary = new LogSummary(); @@ -60,7 +137,7 @@ private class DatabaseLayerNebulaLoggerAdapterTest { Test.startTest(); Exception caughtError; try { - DatabaseLayer.Soql.newQuery(Account.SObjectType)?.query(); + DatabaseLayer.Soql.newQuery(Lead.SObjectType)?.query(); Assert.fail('Did not throw a System.QueryException'); } catch (System.QueryException error) { caughtError = error; From e147222580da72b6a276c2b5ab38e47c3e317b55 Mon Sep 17 00:00:00 2001 From: Jason Siders <41714875+jasonsiders@users.noreply.github.com> Date: Fri, 8 Aug 2025 15:27:05 -0500 Subject: [PATCH 22/38] fixing failing test --- .../source/classes/DatabaseLayerNebulaLoggerAdapterTest.cls | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapterTest.cls b/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapterTest.cls index 78f9253e..c73b8bc8 100644 --- a/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapterTest.cls +++ b/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapterTest.cls @@ -26,7 +26,8 @@ private class DatabaseLayerNebulaLoggerAdapterTest { static void shouldLogDmlConvertOperations() { // Mostly for coverage -- necessary since Nebula Logger doesn't support using generic Objects for its `setDatabaseResult` method Database.LeadConvert leadToConvert = new Database.LeadConvert(); - leadToConvert.leadId = MOCK_RECORD?.Id; + leadToConvert.setConvertedStatus('Some Status'); + leadToConvert.setLeadId(MOCK_RECORD?.Id); Test.startTest(); DatabaseLayer.Dml.doConvert(leadToConvert); From 6312489fce185674368dbaf3e754769bdcecca7a Mon Sep 17 00:00:00 2001 From: Jason Siders <41714875+jasonsiders@users.noreply.github.com> Date: Fri, 8 Aug 2025 15:44:51 -0500 Subject: [PATCH 23/38] prevent logging empty log lines --- .../DatabaseLayerNebulaLoggerAdapter.cls | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapter.cls b/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapter.cls index 4a2cd40c..a078d966 100644 --- a/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapter.cls +++ b/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapter.cls @@ -7,9 +7,16 @@ global class DatabaseLayerNebulaLoggerAdapter implements Dml.PreAndPostProcessor @TestVisible private static final String TAG_NAME = 'apex-database-layer'; private static final String DELIMITER = '\n---\n'; + private static final Boolean ORIGINAL_DEBUG_FLAG_VALUE; + private static final LoggerSettings__c SETTINGS; + + static { + SETTINGS = Logger.getUserSettings(); + ORIGINAL_DEBUG_FLAG_VALUE = SETTINGS?.IsApexSystemDebugLoggingEnabled__c ?? false; + } /** - * @description Configure Nebula Logger to ignore database layer framework classes in log stack traces + * @description Configure Nebula Logger to ignore this framework's internal classes in log stack traces */ global DatabaseLayerNebulaLoggerAdapter() { Logger.ignoreOrigin(DatabaseLayerNebulaLoggerAdapter.class); @@ -122,8 +129,7 @@ global class DatabaseLayerNebulaLoggerAdapter implements Dml.PreAndPostProcessor * @param level The logging level for this log entry */ public LogBuilder(System.LoggingLevel level) { - String temporaryMsg = null; - this.entry = Logger.newEntry(level, temporaryMsg)?.addTag(TAG_NAME); + this.createLogEntry(level); this.lines = new List{}; } @@ -210,5 +216,13 @@ global class DatabaseLayerNebulaLoggerAdapter implements Dml.PreAndPostProcessor } return this; } + + private void createLogEntry(System.LoggingLevel level) { + // Create a basic/empty log entry object for the builder to hydrate with log lines & other information + // Special care must be taken to prevent committing unnecessary '' log lines to System.debug logs: + SETTINGS.IsApexSystemDebugLoggingEnabled__c = false; + this.entry = Logger.newEntry(level, '')?.addTag(TAG_NAME); + SETTINGS.IsApexSystemDebugLoggingEnabled__c = ORIGINAL_DEBUG_FLAG_VALUE; + } } } From 325279a100d29a390edca024b66f1ad6909bb69e Mon Sep 17 00:00:00 2001 From: Jason Siders <41714875+jasonsiders@users.noreply.github.com> Date: Fri, 8 Aug 2025 15:53:20 -0500 Subject: [PATCH 24/38] fixing test --- .../source/classes/DatabaseLayerNebulaLoggerAdapterTest.cls | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapterTest.cls b/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapterTest.cls index c73b8bc8..72b97a54 100644 --- a/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapterTest.cls +++ b/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapterTest.cls @@ -114,8 +114,10 @@ private class DatabaseLayerNebulaLoggerAdapterTest { Test.stopTest(); LogSummary summary = new LogSummary(); - Assert.areEqual(2, summary?.numTotalEntries(), 'Wrong # of log entries'); - Assert.areEqual(2, summary?.numEntriesWithLevel(System.LoggingLevel.FINEST), 'Wrong # of FINEST logs'); + // Note: MockSoql.getQueryLocator uses a normal MockSoql.query under the hood + // As a result, logs are shown for *both* the `getQueryLocator` and `query` methods: + Assert.areEqual(4, summary?.numTotalEntries(), 'Wrong # of log entries'); + Assert.areEqual(4, summary?.numEntriesWithLevel(System.LoggingLevel.FINEST), 'Wrong # of FINEST logs'); } @IsTest From d55fefea1a48682485913d49f99a3d7ea8807df1 Mon Sep 17 00:00:00 2001 From: Jason Siders <41714875+jasonsiders@users.noreply.github.com> Date: Fri, 8 Aug 2025 15:55:06 -0500 Subject: [PATCH 25/38] cleanup --- .../DatabaseLayerNebulaLoggerAdapter.cls | 2 +- .../DatabaseLayerNebulaLoggerAdapterTest.cls | 20 +++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapter.cls b/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapter.cls index a078d966..89f7522f 100644 --- a/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapter.cls +++ b/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapter.cls @@ -8,7 +8,7 @@ global class DatabaseLayerNebulaLoggerAdapter implements Dml.PreAndPostProcessor private static final String TAG_NAME = 'apex-database-layer'; private static final String DELIMITER = '\n---\n'; private static final Boolean ORIGINAL_DEBUG_FLAG_VALUE; - private static final LoggerSettings__c SETTINGS; + private static final LoggerSettings__c SETTINGS; static { SETTINGS = Logger.getUserSettings(); diff --git a/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapterTest.cls b/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapterTest.cls index 72b97a54..b8e00d5a 100644 --- a/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapterTest.cls +++ b/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapterTest.cls @@ -2,13 +2,13 @@ @IsTest private class DatabaseLayerNebulaLoggerAdapterTest { private static final Lead MOCK_RECORD; - private static final String PLUGIN_NAME = DatabaseLayerNebulaLoggerAdapter.class.getName(); static { DatabaseLayer.useMocks(); MOCK_RECORD = (Lead) new MockRecord(Lead.SObjectType)?.withId()?.toSObject(); MockSoql.setGlobalMock().withResults(new List{ MOCK_RECORD }); - DatabaseLayerTestUtils.initDmlAndSoqlPlugins(PLUGIN_NAME); + String pluginName = DatabaseLayerNebulaLoggerAdapter.class.getName(); + DatabaseLayerTestUtils.initDmlAndSoqlPlugins(pluginName); } @IsTest @@ -22,7 +22,7 @@ private class DatabaseLayerNebulaLoggerAdapterTest { Assert.areEqual(2, summary?.numEntriesWithLevel(System.LoggingLevel.FINEST), 'Wrong # of FINEST logs'); } - @IsTest + @IsTest static void shouldLogDmlConvertOperations() { // Mostly for coverage -- necessary since Nebula Logger doesn't support using generic Objects for its `setDatabaseResult` method Database.LeadConvert leadToConvert = new Database.LeadConvert(); @@ -38,7 +38,7 @@ private class DatabaseLayerNebulaLoggerAdapterTest { Assert.areEqual(2, summary?.numEntriesWithLevel(System.LoggingLevel.FINEST), 'Wrong # of FINEST logs'); } - @IsTest + @IsTest static void shouldLogDmlDeleteOperations() { // Mostly for coverage -- necessary since Nebula Logger doesn't support using generic Objects for its `setDatabaseResult` method Test.startTest(); @@ -50,7 +50,7 @@ private class DatabaseLayerNebulaLoggerAdapterTest { Assert.areEqual(2, summary?.numEntriesWithLevel(System.LoggingLevel.FINEST), 'Wrong # of FINEST logs'); } - @IsTest + @IsTest static void shouldLogDmlUndeleteOperations() { // Mostly for coverage -- necessary since Nebula Logger doesn't support using generic Objects for its `setDatabaseResult` method Test.startTest(); @@ -62,7 +62,7 @@ private class DatabaseLayerNebulaLoggerAdapterTest { Assert.areEqual(2, summary?.numEntriesWithLevel(System.LoggingLevel.FINEST), 'Wrong # of FINEST logs'); } - @IsTest + @IsTest static void shouldLogDmlUpsertOperations() { // Mostly for coverage -- necessary since Nebula Logger doesn't support using generic Objects for its `setDatabaseResult` method Test.startTest(); @@ -105,9 +105,9 @@ private class DatabaseLayerNebulaLoggerAdapterTest { Assert.areEqual(2, summary?.numEntriesWithLevel(System.LoggingLevel.FINEST), 'Wrong # of FINEST logs'); } - @IsTest + @IsTest static void shouldLogSoqlQueryLocatorOperations() { - // Mostly for coverage -- Database.QueryLocators are not serializable, + // Mostly for coverage -- Database.QueryLocators are not serializable, // and therefore have special log handling to prevent errors: Test.startTest(); DatabaseLayer.Soql.newQuery(Lead.SObjectType)?.getQueryLocator(); @@ -120,9 +120,9 @@ private class DatabaseLayerNebulaLoggerAdapterTest { Assert.areEqual(4, summary?.numEntriesWithLevel(System.LoggingLevel.FINEST), 'Wrong # of FINEST logs'); } - @IsTest + @IsTest static void shouldLogOtherSoqlOperations() { - // This is mostly for coverage; results from query operations + // This is mostly for coverage; results from query operations // that return something other than a List are serialized Test.startTest(); DatabaseLayer.Soql.newQuery(Lead.SObjectType)?.countQuery(); From a96d2dbc25f0260c1163841569023f21a3ca67fb Mon Sep 17 00:00:00 2001 From: Jason Siders <41714875+jasonsiders@users.noreply.github.com> Date: Fri, 8 Aug 2025 16:01:18 -0500 Subject: [PATCH 26/38] restructuring --- plugins/nebula-logger/README.md | 3 +++ .../{packages/managed/unlocked => }/sfdx-project.json | 0 2 files changed, 3 insertions(+) create mode 100644 plugins/nebula-logger/README.md rename plugins/nebula-logger/{packages/managed/unlocked => }/sfdx-project.json (100%) diff --git a/plugins/nebula-logger/README.md b/plugins/nebula-logger/README.md new file mode 100644 index 00000000..ef491b6a --- /dev/null +++ b/plugins/nebula-logger/README.md @@ -0,0 +1,3 @@ +# Plugin: Nebula Logger for DML & SOQL + +TODO! \ No newline at end of file diff --git a/plugins/nebula-logger/packages/managed/unlocked/sfdx-project.json b/plugins/nebula-logger/sfdx-project.json similarity index 100% rename from plugins/nebula-logger/packages/managed/unlocked/sfdx-project.json rename to plugins/nebula-logger/sfdx-project.json From ed50afd5a704add66c44276f1dc784cde92b9eda Mon Sep 17 00:00:00 2001 From: Jason Siders <41714875+jasonsiders@users.noreply.github.com> Date: Fri, 8 Aug 2025 16:05:48 -0500 Subject: [PATCH 27/38] restructuring project folders --- plugins/nebula-logger/sfdx-project.json | 30 ------------------------- sfdx-project.json | 17 ++++++++++---- 2 files changed, 13 insertions(+), 34 deletions(-) delete mode 100644 plugins/nebula-logger/sfdx-project.json diff --git a/plugins/nebula-logger/sfdx-project.json b/plugins/nebula-logger/sfdx-project.json deleted file mode 100644 index af373159..00000000 --- a/plugins/nebula-logger/sfdx-project.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "packageDirectories": [ - { - "path": "plugins/nebula/logger/source", - "default": true, - "package": "apex-database-layer-plugin-nebula-logger-unlocked", - "versionName": "v1.0.0", - "versionNumber": "1.0.0.NEXT", - "versionDescription": "Use Nebula Logger to automatically log details of DML & SOQL operations", - "dependencies": [ - { - "package": "apex-database-layer-unlocked", - "versionNumber": "3.0.0.LATEST" - }, - { - "package": "nebula-logger-unlocked@4.16.1" - } - ] - } - ], - "name": "Apex Database Layer: Nebula Logger Plugin (Unlocked)", - "namespace": "", - "sfdcLoginUrl": "https://login.salesforce.com", - "sourceApiVersion": "64.0", - "packageAliases": { - "apex-database-layer-unlocked": "0HoDn000000kAONKA2", - "nebula-logger-unlocked@4.16.1": "04tKe0000011MXEIA2", - "apex-database-layer-plugin-nebula-logger-unlocked": "0HoDn0000010wCxKAI" - } -} diff --git a/sfdx-project.json b/sfdx-project.json index 2505d902..6b421f17 100644 --- a/sfdx-project.json +++ b/sfdx-project.json @@ -12,8 +12,17 @@ "versionNumber": "1.0.0.NEXT", "path": "plugins/nebula-logger/source", "default": false, - "package": "Apex Database Layer: Nebula Logger Plugin", - "versionDescription": "Use Nebula Logger to automatically log details of DML & SOQL operations" + "package": "apex-database-layer-plugin-nebula-logger", + "versionDescription": "Use Nebula Logger to automatically log details of DML & SOQL operations", + "dependencies": [ + { + "package": "apex-database-layer-unlocked", + "versionNumber": "3.0.0.LATEST" + }, + { + "package": "nebula-logger-unlocked@4.16.1" + } + ] } ], "name": "apex-database-layer", @@ -23,7 +32,7 @@ "packageAliases": { "apex-database-layer-managed": "0HoDn0000010wCnKAI", "apex-database-layer-unlocked": "0HoDn000000kAONKA2", - "apex-database-layer-nebula-logger-plugin-managed": "0HoDn0000010wD2KAI", - "apex-database-layer-nebula-logger-plugin-unlocked": "0HoDn0000010wCxKAI" + "apex-database-layer-plugin-nebula-logger": "0HoDn0000010wCxKAI", + "nebula-logger-unlocked@4.16.1": "04tKe0000011MXEIA2" } } From 3256785497abcea98c600eac540afd81107c7b35 Mon Sep 17 00:00:00 2001 From: Jason Siders <41714875+jasonsiders@users.noreply.github.com> Date: Fri, 8 Aug 2025 16:39:58 -0500 Subject: [PATCH 28/38] adding missing elipses --- .../source/classes/DatabaseLayerNebulaLoggerAdapter.cls | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapter.cls b/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapter.cls index 89f7522f..7cda3806 100644 --- a/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapter.cls +++ b/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapter.cls @@ -37,7 +37,7 @@ global class DatabaseLayerNebulaLoggerAdapter implements Dml.PreAndPostProcessor global void processPreDml(Dml.Request request) { String op = this.getDmlOperationName(request?.operation); this.log(System.LoggingLevel.FINEST) - ?.addLine('⏳ DML: Processing {0}', op) + ?.addLine('⏳ DML: Processing {0}...', op) ?.addLine('Request:\n{0}', JSON.serialize(request)) ?.setRecords(request?.records) ?.build(); From 9d6955ad97198081f76ac1e099a94c83f98e7d41 Mon Sep 17 00:00:00 2001 From: Jason Siders <41714875+jasonsiders@users.noreply.github.com> Date: Fri, 8 Aug 2025 22:35:30 -0500 Subject: [PATCH 29/38] simplified addLine behavior --- .../DatabaseLayerNebulaLoggerAdapter.cls | 44 +++++-------------- 1 file changed, 11 insertions(+), 33 deletions(-) diff --git a/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapter.cls b/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapter.cls index 7cda3806..8ed99360 100644 --- a/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapter.cls +++ b/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapter.cls @@ -37,8 +37,8 @@ global class DatabaseLayerNebulaLoggerAdapter implements Dml.PreAndPostProcessor global void processPreDml(Dml.Request request) { String op = this.getDmlOperationName(request?.operation); this.log(System.LoggingLevel.FINEST) - ?.addLine('⏳ DML: Processing {0}...', op) - ?.addLine('Request:\n{0}', JSON.serialize(request)) + ?.addLine('⏳ DML: Processing ' + op + '...') + ?.addLine('Request:\n' + JSON.serialize(request)) ?.setRecords(request?.records) ?.build(); } @@ -51,8 +51,8 @@ global class DatabaseLayerNebulaLoggerAdapter implements Dml.PreAndPostProcessor global void processPostDml(Dml.Request request, List results) { String op = this.getDmlOperationName(request?.operation); this.log(System.LoggingLevel.FINEST) - ?.addLine('✅ DML: Processed {0}', op) - ?.addLine('Request: {0}', JSON.serialize(request)) + ?.addLine('✅ DML: Processed ' + op) + ?.addLine('Request: ' + JSON.serialize(request)) ?.setDatabaseResults(results) ?.setRecords(request?.records) ?.build(); @@ -66,9 +66,9 @@ global class DatabaseLayerNebulaLoggerAdapter implements Dml.PreAndPostProcessor global void processDmlError(Dml.Request request, Exception error) { String op = this.getDmlOperationName(request?.operation); this.log(System.LoggingLevel.ERROR) - ?.addLine('🚨 DML: Error processing {0}', op) - ?.addLine('{0}\n{1}', error, error?.getStackTraceString()) - ?.addLine('Request:\n{0}', JSON.serialize(request)) + ?.addLine('🚨 DML: Error processing ' + op) + ?.addLine(error?.toString()) + ?.addLine('Request:\n' + JSON.serialize(request)) ?.setRecords(request?.records) ?.build(); } @@ -79,7 +79,7 @@ global class DatabaseLayerNebulaLoggerAdapter implements Dml.PreAndPostProcessor */ global void processPreSoql(Soql.Request request) { this.log(System.LoggingLevel.FINEST) - ?.addLine('⏳ SOQL: Processing {0}...', request?.operation) + ?.addLine('⏳ SOQL: Processing ' + request?.operation + '...') ?.addLine(request?.queryString) ?.build(); } @@ -91,7 +91,7 @@ global class DatabaseLayerNebulaLoggerAdapter implements Dml.PreAndPostProcessor */ global void processPostSoql(Soql.Request request, Object results) { this.log(System.LoggingLevel.FINEST) - ?.addLine('✅ SOQL: Processed {0}', request?.operation) + ?.addLine('✅ SOQL: Processed ' + request?.operation) ?.addLine(request?.queryString) ?.setRecords(results) ?.build(); @@ -104,9 +104,9 @@ global class DatabaseLayerNebulaLoggerAdapter implements Dml.PreAndPostProcessor */ global void processSoqlError(Soql.Request request, Exception error) { this.log(System.LoggingLevel.ERROR) - ?.addLine('🚨 SOQL: Error processing {0}', request?.operation) + ?.addLine('🚨 SOQL: Error processing ' + request?.operation) ?.addLine(request?.queryString) - ?.addLine('{0}\n{1}', error, error?.getStackTraceString()) + ?.addLine(error?.toString()) ?.build(); } @@ -143,28 +143,6 @@ global class DatabaseLayerNebulaLoggerAdapter implements Dml.PreAndPostProcessor return this; } - /** - * @description Adds a formatted line to the log message with two arguments - * @param tmp The string template with placeholders - * @param arg1 The first argument to substitute in the template - * @param arg2 The second argument to substitute in the template - * @return This LogBuilder instance for method chaining - */ - public LogBuilder addLine(String tmp, Object arg1, Object arg2) { - String line = String.format(tmp, new List{ arg1?.toString(), arg2?.toString() }); - return this.addLine(line); - } - - /** - * @description Adds a formatted line to the log message with one argument - * @param tmp The string template with placeholders - * @param arg1 The argument to substitute in the template - * @return This LogBuilder instance for method chaining - */ - public LogBuilder addLine(String tmp, Object arg1) { - return this.addLine(tmp, arg1, ''); - } - /** * @description Builds the final log entry with all accumulated lines * @return The LogEntryEventBuilder with the complete log message From b5800e65f6b4e1ae1a411a3820fc2f53b463d0f5 Mon Sep 17 00:00:00 2001 From: Jason Siders <41714875+jasonsiders@users.noreply.github.com> Date: Fri, 8 Aug 2025 22:54:22 -0500 Subject: [PATCH 30/38] adding readme --- plugins/nebula-logger/README.md | 96 ++++++++++++++++++++++++++++++++- 1 file changed, 94 insertions(+), 2 deletions(-) diff --git a/plugins/nebula-logger/README.md b/plugins/nebula-logger/README.md index ef491b6a..143b89ef 100644 --- a/plugins/nebula-logger/README.md +++ b/plugins/nebula-logger/README.md @@ -1,3 +1,95 @@ -# Plugin: Nebula Logger for DML & SOQL +This plugin leverages the [Plugin Framework](https://github.com/jasonsiders/apex-database-layer/wiki/The-Plugin-Framework) to automatically logs details about your DML and SOQL operations, via _Nebula Logger_. -TODO! \ No newline at end of file +[Nebula Logger](https://github.com/jongpie/NebulaLogger/tree/main) is a popular logging framework for Salesforce. Like Apex Database Layer, it's free, and open-source. + +## Getting Started + +### Prerequisites +To use this plugin, you must have the most recent version of [Apex Database Layer](https://github.com/jasonsiders/apex-database-layer) and [Nebula Logger](https://github.com/jongpie/NebulaLogger/tree/main) installed. + +### Installation + +If you're using both the latest & unmanaged versions of _Apex Database Layer_ and _Nebula Logger_ installed, you may install this plugin as an unlocked package. + +First, locate the latest version of the plugin package, called `nebula-logger-plugin@latest` in [`sfdx-project.json`](https://github.com/jasonsiders/apex-database-layer/blob/main/sfdx-project.json): + +```sh +sf package install --package <> --wait 10 +``` + +> :warning: **Note:** If you are using a managed version of _Apex Database Layer_ and/or _Nebula Logger_, you won't be able to formally install the package. Instead, manually copy the contents of these two Apex Classes in your desired environment: +> +>- [`DatabaseLayerNebulaLoggerAdapter.cls`](https://github.com/jasonsiders/apex-database-layer/blob/main/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapter.cls) +>- [`DatabaseLayerNebulaLoggerAdapterTest.cls`](https://github.com/jasonsiders/apex-database-layer/blob/main/plugins/nebula-logger/source/classes/DatabaseLayerNebulaLoggerAdapterTest.cls)) + +### Setup + +Once installed, navigate to `Setup > Custom Metadata > Database Layer Settings`. If a record already exists, use that record. Else, create a new record, called "Default". + +Set the Custom Metadata record's _DML: Pre & Post Processor_ and _SOQL: Pre & Post Processor_ fields to be the name of the Apex class: `DatabaseLayerNebulaLoggerAdapter`: + +image + +**Note:** Once configured, this custom metadata record won't be altered by upgrading the _Apex Database Layer_ package, or the plugin package itself. + +--- + +## Usage + +Whenever a DML or SOQL operation runs, the plugin will log the details of those operations to Nebula Logger. This results in log entries with the `apex-database-layer` _Log Entry Tag_. + +### DML Logging +Just before a DML operation is processed, the plugin will issue a `FINEST` log entry summarizing the action that's about to take place. +- The [`Dml.Request`](https://github.com/jasonsiders/apex-database-layer/wiki/The-Dml.Request-Class) is serialized and shown in the message body +- The records being operated on are shown in the `Related Records` tab + +image +image + +After a DML operation is processed, the plugin issues another `FINEST` log entry summarizing the action that took place. + +- The [`Dml.Request`](https://github.com/jasonsiders/apex-database-layer/wiki/The-Dml.Request-Class) is serialized and shown in the message body +- The records that were operated on are shown in the `Related Records` tab +- The relevant database result objects (ex., `Database.SaveResult`) are shown in the `Related Records` tab + +image +image + +If an exception is thrown during a DML operation, an `ERROR` log entry is issued: + +- The `Exception` message is shown in the message body +- The [`Dml.Request`](https://github.com/jasonsiders/apex-database-layer/wiki/The-Dml.Request-Class) is serialized and shown in the message body +- The records that were operated on are shown in the `Related Records` tab + +image + +### SOQL Logging + +Just before a SOQL operation is processed, the plugin issues a `FINEST` log entry summarizing the query about to take place: + +- The text of the query is available in the message body + +image + +After a SOQL operation is processed, the plugin issues another `FINEST` log entry summarizing the query and its results: + +- The text of the query is available in the message body +- The resulting SObject records are available in the `Related Records` tab + - Note: Other query operations (ex., `getCursor`, `countQuery`) that do _not_ output SObjects will be printed in the message body instead + +image +image + +If an exception is thrown during a SOQL operation, an `ERROR` log entry is issued: + +- The text of the query is available in the message body +- The `Exception` message is shown in the message body + +image + +### Considerations + +#### `MockSoql`: Additional query logs for non-standard SOQL operations +Many `MockSoql` query operations use the `query` method as the basis for building mock results. This may result in additional logs being issued. + +For example, `MockSoql.getQueryLocator` calls `MockSoql.query` to generate the list of records to be returned, and then wraps the results in a `Soql.QueryLocator`. In this scenario, the plugin issues 4 `FINEST` logs: one before/after `MockSoql.getQueryLocator`, and one before/after `MockSoql.query`. \ No newline at end of file From 152b0e07adf6b91cbbdef135cf048cfb59d32ec3 Mon Sep 17 00:00:00 2001 From: Jason Siders <41714875+jasonsiders@users.noreply.github.com> Date: Fri, 8 Aug 2025 22:54:37 -0500 Subject: [PATCH 31/38] capitalizing SOQL plugin field to match --- .../fields/SoqlPreAndPostProcessor__c.field-meta.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/objects/DatabaseLayerSetting__mdt/fields/SoqlPreAndPostProcessor__c.field-meta.xml b/source/objects/DatabaseLayerSetting__mdt/fields/SoqlPreAndPostProcessor__c.field-meta.xml index c05a8fa6..6ae475d5 100644 --- a/source/objects/DatabaseLayerSetting__mdt/fields/SoqlPreAndPostProcessor__c.field-meta.xml +++ b/source/objects/DatabaseLayerSetting__mdt/fields/SoqlPreAndPostProcessor__c.field-meta.xml @@ -7,7 +7,7 @@ >The name of an Apex Class that runs special processing before & after SOQL is processed. You can use this for things like automatic logging. Must be the fully qualified API Name (including namespace, if applicable) of an Apex Class that implements the Soql.PreAndPostProcessor interface, and has a public 0-arg constructor. - + 255 false Text From 93e82d6dd277281ec1d0f9456d925eaa4dcc507d Mon Sep 17 00:00:00 2001 From: Jason Siders <41714875+jasonsiders@users.noreply.github.com> Date: Fri, 8 Aug 2025 22:56:05 -0500 Subject: [PATCH 32/38] bumping nebula dependency to 4.16.4 --- sfdx-project.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sfdx-project.json b/sfdx-project.json index 6b421f17..e5b055fc 100644 --- a/sfdx-project.json +++ b/sfdx-project.json @@ -33,6 +33,6 @@ "apex-database-layer-managed": "0HoDn0000010wCnKAI", "apex-database-layer-unlocked": "0HoDn000000kAONKA2", "apex-database-layer-plugin-nebula-logger": "0HoDn0000010wCxKAI", - "nebula-logger-unlocked@4.16.1": "04tKe0000011MXEIA2" + "nebula-logger-unlocked@4.16.4": "04tKe0000011MyWIAU" } } From e20ec88e7a79321d5d2266f47b9ffe8bd7b89633 Mon Sep 17 00:00:00 2001 From: Jason Siders <41714875+jasonsiders@users.noreply.github.com> Date: Fri, 8 Aug 2025 22:56:18 -0500 Subject: [PATCH 33/38] updated alias --- sfdx-project.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sfdx-project.json b/sfdx-project.json index e5b055fc..3cceadb3 100644 --- a/sfdx-project.json +++ b/sfdx-project.json @@ -20,7 +20,7 @@ "versionNumber": "3.0.0.LATEST" }, { - "package": "nebula-logger-unlocked@4.16.1" + "package": "nebula-logger-unlocked@4.16.4" } ] } From 30d7c7051723cce00a2b6d44532e024da92d0d40 Mon Sep 17 00:00:00 2001 From: Jason Siders <41714875+jasonsiders@users.noreply.github.com> Date: Fri, 8 Aug 2025 23:17:32 -0500 Subject: [PATCH 34/38] updating dependency --- sfdx-project.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sfdx-project.json b/sfdx-project.json index 43907543..d2b23f55 100644 --- a/sfdx-project.json +++ b/sfdx-project.json @@ -17,7 +17,7 @@ "dependencies": [ { "package": "apex-database-layer-unlocked", - "versionNumber": "3.0.0.LATEST" + "versionNumber": "3.2.0.LATEST" }, { "package": "nebula-logger-unlocked@4.16.4" From d532b1e7dfbf19a35fdaacff7fd23a2007b63b62 Mon Sep 17 00:00:00 2001 From: Jason Siders <41714875+jasonsiders@users.noreply.github.com> Date: Sat, 9 Aug 2025 06:49:07 -0500 Subject: [PATCH 35/38] packaged v1.0.0 of nebula logger package --- sfdx-project.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sfdx-project.json b/sfdx-project.json index d2b23f55..1d00fdce 100644 --- a/sfdx-project.json +++ b/sfdx-project.json @@ -33,6 +33,7 @@ "apex-database-layer-managed": "0HoDn0000010wCnKAI", "apex-database-layer-unlocked": "0HoDn000000kAONKA2", "apex-database-layer-plugin-nebula-logger": "0HoDn0000010wCxKAI", - "nebula-logger-unlocked@4.16.4": "04tKe0000011MyWIAU" + "nebula-logger-unlocked@4.16.4": "04tKe0000011MyWIAU", + "apex-database-layer-plugin-nebula-logger@1.0.0-1": "04tDn0000011OVJIA2" } } From 536057dcf5bbeda724dcd6b779fd70399ddb178a Mon Sep 17 00:00:00 2001 From: Jason Siders <41714875+jasonsiders@users.noreply.github.com> Date: Sat, 9 Aug 2025 07:00:19 -0500 Subject: [PATCH 36/38] enable claude PR action to run on changes outside of `source/` --- .github/workflows/ci.yml | 38 +++++++++++++++++++++++++++++++------- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8707293f..ee64d9be 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,8 +4,6 @@ on: pull_request: branches: - main - paths: - - "source/**" types: [opened, ready_for_review, reopened, synchronize] concurrency: @@ -61,7 +59,7 @@ jobs: scan: name: Run Static Analysis - if: ${{ '! github.event.pull_request.draft' }} + if: ${{ !github.event.pull_request.draft }} runs-on: ubuntu-latest timeout-minutes: 10 steps: @@ -69,13 +67,23 @@ jobs: uses: actions/checkout@v4 with: fetch-depth: 2 + + - name: Check for source changes + uses: dorny/paths-filter@v2 + id: changes + with: + filters: | + source: + - 'source/**' - name: Setup SF CLI + if: steps.changes.outputs.source == 'true' uses: ./.github/actions/setup-sf-cli with: install-plugins: "true" - name: Get Changed Files + if: steps.changes.outputs.source == 'true' run: | # Ensure this check only runs against changed files in the current PR; not the entire codebase set -euo pipefail @@ -85,6 +93,7 @@ jobs: ls "$CHANGES_DIR" - name: Scan + if: steps.changes.outputs.source == 'true' id: scan run: | # Perform the scanning logic, via a .py script that creates various artifacts: @@ -99,6 +108,7 @@ jobs: cat "$SCAN_OUTPUTS" >> $GITHUB_OUTPUT - name: Summarize + if: steps.changes.outputs.source == 'true' run: | # Add the .md Summary to a PR Review, and the Step Summary; SUMMARY=$(cat "$SCAN_SUMMARY") @@ -106,6 +116,7 @@ jobs: gh pr review "$PR_NUMBER" --body "$SUMMARY" --comment - name: Upload Artifacts + if: steps.changes.outputs.source == 'true' uses: actions/upload-artifact@v4 with: name: Raw Results @@ -113,7 +124,7 @@ jobs: ${{ env.SFCA_RESULTS }} - name: Report Violations - if: ${{ steps.scan.outputs.num-violations-above-threshold > 0 }} + if: ${{ steps.changes.outputs.source == 'true' && steps.scan.outputs.num-violations-above-threshold > 0 }} run: | # If any violations above the specified threshold, post the results to the PR and fail the check: NUM_VIOLATIONS=${{ steps.scan.outputs.num-violations-above-threshold}} @@ -123,17 +134,27 @@ jobs: run-unit-tests: name: Run Unit Tests - if: ${{ '! github.event.pull_request.draft' }} + if: ${{ !github.event.pull_request.draft }} runs-on: ubuntu-latest timeout-minutes: 30 steps: - name: Checkout uses: actions/checkout@v4 + + - name: Check for source changes + uses: dorny/paths-filter@v2 + id: changes + with: + filters: | + source: + - 'source/**' - name: Setup SF CLI + if: steps.changes.outputs.source == 'true' uses: ./.github/actions/setup-sf-cli - name: Authenticate Devhub + if: steps.changes.outputs.source == 'true' env: CLIENT_ID: ${{ secrets.SALESFORCE_CONSUMER_KEY }} JWT_KEY: ${{ secrets.SALESFORCE_JWT_KEY }} @@ -148,6 +169,7 @@ jobs: --username "$USERNAME" - name: Get Existing Scratch Org + if: steps.changes.outputs.source == 'true' id: get-existing continue-on-error: true env: @@ -175,7 +197,7 @@ jobs: fi - name: Create Scratch Org - if: ${{ steps.get-existing.outputs.success != 'true' }} + if: ${{ steps.changes.outputs.source == 'true' && steps.get-existing.outputs.success != 'true' }} run: | # Create a new scratch org, with a dynamic username ending in the repo's domain: set -euo pipefail @@ -190,6 +212,7 @@ jobs: --wait 10 - name: Run Tests + if: steps.changes.outputs.source == 'true' run: | sf project deploy start \ --coverage-formatters json-summary \ @@ -201,6 +224,7 @@ jobs: --wait 30 - name: Check Code Coverage + if: steps.changes.outputs.source == 'true' run: | # Parse the JSON coverage summary file to extract overall coverage percentage COVERAGE_PERCENT=$(cat coverage/coverage/coverage-summary.json | jq -r '.total.lines.pct') @@ -211,5 +235,5 @@ jobs: fi - name: Post Authenticate Devhub - if: ${{ always() }} + if: ${{ always() && steps.changes.outputs.source == 'true' }} run: rm -f server.key From 40e155e9fe0bff0db360816ffe9ac9f3ebc3086c Mon Sep 17 00:00:00 2001 From: Jason Siders <41714875+jasonsiders@users.noreply.github.com> Date: Sat, 9 Aug 2025 07:02:21 -0500 Subject: [PATCH 37/38] Revert "enable claude PR action to run on changes outside of `source/`" This reverts commit 536057dcf5bbeda724dcd6b779fd70399ddb178a. --- .github/workflows/ci.yml | 38 +++++++------------------------------- 1 file changed, 7 insertions(+), 31 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ee64d9be..8707293f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,6 +4,8 @@ on: pull_request: branches: - main + paths: + - "source/**" types: [opened, ready_for_review, reopened, synchronize] concurrency: @@ -59,7 +61,7 @@ jobs: scan: name: Run Static Analysis - if: ${{ !github.event.pull_request.draft }} + if: ${{ '! github.event.pull_request.draft' }} runs-on: ubuntu-latest timeout-minutes: 10 steps: @@ -67,23 +69,13 @@ jobs: uses: actions/checkout@v4 with: fetch-depth: 2 - - - name: Check for source changes - uses: dorny/paths-filter@v2 - id: changes - with: - filters: | - source: - - 'source/**' - name: Setup SF CLI - if: steps.changes.outputs.source == 'true' uses: ./.github/actions/setup-sf-cli with: install-plugins: "true" - name: Get Changed Files - if: steps.changes.outputs.source == 'true' run: | # Ensure this check only runs against changed files in the current PR; not the entire codebase set -euo pipefail @@ -93,7 +85,6 @@ jobs: ls "$CHANGES_DIR" - name: Scan - if: steps.changes.outputs.source == 'true' id: scan run: | # Perform the scanning logic, via a .py script that creates various artifacts: @@ -108,7 +99,6 @@ jobs: cat "$SCAN_OUTPUTS" >> $GITHUB_OUTPUT - name: Summarize - if: steps.changes.outputs.source == 'true' run: | # Add the .md Summary to a PR Review, and the Step Summary; SUMMARY=$(cat "$SCAN_SUMMARY") @@ -116,7 +106,6 @@ jobs: gh pr review "$PR_NUMBER" --body "$SUMMARY" --comment - name: Upload Artifacts - if: steps.changes.outputs.source == 'true' uses: actions/upload-artifact@v4 with: name: Raw Results @@ -124,7 +113,7 @@ jobs: ${{ env.SFCA_RESULTS }} - name: Report Violations - if: ${{ steps.changes.outputs.source == 'true' && steps.scan.outputs.num-violations-above-threshold > 0 }} + if: ${{ steps.scan.outputs.num-violations-above-threshold > 0 }} run: | # If any violations above the specified threshold, post the results to the PR and fail the check: NUM_VIOLATIONS=${{ steps.scan.outputs.num-violations-above-threshold}} @@ -134,27 +123,17 @@ jobs: run-unit-tests: name: Run Unit Tests - if: ${{ !github.event.pull_request.draft }} + if: ${{ '! github.event.pull_request.draft' }} runs-on: ubuntu-latest timeout-minutes: 30 steps: - name: Checkout uses: actions/checkout@v4 - - - name: Check for source changes - uses: dorny/paths-filter@v2 - id: changes - with: - filters: | - source: - - 'source/**' - name: Setup SF CLI - if: steps.changes.outputs.source == 'true' uses: ./.github/actions/setup-sf-cli - name: Authenticate Devhub - if: steps.changes.outputs.source == 'true' env: CLIENT_ID: ${{ secrets.SALESFORCE_CONSUMER_KEY }} JWT_KEY: ${{ secrets.SALESFORCE_JWT_KEY }} @@ -169,7 +148,6 @@ jobs: --username "$USERNAME" - name: Get Existing Scratch Org - if: steps.changes.outputs.source == 'true' id: get-existing continue-on-error: true env: @@ -197,7 +175,7 @@ jobs: fi - name: Create Scratch Org - if: ${{ steps.changes.outputs.source == 'true' && steps.get-existing.outputs.success != 'true' }} + if: ${{ steps.get-existing.outputs.success != 'true' }} run: | # Create a new scratch org, with a dynamic username ending in the repo's domain: set -euo pipefail @@ -212,7 +190,6 @@ jobs: --wait 10 - name: Run Tests - if: steps.changes.outputs.source == 'true' run: | sf project deploy start \ --coverage-formatters json-summary \ @@ -224,7 +201,6 @@ jobs: --wait 30 - name: Check Code Coverage - if: steps.changes.outputs.source == 'true' run: | # Parse the JSON coverage summary file to extract overall coverage percentage COVERAGE_PERCENT=$(cat coverage/coverage/coverage-summary.json | jq -r '.total.lines.pct') @@ -235,5 +211,5 @@ jobs: fi - name: Post Authenticate Devhub - if: ${{ always() && steps.changes.outputs.source == 'true' }} + if: ${{ always() }} run: rm -f server.key From 33e6dfcc4bd6af7b06945212f27dac6272a6dd59 Mon Sep 17 00:00:00 2001 From: Jason Siders <41714875+jasonsiders@users.noreply.github.com> Date: Sat, 9 Aug 2025 07:03:03 -0500 Subject: [PATCH 38/38] simpler approach --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8707293f..1660ea75 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,6 +6,7 @@ on: - main paths: - "source/**" + - "plugins/nebula-logger/source/**" types: [opened, ready_for_review, reopened, synchronize] concurrency: