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.
'hixtory' is design to be a flexible, fast and extensible universal logging library with multiple appenders.
There is many good logging libraries on the market like winston
and log4js
. Why did we created a new one? Answer is simple, they are not enough for our needs. We created hixtory
to use in our enterprise level projects as logging core.
The key point of hixtory
is maximum flexibility, which needs by multi modular applications like micro service SaaS servers.
hixtory
has Appenders
which work like transform streams. winston
has Transports in same manner. Unlike winston
, hixtory
's appenders can be used by multiple loggers and each logger can use same appender with different configuration.
Both winston
and hixtory
supports creating child loggers. winston
's child loggers can not be configured and just allows adding some meta data.
hixtory
's child loggers can be reconfigured. You can disable or reconfigure any ancestor appender, overwrite an appender or can add additional appenders.
const {createLogger, appenders, formatters} = require('hixtory');
const logger = createLogger({
level: 'info', // Default level is info
defaultMeta: {pid: 1},
label: 'main',
levels: 'npm', // Use one of preconfigured levels
targets: {
console: {
appender: new appenders.ConsoleAppender(),
format: formatters.printConsole() // Format data using console formatter which is an combination of many formats
},
file: {
appender: new appenders.RollingFileAppender({
filename: __dirname + '/errorlog.log'
}),
level: 'error', // Overwrite default level
format: formatters.printFile() // Format data using file formatter which is an combination of many formats
}
}
});
// Create a child logger
const child1 = logger.createChild({
level: 'warn', // Default level is warn
label: 'child1',
targets: {
file: false, // disable file appender
console: { // overwrite console appender
level: 'debug',
format: [
// Add data to metadata for just this appender
formatters.add({
label: 'child_debug1', // Overwrite label for this appender
debugLevel: process.env.DEBUG
}),
formatters.upperCase('level', 'label'), // Convert level and label to uppercase characters
formatters.printJson()
] // User json formatter for appender
},
dateFile: {
appender: new appenders.DateFileAppender({
filename: __dirname + '/child.log'
}),
level: 'error', // Overwrite level
format: formatters.printJson(),
filter: (meta) => {
// Only log if message includes ID:1001
return meta.message.includes('ID:1001!');
}
}
}
});
child1.info('Application starting');
child1.meta({service: 'db'}).info('Starting service');
Hixtory module exposes Logger
class and createLogger()
method to construct a new logger.
new Logger([options:Object])
createLogger([options:Object])
options
: If options
argument is present, configure method will be called on construct.const {createLogger} = require('hixtory');
const logger = createLogger({
levels: 'syslog',
level: 'info', // Default level is info
targets: {
console: new appenders.ConsoleAppender()
}
});
const {Logger} = require('hixtory');
const logger = new Logger({
level: 'info', // Default level is info
targets: {
console: new appenders.ConsoleAppender()
}
});
Logger class has the following properties.
Name | Type | R/W | Description |
---|---|---|---|
children | Array<Logger> | r | Returns array of child loggers |
colors | {string: string} | r | Returns default color values |
isChild | boolean | r | Returns if logger is a child logger |
label | string | r/w | Gets or sets default label of logger. Targets may overwrite this value. |
level | string | r/w | Gets or sets default logging level. Targets may overwrite this value. |
levels | object | r | Returns Array of levels |
parent | Logger | r | If this logger is a child returns parent logger, null otherwise |
root | Logger | r | If this logger is a child returns root logger, self otherwise |
targets | {string: Object} | r | Returns logging targets |
Logger.prototype.configure() method is used to configure the logger. Configuration can be changed in runtime.
logger.configure(options:Object)
options:Object
: An object representing options
defaultMeta:Object
(optional): An object representing default meta-data of each log.levels:Array<string>
(optional): Array of levels ordering from high priority to low.label:string
(optional): A string representing default label value of each log.level:string
(optional): A string representing default level of targets. Default: info
levels:string
(optional): Name of predefined levels descriptor (hixtory,npm,syslog etc). Note that predefined levels also contains color info.levels:Array<String>
(optional): Array of levels ordering from high priority to low.colors:Object
(optional): An object representing default color valuestargets:Object
(optional): An object representing log targetsconst logger = new Logger({
defaultMeta: {id: 1},
levels: ['emerg', 'alert', 'crit', 'error', 'warning', 'notice', 'info', 'debug'],
level: 'info',
label: 'MAIN',
colors: {
emerg: 'yellow bgRed',
alert: 'yellow bgRed',
crit: 'red bgYellow',
error: 'red',
warning: 'yellow',
notice: 'yellow',
info: 'green',
verbose: 'cyan',
debug: 'blue'
},
targets: {
console: {
appender: new appenders.ConsoleAppender(),
label: 'CMAIN', // over-write label
level: 'silly',
format: Hixtory.formatters.printConsole()
},
file: {
appender: new appenders.DateFileAppender(),
level: 'error',
format: Hixtory.formatters.printFile()
}
}
});
Logger.prototype.addTarget() method is used to add a new target the logger in runtime.
logger.addTarget(name:string, options:Object): Logger
name:string
: Name of the target.options:Object
: An object representing options
appender:Appender
(optional): An Appender instance. If value is not present, logger will make a deep lookup for parent targets till an appender instance found.enabled:boolean
(optional): If true, logging will be enabled for this target, otherwise no log will be written to appender. Default: trueformat:Array<Function>
(optional): An array of formatter methodslevel:string
(optional): Logging level for this target. If value is not present, logger will make a deep lookup for parent targets. If no value found, logger's level will be used.filter:Function
(optional): A function for filtering logs. If function returns true log will be written, otherwise will be ignored.logger.addTarget('mylogs', {
appender: new appenders.ConsoleAppender(),
label: 'CMAIN', // overwrite label
level: 'silly', // overwrite level
format: [
Hixtory.formatters.timestamp('YYYYMMDDHHmmss'),
Hixtory.formatters.upperCase('level', 'label'),
Hixtory.formatters.add({sectionId: 'section1'})
],
filter: (meta) => {
// Ignore messages which includes "Server error" string
return !meta.message.includes('Server error');
}
})
Logger.prototype.removeTarget() method is used to remove a target from logger in runtime.
logger.removeTarget(name:string): Logger
name:string
: Name of the target.
Return: This method returns this Logger for method chaining
Logger.prototype.createChild() method is used to create a new child Logger in runtime.
logger.createChild(options:Object): Logger
name:string
: Name of the target.options:Object
: An object representing options
label:string
(optional): A string representing default label value of each log.level:string
(optional): A string representing default level of targets. Default: info
targets:Object
(optional): An object representing log targetsconst logger = new Logger({
level: 'error',
targets: {
console: {
appender: new appenders.ConsoleAppender(),
level: 'info',
format: Hixtory.formatters.printConsole()
},
file: {
appender: new appenders.DateFileAppender(),
format: Hixtory.formatters.printFile()
}
}
});
logger.createChild({
label: 'child1', // overwrite label
level: 'warn', // overwrite level
targets: {
// extend console target from parent
console: {
level: 'debug' // Child logger will use debug level for console appender
},
file: {
enabled: false // Child logger will not log to file of parent logger
},
file2: { // Child logger will log to file using json formatter
appender: new appenders.DateFileAppender(),
format: Hixtory.formatters.json()
}
}
})
Logger.prototype.setLevels() method is used to set logging levels in runtime.
logger.setLevels(name:string): Logger
name:string
: Name of predefined levels descriptor. Note that predefined levels also contains color info.
Return: This method returns this Logger for method chaining
descriptor | Levels (from high priority to low) |
---|---|
hixtory | fatal, error, warn, info, verbose, debug, trace |
cli | error, warn, help, data, info, debug, prompt, verbose, input, silly |
npm | error, warn, info, http, verbose, debug, silly |
syslog | emerg, alert, crit, error, warning, notice, info, debug |
logger.setLevels('syslog');
logger.setLevels(levels:Array<string>): Logger
levels:Array<String>
: Array of levels ordering from high priority to low.
Return: This method returns this Logger for method chaining
logger.setLevels(['emerg', 'alert', 'crit', 'error', 'warning',
'notice', 'info', 'debug']);
Logger.prototype.setColors() method is used to set default colors in runtime.
logger.setColors(colors:object): Logger
colors:object
: An object representing default color values.
Return: This method returns this Logger for method chaining
logger.setColors({
emerg: 'yellow bgRed',
alert: 'yellow bgRed',
crit: 'red bgYellow',
error: 'red',
warning: 'yellow',
notice: 'yellow',
info: 'green',
verbose: 'cyan',
debug: 'blue'
});
Writing log messages to appenders can be accomplished in one of two ways. You can pass a level name the Logger.prototype.log() method or use the level specified methods.
logger.log(level:string, message: string, ...args:*): Logger
level:string
: Name of the log level
message:string
: Message text
...args:*
: Arguments to format message with util.format()
Return: This method returns this Logger for method chaining
logger.log('error', 'Any error message. Code %s', 'E001');
logger.error('Any error message. Code %s', 'E001');
logger.error(new Error('Any error message'));
logger.info('Info: %s', 'This is an info message')
.debug('%j', {debugdata: 12334});
Logger has a meta()
method to merge meta-data to log. This method allows over-writing and adding properties to meta-data.
logger.meta({id: 2, name: 'name1'}).info('Logging message');
logger.meta({label: 'LabelOverwrite'}).info('Logging message');
Logger.prototype.close() method is used to close the logger and its children.
close
method also closes all appenders if appender is not attached to other logger.
logger.close(): Promise
logger.close().then(() => process.exit());
Formatters are functions which process output data before written to appender.
Formatters are functions used by appenders to transform log data for output. Formatters can modify output data properties or return a new value.
function(metadata, outdata, logger, appender)
metadata:object
: An object representing original log data. The metadata object is frozen and can not be modified.outdata:*
: A clone of metadata or any value returned by previous formatter.logger:Logger
: The Logger instanceappender:Appender
: The Appender instancefunction customFormatter(metadata, outdata, logger, appender) {
outdata.level = metadata.level.toUpperCase();
outdata.message = outdata.message+ ': This is hixtory';
}
const logger = createLogger({
targets: {
console: {
appender: new appenders.ConsoleAppender(),
format: customFormatter
}
}
});
logger.info('Hello world');
{"level":"INFO","message":"Hello world: This is hixtory"}
function customFormatter(metadata, outdata, logger, appender) {
return outdata.level + '-' + outdata.message;
}
const logger = createLogger({
targets: {
console: {
appender: new appenders.ConsoleAppender(),
format: customFormatter
}
}
});
logger.info('Hello world');
info-Hello world
function customFormatter(metadata, outdata, logger, appender) {
return {
lvl: outdata.level,
msg: outdata.message
};
}
const logger = createLogger({
targets: {
console: {
appender: new appenders.ConsoleAppender(),
format: customFormatter
}
}
});
logger.info('Hello world');
{"lvl":"info","msg":"Hello world"}
It is possible to use an Array as formatter stack to perform propagation. Every Appender will call all formatter functions in the stack before writing to its target.
const logger = createLogger({
targets: {
console: {
appender: new appenders.ConsoleAppender(),
format: [
// Add timestamp property
(meta, out) => {
out.time = new Date();
},
// format timestamp property
(meta, out) => {
out.time = fecha.format(out.time, 'YYYY-MM-DD');
},
// Convert object to string
(meta, out) => {
return out.time + '|' + out.level + '|' + out.message;
},
// Modify output string
(meta, out) => {
return '>> ' + out;
}
]
}
}
});
logger.info('Hello world');
>> 2019-01-11|info|Hello world
Hixtory has many built-in formatter functions to help you writing logs with desired format. All build-in formatters can be accessed within Hixtory.formatters
.
You can check additional Built-in Formatters documentation for details.
>= 6.x
;FAQs
Most flexible and fast logging library for NodeJS
The npm package hixtory receives a total of 3 weekly downloads. As such, hixtory popularity was classified as not popular.
We found that hixtory demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 2 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.