diff --git a/docs/apex/Logger-Engine/Logger.md b/docs/apex/Logger-Engine/Logger.md index cc44af41b..bcab86970 100644 --- a/docs/apex/Logger-Engine/Logger.md +++ b/docs/apex/Logger-Engine/Logger.md @@ -36,7 +36,7 @@ StatusApiResponse **Description** -Instance of `Logger.StatusApiResponse`, a data transfer object (DTO) that maps to the JSON returned +An instance of `Logger.StatusApiResponse` (a DTO that maps to the JSON returned by the status API endpoint), #### `createSettings()` → `LoggerSettings__c` diff --git a/docs/apex/Test-Utilities/LoggerMockDataCreator.md b/docs/apex/Test-Utilities/LoggerMockDataCreator.md index a619572e0..825e2078c 100644 --- a/docs/apex/Test-Utilities/LoggerMockDataCreator.md +++ b/docs/apex/Test-Utilities/LoggerMockDataCreator.md @@ -604,6 +604,8 @@ A new copy of the original `SObject` record that has the specified read-only fie ###### `statusCode` → `Integer` +###### `statusMessage` → `String` + --- ##### Methods @@ -612,6 +614,8 @@ A new copy of the original `SObject` record that has the specified read-only fie ###### `setResponseBody(String responseBody)` → `MockHttpCallout` +###### `setStatus(String statusMessage)` → `MockHttpCallout` + ###### `setStatusCode(Integer statusCode)` → `MockHttpCallout` --- diff --git a/nebula-logger/core/main/log-management/classes/LoggerHomeHeaderController.cls b/nebula-logger/core/main/log-management/classes/LoggerHomeHeaderController.cls index 6a4480bdd..89fab6777 100644 --- a/nebula-logger/core/main/log-management/classes/LoggerHomeHeaderController.cls +++ b/nebula-logger/core/main/log-management/classes/LoggerHomeHeaderController.cls @@ -52,15 +52,17 @@ public without sharing class LoggerHomeHeaderController { private static void setStatusApiResponseDetails(Environment environment) { Logger.StatusApiResponse statusApiResponse = Logger.callStatusApi(); - if (statusApiResponse != null) { - environment.organizationInstanceLocation = statusApiResponse.location; - environment.organizationMaintenanceWindow = statusApiResponse.maintenanceWindow; - environment.organizationReleaseNumber = statusApiResponse.releaseNumber; - environment.organizationReleaseVersion = statusApiResponse.releaseVersion; + if (statusApiResponse == null) { + return; + } + + environment.organizationInstanceLocation = statusApiResponse.location; + environment.organizationMaintenanceWindow = statusApiResponse.maintenanceWindow; + environment.organizationReleaseNumber = statusApiResponse.releaseNumber; + environment.organizationReleaseVersion = statusApiResponse.releaseVersion; - if (statusApiResponse.Products != null && statusApiResponse.Products.isEmpty() == false) { - environment.organizationInstanceProducts = getInstanceProductNames(statusApiResponse); - } + if (statusApiResponse.Products != null && statusApiResponse.Products.isEmpty() == false) { + environment.organizationInstanceProducts = getInstanceProductNames(statusApiResponse); } } diff --git a/nebula-logger/core/main/logger-engine/classes/Logger.cls b/nebula-logger/core/main/logger-engine/classes/Logger.cls index 0b6565040..b25c108e4 100644 --- a/nebula-logger/core/main/logger-engine/classes/Logger.cls +++ b/nebula-logger/core/main/logger-engine/classes/Logger.cls @@ -3228,8 +3228,8 @@ global with sharing class Logger { * Calls Salesforce's API endpoint https://api.status.salesforce.com/v1/instances/ * to get more details about the current org, including the org's release number and release version. * Trust API docs available at https://api.status.salesforce.com/v1/docs/ - * @return Instance of `Logger.StatusApiResponse`, a data transfer object (DTO) that maps to the JSON returned - * by the status API endpoint. + * @return An instance of `Logger.StatusApiResponse` (a DTO that maps to the JSON returned by the status API endpoint), + * or `null` when the callout fails (due to the remote site setting being disabled, the endpoint being unable, etc.). */ public static StatusApiResponse callStatusApi() { if (LoggerParameter.CALL_STATUS_API == false) { @@ -3243,20 +3243,27 @@ global with sharing class Logger { request.setEndpoint(statusApiEndpoint); request.setMethod('GET'); - System.HttpResponse response = new System.Http().send(request); - if (response.getStatusCode() >= 400) { - String errorMessage = - 'Callout failed for ' + - statusApiEndpoint + - '\nReceived request status code ' + - response.getStatusCode() + - ', status message: ' + - response.getStatus(); - throw new System.CalloutException(errorMessage); - } + try { + System.HttpResponse response = new System.Http().send(request); + if (response.getStatusCode() >= 400) { + String errorMessage = + 'Callout failed for ' + + statusApiEndpoint + + '\nReceived request status code ' + + response.getStatusCode() + + ', status message: ' + + response.getStatus(); + throw new System.CalloutException(errorMessage); + } - StatusApiResponse statusApiResponse = (StatusApiResponse) JSON.deserialize(response.getBody(), StatusApiResponse.class); - return statusApiResponse; + StatusApiResponse statusApiResponse = (StatusApiResponse) JSON.deserialize(response.getBody(), StatusApiResponse.class); + return statusApiResponse; + } catch (Exception ex) { + if (LoggerParameter.ENABLE_SYSTEM_MESSAGES == true) { + finest('Callout to api.status.salesforce.com failed').setExceptionDetails(ex); + } + return null; + } } // Private logging level methods to keep global methods simpler/cleaner diff --git a/nebula-logger/core/tests/configuration/utilities/LoggerMockDataCreator.cls b/nebula-logger/core/tests/configuration/utilities/LoggerMockDataCreator.cls index dcf66e8c0..fd5b96bad 100644 --- a/nebula-logger/core/tests/configuration/utilities/LoggerMockDataCreator.cls +++ b/nebula-logger/core/tests/configuration/utilities/LoggerMockDataCreator.cls @@ -458,6 +458,7 @@ public class LoggerMockDataCreator { public System.HttpResponse response { get; private set; } public String responseBody { get; private set; } public Integer statusCode { get; private set; } + public String statusMessage { get; private set; } public MockHttpCallout() { } @@ -472,6 +473,11 @@ public class LoggerMockDataCreator { return this; } + public MockHttpCallout setStatus(String statusMessage) { + this.statusMessage = statusMessage; + return this; + } + public System.HttpResponse respond(System.HttpRequest request) { this.request = request; @@ -480,6 +486,7 @@ public class LoggerMockDataCreator { response.setBody(this.responseBody); } response.setStatusCode(this.statusCode); + response.setStatus(this.statusMessage); return response; } } diff --git a/nebula-logger/core/tests/logger-engine/classes/Logger_Tests.cls b/nebula-logger/core/tests/logger-engine/classes/Logger_Tests.cls index a5edc63cc..f946e6543 100644 --- a/nebula-logger/core/tests/logger-engine/classes/Logger_Tests.cls +++ b/nebula-logger/core/tests/logger-engine/classes/Logger_Tests.cls @@ -7814,6 +7814,41 @@ private class Logger_Tests { System.Assert.areEqual(JSON.serialize(mockApiResponse), JSON.serialize(returnedApiResponse)); } + @IsTest + static void it_should_gracefully_return_null_when_callout_fails_to_status_api() { + LoggerDataStore.setMock(LoggerMockDataStore.getEventBus()); + LoggerParameter.setMock(new LoggerParameter__mdt(DeveloperName = 'CallStatusApi', Value__c = System.JSON.serialize(true))); + LoggerParameter.setMock(new LoggerParameter__mdt(DeveloperName = 'EnableLoggerSystemMessages', Value__c = System.JSON.serialize(true))); + System.Assert.isTrue(LoggerParameter.CALL_STATUS_API); + String mockErrorMessage = 'It broke! 😥'; + Integer errorStatusCode = 404; + String errorStatusMessage = 'Oops, did not understand request, or something... IDK.... it\'s broken ¯\\_(ツ)_/¯'; + System.Test.setMock( + System.HttpCalloutMock.class, + LoggerMockDataCreator.createHttpCallout().setStatusCode(errorStatusCode).setResponseBody(mockErrorMessage) + ); + System.Assert.areEqual(0, LoggerMockDataStore.getEventBus().getPublishedPlatformEvents().size()); + + Logger.StatusApiResponse returnedApiResponse = Logger.callStatusApi(); + + System.Assert.isNull(returnedApiResponse); + Logger.saveLog(); + System.Assert.areEqual(2, LoggerMockDataStore.getEventBus().getPublishedPlatformEvents().size()); + LogEntryEvent__e logEntryEvent = (LogEntryEvent__e) LoggerMockDataStore.getEventBus().getPublishedPlatformEvents().get(0); + System.Assert.areEqual('Callout to api.status.salesforce.com failed', logEntryEvent.Message__c); + System.Assert.areEqual('System.CalloutException', logEntryEvent.ExceptionType__c); + Organization organization = LoggerEngineDataSelector.getInstance().getCachedOrganization(); + String expectedExceptionMessage = + 'Callout failed for https://api.status.salesforce.com/v1/instances/' + + organization.InstanceName + + '/status' + + '\nReceived request status code ' + + errorStatusCode + + ', status message: ' + + errorStatusMessage; + System.Assert.isTrue(logEntryEvent.ExceptionMessage__c.startsWith('Callout failed for https://api.status.salesforce.com/v1/instances/')); + } + @IsTest static void it_should_return_null_when_status_api_callout_is_disabled() { LoggerParameter.setMock(new LoggerParameter__mdt(DeveloperName = 'CallStatusApi', Value__c = System.JSON.serialize(false)));