New: add SUCCESS log level, & scope logging (fixes #818)#832
New: add SUCCESS log level, & scope logging (fixes #818)#832joe-replin wants to merge 8 commits into
Conversation
There was a problem hiding this comment.
Using:
const circularObj = {
name: 'Example Object',
description: 'This is a large circular JSON object used for testing log truncation.',
data: Array.from({ length: 100 }, (_, i) => `Item ${i + 1}`)
};
circularObj.self = circularObj; // Create a circular reference for testing
const largeJSON = {
name: 'Example Object',
description: 'This is a large JSON object used for testing log truncation.',
data: Array.from({ length: 100 }, (_, i) => `Item ${i + 1}`)
};
const shortJSON = { name: 'Short Object', value: 42 };
const scopeA = logging.scope('KeyA', 'Display Name');
const scopeB = logging.scope('KeyB');
scopeA.debug('This is a debug message from Scope A', circularObj, largeJSON, shortJSON);
scopeA.info('This is an info message from Scope A', circularObj, largeJSON, shortJSON);
scopeA.success('This is a success message from Scope A', circularObj, largeJSON, shortJSON);
scopeA.error('This is an error message from Scope A', circularObj, largeJSON, shortJSON);
scopeA.warn('This is a warning from Scope A', circularObj, largeJSON, shortJSON);
scopeA.fatal('This is a fatal error from Scope A', circularObj, largeJSON, shortJSON);
scopeB.debug('This is a debug message from Scope B', circularObj, largeJSON, shortJSON);
scopeB.info('This is an info message from Scope B', circularObj, largeJSON, shortJSON);
scopeB.success('This is a success message from Scope B', circularObj, largeJSON, shortJSON);
scopeB.error('This is an error message from Scope B', circularObj, largeJSON, shortJSON);
scopeB.warn('This is a warning from Scope B', circularObj, largeJSON, shortJSON);
scopeB.fatal('This is a fatal error from Scope B', circularObj, largeJSON, shortJSON);
logging.debug('This is a debug message', circularObj, largeJSON, shortJSON);
logging.info('This is an info message', circularObj, largeJSON, shortJSON);
logging.success('This is a success message', circularObj, largeJSON, shortJSON);
logging.error('This is an error message', circularObj, largeJSON, shortJSON);
logging.warn('This is a warning', circularObj, largeJSON, shortJSON);
logging.fatal('This is a fatal error', circularObj, largeJSON, shortJSON);
scopeA.debug('This is a debug message from Scope A');
scopeA.info('This is an info message from Scope A');
scopeA.success('This is a success message from Scope A');
scopeA.error('This is an error message from Scope A');
scopeA.warn('This is a warning from Scope A');
scopeA.fatal('This is a fatal error from Scope A');
scopeB.debug('This is a debug message from Scope B');
scopeB.info('This is an info message from Scope B');
scopeB.success('This is a success message from Scope B');
scopeB.error('This is an error message from Scope B');
scopeB.warn('This is a warning from Scope B');
scopeB.fatal('This is a fatal error from Scope B');
logging.debug('This is a debug message');
logging.info('This is an info message');
logging.success('This is a success message');
logging.error('This is an error message');
logging.warn('This is a warning');
logging.fatal('This is a fatal error');tldr: Remove the color, displayName and JSON serialisation stuff. Keep scope and success.
| */ | ||
| _getColorForLevel(level) { | ||
| const colors = { | ||
| debug: 'RoyalBlue', |
| * @returns {string} String representation of the value | ||
| * @private | ||
| */ | ||
| _serializeArg(item) { |
There was a problem hiding this comment.
The console in most browsers handles JSON objects by using a separate console JSON view. The view allows you to expand and collapse the object in the console. The JSON view in the console deals with circular references just fine.
Converting the object to a string and/or truncating it will prevent the console JSON view from appearing. Circular object now don't appear in the console. The JSON as text view is much longer than the JSON view output.
| this._config = Adapt.config.get('_logging'); | ||
| const courseConfig = Adapt.config.get('_logging'); | ||
| // Merge course config with defaults instead of replacing | ||
| this._config = Object.assign({}, this._config, courseConfig); |
There was a problem hiding this comment.
As the override colors config is only applied at configModel:dataLoaded, it's possible to have a mixed colourization; default might be true but the config false. Any logs triggered before this event might have a different colors config.
| * const logger = logging.scope('MyPlugin', 'Feature-X'); | ||
| * logger.warn('Retrying…'); | ||
| */ | ||
| scope(source, name) { |
There was a problem hiding this comment.
I don't understand what the displayName name parameter is for. It seems an unnecessary feature. It is passed through the rest of the code as source. Being that the third arguments of _log and _logToConsole are source, which are derived from displayName here.
|
|
||
| const log = [level.asUpperCase + ':']; | ||
| data && log.push(...data); | ||
| const useColors = this._config._colors && source; |
There was a problem hiding this comment.
Colours and serialisation are only applied to scopped logs, is this intended?
Add and refine JSDoc across logging files: add file/module header to logLevelEnum, include usage example in logging.js, mark Logging class with @Class and @extends, simplify @fires tags to short event names, and add docblocks for onLoadConfigData and loadConfig. Also document args for removed/deprecated/warnOnce methods and update _log event annotations. These changes improve documentation, readability and tooling support for the logging service.
Document that course `_logging` is applied at `configModel:dataLoaded` and that logs emitted before that use constructor defaults. Remove the optional `name` parameter and `_displayName` handling from `logging.scope`; scoped loggers are now cached by source and always use the source string as the console display name. This simplifies the API, avoids inconsistent display-name warnings for cached scopes, and clarifies why early log output may not reflect course-configured settings.
When useColors is enabled, log output now prints a CSS-styled prefix and spreads the original arguments into the console method instead of pre-serializing and joining them. This preserves native object inspection in devtools, avoids costly JSON serialization and truncation, and reduces console spam/overhead. Removes the _serializeArg helper and its truncation/serialization logic.
Remove support for colorized console styling and the internal _colors config. Simplifies Logging by always prefixing messages with a source or level and using the appropriate console method (falling back to console.log). Removes _getColorForLevel and related branching, and updates comments to reflect that console output is no longer colorized.
Add a CONSOLE_METHOD constant to centralize console method mapping and use it in _getConsoleMethod; add @fires JSDoc tags to each level method (debug, info, success, warn, error, fatal); simplify _log by inlining the isEnabled and level-comparison checks and using nullish coalescing for the default config level. Purely refactoring for clarity and maintainability; no intended behavior changes.
Update
|
Delete the deprecated `_colors` boolean setting from schema/config.model.schema and schema/config.schema.json. This removes the now-unused configuration for enabling colored console output.




I've been using logging methods like this for the better part of a year now after being shown this by @chris-steele. I find it handy in all my work now and wanted to contribute it back.
Fixes #818
Fix
_loggingwas replacing the entire logging config object, which could silently disable logging if keys like_isEnabledweren't present in the course JSON — it now merges with the defaults instead.?loglevel=query string with an empty value was incorrectly passing the override guard; it is now ignored as expected.console.logregardless of severity; it now routes to the correct console method for each level (e.g.console.errorfor ERROR/FATAL).logging.scope()a second time with the same source but a different display name silently discarded the new name; it now logs a warning so the mismatch is visible._levelconfig value would throw at runtime; it now falls back toinfosafely.Update
trigger()now include the source name, so plugins listening tologevents can tell which module sent the message.removed()anddeprecated()no longer mutate their arguments array before passing towarnOnce().checkQueryStringOverride()renamed to_checkQueryStringOverride()to follow the existing private method convention.logging.jsandlogLevelEnum.js:@file/@moduleblocks,@classdescwith@firesfor all public events,@param/@returnson public methods,@exampleonscope(),removed()anddeprecated(),@throwsonscope(),@privateon all internal methods, and aScopedLoggertypedef.New
SUCCESSlog level, sitting betweenINFOandWARN, for confirming things worked correctly without it looking like a warning.logging.success()as a convenience method, consistent with the existingdebug(),info(),warn()etc.logging.scope('PluginName')to create a named logger for a plugin — output is prefixed[PluginName]in the console and optionally colour-coded by level._colorsconfig option (defaulttrue) that enables coloured console output for scoped loggers.Testing
{ "_logging": { "_level": "debug" } }toconfig.jsonand confirm logging still works as normal (defaults are not wiped).?loglevel=successin the URL — only SUCCESS, WARN, ERROR and FATAL messages should appear in the console.logging.scope('MyPlugin').success('hello')— confirm[MyPlugin] helloappears with a coloured background in the console.logging.scope('MyPlugin').error('oops')-- confirm the message appears asconsole.erroroutput (red in DevTools), notconsole.log.logging.scope('MyPlugin', 'Name1')thenlogging.scope('MyPlugin', 'Name2')-- confirm a warning is logged about the ignored display name.?loglevel=(empty value) and confirm no override is applied and no errors occur.