Skip to content

Releases: jongpie/NebulaLogger

Added more details in logger LWC's console statements

01 Nov 03:54
e58b02f
Compare
Choose a tag to compare

Core Unlocked Package Changes

When using the logger LWC and JavaScript console logging is enabled (via LoggerSettings__c.IsJavaScriptConsoleLoggingEnabled__c), Nebula Logger automatically calls functions in the browser's console so devs can easily see the generated log entry directly in their browser (instead of having to find it in LogEntry__c). This release improves the component log entry JSON that's printed using console statements - the stringified object now includes more details, such as details about the logged error/exception (when an error is logged), any tags added to the entry, and the user's localized version of the entry's timestamp.

  • This includes a change to how Nebula Logger internally calls the browser's console functions: now, console functions are called with a 1 second delay (using setTimeout()) so that additional details can be added to the log entry (using the builder methods) before the log entry is stringified & printed out. This may not be a perfect solution, but seems to work in most cases of using the JS builder methods.

To show the difference in the changes, this JavaScript was used to generate some example log entries:

this.logger.setScenario('Some demo scenario')
const someError = new TypeError('oops');
this.logger.error('Hello, world!')
   .setExceptionDetails(someError)
   .setField({ SomeLogEntryField__c: 'some text from loggerLWCGetLoggerImportDemo' })
   .addTags(['Tag-one', 'Another tag here']);

In the screenshot below, 2 versions of the output are shown:

  1. Before:
    • No details are included in the output about the error, the tags, or the custom field mappings.
    • The timestamp is only shown in UTC.
  2. After:
    • The output now includes details about any errors/exceptions logged using the builder function setExceptionDetails().
      • Previously the output only included the logged message - no details were included about the logged exception/error, which made it much harder to troubleshoot whatever the relevant error was 🙃
      • The error output includes details about the metadata source, making it easy to tell if the error was caused by another LWC, an Apex controller method, or an Apex trigger.
    • When devs use any of Nebula Logger's optional JS features (scenarios, tags, and custom field mappings), the details are automatically included in the console statements' output
    • For devs that do not leverage these optional features, the relevant properties are automatically excluded from the console output. This keeps the output slimmer & easier to read.
    • Each entry's timestamp is now shown both in UTC time (using new Date().toISOString()) and in the user's local format (using new Date().toLocaleString()). This makes it a little easier for devs to troubleshoot exactly when an entry was generated in their browser.

image

Installation Info

Core Unlocked Package - no namespace

Full Changelog: v4.14.16...v4.14.17

CallableLogger Enhancements

31 Oct 21:02
1707c41
Compare
Choose a tag to compare

Thanks to @aBerean2 for some great feedback on logging in OmniStudio (see discussion #785)!

Core Unlocked Package Changes

Added 3 enhancements to the CallableLogger class (used for OmniStudio logging & loosely-coupled dependencies)

  1. Resolved #788 - It now automatically appends OmniStudio's input data for OmniScript steps as JSON to the Message__c fields on LogEntryEvent__e and LogEntry__c.

    • In the screenshot below, a simple OmniScript has been setup with 2 steps (each of which shows some inputs), with logging actions after each step:

      image

    • It now automatically stores the JSON from each of the steps & appends it to the message. Long-term, it might make sense to create a new field to store this data, but for now, appending it to Message__c is a quick & easy way to capture the JSON.

      image

  2. The newEntry action now supports setting the parent log transaction ID, using the optional argument parentLogTransactionId.

  3. Transaction details are now returned in the output for all actions:

    • transactionId: The current transaction ID, returned from Logger.getTransactionId()
    • parentLogTransactionId: The parent log transaction ID (or null if no parent has been set), returned from Logger.getParentLogTransactionId()
    • requestId: The Salesforce-generated request ID, returned from System.Request.getCurrent().getRequestId()

    {073C80F5-D96D-4E03-B383-A1168D906A82}

Installation Info

Core Unlocked Package - no namespace

Full Changelog: v4.14.15...v4.14.16

Enhanced Queueable FinalizerContext logging messages

29 Oct 14:14
8e859c2
Compare
Choose a tag to compare
  • Utilizes Nebula Logger system messages framework to internally log info about different Apex async contexts and provides more information in the body of the associated log entry (as well as what prints to the Apex debug console) for Queueable finalizers:
    image

  • Added methods in Logger_Tests to validate that an INFO log entry is auto-generated for async contexts (when system messages are enabled). This also includes a few changes for handling Exception instances not being serializable & adding some test helper methods

  • Re-ran prettier on README.md to fix some code formatting

Added new Apex method & JavaScript function Logger.setField()

16 Oct 03:00
68c4187
Compare
Choose a tag to compare

Many thanks to @surajp for sharing some awesome perspectives & suggestions for improving custom field mappings!

Custom field mappings were introduced a few months ago for Apex in v4.13.14, and for lightning components in v4.14.16. Both releases provided the ability to set custom fields on individual log entries - but in situations where a custom field should be set on every generated log entry, setting the field on each entry was very repetitive & tedious & repetitive. It was also repetitive.

  • This release adds the ability to now set fields once per transaction (in Apex) or once per component instance (in JavaScript) - all subsequent log entries will then automatically have your custom field values applied.
  • Individual log entries can still have additional field values set & mapped, giving you multiple options for setting your custom fields.

Core Unlocked Package Changes

New Apex Method Logger.setField()

Resolved #769 by adding a new static method, Logger.setField(). This gives Apex developers a way to set field(s) once per transaction, and the fields will be auto-populated on all LogEntryEvent__e records generated in that transaction.

  • The setField() method has 2 overloads (same as the instance method overloads LogEntryEventBuilder.setField() that were introduced in release v4.13.14)

    1. Logger.setField(Schema.SObjectField field, Object fieldValue) - useful for easily setting the value for a single field
    2. Logger.setField(Map<Schema.SObjectField, Object> fieldToValue) - useful for setting the value for multiple fields
  • The new method supplements the functionality introduced in release v4.13.14, as shown below:

    // 🥳New!
    // Set a custom field on all LogEntryEvent__e records in a transaction,
    // using static method Logger.setField()
    Logger.setField(LogEntryEvent__e.Some_Custom_Field__c, 'the value for all generated entries')
    
    // ℹ️Existing - introduced in v4.13.14
    // Set a custom field on a single LogEntryEvent__e record,
    // using instance method LogEntryEventBuilder.setField()
    Logger.info('hello, world').setField(LogEntryEvent__e.Another_Custom_Field__c, 'the value for this specific entry');

New JavaScript Function logger.setField()

Added JavaScript support for setting fields once per component instance, using logger.setField(). This is the JS equivalent of the new Apex method Logger.setField() (above).

export default class LoggerLWCImportDemo extends LightningElement {
  logger = getLogger();

  connectedCallback() {
    // 🥳New!
    // Set My_Field__c on every log entry event created in this component with the same value
    this.logger.setField({My_Field__c, 'some value that applies to any subsequent entry'});

      // ℹ️Existing - introduced in v4.14.6
    this.logger.warn('hello, world - "a value" set for Some_Other_Field__c').setField({ Some_Other_Field__c: 'a value' });
  }
}

Installation Info

Core Unlocked Package - no namespace

Full Changelog: v4.14.13...v4.14.14

New synchronous JavaScript function getLogger() + deprecated async function createLogger()

11 Oct 18:27
2445dcb
Compare
Choose a tag to compare

This release is focused on several enhancements & bugfixes for logging in JavaScript (aura & lightning web components). Thanks to @jamessimone for reviewing & discussing the changes for this release, and thanks to @srikanth3107 for reporting issue #776!

Core Unlocked Package Changes

New getLogger() function

Partially fixed #728 by adding a new function getLogger() in logger LWC that can be called synchronously, and deprecated the async function createLogger()

  • This simplifies how developers use the logger LWC, and avoids some lingering JavaScript (JS) stack trace issues that occur in async functions
  • The function createLogger() is still supported & functional, but it's now considered deprecated since it requires using await (which is no longer necessary with getLogger())

Note

This does not truly "fix" or improve the stack trace parsing used in async functions, and async functions will continue to have inaccurate function names reported in some browsers (typically, eval is listed as the function name). This is a ongoing challenge in JavaScript due to what info is/isn't available in stack traces in some browsers (most notably, webkit browsers like Chrome & Edge).

But with the new getLogger() function, Nebula Logger no longer requires the use of async / await - and often the only reason developers were using async / await was to be able to use Nebula Logger. In these situations, developers can eliminate async / await, and the resulting log entries will have accurate JS function names.

Example: Old createLogger() Usage

Previously, JS developers had to use async, await, and truthy checks to ensure that Nebula Logger's LWC had finished being loaded. This resulted in more complexity for developers, who just want to be able to log some stuff 😢

import { createLogger } from 'c/logger';

export default class LoggerDemo extends LightningElement {
  logger;

  // Some lifecycle event (typically connectedCallback()) has to run async & await createLogger()
  async connectedCallback() {
    this.logger = await createLogger();
    this.logger.info('Hello, world');
    this.logger.saveLog();
  }

  // @wire functions run around the same time as connectedCallback(), but there's no guaranteed order
  // This result in some logging requiring truthy checks using the safe navigator "?.", and some loss of logging data could occur
  @wire(returnSomeString)
  wiredReturnSomeString({ error, data }) {
    this.logger?.info('>>> logging inside a wire function, if the logger is loaded');
    if (data) {
      this.logger?.info('>>> wire function return value: ' + data);
    }
    if (error) {
      this.logger?.error('>>> wire function error: ' + JSON.stringify(error));
    }
  }
}

Example: New getLogger() Usage

Now, await is no longer needed, and a logger instance can be immediately initialized & used. This results in less code, and happier developers 🥳

import { getLogger } from 'c/logger';

export default class LoggerDemo extends LightningElement {
  logger = getLogger();

  connectedCallback() {
    // Immediately use this.logger - no await, no truthy checks
    this.logger.info('Hello, world');
    this.logger.saveLog();
  }
}

  @wire(returnSomeString)
  wiredReturnSomeString({ error, data }) {
    // Immediately use this.logger - no await, no truthy checks
    this.logger.info('>>> logging inside a wire function');
    if (data) {
      this.logger.info('>>> wire function return value: ' + data);
    }
    if (error) {
      this.logger.error('>>> wire function error: ' + JSON.stringify(error));
    }
  }
}

New exception() function

Resolved #763 by adding a JS function equivalent to the Apex method Logger.exception(). Both of these do 3 things in 1 line of code:

  1. Log an exception
  2. Save the log
  3. Rethrow the exception

Previously in JS, this would have been 3 lines of code:

this.logger.error('An error occurred').setExceptionDetails(someJSError);
this.logger.saveLog();
throw someJSError;

Now, 1 line of code provides the same functionality:

this.logger.exception('An error occurred', someJSError);

More Improvements for JS Stack Trace Parsing

Fixed #776 by updating logic in loggerStackTrace.js to better handle parsing when lightning debug mode is disabled
Previously, stack traces worked when debug mode was enabled, but was still inaccurate in some browsers when debug mode was off due to some subtle differences in the generated stack traces.

Installation Info

Core Unlocked Package - no namespace

Full Changelog: v4.14.12...v4.14.13

Deprecated & replaced HttpRequestEndpoint__c fields with new HttpRequestEndpointAddress__c fields

27 Sep 17:05
cfdae6b
Compare
Choose a tag to compare

Thanks to @ILambuRI for reporting this issue!

Core Unlocked Package Changes

Fixed #768 by deprecating the HttpRequestEndpoint__c fields on LogEntryEvent__e and LogEntry__c, and replaced them with new HttpRequestEndpointAddress__c fields that have a much longer max length (2,000 vs 255).

  • HttpRequestEndpoint__c fields are now considered deprecated since they're not capable of storing the full value of long endpoints
    • The HttpRequestEndpoint__c fields will continue to be populated for the time being, and the value is now auto-truncated to 255 characters to fix the STRING_TOO_LONG error that was reported in #768
    • The businessStatus on the fields has been updated to DeprecateCandidate
    • The inlineHelpText on the fields has been update to Deprecated: instead use the field HttpRequestEndpointAddress__c
    • The label on the fields has been update to DEPRECATED: HTTP Request Endpoint
  • Updated LogEntryRecordPage flexipage to have both HttpRequestEndpoint__c (existing) and HttpRequestEndpointAddress__c fields
    • Both fields will be shown for now, and eventually HttpRequestEndpoint__c will be removed

Related context: this is essentially the same issue & solution used in release v4.13.15 for the now-deprecated text(255) fields BrowserUrl__c not being long enough, and they were replaced with new long textarea(2000) field BrowserAddress__c (PR #720). It's probably worth reviewing all of the text fields in the data model (especially some of the older fields) to see if it would make sense to take the same approach for any other existing fields.

Installation Info

Core Unlocked Package - no namespace

Full Changelog: v4.14.11...v4.14.12

Bugfix: Updated Logger.setAsyncContext() behavior

25 Sep 01:25
ab92cd5
Compare
Choose a tag to compare

Core Unlocked Package Changes

  • Updated the behavior of Logger.setAsyncContext() to only set the context the first time a non-null context value is provided
    • Previously, subsequent calls would overwrite the context value, which wasn't really the intended behavior - it's intended to be used to set context at the beginning of a transaction

Pipeline Changes

  • Made some optimizations in build.yml so some steps don't run on draft PRs

Installation Info

Core Unlocked Package - no namespace

Full Changelog: v4.14.10...v4.14.11

Double Feature: OmniStudio Logging + Loosely-Coupled Dependency Support

17 Sep 20:45
1629291
Compare
Choose a tag to compare

This release is a fun one - with one new Apex class, 2 great enhancements have been added to Nebula Logger

  • It's now even easier for ISVs, consultants, and package developers to optionally leverage Nebula Logger when its available in your customers' orgs
  • OmniStudio logging is now provided out of the box

Many thanks to @tscottdev for originally opening #371, as well as providing a very helpful sample implementation... and then waiting 2 years for me to finally be convinced that adding a Callable implementation is a great enhancement 😅

Thanks as well to all of the feedback & interest people have provided about adding support for OmniStudio logging, and to @jamessimone for the help with setting up a usable OmniStudio scratch org for testing

Core Unlocked Package Changes

New Support for Using Nebula Logger as a Loosely-Coupled Dependency

Resolved #371 by introducing a new CallableLogger Apex class that implements Apex's Callable interface - see the new wiki page for full docs.

This class provides dynamic access to Nebula Logger's core features - ISVs, consultants, and package developers can use this to optionally leverage Nebula Logger in a customer's org when it's available, without requiring a package dependency. For example, this sample code can be executed in any Salesforce org - and when Nebula Logger is available, 2 log entries will be saved.

// Dynamically create a instance Nebula Logger's Callable Apex class (if it's available)
Type nebulaLoggerCallableType = Type.forName('Nebula', 'CallableLogger') ?? Type.forName('CallableLogger');
Callable nebulaLoggerCallable = (Callable) nebulaLoggerCallableType?.newInstance();
if (nebulaLoggerCallable == null) {
  return;
}

// Example action: Add a basic "hello, world!" INFO entry
Map<String, Object> infoEntryInput = new Map<String, Object>{
  'loggingLevel' => System.LoggingLevel.INFO,
  'message' => 'hello, world!'
};
nebulaLoggerCallable.call('newEntry', infoEntryInput);

// Example action: Add an ERROR entry with an Apex exception
Exception someException = new DmlException('oops');
Map<String, Object> errorEntryInput = new Map<String, Object>{
  'exception' => someException,
  'loggingLevel' => LoggingLevel.ERROR,
  'message' => 'An unexpected exception was thrown'
};
nebulaLoggerCallable.call('newEntry', errorEntryInput);

// Example: Save any pending log entries
nebulaLoggerCallable.call('saveLog', null);

New Support for OmniStudio Logging

Resolved #644 by adding support for logging in OmniStudio, using the new CallableLogger Apex class - see the new wiki page for full docs. The included CallableLogger Apex class can be used in 2 places within OmniStudio:

Once you've added logging in OmniStudio, any log entries generated in OmniStudio can be seen using the LogEntry__c object's included list view AllOmniStudioLogEntries

image

Slack Plugin Package Changes

Fixed #764 by correcting the logic used in SlackLoggerPlugin to format text fields containing line breaks

Thanks to @kacrouse for reporting this issue & providing the fix 🥳

Documentation Changes

Started rewriting & consolidating all documentation to live just in the wiki. Currently, content is split across the wiki, README.md, and the GitHub Pages site

  • README.md has been updated to link to wiki pages (instead of putting all content directly into README.md)
  • Eventually most of the content current in README.md will be removed/moved to the wiki

Pipeline Changes

  • Renamed all of the scratch definition files (again 😅 )
  • Updated build.yml so that 2 additional scratch orgs (6 total) are used for automated testing in the pipeline
    • "Advanced" scratch org (formerly called the 'dev' scratch org) - existing scratch org, but was not previously used by the pipeline
    • "OmniStudio" scratch org - new scratch definition file, used to validate that queries on OmniProcess work correctly, and to validate that sample OmniScript and OmniIntegrationProcedure metadata that leverage CallableLogger can successfully be deployed

Installation Info

Core Unlocked Package - no namespace

Slack Unlocked Package Plugin - no namespace

Bugfix: Apex code snippets auto-truncated

03 Sep 22:41
ff06c25
Compare
Choose a tag to compare

Core Unlocked Package Changes

Fixed #756 by truncating the Apex code snippets that are saved in the LogEntry__c fields OriginSourceSnippet__c and ExceptionSourceSnippet__c

  • Previously, the code snippet was only limited by the number of lines of code - but for Apex classes & triggers that have very looooooooooooong code on some lines, the code snippet could cause the JSON value to exceed the corresponding field's max length
  • Now, the code snippet only grabs the first 1,500 characters - both fields are set to a max of 2,000 characters, allowing 500 characters for the remaining JSON data

Installation Info

Core Unlocked Package - no namespace

Full Changelog: v4.14.8...v4.14.9

Added the ability to capture HttpRequest headers

02 Sep 17:46
5c4e09e
Compare
Choose a tag to compare

Resolved #701 by providing a way to indicate which HttpRequest headers (and values) should be logged - previously, no header information was stored for HttpRequest objects.

Conceptually, this is the same idea as logging headers when calling the other LogEntryBuilder methods below:

  • setHttpResponseDetails(System.HttpResponse response)
  • setRestRequestDetails(System.RestRequest request)
  • setRestResponseDetails(System.RestResponse response)

However, there is one notable difference in the new behavior for logging HttpRequest headers - the HttpRequest class does not have a method to get all of the header keys, so you must explicitly tell Nebula Logger which header keys you want to log.

Core Unlocked Package Changes

  • Added instance method overload LogEntryEventBuilder.setHttpRequestDetails(System.HttpRequest request, List<String> headersToLog)
    • Any header keys provided in headersToLog will now be logged
    • The existing overload, setHttpRequestDetails(System.HttpRequest request), will not log any header information
  • Added new LogEntryEvent__e fields HttpRequestHeaderKeys__c and HttpRequestHeaders__c to capture the specified list of header keys
    • The new fields mimic these existing fields:
      • HttpResponse header data stored in HttpResponseHeaderKeys__c and HttpResponseHeaders__c
      • RestRequest header data stored in RestRequestHeaderKeys__c and RestRequestHeaders__c
      • RestResponse header data stored in RestResponseHeaderKeys__c and RestRequestHeaders__c
  • Added new LoggerParameter__mdt record StoreHttpRequestHeaderValues to globally control if HttpResponse header values are stored when calling setHttpRequestDetails(System.HttpRequest request, List<String> headersToLog)
    • This new record mimics the existing record StoreHttpResponseHeaderValues (used for responses)
  • Added new LogEntry__c fields to store the HttpRequest header keys & values captured on LogEntryEvent__e, as well as some checkbox fields to aid with filtering in SOQL, list views, etc.
    • HttpRequestHeaderKeys__c
    • HasHttpRequestHeaderKeys__c - set to true when HttpRequestHeaderKeys__c is not null
    • HttpRequestHeaders__c
    • HasHttpRequestHeaders__c - set to true when HttpRequestHeaders__c is not null
  • Updated the flexipage LogEntryRecordPage to add the 2 new LogEntry__c fields HttpRequestHeaderKeys__c and HttpRequestHeaders__c
    • Both fields are conditionally displayed when populated, based on their corresponding checkbox fields HasHttpRequestHeaderKeys__c and HasHttpRequestHeaders__c (respectively)
  • Updated the existing list view AllHttpRequestLogEntries to include the new field HttpRequestHeaderKeys__c (HttpRequestHeader__c is intentionally not included at this time)

Example

This example Apex script will create a LogEntry__c record with data about the HttpRequest - including the 2 specified header keys & their values.

System.HttpRequest httpRequest = new System.HttpRequest();
httpRequest.setBody('{ "hello": "world" }');
httpRequest.setEndpoint('https://fake.salesforce.com');
httpRequest.setHeader('some-header', 'some value');
httpRequest.setHeader('another-header', 'another value');
httpRequest.setMethod('GET');

List<String> headersToLog = new List<String>{ 'some-header', 'another-header' };
Logger.info('logging an HTTP request').setHttpRequestDetails(httpRequest, headersToLog);
Logger.saveLog();

image

Pipeline Changes

  • Updated build.yml to fail the build if the codecov upload action fails

Installation Info

Core Unlocked Package - no namespace

Full Changelog: v4.14.7...v4.14.8