You can now do this by way of Platform Events. Unlike normal DML operations, Platform Events are not rolled back in a transaction, even if there are failures. To use this feature, there's a few steps involved.
First, create your Log object, if you haven't already. Next, create a Platform Event that has a field (or more, if you desire). After that, add a trigger to actually create the logs. Here's an example:
// Note: __e is not a typo. It is the special extension used by Platform Events.
trigger LogErrorEventTrigger on LogErrorEvent__e (after insert) {
Log__c[] logs = new Log__c[0];
for(LogErrorEvent__e record: Trigger.new) {
logs.add(new Log__c(Log__c=record.Log__c));
}
insert logs;
}
To actually use this, you need to "publish" these events:
trigger CustomObjectTrigger on CustomObject__c (after insert) {
LogErrorEvent__e[] logs = new LogErrorEvent__e[0];
for(CustomObject__c record: Trigger.new) {
logs.add(new LogErrorEvent__e(Log__c='Blocked insert'));
record.addError('Block insert');
}
EventBus.publish(logs);
}
This will prevent inserts, updates, deletes, undeletes, whichever you would like to handle, and log the appropriate message into your log queue.