Skip to content

Commit

Permalink
Initial commit with fix for #117 by adding support for logging to big…
Browse files Browse the repository at this point in the history
… objects in addition to the existing custom objects
  • Loading branch information
jamessimone committed Apr 11, 2021
1 parent 0d5b6db commit 1b0ee74
Show file tree
Hide file tree
Showing 111 changed files with 1,787 additions and 30 deletions.
18 changes: 14 additions & 4 deletions nebula-logger/main/log-management/profiles/Admin.profile-meta.xml
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@
<apexClass>LogMassDeleteExtension</apexClass>
<enabled>true</enabled>
</classAccesses>
<classAccesses>
<apexClass>LogMassDeleteExtension_Tests</apexClass>
<enabled>true</enabled>
</classAccesses>
<classAccesses>
<apexClass>LogMessage</apexClass>
<enabled>true</enabled>
Expand All @@ -106,6 +110,11 @@
<enabled>true</enabled>
</classAccesses>
<custom>false</custom>
<fieldPermissions>
<editable>true</editable>
<field>LogArchive__b.LoggedBy__c</field>
<readable>true</readable>
</fieldPermissions>
<fieldPermissions>
<editable>false</editable>
<field>LogEntryEvent__e.ApiVersion__c</field>
Expand Down Expand Up @@ -1386,9 +1395,6 @@
<field>Log__c.WasLoggedByCurrentUser__c</field>
<readable>true</readable>
</fieldPermissions>
<layoutAssignments>
<layout>Account-Account Layout</layout>
</layoutAssignments>
<layoutAssignments>
<layout>LogEntry__c-Log Entry Layout</layout>
</layoutAssignments>
Expand All @@ -1404,7 +1410,7 @@
<allowEdit>true</allowEdit>
<allowRead>true</allowRead>
<modifyAllRecords>true</modifyAllRecords>
<object>Account</object>
<object>LogArchive__b</object>
<viewAllRecords>true</viewAllRecords>
</objectPermissions>
<objectPermissions>
Expand Down Expand Up @@ -1434,6 +1440,10 @@
<object>Log__c</object>
<viewAllRecords>true</viewAllRecords>
</objectPermissions>
<pageAccesses>
<apexPage>LogMassDelete</apexPage>
<enabled>true</enabled>
</pageAccesses>
<tabVisibilities>
<tab>LogEntry__c</tab>
<visibility>DefaultOn</visibility>
Expand Down
111 changes: 111 additions & 0 deletions nebula-logger/main/logger-engine/classes/LogEntryArchiveBuilder.cls
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
global with sharing class LogEntryArchiveBuilder extends LogEntryEventBuilder {
@testVisible private static LogEntryEvent__e mockEvent;

public LogEntryArchiveBuilder(LoggingLevel loggingLevel, Boolean shouldSave) {
super(loggingLevel, shouldSave);
}

public override LogEntryEvent__e getLogEntryEvent() {
return mockEvent != null ? mockEvent : super.getLogEntryEvent();
}

public LogArchive__b getLogEntryArchive() {
LogEntryEvent__e cachedEvent = this.getLogEntryEvent();
return new LogArchive__b(
ApiVersion__c = cachedEvent.ApiVersion__c,
DatabaseResultCollectionType__c = cachedEvent.DatabaseResultCollectionType__c,
DatabaseResultJson__c = cachedEvent.DatabaseResultJson__c,
DatabaseResultType__c = cachedEvent.DatabaseResultType__c,
ExceptionMessage__c = cachedEvent.ExceptionMessage__c,
ExceptionStackTrace__c = cachedEvent.ExceptionStackTrace__c,
ExceptionType__c = cachedEvent.ExceptionType__c,
LimitsAggregateQueriesMax__c = cachedEvent.LimitsAggregateQueriesMax__c,
LimitsAggregateQueriesUsed__c = cachedEvent.LimitsAggregateQueriesUsed__c,
LimitsAsyncCallsMax__c = cachedEvent.LimitsAsyncCallsMax__c,
LimitsAsyncCallsUsed__c = cachedEvent.LimitsAsyncCallsUsed__c,
LimitsCalloutsUsed__c = cachedEvent.LimitsCalloutsUsed__c,
LimitsCpuTimeMax__c = cachedEvent.LimitsCpuTimeMax__c,
LimitsCpuTimeUsed__c = cachedEvent.LimitsCpuTimeUsed__c,
LimitsDmlRowsMax__c = cachedEvent.LimitsDmlRowsMax__c,
LimitsDmlRowsUsed__c = cachedEvent.LimitsDmlRowsUsed__c,
LimitsDmlStatementsMax__c = cachedEvent.LimitsDmlStatementsMax__c,
LimitsDmlStatementsUsed__c = cachedEvent.LimitsDmlStatementsUsed__c,
LimitsEmailInvocationsMax__c = cachedEvent.LimitsEmailInvocationsMax__c,
LimitsEmailInvocationsUsed__c = cachedEvent.LimitsEmailInvocationsUsed__c,
LimitsFutureCallsMax__c = cachedEvent.LimitsFutureCallsMax__c,
LimitsFutureCallsUsed__c = cachedEvent.LimitsFutureCallsUsed__c,
LimitsHeapSizeMax__c = cachedEvent.LimitsHeapSizeMax__c,
LimitsHeapSizeUsed__c = cachedEvent.LimitsHeapSizeUsed__c,
LimitsMobilePushApexCallsMax__c = cachedEvent.LimitsMobilePushApexCallsMax__c,
LimitsMobilePushApexCallsUsed__c = cachedEvent.LimitsMobilePushApexCallsUsed__c,
LimitsQueueableJobsMax__c = cachedEvent.LimitsQueueableJobsMax__c,
LimitsQueueableJobsUsed__c = cachedEvent.LimitsQueueableJobsUsed__c,
LimitsSoqlQueriesMax__c = cachedEvent.LimitsSoqlQueriesMax__c,
LimitsSoqlQueriesUsed__c = cachedEvent.LimitsSoqlQueriesUsed__c,
LimitsSoqlQueryLocatorRowsMax__c = cachedEvent.LimitsSoqlQueryLocatorRowsMax__c,
LimitsSoqlQueryLocatorRowsUsed__c = cachedEvent.LimitsSoqlQueryLocatorRowsUsed__c,
LimitsSoqlQueryRowsMax__c = cachedEvent.LimitsSoqlQueryRowsMax__c,
LimitsSoqlQueryRowsUsed__c = cachedEvent.LimitsSoqlQueryRowsUsed__c,
LimitsSoslSearchesMax__c = cachedEvent.LimitsSoslSearchesMax__c,
LimitsSoslSearchesUsed__c = cachedEvent.LimitsSoslSearchesUsed__c,
Locale__c = cachedEvent.Locale__c,
LoggedBy__c = UserInfo.getUserId(),
LoggedByString__c = UserInfo.getUserId(),
LoggedByUsername__c = cachedEvent.LoggedByUsername__c,
LoggingLevel__c = cachedEvent.LoggingLevel__c,
LoggingLevelOrdinal__c = cachedEvent.LoggingLevelOrdinal__c,
LoginApplication__c = cachedEvent.LoginApplication__c,
LoginBrowser__c = cachedEvent.LoginBrowser__c,
LoginHistoryId__c = cachedEvent.LoginHistoryId__c,
LoginPlatform__c = cachedEvent.LoginPlatform__c,
LoginType__c = cachedEvent.LoginType__c,
LogoutUrl__c = cachedEvent.LogoutUrl__c,
Message__c = cachedEvent.Message__c,
NetworkId__c = cachedEvent.NetworkId__c,
NetworkLoginUrl__c = cachedEvent.NetworkLoginUrl__c,
NetworkLogoutUrl__c = cachedEvent.NetworkLogoutUrl__c,
NetworkSelfRegistrationUrl__c = cachedEvent.NetworkSelfRegistrationUrl__c,
NetworkUrlPathPrefix__c = cachedEvent.NetworkUrlPathPrefix__c,
OrganizationDomainUrl__c = cachedEvent.OrganizationDomainUrl__c,
OrganizationEnvironmentType__c = cachedEvent.OrganizationEnvironmentType__c,
OrganizationId__c = cachedEvent.OrganizationId__c,
OrganizationInstanceName__c = cachedEvent.OrganizationInstanceName__c,
OrganizationName__c = cachedEvent.OrganizationName__c,
OrganizationNamespacePrefix__c = cachedEvent.OrganizationNamespacePrefix__c,
OrganizationType__c = cachedEvent.OrganizationType__c,
OriginLocation__c = cachedEvent.OriginLocation__c,
OriginType__c = cachedEvent.OriginType__c,
ParentLogTransactionId__c = cachedEvent.ParentLogTransactionId__c,
ProfileId__c = cachedEvent.ProfileId__c,
ProfileName__c = cachedEvent.ProfileName__c,
RecordCollectionType__c = cachedEvent.RecordCollectionType__c,
RecordId__c = cachedEvent.RecordId__c,
RecordJson__c = cachedEvent.RecordJson__c,
RecordSObjectClassification__c = cachedEvent.RecordSObjectClassification__c,
RecordSObjectType__c = cachedEvent.RecordSObjectType__c,
RecordSObjectTypeNamespace__c = cachedEvent.RecordSObjectTypeNamespace__c,
SessionId__c = cachedEvent.SessionId__c,
SessionSecurityLevel__c = cachedEvent.SessionSecurityLevel__c,
SessionType__c = cachedEvent.SessionType__c,
SourceIp__c = cachedEvent.SourceIp__c,
StackTrace__c = cachedEvent.StackTrace__c,
SystemMode__c = cachedEvent.SystemMode__c,
ThemeDisplayed__c = cachedEvent.ThemeDisplayed__c,
Timestamp__c = cachedEvent.Timestamp__c,
TimestampString__c = cachedEvent.TimestampString__c,
TimeZoneId__c = cachedEvent.TimeZoneId__c,
Topics__c = cachedEvent.Topics__c,
TransactionEntryNumber__c = cachedEvent.TransactionEntryNumber__c,
TransactionId__c = cachedEvent.TransactionId__c,
TriggerOperationType__c = cachedEvent.TriggerOperationType__c,
TriggerSObjectType__c = cachedEvent.TriggerSObjectType__c,
UserLicenseDefinitionKey__c = cachedEvent.UserLicenseDefinitionKey__c,
UserLicenseName__c = cachedEvent.UserLicenseName__c,
UserLoggingLevel__c = cachedEvent.UserLoggingLevel__c,
UserLoggingLevelOrdinal__c = cachedEvent.UserLoggingLevelOrdinal__c,
UserRoleId__c = cachedEvent.UserRoleId__c,
UserRoleName__c = cachedEvent.UserRoleName__c,
UserType__c = cachedEvent.UserType__c
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>51.0</apiVersion>
<status>Active</status>
</ApexClass>
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
* @description Builder class that generates each `LogEntryEvent__e` record
* @see Logger
*/
global with sharing class LogEntryEventBuilder {
global with sharing virtual class LogEntryEventBuilder {
private static final String API_VERSION = getApiVersion();
private static final AuthSession AUTH_SESSION = getAuthSession();
private static final List<String> IGNORED_CLASSES = getIgnoredClasses();
Expand Down Expand Up @@ -459,7 +459,7 @@ global with sharing class LogEntryEventBuilder {
* @description Returns the `LogEntryEvent__e` record for this instance of LogEntryEventBuilder
* @return The `LogEntryEvent__e` record
*/
public LogEntryEvent__e getLogEntryEvent() {
public virtual LogEntryEvent__e getLogEntryEvent() {
if (this.shouldSave == false) {
return null;
}
Expand Down
72 changes: 48 additions & 24 deletions nebula-logger/main/logger-engine/classes/Logger.cls
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@
* @see LogMessage
*/
global with sharing class Logger {
@testVisible
private static final List<LogArchive__b> LOG_ARCHIVES_BUFFER = new List<LogArchive__b>();
private static final LoggingLevel DEFAULT_LOGGING_LEVEL = LoggingLevel.DEBUG;
private static final List<LogEntryEventBuilder> LOG_ENTRIES_BUFFER = new List<LogEntryEventBuilder>();
private static final Boolean SYSTEM_MESSAGES_ENABLED = LoggerSettings__c.getInstance().EnableSystemMessages__c;
private static final String TRANSACTION_ID = System.Request.getCurrent().getRequestId();
private static final Quiddity TRANSACTION_QUIDDITY = System.Request.getCurrent().getQuiddity();
private static final Boolean USER_CAN_CREATE_LOG_ENTRY_EVENTS = SObjectType.LogEntryEvent__e.isCreateable();
private static final Boolean USER_CAN_CREATE_LOG_ENTRY_EVENTS = SObjectType.LogEntryEvent__e.isCreateable() || SObjectType.LogArchive__b.isCreateable();
private static final Boolean USER_HAS_LOGGING_ENABLED = LoggerSettings__c.getInstance().IsEnabled__c;
private static final LoggingLevel USER_LOGGING_LEVEL = getLoggingLevel(LoggerSettings__c.getInstance().LoggingLevel__c);

Expand Down Expand Up @@ -199,14 +201,15 @@ global with sharing class Logger {
* @return Integer
*/
global static Integer getBufferSize() {
return LOG_ENTRIES_BUFFER.size();
return LOG_ENTRIES_BUFFER.size() + LOG_ARCHIVES_BUFFER.size();
}

/**
* @description Discards any entries that have been generated but not yet saved
*/
global static void flushBuffer() {
LOG_ENTRIES_BUFFER.clear();
LOG_ARCHIVES_BUFFER.clear();
}

// ERROR logging level methods
Expand Down Expand Up @@ -4734,7 +4737,7 @@ global with sharing class Logger {
return;
}

if (LOG_ENTRIES_BUFFER.isEmpty()) {
if (getBufferSize() == 0) {
return;
}

Expand All @@ -4750,34 +4753,36 @@ global with sharing class Logger {
finest(getSavingLogSystemMessage(saveMethod));
}

// TODO - Jonathan, is this extra list even necessary? We've already checked "shouldSave()"
// on the builder prior to adding to the buffer
List<LogEntryEvent__e> logEntryEvents = new List<LogEntryEvent__e>();
for (LogEntryEventBuilder logEntryEventBuilder : LOG_ENTRIES_BUFFER) {
if (logEntryEventBuilder.shouldSave()) {
LogEntryEvent__e logEntryEvent = logEntryEventBuilder.getLogEntryEvent();
logEntryEvent.ParentLogTransactionId__c = getParentLogTransactionId();
logEntryEvent.SystemMode__c = getCurrentQuiddity().name();
logEntryEvent.TransactionId__c = getTransactionId();

logEntryEvents.add(logEntryEvent);
logEntryEvents.add(logEntryEventBuilder.getLogEntryEvent());
}
}

QueueableSaver saver = new QueueableSaver(logEntryEvents, LOG_ARCHIVES_BUFFER);
switch on saveMethod {
when EVENT_BUS {
// TODO add error handling for when event bus fails to publish
List<Database.SaveResult> results = EventBus.publish(logEntryEvents);
System.debug('saveResults =' + results);
saver.execute(null);
}
when QUEUEABLE {
System.enqueueJob(new QueueableSaver(logEntryEvents));
System.enqueueJob(saver);
}
when REST {
// If the user doesn't have a session ID (e.g., site guest user), the REST API call will fail
// To avoid that, use the EventBus instead (even though REST was specified)
if (String.isBlank(UserInfo.getUserId())) {
saveLog(Logger.SaveMethod.EVENT_BUS);
} else {
new RestApiSaver().insertRecords(logEntryEvents);
// it wasn't possible to initialize the list by passing in one of the other collections due to:
// Invalid initializer type List<LogArchive__b> found for List<SObject>
// or Invalid initializer type List<LogEntryEvent__e> found for List<SObject>
List<SObject> combinedEntriesToSave = new List<SObject>();
combinedEntriesToSave.addAll(LOG_ARCHIVES_BUFFER);
combinedEntriesToSave.addAll(logEntryEvents);
new RestApiSaver().insertRecords(combinedEntriesToSave);
}
}
}
Expand Down Expand Up @@ -4895,14 +4900,22 @@ global with sharing class Logger {

private static LogEntryEventBuilder newEntry(LoggingLevel loggingLevel, Boolean shouldSave) {
shouldSave = shouldSave && USER_CAN_CREATE_LOG_ENTRY_EVENTS;

LogEntryEventBuilder logEntryEventBuilder = new LogEntryEventBuilder(LoggingLevel, shouldSave);
if (logEntryEventBuilder.shouldSave() == true) {
logEntryEventBuilder.getLogEntryEvent().TransactionEntryNumber__c = currentTransactionEntryNumber++;
LOG_ENTRIES_BUFFER.add(logEntryEventBuilder);
LoggerSettings__c settings = LoggerSettings__c.getInstance();

LogEntryArchiveBuilder logEntryArchiveBuilder = new LogEntryArchiveBuilder(loggingLevel, shouldSave);
if (logEntryArchiveBuilder.shouldSave() && settings?.IsPlatformEventPublishingEnabled__c == true) {
LogEntryEvent__e logEntryEvent = logEntryArchiveBuilder.getLogEntryEvent();
logEntryEvent.TransactionEntryNumber__c = currentTransactionEntryNumber++;
logEntryEvent.ParentLogTransactionId__c = getParentLogTransactionId();
logEntryEvent.SystemMode__c = getCurrentQuiddity().name();
logEntryEvent.TransactionId__c = getTransactionId();
LOG_ENTRIES_BUFFER.add(logEntryArchiveBuilder);
}
if (logEntryArchiveBuilder.shouldSave() && settings?.IsBigObjectInsertingEnabled__c == true) {
LOG_ARCHIVES_BUFFER.add(logEntryArchiveBuilder.getLogEntryArchive());
}

return logEntryEventBuilder;
return logEntryArchiveBuilder;
}

private static SaveMethod getDefaultSaveMethod() {
Expand All @@ -4929,7 +4942,7 @@ global with sharing class Logger {
}

private static String getSavingLogSystemMessage(SaveMethod saveMethod) {
String savingLogMessage = 'Saving ' + LOG_ENTRIES_BUFFER.size() + ' log entries via ';
String savingLogMessage = 'Saving ' + getBufferSize() + ' log entries via ';
switch on getCurrentQuiddity() {
when ANONYMOUS {
savingLogMessage += 'Anonymous Apex';
Expand Down Expand Up @@ -4968,14 +4981,25 @@ global with sharing class Logger {
global class QueueableSaver implements Queueable {
private Id jobId;

private List<LogEntryEvent__e> logEntryEvents = new List<LogEntryEvent__e>();
private final List<LogEntryEvent__e> logEntryEvents;
private final List<LogArchive__b> logArchives;

private QueueableSaver(List<LogEntryEvent__e> logEntryEvents) {
private QueueableSaver(List<LogEntryEvent__e> logEntryEvents, List<LogArchive__b> logArchives) {
this.logEntryEvents = logEntryEvents;
this.logArchives = logArchives;
}

global void execute(System.QueueableContext queueableContext) {
EventBus.publish(this.logEntryEvents);
// TODO - handling (if any) of save result failures
List<Database.SaveResult> results = new List<Database.SaveResult>();
if (this.logEntryEvents.isEmpty() == false) {
results.addAll(EventBus.publish(this.logEntryEvents));
}
// read and weep: unit tests can't insert Big Objects
if (this.logArchives.isEmpty() == false && Test.isRunningTest() == false) {
results.addAll(Database.insertImmediate(this.logArchives));
}
System.debug('Logging save results =' + JSON.serializePretty(results));
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<CustomObject xmlns="http://soap.sforce.com/2006/04/metadata">
<deploymentStatus>InDevelopment</deploymentStatus>
<description>Big Object representation of Logger data. Can be used as a standalone archive, or in conjunction with platform events and the existing Log__c custom object.</description>
<label>Log Archive</label>
<pluralLabel>Log Archives</pluralLabel>
</CustomObject>
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
<fullName>ApiVersion__c</fullName>
<externalId>false</externalId>
<isFilteringDisabled>false</isFilteringDisabled>
<isNameField>false</isNameField>
<isSortingDisabled>false</isSortingDisabled>
<label>API Version</label>
<length>5</length>
<required>false</required>
<type>Text</type>
<unique>false</unique>
</CustomField>
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
<fullName>DatabaseResultCollectionType__c</fullName>
<externalId>false</externalId>
<isFilteringDisabled>false</isFilteringDisabled>
<isNameField>false</isNameField>
<isSortingDisabled>false</isSortingDisabled>
<label>Database Result Collection Type</label>
<length>255</length>
<required>false</required>
<type>Text</type>
<unique>false</unique>
</CustomField>
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
<fullName>DatabaseResultJson__c</fullName>
<externalId>false</externalId>
<isFilteringDisabled>false</isFilteringDisabled>
<isNameField>false</isNameField>
<isSortingDisabled>false</isSortingDisabled>
<label>Database Result JSON</label>
<length>131072</length>
<type>LongTextArea</type>
<visibleLines>8</visibleLines>
</CustomField>
Loading

0 comments on commit 1b0ee74

Please sign in to comment.