@boost/log
Advanced tools
Comparing version 2.0.1 to 2.1.0
@@ -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
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
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
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
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
35
712
1
Updated@boost/common@^2.2.0
Updated@boost/internal@^2.1.0
Updated@boost/translate@^2.1.0