Security News
Research
Data Theft Repackaged: A Case Study in Malicious Wrapper Packages on npm
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
diagnostics
Advanced tools
The 'diagnostics' npm package is designed to provide a comprehensive set of utilities for debugging and diagnostic purposes. It allows developers to output debug information, create diagnostic channels, and manage the verbosity of logging.
Debugging
This feature allows developers to create namespaced debug logs. The code sample demonstrates how to create a debug instance for a specific namespace and log a message.
const debug = require('diagnostics')('my-namespace');
debug('doing some work');
Enabling Namespaces
This feature enables specific namespaces for logging. By setting the DEBUG environment variable, you can control which debug instances output logs. The code sample shows how to enable a namespace and log a message that will be output.
process.env.DEBUG = 'my-namespace';
const debug = require('diagnostics')('my-namespace');
debug('this will be logged');
Diagnostic Channels
This feature allows the creation of diagnostic channels for emitting and listening to custom events. The code sample creates a channel, listens for 'data' events, and emits a message.
const diagnostics = require('diagnostics');
const channel = diagnostics.channel('my-channel');
channel.on('data', (message) => {
console.log('Received message:', message);
});
channel.write('Hello, diagnostics!');
The 'debug' package is similar to 'diagnostics' in that it allows developers to create small debugging utilities. It is widely used and focuses on simplicity and minimalism. Unlike 'diagnostics', it does not provide channels but is very popular for its ease of use and integration.
The 'winston' package is a multi-transport async logging library for Node.js. It is more feature-rich than 'diagnostics', providing support for multiple storage options, custom log levels, and transports for logging to various outputs such as files, consoles, or remote services.
The 'bunyan' package is a simple and fast JSON logging library for Node.js services. It provides features like log rotation, streams, and serializers. It is similar to 'diagnostics' in providing structured logging but differs in its focus on JSON format and log management features.
diagnostics
Diagnostics in the evolution of debug pattern that is used in the Node.js core,
this extremely small but powerful technique can best be compared as feature
flags for loggers. The created debug logger is disabled by default but can be
enabled without changing a line of code, using flags.
The module is released in the public npm registry and can be installed by running:
npm install --save diagnostics
To create a new logger simply require
the diagnostics
module and call
the returned function. It accepts 2 arguments:
namespace
Required This is the namespace of your logger so we know if we need to
enable your logger when a debug flag is used. Generally you use the name of
your library or application as first root namespace. For example if you're
building a parser in a library (example) you would set namespace
example:parser
.options
An object with additional configuration for the logger.
following keys are recognized:
force
Force the logger to be enabled.colors
Colors are enabled by default for the logs, but you can set this
option to false
to disable it.const debug = require('diagnostics')('foo:bar:baz');
const debug = require('diagnostics')('foo:bar:baz', { options });
debug('this is a log message %s', 'that will only show up when enabled');
debug('that is pretty neat', { log: 'more', data: 1337 });
Unlike console.log
statements that add and remove during your development
lifecycle you create meaningful log statements that will give you insight in
the library or application that you're developing.
The created debugger uses different "adapters" to extract the debug flag out of the JavaScript environment. To learn more about enabling the debug flag in your specific environment click on one of the enabled adapters below.
Please note that the returned logger is fully configured out of the box, you do not need to set any of the adapters/modifiers your self, they are there for when you want more advanced control over the process. But if you want to learn more about that, read the next section.
There are 2 specific usage patterns for diagnostic
, library developers who
implement it as part of their modules and applications developers who either
use it in their application or are searching for ways to consume the messages.
With the simple log interface as discussed in the introduction section we make it easy for developers to add it as part of their libraries and applications, and with powerful API we allow infinite customization by allowing custom adapters, loggers and modifiers to ensure that this library maintains relevant. These methods not only allow introduction of new loggers, but allow you think outside the box. For example you can maintain a history of past log messages, and output those when an uncaught exception happens in your application so you have additional context
const diagnostics = require('diagnostics');
let index = 0;
const limit = 200;
const history = new Array(limit);
//
// Force all `diagnostic` loggers to be enabled.
//
diagnostics.force = process.env.NODE_ENV === 'prod';
diagnostics.set(function customLogger(meta, message) {
history[index]= { meta, message, now: Date.now() };
if (index++ === limit) index = 0;
//
// We're running a development build, so output.
//
if (meta.dev) console.log.apply(console, message);
});
process.on('uncaughtException', async function (err) {
await saveErrorToDisk(err, history);
process.exit(1);
});
The small snippet above will maintain a 200 limited FIFO (First In First Out) queue of all debug messages that can be referenced when your application crashes
When you require
the diagnostics
module you will be given a logger that is
optimized for development
so it can provide the best developer experience
possible.
The development logger enables all the adapters for your
JavaScript environment, adds a logger that outputs the messages to console.log
and registers our message modifiers so log messages will be prefixed with the
supplied namespace so you know where the log messages originates from.
The development logger does not have any adapter, modifier and logger enabled
by default. This ensures that your log messages never accidentally show up in
production. However this does not mean that it's not possible to get debug
messages in production. You can force
the debugger to be enabled, and
supply a custom logger.
const diagnostics = require('diagnostics');
const debug = debug('foo:bar', { force: true });
//
// Or enable _every_ diagnostic instance:
//
diagnostics.force = true;
WebPack has the concept of mode's which creates different
module.exports = {
mode: 'development' // 'production'
}
When you are building your app using the WebPack CLI you can use the --mode
flag:
webpack --mode=production app.js -o /dist/bundle.js
When you are running your app using Node.js
you should the NODE_ENV
environment variable to production
to ensure that you libraries that you
import are optimized for production.
NODE_ENV=production node app.js
The returned logger exposes some addition properties that can be used used in your application or library:
The returned logger will have a .enabled
property assigned to it. This boolean
can be used to check if the logger was enabled:
const debug = require('diagnostics')('foo:bar');
if (debug.enabled) {
//
// Do something special
//
}
This property is exposed as:
This is the namespace that you originally provided to the function.
const debug = require('diagnostics')('foo:bar');
console.log(debug.namespace); // foo:bar
This property is exposed as:
There are different builds available of diagnostics
, when you create a
production build of your application using NODE_ENV=production
you will be
given an optimized, smaller build of diagnostics
to reduce your bundle size.
The dev
and prod
booleans on the returned logger indicate if you have a
production or development version of the logger.
const debug = require('diagnostics')('foo:bar');
if (debug.prod) {
// do stuff
}
This property is exposed as:
Sets a new logger as default for all diagnostic
instances. The passed
argument should be a function that write the log messages to where ever you
want. It receives 2 arguments:
meta
An object with all the options that was provided to the original
logger that wants to write the log message as well as properties of the
debugger such as prod
, dev
, namespace
, enabled
. See API for
all exposed properties.args
An array of the log messages that needs to be written.const debug = require('diagnostics')('foo:more:namespaces');
debug.use(function logger(meta, args) {
console.log(meta);
console.debug(...args);
});
This method is exposed as:
diagnostics
module.The modify method allows you add a new message modifier to all diagnostic
instances. The passed argument should be a function that returns the passed
message after modification. The function receives 2 arguments:
message
, Array, the log message.options
, Object, the options that were passed into the logger when it was
initially created.const debug = require('diagnostics')('example:modifiers');
debug.modify(function (message, options) {
return messages;
});
This method is exposed as:
diagnostics
module.See modifiers for more information.
Adds a new adapter
to all diagnostic
instances. The passed argument
should be a function returns a boolean that indicates if the passed in
namespace
is allowed to write log messages.
const diagnostics = require('diagnostics');
const debug = diagnostics('foo:bar');
debug.use(function (namespace) {
return namespace === 'foo:bar';
});
This method is exposed as:
diagnostics
module.See adapters for more information.
To be as flexible as possible when it comes to transforming messages we've
come up with the concept of modifiers
which can enhance the debug messages.
This allows you to introduce functionality or details that you find important
for debug messages, and doesn't require us to add additional bloat to the
diagnostic
core.
For example, you want the messages to be prefixed with the date-time of when the log message occured:
const diagnostics = require('diagnostics');
diagnostics.modify(function datetime(args, options) {
args.unshift(new Date());
return args;
});
Now all messages will be prefixed with date that is outputted by new Date()
.
The following modifiers are shipped with diagnostics
and are enabled in
development mode only:
This modifier is enabled for all debug instances and prefixes the messages
with the name of namespace under which it is logged. The namespace is colored
using the colorspace
module which groups similar namespaces under the same
colorspace. You can have multiple namespaces for the debuggers where each
namespace should be separated by a :
foo
foo:bar
foo:bar:baz
For console based output the namespace-ansi
is used.
Adapters allows diagnostics
to pull the DEBUG
and DIAGNOSTICS
environment
variables from different sources. Not every JavaScript environment has a
process.env
that we can leverage. Adapters allows us to have different
adapters for different environments. It means you can write your own custom
adapter if needed as well.
The adapter
function should be passed a function as argument, this function
will receive the namespace
of a logger as argument and it should return a
boolean that indicates if that logger should be enabled or not.
const debug = require('diagnostics')('example:namespace');
debug.adapter(require('diagnostics/adapters/localstorage'));
The modifiers are only enabled for development
. The following adapters are
available are available:
This adapter is enabled for node.js
.
Uses the DEBUG
or DIAGNOSTICS
(both are recognized) environment variables to
pass in debug flag:
UNIX/Linux/Mac
DEBUG=foo* node index.js
Using environment variables on Windows is a bit different, and also depends on toolchain you are using:
Windows
set DEBUG=foo* & node index.js
Powershell
$env:DEBUG='foo*';node index.js
This adapter is enabled for browsers
.
This adapter uses the window.location.hash
of as source for the environment
variables. It assumes that hash is formatted using the same syntax as query
strings:
http://example.com/foo/bar#debug=foo*
It triggers on both the debug=
and diagnostics=
names.
This adapter is enabled for browsers
.
This adapter uses the localStorage
of the browser to store the debug flags.
You can set the debug flag your self in your application code, but you can
also open browser WebInspector and enable it through the console.
localStorage.setItem('debug', 'foo*');
It triggers on both the debug
and diagnostics
storage items. (Please note
that these keys should be entered in lowercase)
This adapter is enabled for react-native
.
This adapter uses the AsyncStorage
API that is exposed by the react-native
library to store and read the debug
or diagnostics
storage items.
import { AsyncStorage } from 'react-native';
AsyncStorage.setItem('debug', 'foo*');
Unlike other adapters, this is the only adapter that is async
so that means
that we're not able to instantly determine if a created logger should be
enabled or disabled. So when a logger is created in react-native
we initially
assume it's disabled, any message that send during period will be queued
internally.
Once we've received the data from the AsyncStorage
API we will determine
if the logger should be enabled, flush the queued messages if needed and set
all enabled
properties accordingly on the returned logger.
By default it will log all messages to console.log
in when the logger is
enabled using the debug flag that is set using one of the adapters.
FAQs
Tools for debugging your node.js modules and event loop
We found that diagnostics demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 3 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Research
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
Research
Security News
Attackers used a malicious npm package typosquatting a popular ESLint plugin to steal sensitive data, execute commands, and exploit developer systems.
Security News
The Ultralytics' PyPI Package was compromised four times in one weekend through GitHub Actions cache poisoning and failure to rotate previously compromised API tokens.