diff --git a/nebula-logger/main/logger-engine/classes/Logger.cls b/nebula-logger/main/logger-engine/classes/Logger.cls index de4e71671..bf93b1b3e 100644 --- a/nebula-logger/main/logger-engine/classes/Logger.cls +++ b/nebula-logger/main/logger-engine/classes/Logger.cls @@ -12,7 +12,7 @@ global with sharing class Logger { private static final LoggingLevel DEFAULT_LOGGING_LEVEL = LoggingLevel.DEBUG; private static final List LOG_ENTRIES_BUFFER = new List(); - private static final String TRANSACTION_ID = System.Request.getCurrent().getRequestId(); + private static final String TRANSACTION_ID = setTransactionId(); private static final Quiddity TRANSACTION_QUIDDITY = System.Request.getCurrent().getQuiddity(); private static Integer currentTransactionEntryNumber = 1; @@ -2636,6 +2636,11 @@ global with sharing class Logger { return logEntryEventBuilder; } + private static String setTransactionId() { + String transactionId = System.Request.getCurrent().getRequestId(); + return String.isNotBlank(transactionId) ? transactionId : new Uuid().getValue(); + } + private static SaveMethod getDefaultSaveMethod() { SaveMethod defaultSaveMethod; @@ -2756,4 +2761,95 @@ global with sharing class Logger { this.records = records; } } + + /****************************************************************************************** + * This code is based on the Apex UUID project, released under the MIT License. * + * See LICENSE file or go to https://github.com/jongpie/ApexUuid for full license details. * + ******************************************************************************************/ + @testVisible + private without sharing class Uuid { + + private final String HEX_CHARACTERS = '0123456789abcdef'; + private final String HEX_PREFIX = '0x'; + private final List HEX_CHARACTER_LIST = HEX_CHARACTERS.split(''); + private final Integer UUID_V4_LENGTH = 36; + private final String UUID_V4_REGEX = '[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}'; + + + private final String value; + + public Uuid() { + this.value = this.generateValue(); + } + + public String getValue() { + return this.value; + } + + private String generateValue() { + String hexValue = EncodingUtil.convertToHex(Crypto.generateAesKey(128)); + + // Version Calculation: (i & 0x0f) | 0x40 + // Version Format: Always begins with 4 + String versionShiftedHexBits = this.getShiftedHexBits(hexValue.substring(14, 16), this.convertHexToInteger('0x0f'), this.convertHexToInteger('0x40')); + + // Variant Calculation: (i & 0x3f) | 0x80 + // Variant Format: Always begins with 8, 9, A or B + String variantShiftedHexBits = this.getShiftedHexBits(hexValue.substring(18, 20), this.convertHexToInteger('0x3f'), this.convertHexToInteger('0x80')); + + String uuidValue = hexValue.substring(0, 8) // time-low + + hexValue.substring(8, 12) // time-mid + + versionShiftedHexBits + hexValue.substring(14, 16) // time-high-and-version + + variantShiftedHexBits + hexValue.substring(18, 20) // clock-seq-and-reserved + clock-seq-low + + hexValue.substring(20); // node + + return formatValue(uuidValue); + } + + private String formatValue(String unformattedValue) { + final String invalidValueError = unformattedValue + ' is not a valid UUID value'; + + // Remove any non-alphanumeric characters + unformattedValue = unformattedValue.replaceAll('[^a-zA-Z0-9]', ''); + + // UUID Pattern: 8-4-4-4-12 + String formattedValue = unformattedValue.substring(0, 8) + + '-' + unformattedValue.substring(8, 12) + + '-' + unformattedValue.substring(12, 16) + + '-' + unformattedValue.substring(16, 20) + + '-' + unformattedValue.substring(20); + + return formattedValue.toLowerCase(); + } + + private String getShiftedHexBits(String hexSubstring, Integer lowerThreshold, Integer upperThreshold) { + Integer shiftedIntegerBits = (this.convertHexToInteger(hexSubstring) & lowerThreshold) | upperThreshold; + return this.convertIntegerToHex(shiftedIntegerBits); + } + + private Integer convertHexToInteger(String hexValue) { + hexValue = hexValue.toLowerCase(); + + if(hexValue.startsWith(HEX_PREFIX)) hexValue = hexValue.substringAfter(HEX_PREFIX); + + Integer integerValue = 0; + for(String hexCharacter : hexValue.split('')) { + Integer hexCharacterIndex = HEX_CHARACTERS.indexOf(hexCharacter); + + integerValue = HEX_CHARACTERS.length() * integerValue + hexCharacterIndex; + } + return integerValue; + } + + private String convertIntegerToHex(Integer integerValue) { + String hexValue = ''; + while(integerValue > 0) { + Integer hexCharacterIndex = Math.mod(integerValue, HEX_CHARACTERS.length()); + + hexValue = HEX_CHARACTER_LIST[hexCharacterIndex] + hexValue; + integerValue = integerValue / HEX_CHARACTERS.length(); + } + return hexValue; + } + } } diff --git a/nebula-logger/tests/logger-engine/classes/Logger_Tests.cls b/nebula-logger/tests/logger-engine/classes/Logger_Tests.cls index 600ed335d..2fbf1d6f9 100644 --- a/nebula-logger/tests/logger-engine/classes/Logger_Tests.cls +++ b/nebula-logger/tests/logger-engine/classes/Logger_Tests.cls @@ -229,6 +229,16 @@ private class Logger_Tests { System.assertEquals(System.Request.getCurrent().getRequestId(), transactionId); } + @isTest + static void it_should_generate_a_valid_uuid() { + Pattern pattern = Pattern.compile('[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}'); + + Logger.Uuid uuid = new Logger.Uuid(); + System.assertEquals(36, uuid.getValue().length()); + Matcher matcher = pattern.matcher(uuid.getValue()); + System.assert(matcher.matches(), 'Generated UUID=' + uuid.getValue()); + } + @isTest static void it_should_set_transaction_entry_number() { for (Integer i = 0; i < 10; i++) {