@boost/log
Advanced tools
Comparing version
@@ -6,2 +6,22 @@ # Change Log | ||
## 2.1.0 - 2020-08-17 | ||
#### 🚀 Updates | ||
- Build packages with Rollup to support web and node targets. ([38cdad9](https://github.com/milesj/boost/commit/38cdad9)) | ||
#### 📘 Docs | ||
- Migrate to Docusaurus. (#105) ([24196b8](https://github.com/milesj/boost/commit/24196b8)), closes [#105](https://github.com/milesj/boost/issues/105) | ||
#### 📦 Dependencies | ||
- Update root dependencies. ([9c3203a](https://github.com/milesj/boost/commit/9c3203a)) | ||
**Note:** Version bump only for package @boost/log | ||
### 2.0.1 - 2020-07-29 | ||
@@ -8,0 +28,0 @@ |
612
lib/index.js
@@ -1,50 +0,568 @@ | ||
"use strict"; | ||
/** | ||
* @copyright 2020, Miles Johnson | ||
* @license https://opensource.org/licenses/MIT | ||
*/ | ||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { | ||
if (k2 === undefined) k2 = k; | ||
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); | ||
}) : (function(o, m, k, k2) { | ||
if (k2 === undefined) k2 = k; | ||
o[k2] = m[k]; | ||
})); | ||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { | ||
Object.defineProperty(o, "default", { enumerable: true, value: v }); | ||
}) : function(o, v) { | ||
o["default"] = v; | ||
'use strict'; | ||
Object.defineProperty(exports, '__esModule', { value: true }); | ||
function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; } | ||
var common = require('@boost/common'); | ||
var os = _interopDefault(require('os')); | ||
var util = _interopDefault(require('util')); | ||
var internal = require('@boost/internal'); | ||
var chalk = _interopDefault(require('chalk')); | ||
var path = _interopDefault(require('path')); | ||
var translate = require('@boost/translate'); | ||
var fs = _interopDefault(require('fs')); | ||
var zlib = _interopDefault(require('zlib')); | ||
function formatMetadata(metadata) { | ||
const items = []; | ||
const keys = Object.keys(metadata).sort(); | ||
keys.forEach(key => { | ||
items.push(`${key}=${metadata[key]}`); | ||
}); | ||
return `(${items.join(', ')})`; | ||
} | ||
function console$1(item) { | ||
let output = item.message; | ||
if (item.level !== 'log') { | ||
output = `${item.label} ${output}`; | ||
} | ||
return output; | ||
} | ||
function debug(item) { | ||
return `[${item.time.toISOString()}] ${item.level.toUpperCase()} ${item.message} ${formatMetadata({ ...item.metadata, | ||
host: item.host, | ||
name: item.name, | ||
pid: item.pid | ||
})}`; | ||
} | ||
function json(item) { | ||
return JSON.stringify(item); | ||
} | ||
function message(item) { | ||
return item.message; | ||
} | ||
var formats = /*#__PURE__*/Object.freeze({ | ||
__proto__: null, | ||
console: console$1, | ||
debug: debug, | ||
json: json, | ||
message: message | ||
}); | ||
var __importStar = (this && this.__importStar) || function (mod) { | ||
if (mod && mod.__esModule) return mod; | ||
var result = {}; | ||
if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); | ||
__setModuleDefault(result, mod); | ||
return result; | ||
var msg = translate.createTranslator('log', path.join(__dirname, '../res')); | ||
// In order of priority! | ||
const LOG_LEVELS = ['log', 'trace', 'debug', 'info', 'warn', 'error']; | ||
const DEFAULT_LABELS = { | ||
debug: chalk.gray(msg('log:levelDebug')), | ||
error: chalk.red(msg('log:levelError')), | ||
info: chalk.cyan(msg('log:levelInfo')), | ||
log: chalk.yellow(msg('log:levelLog')), | ||
trace: chalk.magenta(msg('log:levelTrace')), | ||
warn: chalk.yellow(msg('log:levelWarn')) | ||
}; | ||
var __exportStar = (this && this.__exportStar) || function(m, exports) { | ||
for (var p in m) if (p !== "default" && !exports.hasOwnProperty(p)) __createBinding(exports, m, p); | ||
}; | ||
var __importDefault = (this && this.__importDefault) || function (mod) { | ||
return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.StreamTransport = exports.RotatingFileTransport = exports.FileTransport = exports.ConsoleTransport = exports.Transport = exports.Logger = exports.formats = exports.createLogger = void 0; | ||
const createLogger_1 = __importDefault(require("./createLogger")); | ||
exports.createLogger = createLogger_1.default; | ||
const Logger_1 = __importDefault(require("./Logger")); | ||
exports.Logger = Logger_1.default; | ||
const Transport_1 = __importDefault(require("./Transport")); | ||
exports.Transport = Transport_1.default; | ||
const ConsoleTransport_1 = __importDefault(require("./transports/ConsoleTransport")); | ||
exports.ConsoleTransport = ConsoleTransport_1.default; | ||
const FileTransport_1 = __importDefault(require("./transports/FileTransport")); | ||
exports.FileTransport = FileTransport_1.default; | ||
const RotatingFileTransport_1 = __importDefault(require("./transports/RotatingFileTransport")); | ||
exports.RotatingFileTransport = RotatingFileTransport_1.default; | ||
const StreamTransport_1 = __importDefault(require("./transports/StreamTransport")); | ||
exports.StreamTransport = StreamTransport_1.default; | ||
const formats = __importStar(require("./formats")); | ||
const MAX_LOG_SIZE = 10485760; | ||
class Transport extends common.Contract { | ||
constructor(options) { | ||
super(options); | ||
this.levels = []; | ||
this.levels = this.options.levels; | ||
} | ||
blueprint({ | ||
array, | ||
func, | ||
string | ||
}) { | ||
return { | ||
eol: string(os.EOL), | ||
format: func().nullable(), | ||
levels: array(string().oneOf(LOG_LEVELS)) | ||
}; | ||
} | ||
/** | ||
* Format the log item into a message string, and append a trailing newline if missing. | ||
*/ | ||
format(item) { | ||
const { | ||
eol, | ||
format | ||
} = this.options; | ||
let output = typeof format === 'function' ? format(item) : debug(item); | ||
if (!output.endsWith(eol)) { | ||
output += eol; | ||
} | ||
return output; | ||
} | ||
/** | ||
* Write the formatted message according to the transport. | ||
*/ | ||
} | ||
class ConsoleTransport extends Transport { | ||
constructor(options) { | ||
super({ | ||
format: console$1, | ||
levels: LOG_LEVELS, | ||
...options | ||
}); | ||
} | ||
write(message, { | ||
level | ||
}) { | ||
// eslint-disable-next-line no-console | ||
console[level](message.trim()); | ||
} | ||
} | ||
var debug$1 = internal.createInternalDebugger('log'); | ||
class Logger extends common.Contract { | ||
constructor(options) { | ||
super(options); | ||
this.silent = false; | ||
const defaultLevel = internal.env('LOG_DEFAULT_LEVEL'); | ||
const maxLevel = internal.env('LOG_MAX_LEVEL'); | ||
debug$1('New logger "%s" created: %s %s', this.options.name, defaultLevel ? `${defaultLevel} level` : 'all levels', maxLevel ? `(max ${maxLevel})` : ''); | ||
} | ||
blueprint({ | ||
array, | ||
func, | ||
object, | ||
shape, | ||
string | ||
}) { | ||
return { | ||
labels: object(string()), | ||
metadata: object(), | ||
name: string().required().notEmpty(), | ||
transports: array(shape({ | ||
format: func().notNullable(), | ||
// eslint-disable-next-line react/forbid-prop-types | ||
levels: array(string()), | ||
write: func().notNullable() | ||
}), [new ConsoleTransport()]) | ||
}; | ||
} | ||
disable() { | ||
debug$1('Logger %s disabled', this.options.name); | ||
this.silent = true; | ||
} | ||
enable() { | ||
debug$1('Logger %s enabled', this.options.name); | ||
this.silent = false; | ||
} | ||
isAllowed(level, maxLevel) { | ||
if (!maxLevel) { | ||
return true; | ||
} // eslint-disable-next-line no-restricted-syntax | ||
for (const currentLevel of LOG_LEVELS) { | ||
if (currentLevel === level) { | ||
return true; | ||
} | ||
if (currentLevel === maxLevel) { | ||
break; | ||
} | ||
} | ||
return false; | ||
} | ||
log({ | ||
args = [], | ||
level, | ||
message, | ||
metadata = {} | ||
}) { | ||
const logLevel = level || internal.env('LOG_DEFAULT_LEVEL') || 'log'; | ||
if (this.silent || !this.isAllowed(logLevel, internal.env('LOG_MAX_LEVEL'))) { | ||
return; | ||
} | ||
const item = { | ||
host: os.hostname(), | ||
label: this.options.labels[logLevel] || DEFAULT_LABELS[logLevel] || '', | ||
level: logLevel, | ||
message: util.format(message, ...args), | ||
metadata: { ...this.options.metadata, | ||
...metadata | ||
}, | ||
name: this.options.name, | ||
pid: process.pid, | ||
time: new Date() | ||
}; | ||
this.options.transports.forEach(transport => { | ||
if (transport.levels.includes(item.level)) { | ||
void transport.write(transport.format(item), item); | ||
} | ||
}); | ||
} | ||
} | ||
function pipeLog(logger, level) { | ||
return (...args) => { | ||
let metadata = {}; | ||
let message = ''; | ||
if (common.isObject(args[0])) { | ||
metadata = args.shift(); | ||
} | ||
message = args.shift(); | ||
logger.log({ | ||
args, | ||
level, | ||
message, | ||
metadata | ||
}); | ||
}; | ||
} | ||
function createLogger(options) { | ||
const logger = new Logger(options); | ||
const log = pipeLog(logger); | ||
LOG_LEVELS.forEach(level => { | ||
Object.defineProperty(log, level, { | ||
value: pipeLog(logger, level) | ||
}); | ||
}); | ||
Object.defineProperty(log, 'disable', { | ||
value: () => logger.disable() | ||
}); | ||
Object.defineProperty(log, 'enable', { | ||
value: () => logger.enable() | ||
}); | ||
return log; | ||
} | ||
class FileTransport extends Transport { | ||
constructor(options) { | ||
super(options); | ||
this.path = void 0; | ||
this.stream = void 0; | ||
this.buffer = ''; | ||
this.draining = false; | ||
this.lastSize = 0; | ||
this.rotating = false; | ||
this.path = common.Path.resolve(this.options.path); | ||
this.checkFolderRequirements(); | ||
} | ||
blueprint(preds) { | ||
const { | ||
bool, | ||
instance, | ||
union, | ||
number, | ||
string | ||
} = preds; | ||
return { ...super.blueprint(preds), | ||
gzip: bool(), | ||
maxSize: number(MAX_LOG_SIZE).positive(), | ||
path: union([string(), instance(common.Path)], '').required() | ||
}; | ||
} | ||
/** | ||
* Close the file stream and trigger the callback when finished. | ||
*/ | ||
close(cb) { | ||
const onClose = () => { | ||
if (cb) { | ||
cb(); | ||
} | ||
this.stream = undefined; | ||
}; | ||
if (this.stream) { | ||
this.stream.once('finish', onClose).end(); | ||
} else { | ||
onClose(); | ||
} | ||
} | ||
/** | ||
* Open the file stream for writing. | ||
*/ | ||
open() { | ||
if (this.stream) { | ||
return this.stream; | ||
} | ||
this.stream = this.createStream(); | ||
if (this.path.exists()) { | ||
this.lastSize = fs.statSync(this.path.path()).size; | ||
} | ||
if (this.buffer) { | ||
const message = this.buffer; | ||
this.buffer = ''; | ||
this.write(message); | ||
} | ||
return this.stream; | ||
} | ||
/** | ||
* Write a message to the file stream, and rotate files once written if necessary. | ||
*/ | ||
write(message) { | ||
if (this.rotating) { | ||
this.buffer += message; | ||
return; | ||
} | ||
const stream = this.open(); | ||
const written = stream.write(message, 'utf8', () => { | ||
this.lastSize += Buffer.byteLength(message); | ||
this.checkIfNeedsRotation(); | ||
}); // istanbul ignore next | ||
if (!written) { | ||
this.draining = true; | ||
stream.once('drain', () => { | ||
this.draining = false; | ||
}); | ||
} | ||
} | ||
/** | ||
* Check that the parent folder exists and has the correct permissions. | ||
*/ | ||
checkFolderRequirements() { | ||
fs.mkdirSync(this.path.parent().path(), { | ||
recursive: true | ||
}); | ||
} | ||
/** | ||
* Check if we should change and rotate files because of max size. | ||
*/ | ||
checkIfNeedsRotation() { | ||
if (this.lastSize > this.options.maxSize) { | ||
this.closeStreamAndRotateFile(); | ||
} | ||
} | ||
/** | ||
* Open and create a file stream for the defined path. | ||
* Apply file size and gzip checks. | ||
*/ | ||
createStream() { | ||
const stream = fs.createWriteStream(this.path.path(), { | ||
encoding: 'utf8', | ||
flags: 'a' | ||
}); // Apply gzip compression to the stream | ||
if (this.options.gzip) { | ||
const gzip = zlib.createGzip(); | ||
gzip.pipe(stream); | ||
return gzip; | ||
} | ||
return stream; | ||
} | ||
/** | ||
* Return the file name with extension, of the newly rotated file. | ||
*/ | ||
getRotatedFileName() { | ||
return this.path.name(); | ||
} | ||
/** | ||
* Count the number of files within path directory that matches the given file name. | ||
*/ | ||
getNextIncrementCount(name) { | ||
const files = fs.readdirSync(this.path.parent().path()); // eslint-disable-next-line security/detect-non-literal-regexp | ||
const pattern = new RegExp(`^${name}.\\d+$`, 'u'); | ||
let count = 0; | ||
files.forEach(file => { | ||
if (file.match(pattern)) { | ||
count += 1; | ||
} | ||
}); | ||
return count; | ||
} | ||
/** | ||
* Close the open stream and attempt to rotate the file. | ||
*/ | ||
closeStreamAndRotateFile() { | ||
// istanbul ignore next | ||
if (this.draining || this.rotating) { | ||
return; | ||
} | ||
this.rotating = true; | ||
this.close(() => { | ||
this.rotateFile(); | ||
this.rotating = false; | ||
}); | ||
} | ||
/** | ||
* Rotate the current file into a new file with an incremented name. | ||
*/ | ||
rotateFile() { | ||
let fileName = this.getRotatedFileName(); | ||
if (this.options.gzip) { | ||
fileName += '.gz'; | ||
} | ||
fileName += `.${this.getNextIncrementCount(fileName)}`; | ||
fs.renameSync(this.path.path(), this.path.parent().append(fileName).path()); | ||
this.lastSize = 0; | ||
} | ||
} | ||
const DAYS_IN_WEEK = 7; | ||
class RotatingFileTransport extends FileTransport { | ||
constructor(...args) { | ||
super(...args); | ||
this.lastTimestamp = this.formatTimestamp(Date.now()); | ||
} | ||
blueprint(preds) { | ||
const { | ||
string | ||
} = preds; | ||
return { ...super.blueprint(preds), | ||
rotation: string().oneOf(['hourly', 'daily', 'weekly', 'monthly']) | ||
}; | ||
} | ||
/** | ||
* Format a `Date` object into a format used within the log file name. | ||
*/ | ||
formatTimestamp(ms) { | ||
const { | ||
rotation | ||
} = this.options; | ||
const date = new Date(ms); | ||
let timestamp = `${date.getFullYear()}${String(date.getMonth() + 1).padStart(2, '0')}`; | ||
if (rotation === 'monthly') { | ||
return timestamp; | ||
} // Special case, calculate the week manually and return, | ||
// but do not append so other rotations inherit! | ||
if (rotation === 'weekly') { | ||
const firstDay = new Date(date.getFullYear(), date.getMonth(), 1).getDay(); | ||
const offsetDate = date.getDate() + firstDay - 1; | ||
timestamp += `.W${Math.floor(offsetDate / DAYS_IN_WEEK) + 1}`; | ||
return timestamp; | ||
} | ||
timestamp += String(date.getDate()).padStart(2, '0'); | ||
if (rotation === 'daily') { | ||
return timestamp; | ||
} | ||
timestamp += `.${String(date.getHours()).padStart(2, '0')}`; | ||
return timestamp; | ||
} | ||
/** | ||
* @inheritdoc | ||
*/ | ||
checkIfNeedsRotation() { | ||
if (this.lastSize > this.options.maxSize || this.formatTimestamp(Date.now()) !== this.lastTimestamp) { | ||
this.closeStreamAndRotateFile(); | ||
} | ||
} | ||
/** | ||
* @inheritdoc | ||
*/ | ||
getRotatedFileName() { | ||
const name = this.path.name(true); | ||
const ext = this.path.ext(true); | ||
return `${name}-${this.lastTimestamp}.${ext}`; | ||
} | ||
/** | ||
* @inheritdoc | ||
*/ | ||
rotateFile() { | ||
super.rotateFile(); // Update timestamp to the new format | ||
this.lastTimestamp = this.formatTimestamp(Date.now()); | ||
} | ||
} | ||
class StreamTransport extends Transport { | ||
blueprint(preds) { | ||
const { | ||
func, | ||
shape | ||
} = preds; | ||
return { ...super.blueprint(preds), | ||
stream: shape({ | ||
write: func().required().notNullable() | ||
}) | ||
}; | ||
} | ||
write(message) { | ||
this.options.stream.write(message); | ||
} | ||
} | ||
exports.ConsoleTransport = ConsoleTransport; | ||
exports.DEFAULT_LABELS = DEFAULT_LABELS; | ||
exports.FileTransport = FileTransport; | ||
exports.LOG_LEVELS = LOG_LEVELS; | ||
exports.Logger = Logger; | ||
exports.MAX_LOG_SIZE = MAX_LOG_SIZE; | ||
exports.RotatingFileTransport = RotatingFileTransport; | ||
exports.StreamTransport = StreamTransport; | ||
exports.Transport = Transport; | ||
exports.createLogger = createLogger; | ||
exports.formats = formats; | ||
__exportStar(require("./constants"), exports); | ||
__exportStar(require("./types"), exports); |
@@ -1,3 +0,3 @@ | ||
import { LoggerFunction } from './types'; | ||
import type { LoggerFunction } from './index'; | ||
export declare function mockLogger(): LoggerFunction; | ||
//# sourceMappingURL=testing.d.ts.map |
@@ -1,17 +0,19 @@ | ||
"use strict"; | ||
/* eslint-disable jest/prefer-spy-on */ | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.mockLogger = void 0; | ||
'use strict'; | ||
Object.defineProperty(exports, '__esModule', { value: true }); | ||
/* eslint-disable unicorn/import-index, jest/prefer-spy-on */ | ||
function mockLogger() { | ||
const log = jest.fn(); | ||
log.disable = jest.fn(); | ||
log.enable = jest.fn(); | ||
log.debug = jest.fn(); | ||
log.error = jest.fn(); | ||
log.log = jest.fn(); | ||
log.info = jest.fn(); | ||
log.trace = jest.fn(); | ||
log.warn = jest.fn(); | ||
return log; | ||
const log = jest.fn(); | ||
log.disable = jest.fn(); | ||
log.enable = jest.fn(); | ||
log.debug = jest.fn(); | ||
log.error = jest.fn(); | ||
log.log = jest.fn(); | ||
log.info = jest.fn(); | ||
log.trace = jest.fn(); | ||
log.warn = jest.fn(); | ||
return log; | ||
} | ||
exports.mockLogger = mockLogger; |
{ | ||
"name": "@boost/log", | ||
"version": "2.0.1", | ||
"version": "2.1.0", | ||
"release": "1594765247526", | ||
@@ -24,5 +24,5 @@ "description": "Lightweight level based logging system.", | ||
"dependencies": { | ||
"@boost/common": "^2.1.0", | ||
"@boost/internal": "^2.0.0", | ||
"@boost/translate": "^2.0.1", | ||
"@boost/common": "^2.2.0", | ||
"@boost/internal": "^2.1.0", | ||
"@boost/translate": "^2.1.0", | ||
"chalk": "^4.1.0" | ||
@@ -34,3 +34,3 @@ }, | ||
}, | ||
"gitHead": "fcbd70d657c873083d0c739128c0cc1b16978be2" | ||
"gitHead": "7982950ce80d97d1815234ee0d66b5dc4ac3a843" | ||
} |
@@ -22,3 +22,3 @@ # Logging - Boost | ||
- Handles default and max logging levels. | ||
- Customizable colors, labels, and writable streams. | ||
- Customizable transports with writable streams. | ||
- Toggleable logging at runtime. | ||
@@ -34,2 +34,2 @@ | ||
[https://milesj.gitbook.io/boost/log](https://milesj.gitbook.io/boost/log) | ||
[https://boostlib.dev/docs/log](https://boostlib.dev/docs/log) |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
38979
-17.69%35
-25.53%712
-14.83%1
Infinity%Updated
Updated
Updated