Giter VIP home page Giter VIP logo

jongpie / nebulalogger Goto Github PK

View Code? Open in Web Editor NEW
616.0 27.0 144.0 91.44 MB

The most robust logger for Salesforce. Works with Apex, Lightning Components, Flow, Process Builder & Integrations. Designed for Salesforce admins, developers & architects.

License: MIT License

Apex 84.36% JavaScript 11.69% CSS 0.26% HTML 2.97% PowerShell 0.69% Shell 0.01% ObjectScript 0.02%
salesforce salesforce-developers salesforce-admins apex flow process-builder logging aura lightning-web-components lwc

nebulalogger's Introduction

Nebula Logger for Salesforce

Build codecov

The most robust logger for Salesforce. Works with Apex, Lightning Components, Flow, Process Builder & Integrations. Designed for Salesforce admins, developers & architects.

Unlocked Package - v4.13.11

Install Unlocked Package in a Sandbox Install Unlocked Package in Production View Documentation

sf package install --wait 20 --security-type AdminsOnly --package 04t5Y0000027L98QAE

sfdx force:package:install --wait 20 --securitytype AdminsOnly --package 04t5Y0000027L98QAE


Managed Package - v4.13.0

Install Managed Package in a Sandbox Install Managed Package in Production View Milestone

sf package install --wait 30 --security-type AdminsOnly --package 04t5Y000001Mk8YQAS

sfdx force:package:install --wait 30 --securitytype AdminsOnly --package 04t5Y000001Mk8YQAS


Features

  1. Easily add log entries via Apex, Lightning Components (lwc & aura), Flow & Process Builder to generate 1 consolidated, unified log
  2. Manage & report on logging data using the Log__c and LogEntry__c objects
  3. Leverage LogEntryEvent__e platform events for real-time monitoring & integrations
  4. Enable logging and set the logging level for different users & profiles using LoggerSettings__c custom hierarchy setting
    • In addition to the required fields on this Custom Setting record, LoggerSettings__c ships with SystemLogMessageFormat__c, which uses Handlebars-esque syntax to refer to fields on the LogEntryEvent__e Platform Event. You can use curly braces to denote merge field logic, eg: {OriginLocation__c}\n{Message__c} - this will output the contents of LogEntryEvent__e.OriginLocation__c, a line break, and then the contents of LogEntryEvent__e.Message__c
  5. Automatically mask sensitive data by configuring LogEntryDataMaskRule__mdt custom metadata rules
  6. View related log entries on any Lightning SObject flexipage by adding the 'Related Log Entries' component in App Builder
  7. Dynamically assign tags to Log__c and LogEntry__c records for tagging/labeling your logs
  8. Plugin framework: easily build or install plugins that enhance the Log__c and LogEntry__c objects, using Apex or Flow (not currently available in the managed package)
  9. Event-Driven Integrations with Platform Events, an event-driven messaging architecture. External integrations can subscribe to log events using the LogEntryEvent__e object - see more details at the Platform Events Developer Guide site

Learn more about the design and history of the project on Joys Of Apex blog post

Architecture Overview

Nebula Logger is built natively on Salesforce, using Apex, lightning components and various types of objects. There are no required external dependencies. To learn more about the architecture, check out the architecture overview in the wiki.

Installing

Nebula Logger is available as both an unlocked package and a managed package. The metadata is the same in both packages, but there are some differences in the available functionality & features. All examples in README are for the unlocked package (no namespace) - simply add the Nebula namespace to the examples if you are using the managed package.

Unlocked Package (Recommended) Managed Package
Namespace none Nebula
Future Releases Faster release cycle: new patch versions are released (e.g., v4.4.x) for new enhancements & bugfixes that are merged to the main branch in GitHub Slower release cycle: new minor versions are only released (e.g., v4.x) once new enhancements & bugfixes have been tested and code is stabilized
Public & Protected Apex Methods Any public and protected Apex methods are subject to change in the future - they can be used, but you may encounter deployment issues if future changes to public and protected methods are not backwards-compatible Only global methods are available in managed packages - any global Apex methods available in the managed package will be supported for the foreseeable future
Apex Debug Statements System.debug() is automatically called - the output can be configured with LoggerSettings__c.SystemLogMessageFormat__c to use any field on LogEntryEvent__e Requires adding your own calls for System.debug() due to Salesforce limitations with managed packages
Apex Stack Traces Automatically stored in LogEntry__c.StackTrace__c when calling methods like Logger.debug('my message'); Requires calling parseStackTrace() due to Salesforce limitations with managed packages. For example:
Logger.debug('my message').parseStackTrace(new DmlException().getStackTrace());
Logger Plugin Framework Leverage Apex or Flow to build your own "plugins" for Logger - easily add your own automation to the any of the included objects: LogEntryEvent__e, Log__c, LogEntry__c, LogEntryTag__c and LoggerTag__c. The logger system will then automatically run your plugins for each trigger event (BEFORE_INSERT, BEFORE_UPDATE, AFTER_INSERT, AFTER_UPDATE, and so on). This functionality is not currently available in the managed package

Getting Started

After installing Nebula Logger in your org, there are a few additional configuration changes needed...

  • Assign permission set(s) to users
    • LoggerLogCreator provides the minimum access needed for users to generate logs via Apex, Lightning Components, Flow or Process Builder
    • LoggerEndUser provides access to generate logs, as well as read-only access to any log records shared with the user.
    • LoggerLogViewer provides view-all access (read-only) to all log records. This does not provide access to generate logs.
    • LoggerAdmin provides view-all and modify-all access to all log records.
  • Customize the default settings in LoggerSettings__c
    • You can customize settings at the org, profile and user levels

Logger for Apex: Quick Start

For Apex developers, the Logger class has several methods that can be used to add entries with different logging levels. Each logging level's method has several overloads to support multiple parameters.

// This will generate a debug statement within developer console
System.debug('Debug statement using native Apex');

// This will create a new `Log__c` record with multiple related `LogEntry__c` records
Logger.error('Add log entry using Nebula Logger with logging level == ERROR');
Logger.warn('Add log entry using Nebula Logger with logging level == WARN');
Logger.info('Add log entry using Nebula Logger with logging level == INFO');
Logger.debug('Add log entry using Nebula Logger with logging level == DEBUG');
Logger.fine('Add log entry using Nebula Logger with logging level == FINE');
Logger.finer('Add log entry using Nebula Logger with logging level == FINER');
Logger.finest('Add log entry using Nebula Logger with logging level == FINEST');
Logger.saveLog();

This results in 1 Log__c record with several related LogEntry__c records.

Apex Log Results


Logger for Lightning Components: Quick Start

For lightning component developers, the logger lwc provides very similar functionality that is offered in Apex. Simply embed the logger lwc in your component, and call the desired logging methods within your code.

// For lwc, retrieve logger from your component's template
const logger = this.template.querySelector('c-logger');

logger.error('Hello, world!').addTag('some important tag');
logger.warn('Hello, world!');
logger.info('Hello, world!');
logger.debug('Hello, world!');
logger.fine('Hello, world!');
logger.finer('Hello, world!');
logger.finest('Hello, world!');
logger.saveLog();
// For aura, retrieve logger from your component's markup
const logger = component.find('logger');

logger.error('Hello, world!').addTag('some important tag');
logger.warn('Hello, world!');
logger.info('Hello, world!');
logger.debug('Hello, world!');
logger.fine('Hello, world!');
logger.finer('Hello, world!');
logger.finest('Hello, world!');
logger.saveLog();

Logger for Flow & Process Builder: Quick Start

Within Flow & Process Builder, you can select 1 of the several Logging actions

Flow Logger Actions

In this simple example, a Flow is configured after-insert and after-update to log a Case record (using the action 'Add Log Entry for an SObject Record')

Flow Builder: Log Case

This results in a Log__c record with related LogEntry__c records.

Flow Log Results


All Together: Apex, Lightning Components & Flow in One Log

After incorporating Logger into your Flows & Apex code (including controllers, trigger framework, etc.), you'll have a unified transaction log of all your declarative & custom code automations.

Case currentCase = [SELECT Id, CaseNumber, Type, Status, IsClosed FROM Case LIMIT 1];

Logger.info('First, log the case through Apex', currentCase);

Logger.debug('Now, we update the case in Apex to cause our record-triggered Flow to run');
update currentCase;

Logger.info('Last, save our log');
Logger.saveLog();

This generates 1 consolidated Log__c, containing LogEntry__c records from both Apex and Flow

Flow Log Results


Features for Apex Developers

Within Apex, there are several different methods that you can use that provide greater control over the logging system.

Transaction Controls

Apex developers can use additional Logger methods to dynamically control how logs are saved during the current transaction.

  • Logger.suspendSaving() – causes Logger to ignore any calls to saveLog() in the current transaction until resumeSaving() is called. Useful for reducing DML statements used by Logger
  • Logger.resumeSaving() – re-enables saving after suspendSaving() is used
  • Logger.flushBuffer() – discards any unsaved log entries
  • Logger.setSaveMethod(SaveMethod saveMethod) - sets the default save method used when calling saveLog(). Any subsequent calls to saveLog() in the current transaction will use the specified save method
  • Logger.saveLog(SaveMethod saveMethod) - saves any entries in Logger's buffer, using the specified save method for only this call. All subsequent calls to saveLog() will use the default save method.
  • Enum Logger.SaveMethod - this enum can be used for both Logger.setSaveMethod(saveMethod) and Logger.saveLog(saveMethod)
    • Logger.SaveMethod.EVENT_BUS - The default save method, this uses the EventBus class to publish LogEntryEvent__e records. The default save method can also be controlled declaratively by updating the field LoggerSettings__c.DefaultSaveMethod__c
    • Logger.SaveMethod.QUEUEABLE - This save method will trigger Logger to save any pending records asynchronously using a queueable job. This is useful when you need to defer some CPU usage and other limits consumed by Logger.
    • Logger.SaveMethod.REST - This save method will use the current user’s session ID to make a synchronous callout to the org’s REST API. This is useful when you have other callouts being made and you need to avoid mixed DML operations.
    • Logger.SaveMethod.SYNCHRONOUS_DML - This save method will skip publishing the LogEntryEvent__e platform events, and instead immediately creates Log__c and LogEntry__c records. This is useful when you are logging from within the context of another platform event and/or you do not anticipate any exceptions to occur in the current transaction. Note: when using this save method, any exceptions will prevent your log entries from being saved - Salesforce will rollback any DML statements, including your log entries! Use this save method cautiously.

Track Related Logs in Batchable and Queuable Jobs

In Salesforce, asynchronous jobs like batchable and queuable run in separate transactions - each with their own unique transaction ID. To relate these jobs back to the original log, Apex developers can use the method Logger.setParentLogTransactionId(String). Logger uses this value to relate child Log__c records, using the field Log__c.ParentLog__c.

This example batchable class shows how you can leverage this feature to relate all of your batch job’s logs together.

ℹ️ If you deploy this example class to your org,you can run it using Database.executeBatch(new BatchableLoggerExample());

public with sharing class BatchableLoggerExample implements Database.Batchable<SObject>, Database.Stateful {
    private String originalTransactionId;

    public Database.QueryLocator start(Database.BatchableContext batchableContext) {
        // Each batchable method runs in a separate transaction,
        // so store the first transaction ID to later relate the other transactions
        this.originalTransactionId = Logger.getTransactionId();

        Logger.info('Starting BatchableLoggerExample');
        Logger.saveLog();

        // Just as an example, query all accounts
        return Database.getQueryLocator([SELECT Id, Name, RecordTypeId FROM Account]);
    }

    public void execute(Database.BatchableContext batchableContext, List<Account> scope) {
        // One-time call (per transaction) to set the parent log
        Logger.setParentLogTransactionId(this.originalTransactionId);

        for (Account account : scope) {
            // Add your batch job's logic here

            // Then log the result
            Logger.info('Processed an account record', account);
        }

        Logger.saveLog();
    }

    public void finish(Database.BatchableContext batchableContext) {
        // The finish method runs in yet-another transaction, so set the parent log again
        Logger.setParentLogTransactionId(this.originalTransactionId);

        Logger.info('Finishing running BatchableLoggerExample');
        Logger.saveLog();
    }
}

Queueable jobs can also leverage the parent transaction ID to relate logs together. This example queueable job will run several chained instances. Each instance uses the parentLogTransactionId to relate its log back to the original instance's log.

ℹ️ If you deploy this example class to your org,you can run it using System.enqueueJob(new QueueableLoggerExample(3));

public with sharing class QueueableLoggerExample implements Queueable {
    private Integer numberOfJobsToChain;
    private String parentLogTransactionId;

    private List<LogEntryEvent__e> logEntryEvents = new List<LogEntryEvent__e>();

    // Main constructor - for demo purposes, it accepts an integer that controls how many times the job runs
    public QueueableLoggerExample(Integer numberOfJobsToChain) {
        this(numberOfJobsToChain, null);
    }

    // Second constructor, used to pass the original transaction's ID to each chained instance of the job
    // You don't have to use a constructor - a public method or property would work too.
    // There just needs to be a way to pass the value of parentLogTransactionId between instances
    public QueueableLoggerExample(Integer numberOfJobsToChain, String parentLogTransactionId) {
        this.numberOfJobsToChain = numberOfJobsToChain;
        this.parentLogTransactionId = parentLogTransactionId;
    }

    // Creates some log entries and starts a new instance of the job when applicable (based on numberOfJobsToChain)
    public void execute(System.QueueableContext queueableContext) {
        Logger.setParentLogTransactionId(this.parentLogTransactionId);

        Logger.fine('queueableContext==' + queueableContext);
        Logger.info('this.numberOfJobsToChain==' + this.numberOfJobsToChain);
        Logger.info('this.parentLogTransactionId==' + this.parentLogTransactionId);

        // Add your queueable job's logic here

        Logger.saveLog();

        --this.numberOfJobsToChain;
        if (this.numberOfJobsToChain > 0) {
            String parentLogTransactionId = this.parentLogTransactionId != null ? this.parentLogTransactionId : Logger.getTransactionId();
            System.enqueueJob(new QueueableLoggerExample(this.numberOfJobsToChain, parentLogTransactionId));
        }
    }
}

Overloads for Logging Methods

Each of the logging methods in Logger (such as Logger.error(), Logger.debug(), and so on) has several static overloads for various parameters. These are intended to provide simple method calls for common parameters, such as:

  • Log a message and a record - Logger.error(String message, SObject record)
  • Log a message and a record ID - Logger.error(String message, Id recordId)
  • Log a message and a save result - Logger.error(String message, Database.SaveResult saveResult)
  • ...

To see the full list of overloads, check out the Logger class documentation.

Using the Fluent Interface

Each of the logging methods in Logger returns an instance of the class LogEntryEventBuilder. This class provides several additional methods together to further customize each log entry - each of the builder methods can be chained together. In this example Apex, 3 log entries are created using different approaches for calling Logger - all 3 approaches result in identical log entries.

// Get the current user so we can log it (just as an example of logging an SObject)
User currentUser = [SELECT Id, Name, Username, Email FROM User WHERE Id = :UserInfo.getUserId()];

// Using static Logger method overloads
Logger.debug('my string', currentUser);

// Using the instance of LogEntryEventBuilder
LogEntryEventBuilder builder = Logger.debug('my string');
builder.setRecord(currentUser);

// Chaining builder methods together
Logger.debug('my string').setRecord(currentUser);

// Save all of the log entries
Logger.saveLog();

Using LogMessage for Dynamically-Generated Strings

The class LogMessage provides the ability to generate string messages on demand, using String.format(). This provides 2 benefits:

  1. Improved CPU usage by skipping unnecessary calls to String.format()

    // Without using LogMessage, String.format() is always called, even if the FINE logging level is not enabled for a user
    String formattedString = String.format('my example with input: {0}', List<Object>{'myString'});
    Logger.fine(formattedString);
    
    // With LogMessage, when the specified logging level (FINE) is disabled for the current user, `String.format()` is not called
    LogMessage logMessage = new LogMessage('my example with input: {0}', 'myString');
    Logger.fine(logMessage);
  2. Easily build complex strings

     // There are several constructors for LogMessage to support different numbers of parameters for the formatted string
     String unformattedMessage = 'my string with 3 inputs: {0} and then {1} and finally {2}';
     String formattedMessage = new LogMessage(unformattedMessage, 'something', 'something else', 'one more').getMessage();
     String expectedMessage = 'my string with 3 inputs: something and then something else and finally one more';
     System.assertEquals(expectedMessage, formattedMessage);

For more details, check out the LogMessage class documentation.


Features for Lightning Component Developers

For lightning component developers, the included logger lwc can be used in other lwc & aura components for frontend logging. Similar to Logger and LogEntryBuilder Apex classes, the lwc has both logger and logEntryBuilder classes. This provides a fluent API for javascript developers so they can chain the method calls.

Once you've incorporated logger into your lightning components, you can see your LogEntry__c records using the included list view "All Component Log Entries'.

Component Log Entries List View

Each LogEntry__c record automatically stores the component's type ('Aura' or 'LWC'), the component name, and the component function that called logger. This information is shown in the section "Lightning Component Information"

Component Log Entry Record

Example LWC Usage

To use the logger component, it has to be added to your lwc's markup:

<template>
    <c-logger></c-logger>

    <div>My component</div>
</template>

Once you've added logger to your markup, you can call it in your lwc's controller:

import { LightningElement } from 'lwc';

export default class LoggerDemo extends LightningElement {
    logSomeStuff() {
        const logger = this.template.querySelector('c-logger');

        logger.error('Hello, world!').addTag('some important tag');
        logger.warn('Hello, world!');
        logger.info('Hello, world!');
        logger.debug('Hello, world!');
        logger.fine('Hello, world!');
        logger.finer('Hello, world!');
        logger.finest('Hello, world!');

        logger.saveLog();
    }
}

Example Aura Usage

To use the logger component, it has to be added to your aura component's markup:

<aura:component implements="force:appHostable">
    <c:logger aura:id="logger" />

    <div>My component</div>
</aura:component>

Once you've added logger to your markup, you can call it in your aura component's controller:

({
    logSomeStuff: function (component, event, helper) {
        const logger = component.find('logger');

        logger.error('Hello, world!').addTag('some important tag');
        logger.warn('Hello, world!');
        logger.info('Hello, world!');
        logger.debug('Hello, world!');
        logger.fine('Hello, world!');
        logger.finer('Hello, world!');
        logger.finest('Hello, world!');

        logger.saveLog();
    }
});

Features for Flow Builders

Within Flow (and Process Builder), there are 4 invocable actions that you can use to leverage Nebula Logger

  1. 'Add Log Entry' - uses the class FlowLogEntry to add a log entry with a specified message
  2. 'Add Log Entry for an SObject Record' - uses the class FlowRecordLogEntry to add a log entry with a specified message for a particular SObject record
  3. 'Add Log Entry for an SObject Record Collection' - uses the class FlowCollectionLogEntry to add a log entry with a specified message for an SObject record collection
  4. 'Save Log' - uses the class Logger to save any pending logs

Flow Builder: Logging Invocable Actions


Tagging Your Log Entries

Nebula Logger supports dynamically tagging/labeling your LogEntry__c records via Apex, Flow, and custom metadata records in LogEntryTagRule__mdt. Tags can then be stored using one of the two supported modes (discussed below).

Adding Tags in Apex

Apex developers can use 2 new methods in LogEntryBuilder to add tags - LogEntryEventBuilder.addTag(String) and LogEntryEventBuilder.addTags(List<String>).

// Use addTag(String tagName) for adding 1 tag at a time
Logger.debug('my log message').addTag('some tag').addTag('another tag');

// Use addTags(List<String> tagNames) for adding a list of tags in 1 method call
List<String> myTags = new List<String>{'some tag', 'another tag'};
Logger.debug('my log message').addTags(myTags);

Adding Tags in Flow

Flow builders can use the Tags property to specify a comma-separated list of tags to apply to the log entry. This feature is available for all 3 Flow classes: FlowLogEntry, FlowRecordLogEntry and FlowCollectionLogEntry.

Flow Logging with Tags

Adding Tags with Custom Metadata Records

Admins can configure tagging rules to append additional tags using the custom metadata type LogEntryTagRule__mdt.

  • Rule-based tags are only added when LogEntry__c records are created (not on update).
  • Rule-based tags are added in addition to any tags that have been added via Apex and/or Flow.
  • Each rule is configured to apply tags based on the value of a single field on LogEntry__c (e.g., LogEntry__c.Message__c).
  • Each rule can only evaluate 1 field, but multiple rules can evaluate the same field.
  • A single rule can apply mulitple tags. When specifying multiple tags, put each tag on a separate line within the Tags field (LogEntryTagRule__mdt.Tags__c).

Rules can be set up by configuring a custom metadata record with these fields configured:

  1. Logger SObject: currently, only the "Log Entry" object (LogEntry__c) is supported.
  2. Field: the SObject's field that should be evaluated - for example, LogEntry__c.Message__c. Only 1 field can be selected per rule, but multiple rules can use the same field.
  3. Comparison Type: the type of operation you want to use to compare the field's value. Currently supported options are: CONTAINS, EQUALS, MATCHES_REGEX, and STARTS_WITH.
  4. Comparison Value: the comparison value that should be used for the selected field operation.
  5. Tags: a list of tag names that should be dynamically applied to any matching LogEntry__c records.
  6. Is Enabled: only enabled rules are used by Logger - this is a handy way to easily enable/disable a particular rule without having to entirely delete it.

Below is an example of what a rule looks like once configured. Based on this rule, any LogEntry__c records that contain "My Important Text" in the Message__c field will automatically have 2 tags added - "Really important tag" and "A tag with an emoji, whynot?! 🔥"

Tag Rule Example

Choosing a Tagging Mode

Once you've implementing log entry tagging within Apex or Flow, you can choose how the tags are stored within your org. Each mode has its own pros and cons - you can also build your own plugin if you want to leverage your own tagging system (note: plugins are not currently available in the managed package).

Tagging Mode Logger's Custom Tagging Objects (Default) Salesforce Topic and TopicAssignment Objects
Summary Stores your tags in custom objects LoggerTag__c and LogEntryTag__c Leverages Salesforce's Chatter Topics functionality to store your tags. This mode is not available in the managed package.
Data Model
  • LoggerTag__c: this represents the actual tag you want to apply to your log entry record. Tags are unique, based on the field LoggerTag__c.Name. The logging system will automatically create LoggerTag__c records if a matching record does not already exist in your org.
  • LogEntryTag__c: a junction object between LoggerTag__c and LogEntry__c
Data Visibility
  • Access to the LoggerTag__c object can be granted/restricted using standard Salesforce object and record-sharing functionality (OWD, sharing rules, profiles, permission sets, etc). By default, LoggerTag__c OWD is set to 'public read-only' for internal users and 'private' for external users
  • Since LogEntryTag__c is a junction object, access to these records is controlled by a user's access to the related LogEntry__c and LoggerTag__c records
  • In Chatter, all Topic records are visible - including any Topic records created via Logger. For some orgs that are ok with this visibility within Chatter, this is considered a great feature. But for some orgs, this visibility may not be ideal.
  • Although Topic records are visible to all Chatter users, TopicAssignment records are only visible to users that have access to the related EntityId (in this case, the LogEntry__c record)
Leveraging Data Since the data is stored in custom objects, you can leverage any platform functionality you want, such as building custom list views, reports & dashboards, enabling Chatter feeds, creating activities/tasks, and so on. Topics can be used to filter list views, which is a really useful feature. However, using Topics in reports and dashboards is only partially implemented at this time.

Log Management

Logger Console App

The Logger Console app provides access to the tabs for Logger's objects: Log__c, LogEntry__c, LogEntryTag__c and LoggerTag__c (for any users with the correct access).

Logger Console app

Log's 'Manage' Quick Action

To help development and support teams better manage logs (and any underlying code or config issues), some fields on Log__c are provided to track the owner, priority and status of a log. These fields are optional, but are helpful in critical environments (production, QA sandboxes, UAT sandboxes, etc.) for monitoring ongoing user activities.

  • All editable fields on Log__c can be updated via the 'Manage Log' quick action (shown below)

    Manage Log QuickAction

  • Additional fields are automatically set based on changes to Log__c.Status__c

    • Log__c.ClosedBy__c - The user who closed the log
    • Log__c.ClosedDate__c - The datetime that the log was closed
    • Log__c.IsClosed__c - Indicates if the log is closed, based on the selected status (and associated config in the 'Log Status' custom metadata type)
    • Log__c.IsResolved__c - Indicates if the log is resolved (meaning that it required analaysis/work, which has been completed). Only closed statuses can be considered resolved. This is also driven based on the selected status (and associated config in the 'Log Status' custom metadata type)
  • To customize the statuses provided, simply update the picklist values for Log__c.Status__c and create/update corresponding records in the custom metadata type LogStatus__mdt. This custom metadata type controls which statuses are considered closed and resolved.


Log's 'View JSON' Quick Action

Everyone loves JSON - so to make it easy to see a JSON version of a Log__c record, you can use the 'View JSON' quick action button. It displays the current Log__c + all related LogEntry__c records in JSON format, as well as a handy button to copy the JSON to your clipboard. All fields that the current user can view (based on field-level security) are dynamically returned, including any custom fields added directly in your org or by plugins.

View JSON Log QuickAction Button

View JSON Log QuickAction


Real-Time Monitoring with Log Entry Event Stream

Within Logger Console app, the Log Entry Event Stream tab provides real-time monitoring of LogEntryEvent__e platform events. Simply open the tab to start monitoring, and use the filters to further refine with LogEntryEvent__e records display in the stream.

Log Entry Event Stream


View Related Log Entries on a Record Page

Within App Builder, admins can add the 'Related Log Entries' lightning web component (lwc) to any record page. Admins can also control which columns are displayed be creating & selecting a field set on LogEntry__c with the desired fields.

  • The component automatically shows any related log entries, based on LogEntry__c.RecordId__c == :recordId
  • Users can search the list of log entries for a particular record using the component's built-insearch box. The component dynamically searches all related log entries using SOSL.
  • Component automatically enforces Salesforce's security model
    • Object-Level Security - Users without read access to LogEntry__c will not see the component
    • Record-Level Security - Users will only see records that have been shared with them
    • Field-Level Security - Users will only see the fields within the field set that they have access to

Related Log Entries


Deleting Old Logs

Admins can easily delete old logs using 2 methods: list views or Apex batch jobs

Mass Deleting with List Views

Salesforce (still) does not support mass deleting records out-of-the-box. There's been an Idea for 11+ years about it, but it's still not standard functionality. A custom button is available on Log__c list views to provide mass deletion functionality.

  1. Admins can select 1 or more Log__c records from the list view to choose which logs will be deleted

Mass Delete Selection

  1. The button shows a Visualforce page LogMassDelete to confirm that the user wants to delete the records

Mass Delete Confirmation

Batch Deleting with Apex Jobs

Two Apex classes are provided out-of-the-box to handle automatically deleting old logs

  1. LogBatchPurger - this batch Apex class will delete any Log__c records with Log__c.LogRetentionDate__c <= System.today().
    • By default, this field is populated with "TODAY + 14 DAYS" - the number of days to retain a log can be customized in LoggerSettings__c.
    • Admins can also manually edit this field to change the retention date - or set it to null to prevent the log from being automatically deleted
  2. LogBatchPurgeScheduler - this schedulable Apex class can be schedule to run LogBatchPurger on a daily or weekly basis

Beta Feature: Custom Plugin Framework for Log__c and LogEntry__c objects

If you want to add your own automation to the Log__c or LogEntry__c objects, you can leverage Apex or Flow to define "plugins" - the logger system will then automatically run the plugins after each trigger event (BEFORE_INSERT, BEFORE_UPDATE, AFTER_INSERT, AFTER_UPDATE, and so on). This framework makes it easy to build your own plugins, or deploy/install others' prebuilt packages, without having to modify the logging system directly.

  • Flow plugins: your Flow should be built as auto-launched Flows with these parameters:

    1. Input parameter triggerOperationType - The name of the current trigger operation (such as BEFORE_INSERT, BEFORE_UPDATE, etc.)
    2. Input parameter triggerNew - The list of logger records being processed (Log__c or LogEntry__c records)
    3. Output parameter updatedTriggerNew - If your Flow makes any updates to the collection of records, you should return a record collection containing the updated records
    4. Input parameter triggerOld - The list of logger records as they exist in the datatabase
  • Apex plugins: your Apex class should extend the abstract class LoggerSObjectHandlerPlugin. For example:

    public class ExamplePlugin extends LoggerSObjectHandlerPlugin {
        public override void execute(
            TriggerOperation triggerOperationType,
            List<SObject> triggerNew,
            Map<Id, SObject> triggerNewMap,
            List<SObject> triggerOld,
            Map<Id, SObject> triggerOldMap
        ) {
            switch on triggerOperationType {
                when BEFORE_INSERT {
                    for (Log__c log : (List<Log__c>) triggerNew) {
                        log.Status__c = 'On Hold';
                    }
                }
            }
        }
    }

Once you've created your Apex or Flow plugin(s), you will also need to configure the plugin:

  • 'Logger Plugin' - use the custom metadata type LoggerPlugin__mdt to define your plugin, including the plugin type (Apex or Flow) and the API name of your plugin's Apex class or Flow
  • 'Logger Parameter' - use the custom metadata type LoggerParameter__mdt to define any configurable parameters needed for your plugin, such as environment-specific URLs and other similar configurations

Note: the logger plugin framework is not available in the managed package due to some platform limitations & considerations with some of the underlying code. The unlocked package is recommended (instead of the managed package) when possible, including if you want to be able to leverage the plugin framework in your org.

Beta Plugin: Slack Integration

The optional Slack plugin leverages the Nebula Logger plugin framework to automatically send Slack notifications for logs that meet a certain (configurable) logging level. The plugin also serves as a functioning example of how to build your own plugin for Nebula Logger, such as how to:

  • Use Apex to apply custom logic to Log__c and LogEntry__c records
  • Add custom fields and list views to Logger's objects
  • Extend permission sets to include field-level security for your custom fields
  • Leverage the new LoggerParameter__mdt CMDT object to store configuration for your plugin

Check out the Slack plugin for more details on how to install & customize the plugin

Slack plugin: notification


Uninstalling/Removing Logger

If you want to remove the unlocked or managed packages, you can do so by simply uninstalling them in your org under Setup --> Installed Packages.

Uninstall Packages

nebulalogger's People

Contributors

alanjaouen avatar arvindnarsimhan avatar burkhartt avatar chazwatkins avatar confirm4crit avatar dancinllama avatar daveerickson avatar dmgerow avatar jamesgol avatar jamessimone avatar jefersonchaves avatar jmercie avatar jongpie avatar mar-ben avatar mohan-chinnappan-n avatar patrick-skamarak avatar rcastanosgonzalez avatar twentytwo avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

nebulalogger's Issues

Creating TopicAssignment records silently fails in a managed package context

The Logger and LogEntryEventBuilder methods for using Salesforce's Topics are not currently available in the managed package due to an issue with the functionality. New Topic records were successfully created, but the related TopicAssignment records for Log__c and LogEntry__c` were never inserted.

This functionality all works both when running in an org without a namespace (unpackaged code), as well as in a namespaced scratch org (using the same Nebula namespace, but silently fails in orgs that have installed the managed package.

Possible theories

  • Some kind of permission issue with managed package + automated process user
  • Maybe topic assignments should be created async (but still would not explain why this functionality works fine when running as unpackaged code/no namespace)

Add logging for Lightning components

As a lightning component developer, I want to be able to add log entries from lightning components so that I can more easily monitor & troubleshoot frontend errors.

Add support for using database 'result' classes as parameters

Add SObject Type Classification for related records

Currently, you can create a log entry for a particular record by providing the recordId or the SObject record itself - the SObject Type is then automatically set in the field LogEntry__c.RecordSObjectType__c. This works great for tracking the type - but it would also be helpful to track the "classification" (I'm not sure if Salesforce has an actual term for this) to indicate if the SObject Type is a...

  • Standard Object - Account
  • Custom Object - MyObject__c
  • Field History Tracking - MyObject__History
  • Sharing Rules/Sharing Table - MyObject__Share
  • Chatter Feed - MyObject__Feed
  • Custom Setting
  • Custom Metadata Type
  • Change Event
  • Platform Event

Having this field would help with reporting, as well as fixing display issues with the formula field LogEntry__c.RecordLink__c - the current formula works for standard and custom objects, but does not work for custom settings, custom metadata types, etc. Some of the other object classifications either use different URL structures (compared to standard & custom objects), or don't have a page at all within Salesforce to view the data.

Add a lwc to display a Log Entry Event stream

There needs to be a way to easily subscribe to the LogEntryEvent__e platform event stream and display it on screen - admins/devs/architects can then use it for real-type monitoring of logs.

The lwc module empApi seems like the best option - screen flows don't seem to currently support platform events (flow only supports screenless flows for platform events), so build a custom component instead

https://developer.salesforce.com/docs/component-library/bundle/lightning-emp-api/documentation

LogEntry__c.Timestamp__c does not have accurate datetimes

Due to Salesforce limitations and design, datetimes from platform events lose the precision of milliseconds within the timestamp when you have an Apex trigger subscribed to the event. In this case, LogEntryEventHandler normalizes data into Log__c and LogEntry__c but the value of LogEntry__c.Timestamp__c is not 100% accurate.

To circumvent this issue, we need a new field on LogEntryEvent__e that stores the String version of the datetime - LogEntryEventHandler should then convert the String back to a Datetime and use it to set LogEntry__c.Timestamp__c.

Add the ability to exclude some records in the batch purge job

LogBatchPurger currently supports filtering records, but it's based solely on the log's created date. This works fine if you just want to delete any logs that are older than X days ago.

But there are plenty of scenarios where you may want to keep certain logs longer - due to audit requirements, ongoing troubleshooting, and so on. So there should be some way to further control which records are included/excluded in... THE PURGE

Add Platform Event fields for org, user, and record details

Currently, there are a handful of fields on LogEntryEvent__e related to the org, user and related record - but most of the detailed info is later added asynchronously on Log__c where other fields for the org, user, profile, role, etc are populated. This was done to help minimize Logger's CPU usage - but having some of these details on the platform event object would let external systems easily get it directly from LogEntryEvent__e.

General approach:

  • Cache relevant queries in private static variables (can't assume the org will have platform cache) and add private methods to LogEntryEventBuilder for setting fields
  • Update LogEntryEventHandler to simplify code - it should just map fields from the platform event object to the log object

Use Quiddity values for Log__c.SystemMode__c

When this field was first implemented, it was a best-guess approach, and Salesforce did not have the concept of quiddity yet. But it now makes sense to use the current transaction's quiddity enum as the value - the picklist values should be updated to match the list of possible quiddity enums.

Further SessionManagement bugs

Contexts in which Automated Process user invoking Logger throws uncatchable errors:

  • Scheduled jobs
    -@future Apex methods
  • Queueables
  • Batch Apex

Previously addressed:

  • Tests (previously covered)
  • Invocables (previously covered)

Convert aura logger component to lwc

Originally, this repo had an aura component, logger.cmp, that could be used for logging within other aura components (released in v0.12.0-beta). I ended up removing it v3.2.0-beta in anticipation of eventually converting it to a lightning web component instead.

Add metadata examples for flow and apex

Add a new folder (not used by sfdx-project.json) with example metadata to show how to add entries, save, pass an SObject, etc

  • 1 flow
  • 1 apex class or trigger
  • 1 aura component

This would give admins & developers some optional metadata to play around with in a sandbox to demonstrate how they can leverage Logger

Add a custom component to show related logs on an SObject's flexipage

With the current implementation, LogEntry__c records are "related" to SObject records by storing the record ID in the text field LogEntry__c.RecordId__c - as far as Salesforce is concerned, there is no true relationship (lookup field) that relates LogEntry__c to another SObject. This means that log entries for a specific record cannot be added to a page layout using standard related lists.

One workaround for this is to add lookups on LogEntry__c for each desired SObject and populate the appropriate lookup with the value of LogEntry__c.RecordId__c (and then leverage standard related lists), but it's not a scalable approach & can't be provided by this repo. Instead, another workaround is needed - create a Lightning web component that can be added to an object's flexipage (e.g., add it to the flexipage for Account, Lead, Opportunity, etc.) and display any LogEntry__c objects where LogEntry__c.RecordId__c == <recordId>

  • The component should look like & behave like a standard related list (including column sorting, resizing, responsive design/mobile friendly, etc.)
  • The columns displayed should be configurable via field sets on LogEntry__c
  • Within App Builder, admins should be able to specify the name of the desired field set to use
  • Within App Builder, admins should be able to specify the number of rows to display by default, as well as which column(s) to sort on by default
  • The component should honor object security, field-level security & record access security - including hiding the component itself if the user does not have access to Log__c or LogEntry__c

Eliminate TestDataFactory

It's not critical to the repo, and can cause issues in projects/orgs that already have a TestDataFactory class

Add configurable Apex sharing rules to share records with Log__c.LoggedBy__c

Currently, the project provides 'view all' and 'modify all' access to Log__c via the permission sets LoggerLogViewer and LoggerLogAdmin (respectively) - this works for most situations where system admins and power users are the only users monitoring logs. But in some situations, access is also extended to end-users as well; ideally, in the situations, end-users should be able to view their own logs (based on Log__c.LoggedBy__c). To support these types of project implementations:

  • The project should have a configurable way to enable Apex sharing of Log__c records with the logging user. When enabled, the relevant records for Log__Share should be generated/maintained after-insert and after-update of Log__c

    • A new checkbox field Log__c.ShareRecordWithLoggedBy__c (or similar name) should be evaluated by LogHandler to handle creating/deleting Apex sharing records
    • LoggerSettings__c should have a checkbox field to enable/disable Apex sharing rules (LoggerSettings__c.EnableShareRecordWithLoggedBy__c or something similar)
    • LoggerSettings__c should have a checkbox field to control the default value of Log__c.ShareRecordWithLoggedBy__c allowing it to be configured at the org, profile & user levels (LoggerSettings__c.DefaultShareRecordWithLoggedBy__c or something similar)
  • The Log__c object should use the sharing option 'Grant Access Using Hierarchies' - this will allow managers to view the logs of their team members (in addition to viewing their own logs)

  • A batch Apex job might also be needed to handle mass recalculating of Apex sharing for all Log__c records

  • Bonus points: once Spring '21 is GA, make sure that manual sharing is properly configured for Log__c records.

Add status & priority fields

Currently, there are no built-in status or priority fields for the Log__c object - these fields may not be needed on every project or environment, but currently, there's no way to know if someone has reviewed/analyzed a log. This can lead to multiple people reviewing the same log, not realizing that they're duplicating efforts.

I often add these fields (or similar fields) on most projects anyway, so it'd be good to have this built in to the tool/included in the managed package.

Find a workaround for the Salesforce-required SObject type input on flows

Currently, Salesforce requires you to select an SObject type in Flows for the SObject parameter FlowLogEntry.record, even though it's an optional parameter. The easy workaround is to just choose a random SObject when you're not using the parameter, but need to explore if there's a better way to handle this.

Add better support for logging Flow fault exceptions

Currently, the FlowLogEntry and FlowRecordLogEntry classes only have a message field - there's not a way to log both a message and an exception message (which can be done in Apex). There needs to either be new optional properties added to the existing classes, or add new classes specifically for logging Flow fault exceptions.

Add support for formatting messages

Need to further explore the best way to implement, but 1 possibly option is

  • Don't make any changes in Logger's static methods - only string messages would be supported for the static methods, like Logger.info('my message');

  • In LogEntryBuilder have 2 methods for setting the message

    1. Existing method (no changes): public LogEntryBuilder setMessage(String message)
    2. New method: public LogEntryBuilder setFormattedMessage(String unformattedMessage, List<Object> messageInputs) (another option is to instead overload - setMessage(String unformattedMessage, List<Object> messageInputs))

If the log level is disabled for the current user, then the method should skip formatting the message to help reduce CPU usage

Current session unavailable - System.UnexpectedException

I'm trying to implement some logging in a scheduled job and got the following:

Any ideas what to check that could be causing this?

25915:20:00.0 (286295243)|METHOD_EXIT|[55]|01p3f000000TQ0F|Nebula.Logger.saveLog()
26015:20:00.0 (286460664)|FATAL_ERROR|System.UnexpectedException: Current session unavailable
261
262Class.Auth.SessionManagement.getCurrentSession: line 5, column 1
263Class.Nebula.LogEntryEventBuilder.getSessionMap: line 308, column 1
264Class.Nebula.LogEntryEventBuilder.setUserSessionDetails: line 258, column 1
265Class.Nebula.LogEntryEventBuilder.getLogEntryEvent: line 240, column 1
266Class.Nebula.Logger.saveLog: line 689, column 1
267Class.Nebula.Logger.saveLog: line 656, column 1
268Class.InstructorsEmailSender.execute: line 55, column 1
26915:20:00.0 (286694887)|CODE_UNIT_FINISHED| REMOVED
27015:20:00.0 (286723785)|EXECUTION_FINISHED

Sample:

global class InstructorsEmailSender implements Schedulable {
    global void execute(SchedulableContext SC) {
        Nebula.Logger.info('Running the InstructorsEmailSender Class');
        Nebula.Logger.saveLog();
    }
}

Add an option for storing the stack trace on all log entries

The current design only supports logging the stack trace for exceptions, but it would be handy to support logging the stack trace for all entries. This will add some CPU usage, so there should be an option in the custom setting to toggle this.

Add canned reports for Log & Log Entry

Given that one of the benefits of the tool is being able to report on logging data - it's probably a good idea to actually provide a few sample reports. These might be kept in separate folder/outside of the managed package (TBD).

Add limits fields to Log__c

Each LogEntry__c has the limits at the moment that the LogEntry__c was generated - but the Log__c record should also store the final limit info (essentially copy the limits used/max from the last LogEntry__c) to make it easy to see the limits used for the entire log (transaction)

Convert 'related record name' formula field to text

The field LogEntry__c.RelatedRecordName__c is a formula fields that currently relies on lookup fields to be created, as well as the formula to be updated. This approach has 2 drawbacks

  • The formula field cannot be updated when using a managed package
  • The design is not scaleable because of formula limits - orgs that want to log multiple objects would need multiple lookups & a large formula

Instead, the name field of the record (typically 'Name', but there are exceptions like 'CaseNumber', 'Subject', 'Title', etc) should be stored directly on LogEntry__c. This will also provide a snapshot of what the record's name was at the time that the log entry was created.

Add method overrides to support exceptions in WARN logging level

Currently, I've taken the approach with Logger's static methods that if you have an Apex exception to log, then you have to use one of the overloads for Logger.error() method (although the fluent API allows you to bypass this). However, in some situations, developers need to log a non-critical exception (e.g., an exception is caught & logged, but not critical enough to throw the exception). In these situations, it would be useful to still log the exception with a WARN logging level - since there is an exception, it seems fair to consider it at least a warning, and it would give developers the flexibility to choose if it's an ERROR or WARN level entry.

To support this, additional overloads are needed for Logger.warn() - the method signatures should be identical to the overloads for Logger.error(). For now, I don't think it makes sense to add these overloads for other logging levels (INFO, DEBUG, FINE, FINER & FINEST), and the fluent API via LogEntryBuilder can be used to support this if needed.

Add an option for storing a serialized SObject on LogEntry__c

LogEntry__c records can already be related to an SObject record - but on some projects, it's useful to have a snapshot of the SObject's field values at the time that the log entry was generated. To support this...

  • Add a new long text area field on LogEntry__c
  • Add a new field on the custom setting to enable/disable this functionality of serializing an SObject
  • For any methods in LogEntryBuilder that accept an SObject, if the custom setting is enabled, then call Json.serialize(record) and store the JSON string in the new long text area field

Deploy to directly Production crashing

I tried to deploy your package directly into our org but a few test are failing at the moment for us:

`Deployment CompleteTest failure, method: LogglyLogPushScheduler_Tests.it_should_schedule_the_batch_job -- System.AssertException: Assertion Failed: Expected: 0, Actual: 23 stack Class.LogglyLogPushScheduler_Tests.it_should_schedule_the_batch_job: line 12, column 1

Test failure, method: LogglyLogPushScheduler_Tests.it_should_schedule_the_batch_job_schedule_hourly -- System.AssertException: Assertion Failed: Expected: 0, Actual: 23 stack Class.LogglyLogPushScheduler_Tests.it_should_schedule_the_batch_job_schedule_hourly: line 35, column 1

Test failure, method: LogglyLogPushScheduler_Tests.it_should_schedule_the_batch_job_schedule_every_5_minutes -- System.AssertException: Assertion Failed: Expected: 0, Actual: 23 stack Class.LogglyLogPushScheduler_Tests.it_should_schedule_the_batch_job_schedule_every_5_minutes: line 25, column 1

Test failure, method: SlackLogPushScheduler_Tests.it_should_schedule_the_batch_job_schedule_hourly -- System.AssertException: Assertion Failed: Expected: 0, Actual: 23 stack Class.SlackLogPushScheduler_Tests.it_should_schedule_the_batch_job_schedule_hourly: line 35, column 1

Test failure, method: SlackLogPushScheduler_Tests.it_should_schedule_the_batch_job_schedule_every_5_minutes -- System.AssertException: Assertion Failed: Expected: 0, Actual: 23 stack Class.SlackLogPushScheduler_Tests.it_should_schedule_the_batch_job_schedule_every_5_minutes: line 25, column 1

Test failure, method: SlackLogPushScheduler_Tests.it_should_schedule_the_batch_job -- System.AssertException: Assertion Failed: Expected: 0, Actual: 23 stack Class.SlackLogPushScheduler_Tests.it_should_schedule_the_batch_job: line 12, column 1

Test failure, method: TrelloLogPushScheduler_Tests.it_should_schedule_the_batch_job -- System.AssertException: Assertion Failed: Expected: 0, Actual: 23 stack Class.TrelloLogPushScheduler_Tests.it_should_schedule_the_batch_job: line 12, column 1

Test failure, method: TrelloLogPushScheduler_Tests.it_should_schedule_the_batch_job_schedule_every_5_minutes -- System.AssertException: Assertion Failed: Expected: 0, Actual: 23 stack Class.TrelloLogPushScheduler_Tests.it_should_schedule_the_batch_job_schedule_every_5_minutes: line 25, column 1

Test failure, method: TrelloLogPushScheduler_Tests.it_should_schedule_the_batch_job_schedule_hourly -- System.AssertException: Assertion Failed: Expected: 0, Actual: 23 stack Class.TrelloLogPushScheduler_Tests.it_should_schedule_the_batch_job_schedule_hourly: line 35, column 1
`
I've checked those test files and I think there are some areas for improvement :)
I'm not sure if anyone else is getting these errors, but the tests should look differently, example: asserting after testing, not just before the test, etc.. :)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.