backtrace-service
Advanced tools
Comparing version 1.3.0-beta.0 to 1.3.0-beta.1
@@ -7,12 +7,2 @@ /// <reference types="node" /> | ||
import { IDescriptorOpts } from './model/descriptorOptions'; | ||
/** | ||
* getBackupConfig fetches a config file provided by the service itself. | ||
* @param serviceName - service name - for example: share/saml/comments | ||
*/ | ||
export declare function getBackupConfig(serviceName: string): object; | ||
/** | ||
* getProperConfig fetches from the expected place on the machine (outside the service). | ||
* @param serviceName - service name - for example: share/saml/comments | ||
*/ | ||
export declare function getProperConfig(serviceName: string): object | undefined; | ||
export declare function listenDescriptor(descr: IServiceDescriptor, app: http.RequestListener, credentials: https.ServerOptions | undefined, callback: () => void | undefined): void; | ||
@@ -27,7 +17,1 @@ export declare function getCoronerConfig(): ICoronerConfig; | ||
export declare function getDescriptor(serviceName: string, defaultPort: number, opts?: IDescriptorOpts): IServiceDescriptor; | ||
/** | ||
* getConfig fetches from the expected place on the machine (outside the service). | ||
* If configuration doesn't exists then getConfig method will try to fetch configuration from internal service path | ||
* @param serviceName - service name - for example: share/saml/comments | ||
*/ | ||
export declare function getConfig(serviceName: string): object; |
@@ -20,3 +20,2 @@ "use strict"; | ||
var url = __importStar(require("url")); | ||
var logger_1 = require("../log/logger"); | ||
// XXX: better default? | ||
@@ -34,32 +33,2 @@ var defaultCoronerdHostname = '0.0.0.0'; | ||
} | ||
/** | ||
* getBackupConfig fetches a config file provided by the service itself. | ||
* @param serviceName - service name - for example: share/saml/comments | ||
*/ | ||
function getBackupConfig(serviceName) { | ||
try { | ||
var backupJson = readJSONFile(process.cwd() + "/" + serviceName + ".conf"); | ||
return JSON.parse(backupJson); | ||
} | ||
catch (error) { | ||
logger_1.Logger.getLogger().error(error); | ||
return { | ||
bind: {}, | ||
}; | ||
} | ||
} | ||
exports.getBackupConfig = getBackupConfig; | ||
/** | ||
* getProperConfig fetches from the expected place on the machine (outside the service). | ||
* @param serviceName - service name - for example: share/saml/comments | ||
*/ | ||
function getProperConfig(serviceName) { | ||
var scPath = "/etc/backtrace/" + serviceName + "/" + serviceName + ".conf"; | ||
var serviceConfigJson = readJSONFile(scPath); | ||
if (!serviceConfigJson) { | ||
logger_1.Logger.getLogger().warn("No configuration file found at " + scPath + ".\n"); | ||
} | ||
return serviceConfigJson; | ||
} | ||
exports.getProperConfig = getProperConfig; | ||
function getCoronerdHostname(crcfg) { | ||
@@ -109,4 +78,5 @@ var alldescPath = process.env.ALL_DESCRIPTOR_PATH || crcfg.varprefix + "/_all.json"; | ||
if (cfg.json.svclayer) { | ||
if (Array.isArray(cfg.json.svclayer.descriptorpaths) && | ||
cfg.json.svclayer.descriptorpaths.length > 0) { | ||
var descriptionExists = Array.isArray(cfg.json.svclayer.descriptorpaths) && | ||
cfg.json.svclayer.descriptorpaths.length > 0; | ||
if (descriptionExists) { | ||
/* | ||
@@ -217,15 +187,2 @@ * Prefer the last element as it will customarily be one that the | ||
} | ||
/** | ||
* getConfig fetches from the expected place on the machine (outside the service). | ||
* If configuration doesn't exists then getConfig method will try to fetch configuration from internal service path | ||
* @param serviceName - service name - for example: share/saml/comments | ||
*/ | ||
function getConfig(serviceName) { | ||
var config = getProperConfig(serviceName); | ||
if (!config) { | ||
config = getBackupConfig(serviceName); | ||
} | ||
return config; | ||
} | ||
exports.getConfig = getConfig; | ||
//# sourceMappingURL=config.js.map |
@@ -44,3 +44,2 @@ "use strict"; | ||
var crypto_1 = require("crypto"); | ||
var https_1 = __importDefault(require("https")); | ||
var responseResult_1 = require("../model/responseResult"); | ||
@@ -137,3 +136,2 @@ /** | ||
.get("" + url + prefix + "api/config", { | ||
httpsAgent: new https_1.default.Agent({ rejectUnauthorized: false }), | ||
headers: { | ||
@@ -140,0 +138,0 @@ 'X-Service-Name': _this.descr.name, |
@@ -24,3 +24,3 @@ /** | ||
export { IServiceDescriptor } from './model/serviceDescriptor'; | ||
export { getBackupConfig, getConfig, getDescriptor, listenDescriptor, getProperConfig, } from './config/config'; | ||
export { getDescriptor, listenDescriptor, } from './config/config'; | ||
export { ICoronerDescriptor } from './config/model/coronerDescriptor'; | ||
@@ -27,0 +27,0 @@ export { IDescriptorOpts } from './config/model/descriptorOptions'; |
@@ -21,7 +21,4 @@ "use strict"; | ||
var config_1 = require("./config/config"); | ||
exports.getBackupConfig = config_1.getBackupConfig; | ||
exports.getConfig = config_1.getConfig; | ||
exports.getDescriptor = config_1.getDescriptor; | ||
exports.listenDescriptor = config_1.listenDescriptor; | ||
exports.getProperConfig = config_1.getProperConfig; | ||
/** | ||
@@ -28,0 +25,0 @@ * Backtrace service initialization methods |
import { BacktraceClientOptions, IBacktraceClientOptions } from 'backtrace-node'; | ||
export declare class Logger { | ||
private readonly logLevel; | ||
private readonly sendToRemoteServer; | ||
static getLogger(): Logger; | ||
@@ -10,3 +8,3 @@ static initializeBacktrace(opts: BacktraceClientOptions | IBacktraceClientOptions): void; | ||
private transportLevelNumber; | ||
constructor(logLevel?: string, sendToRemoteServer?: boolean); | ||
constructor(logLevel?: string); | ||
trace(message: string): void; | ||
@@ -13,0 +11,0 @@ info(message: string): void; |
"use strict"; | ||
var __importStar = (this && this.__importStar) || function (mod) { | ||
if (mod && mod.__esModule) return mod; | ||
var result = {}; | ||
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; | ||
result["default"] = mod; | ||
return result; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
var backtrace_node_1 = require("backtrace-node"); | ||
var winston_1 = require("winston"); | ||
var winston_1 = __importStar(require("winston")); | ||
var Logger = /** @class */ (function () { | ||
function Logger(logLevel, sendToRemoteServer) { | ||
function Logger(logLevel) { | ||
if (logLevel === void 0) { logLevel = 'debug'; } | ||
if (sendToRemoteServer === void 0) { sendToRemoteServer = false; } | ||
this.logLevel = logLevel; | ||
this.sendToRemoteServer = sendToRemoteServer; | ||
// #define SD_EMERG "<0>" /* system is unusable */ | ||
// transportLevel definition that Logger class respect: | ||
// #define SD_EMERG "<0>" /* system is unusable */ | ||
// #define SD_ALERT "<1>" /* action must be taken immediately */ | ||
@@ -46,15 +51,20 @@ // #define SD_CRIT "<2>" /* critical conditions */ | ||
Logger.initializeBacktrace = function (opts) { | ||
backtrace_node_1.initialize(opts); | ||
try { | ||
backtrace_node_1.initialize(opts); | ||
} | ||
catch (err) { | ||
this.getLogger().warn("Cannot initialize Backtrace. Reason: " + err); | ||
} | ||
}; | ||
Logger.prototype.trace = function (message) { | ||
winston_1.verbose(this.formatScope(message)); | ||
winston_1.default.verbose(this.formatScope(message)); | ||
}; | ||
Logger.prototype.info = function (message) { | ||
winston_1.info(this.formatScope(message)); | ||
winston_1.default.info(this.formatScope(message)); | ||
}; | ||
Logger.prototype.warn = function (message) { | ||
winston_1.warn(this.formatScope(message)); | ||
winston_1.default.warn(this.formatScope(message)); | ||
}; | ||
Logger.prototype.debug = function (message) { | ||
winston_1.debug(this.formatScope(message)); | ||
winston_1.default.debug(this.formatScope(message)); | ||
}; | ||
@@ -64,9 +74,10 @@ Logger.prototype.error = function (data) { | ||
var client = backtrace_node_1.getBacktraceClient(); | ||
if (client && this.sendToRemoteServer === true) { | ||
if (client) { | ||
client.reportSync(exception); | ||
} | ||
winston_1.error(this.formatScope(exception.message), [winston_1.error]); | ||
winston_1.default.error(this.formatScope(exception.message), [exception]); | ||
}; | ||
Logger.prototype.formatScope = function (msg) { | ||
return "<" + this.transportLevelNumber + "> " + this.transportLevel + ":\n [" + new Date().toLocaleString() + "] " + msg; | ||
// tslint:disable-next-line:max-line-length | ||
return "<" + this.transportLevelNumber + ">" + this.transportLevel + ":[" + new Date().toLocaleString() + "] " + msg; | ||
}; | ||
@@ -73,0 +84,0 @@ return Logger; |
import express from 'express'; | ||
export declare class MetricsStorage { | ||
private opts; | ||
private readonly uuid; | ||
private readonly secret; | ||
private opts?; | ||
private logger?; | ||
@@ -10,7 +12,6 @@ beforeSend?: () => Promise<void>; | ||
private _data; | ||
constructor(opts: { | ||
uuid: string; | ||
secret: string; | ||
interval?: number; | ||
}, app?: express.Express, logger?: { | ||
constructor(uuid: string, secret: string, opts?: { | ||
interval?: number | undefined; | ||
hostUrl?: string | undefined; | ||
} | undefined, app?: express.Express, logger?: { | ||
warn: (msg: string) => void | Promise<void>; | ||
@@ -29,4 +30,4 @@ } | undefined); | ||
addMetric(name: string, value: number): void; | ||
private registerRquestLatency; | ||
private registerRequestLatency; | ||
private send; | ||
} |
@@ -44,21 +44,29 @@ "use strict"; | ||
var MetricsStorage = /** @class */ (function () { | ||
function MetricsStorage(opts, app, logger) { | ||
function MetricsStorage(uuid, secret, opts, app, logger) { | ||
var _this = this; | ||
this.uuid = uuid; | ||
this.secret = secret; | ||
this.opts = opts; | ||
this.logger = logger; | ||
this._hostUrl = 'https://api.circonus.com/module/httptrap/'; | ||
this._url = "" + this._hostUrl + this.opts.uuid + "/" + this.opts.secret; | ||
this._url = "" + this._hostUrl + this.uuid + "/" + this.secret; | ||
this._hostName = os_1.default.hostname().split('.')[0]; | ||
this._data = {}; | ||
if (!opts) { | ||
throw new Error('opts object is required'); | ||
} | ||
if (!opts.uuid || !opts.secret) { | ||
if (!this.uuid || !this.secret) { | ||
if (logger) { | ||
logger.warn('Metrics Uuid or secret is not defined. Metrics will be disabled'); | ||
logger.warn('Opts (or uuid/secret) object is required. Metrics are disabled'); | ||
} | ||
else { | ||
console.warn('Opts (or uuid/secret) object is required. Metrics are disabled'); | ||
} | ||
return; | ||
} | ||
// override default circonus url. | ||
// different url might be useful in case | ||
// when we will prepare our own metrics client | ||
if (this.opts && this.opts.hostUrl) { | ||
this._hostUrl = this.opts.hostUrl; | ||
} | ||
if (app) { | ||
app.use(function (req, res, next) { return _this.registerRquestLatency(req, res, next); }); | ||
app.use(function (req, res, next) { return _this.registerRequestLatency(req, res, next); }); | ||
} | ||
@@ -82,3 +90,3 @@ this.addMetric('birthday', Date.now()); | ||
}); | ||
}); }, opts.interval || 1000); | ||
}); }, (this.opts && this.opts.interval) || 1000); | ||
} | ||
@@ -90,2 +98,6 @@ /** | ||
MetricsStorage.prototype.add = function (source) { | ||
// metrics are disabled because of missing secret and uuid parameters | ||
if (!this.uuid || !this.secret) { | ||
return; | ||
} | ||
for (var key in source) { | ||
@@ -104,2 +116,6 @@ if (source.hasOwnProperty(key)) { | ||
MetricsStorage.prototype.addMetric = function (name, value) { | ||
// metrics are disabled because of missing secret and uuid parameters | ||
if (!this.uuid || !this.secret) { | ||
return; | ||
} | ||
name = name + "|ST[hostname:" + this._hostName + "]"; | ||
@@ -109,3 +125,3 @@ this._data[name] = this._data[name] || { _type: 'n', _value: [] }; | ||
}; | ||
MetricsStorage.prototype.registerRquestLatency = function (req, res, next) { | ||
MetricsStorage.prototype.registerRequestLatency = function (req, res, next) { | ||
var _this = this; | ||
@@ -112,0 +128,0 @@ if (!req.path) { |
import { Logger } from './../log/logger'; | ||
export interface IAppOptions { | ||
name: string; | ||
port: number; | ||
confPath?: string; | ||
remoteLogger?: boolean; | ||
logLevel?: 'error' | 'warn' | 'info' | 'verbose' | 'debug'; | ||
logger?: Logger; | ||
skipServiceLayerIntegation?: boolean; | ||
} |
@@ -18,4 +18,2 @@ import { IBacktraceClientOptions } from 'backtrace-node'; | ||
export interface ILoggerOptions { | ||
remoteReport: boolean; | ||
backtraceUrl: string; | ||
logLevel: string; | ||
@@ -22,0 +20,0 @@ } |
import express from 'express'; | ||
import { IdentityManager, IServiceDescriptor } from '..'; | ||
import { IdentityManager } from '..'; | ||
import { MetricsStorage } from '../metrics/metricsStorage'; | ||
@@ -7,2 +7,4 @@ import { IAppOptions } from '../model/appOptions'; | ||
export declare class BacktraceService<T extends {}> { | ||
private readonly name; | ||
private readonly port; | ||
private readonly _opts; | ||
@@ -18,4 +20,4 @@ /** | ||
*/ | ||
static bootstrap<T extends {}>(opts: IAppOptions, app?: express.Express): BacktraceService<T>; | ||
static readConfiguration<T>(name: string, port: number): IConfiguration<T>; | ||
static bootstrap<T extends {}>(name: string, port: number, opts: IAppOptions, app?: express.Express): BacktraceService<T>; | ||
static readConfiguration<T = {}>(name: string, port: number, path?: string): IConfiguration<T>; | ||
/** | ||
@@ -35,5 +37,6 @@ * Setup popular Backtrace middlewares | ||
*/ | ||
static setupServiceLayer(app: express.Express, descriptor?: IServiceDescriptor): void; | ||
static setupServiceLayer(app: express.Express, name?: string, port?: number, coronerdCallback?: (param: string) => void): void; | ||
private static _service; | ||
readonly configuration: IConfiguration<T>; | ||
readonly coronerdAddress: Set<string>; | ||
identityManager: IdentityManager; | ||
@@ -44,5 +47,5 @@ readonly app: express.Express; | ||
private _descriptor; | ||
constructor(_opts: IAppOptions, app?: express.Express); | ||
constructor(name: string, port: number, _opts: IAppOptions, app?: express.Express); | ||
start(): void; | ||
private prepareServiceLayer; | ||
} |
@@ -16,13 +16,18 @@ "use strict"; | ||
var BacktraceService = /** @class */ (function () { | ||
function BacktraceService(_opts, app) { | ||
function BacktraceService(name, port, _opts, app) { | ||
this.name = name; | ||
this.port = port; | ||
this._opts = _opts; | ||
this._logger = _opts.logger ? _opts.logger : new logger_1.Logger(_opts.logLevel); | ||
this.coronerdAddress = new Set(); | ||
this.app = app ? app : express_1.default(); | ||
this.configuration = BacktraceService.readConfiguration(_opts.name, _opts.port); | ||
if (!!_opts.remoteLogger === true || | ||
(this.configuration.backtrace && | ||
this.configuration.backtrace.remoteLog === true)) { | ||
this.configuration = BacktraceService.readConfiguration(this.name, this.port, this._opts.confPath); | ||
var logLevel = this.configuration.logger && this.configuration.logger.logLevel | ||
? this.configuration.logger.logLevel | ||
: undefined; | ||
this._logger = _opts.logger ? _opts.logger : new logger_1.Logger(logLevel); | ||
if (this.configuration.backtrace && | ||
this.configuration.backtrace.remoteLog === true) { | ||
logger_1.Logger.initializeBacktrace(this.configuration.backtrace); | ||
} | ||
this.metrics = new metricsStorage_1.MetricsStorage(this.configuration.metrics, this.app); | ||
this.metrics = new metricsStorage_1.MetricsStorage(this.configuration.metrics.secret, this.configuration.metrics.uuid, undefined, this.app); | ||
this.prepareServiceLayer(); | ||
@@ -40,13 +45,13 @@ BacktraceService._service = this; | ||
*/ | ||
BacktraceService.bootstrap = function (opts, app) { | ||
if (!opts || !opts.port || !opts.name) { | ||
throw new Error("Invalid arguments. Name: " + opts.name + ", port: " + opts.port); | ||
BacktraceService.bootstrap = function (name, port, opts, app) { | ||
if (!port || !name) { | ||
throw new Error("Invalid arguments. Name: " + name + ", port: " + port); | ||
} | ||
var service = new BacktraceService(opts, app); | ||
var service = new BacktraceService(name, port, opts, app); | ||
app = service.app; | ||
this.setupAuth(app); | ||
this.setupMiddlewares(app); | ||
if (opts.skipServiceLayerIntegation !== true) { | ||
this.setupServiceLayer(app, service._descriptor); | ||
} | ||
this.setupServiceLayer(app, name, port, function (ip) { | ||
service.coronerdAddress.add(ip); | ||
}); | ||
app.on('close', function () { | ||
@@ -57,5 +62,8 @@ service._logger.warn('server is closing'); | ||
}; | ||
BacktraceService.readConfiguration = function (name, port) { | ||
BacktraceService.readConfiguration = function (name, port, path) { | ||
var serviceConfiguration = new serviceConfiguration_1.ServiceConfiguration(name, port); | ||
return serviceConfiguration.loadConfiguration(); | ||
if (!path) { | ||
path = serviceConfiguration.getProjectSettingsPath(); | ||
} | ||
return serviceConfiguration.loadConfiguration(path); | ||
}; | ||
@@ -72,3 +80,3 @@ /** | ||
app.get('/', function (_req, res) { | ||
res.status(200).redirect('https://backtrace.io'); | ||
res.sendStatus(200); | ||
}); | ||
@@ -82,4 +90,7 @@ }; | ||
app.use(cors_1.default()); | ||
app.options('*', cors_1.default()); | ||
app.use(helmet_1.default.noCache()); | ||
app.use(helmet_1.default()); | ||
app.use(helmet_1.default({ | ||
hsts: false, | ||
})); | ||
}; | ||
@@ -90,9 +101,14 @@ /** | ||
*/ | ||
BacktraceService.setupServiceLayer = function (app, descriptor) { | ||
BacktraceService.setupServiceLayer = function (app, name, port, coronerdCallback) { | ||
var logger = logger_1.Logger.getLogger(); | ||
var serviceDescriptor = descriptor | ||
? descriptor | ||
: this._service | ||
? this._service._descriptor | ||
: undefined; | ||
var serviceDescriptor = this._service | ||
? this._service._descriptor | ||
: undefined; | ||
// this case handle a situation when we didn't call backtraceService constr | ||
// here we want to read service descriptor from hard drive - if descr exists | ||
// we're using here a service name and port instead of service descriptor | ||
// to avoid extra steps required by service to setup service layer. | ||
if (!serviceDescriptor && name && port) { | ||
serviceDescriptor = __1.getDescriptor(name, port); | ||
} | ||
if (!serviceDescriptor) { | ||
@@ -107,4 +123,7 @@ throw new Error('Descriptor is undefined'); | ||
secret: serviceDescriptor.secret, | ||
coronerdCallback: function (_) { | ||
logger.info("Received message from coronerd"); | ||
coronerdCallback: function (address) { | ||
if (coronerdCallback) { | ||
coronerdCallback(address); | ||
} | ||
logger.info("Coronerd address: " + address); | ||
}, | ||
@@ -129,3 +148,3 @@ })); | ||
if (!this._descriptor) { | ||
var descriptor = __1.getDescriptor(this._opts.name, this._opts.port); | ||
var descriptor = __1.getDescriptor(this.name, this.port); | ||
this._descriptor = descriptor; | ||
@@ -132,0 +151,0 @@ } |
@@ -6,6 +6,14 @@ import { IConfiguration } from './../model/configuration'; | ||
constructor(_serviceName: string, _port: number); | ||
loadConfiguration(): IConfiguration<T>; | ||
reload(): IConfiguration<T>; | ||
private createConfigurationIfNotExists; | ||
private getProjectSettingsPath; | ||
/** | ||
* Select default path for service configuraiton path in different environment | ||
* By default Backtrace expect to run service from root service directory | ||
* by using npm commands. In environment different than production | ||
* @ServiceConfiguration class will read configuration file from root dir. | ||
* Otherwise from default Backtrace configuration directory | ||
*/ | ||
getProjectSettingsPath(): string; | ||
/** | ||
* Loads service configuration file | ||
*/ | ||
loadConfiguration(configurationPath: string): IConfiguration<T>; | ||
} |
@@ -7,3 +7,2 @@ "use strict"; | ||
var fs_1 = __importDefault(require("fs")); | ||
var mkdirp_1 = __importDefault(require("mkdirp")); | ||
var path_1 = __importDefault(require("path")); | ||
@@ -16,25 +15,9 @@ var __1 = require(".."); | ||
} | ||
ServiceConfiguration.prototype.loadConfiguration = function () { | ||
var configurationPath = this.getProjectSettingsPath(); | ||
this.createConfigurationIfNotExists(configurationPath); | ||
var config = JSON.parse(fs_1.default.readFileSync(configurationPath, 'utf8')); | ||
config.isProduction = process.env.NODE_ENV === 'production'; | ||
config.descriptor = __1.getDescriptor(this._serviceName, this._port); | ||
return config; | ||
}; | ||
ServiceConfiguration.prototype.reload = function () { | ||
return this.loadConfiguration(); | ||
}; | ||
ServiceConfiguration.prototype.createConfigurationIfNotExists = function (conf) { | ||
if (!fs_1.default.existsSync(conf)) { | ||
var projPath = path_1.default.join(process.cwd(), this._serviceName + ".conf"); | ||
if (conf !== projPath) { | ||
mkdirp_1.default.sync(path_1.default.dirname(conf)); | ||
fs_1.default.copyFileSync(projPath, conf); | ||
} | ||
else { | ||
throw new Error('Configuration file not found'); | ||
} | ||
} | ||
}; | ||
/** | ||
* Select default path for service configuraiton path in different environment | ||
* By default Backtrace expect to run service from root service directory | ||
* by using npm commands. In environment different than production | ||
* @ServiceConfiguration class will read configuration file from root dir. | ||
* Otherwise from default Backtrace configuration directory | ||
*/ | ||
ServiceConfiguration.prototype.getProjectSettingsPath = function () { | ||
@@ -48,2 +31,14 @@ switch (process.env.NODE_ENV) { | ||
}; | ||
/** | ||
* Loads service configuration file | ||
*/ | ||
ServiceConfiguration.prototype.loadConfiguration = function (configurationPath) { | ||
if (!configurationPath) { | ||
throw new Error("configurationPath is undefined"); | ||
} | ||
var config = JSON.parse(fs_1.default.readFileSync(configurationPath, 'utf8')); | ||
config.isProduction = process.env.NODE_ENV === 'production'; | ||
config.descriptor = __1.getDescriptor(this._serviceName, this._port); | ||
return config; | ||
}; | ||
return ServiceConfiguration; | ||
@@ -50,0 +45,0 @@ }()); |
{ | ||
"name": "backtrace-service", | ||
"version": "1.3.0-beta.0", | ||
"version": "1.3.0-beta.1", | ||
"description": "Common tools for Backtrace Node services", | ||
@@ -8,7 +8,4 @@ "author": "Backtrace", | ||
"main": "./lib/index.js", | ||
"bin": { | ||
"test": "container.js" | ||
}, | ||
"scripts": { | ||
"test": "NODE_ENV=test && jest", | ||
"test": "NODE_ENV=test jest", | ||
"prepublish": "yarn build", | ||
@@ -48,3 +45,3 @@ "lint": "tslint -p ./tsconfig.json", | ||
"axios": "^0.19.0", | ||
"backtrace-node": "^1.0.4", | ||
"backtrace-node": "^1.0.5", | ||
"body-parser": "^1.19.0", | ||
@@ -57,3 +54,2 @@ "chalk": "^2.4.2", | ||
"mkdirp": "^0.5.1", | ||
"shelljs": "^0.8.3", | ||
"winston": "^3.2.1" | ||
@@ -60,0 +56,0 @@ }, |
137
README.md
@@ -336,1 +336,138 @@ # Backtrace Service Layer nodejs library | ||
``` | ||
## Backtrace service API | ||
Backtrace-service contains a lot useful methods that you can use to generate your express application with all Backtrace best practices. By default backtrace-service package allows you to: | ||
* setup auth, | ||
* setup service layer configuration, | ||
* setup metrics, | ||
* setup integration endpoints, | ||
* setup Backtrace (logger), | ||
* read configuration files (app conf + service descriptor), | ||
* and even more... | ||
We recommend to use `BacktraceService.bootstrap` method to generate Backtrace api, but if you want to call each methods separately, feel free to use `BacktraceService` static methods. | ||
Quick sample: | ||
```typescript | ||
const backtraceService = BacktraceService.bootstrap<IConfiguration>({ | ||
name: 'service-name', | ||
port: 9999, | ||
}); | ||
backtraceService.start(); | ||
``` | ||
In TypeScript you can use generic attribute to provide configuration class. In JavaScript, you don't need to provide any generic attribute! | ||
### Configuration | ||
BacktraceService in application root directory will read configuration file. By doing that bootstrap method or BacktraceService constructor will read `configuration` and set configuration data in BacktraceService `configuration` variable. | ||
By default BacktraceService will read from configuration file: | ||
* metrics object, | ||
* ssl object, | ||
* logger object, | ||
* backtrace object, | ||
In addition to that BacktraceService will extend default configuration with values: | ||
* isProduction - determine environment type, | ||
* descriptor - service descriptor, | ||
Example configuration file: | ||
```json | ||
{ | ||
"api": { | ||
"prefix": "/api/symbold" | ||
}, | ||
"db": { | ||
"type": "sqlite", | ||
"port": 5432, | ||
"database": "./symbol-server.db" | ||
}, | ||
"log": { | ||
"level": "debug", | ||
"output": "logs", | ||
}, | ||
"metrics": { | ||
"secret": "fake secret", | ||
"uuid": "fake uuid" | ||
}, | ||
"backtrace": { | ||
"remoteLog": true, | ||
"backtraceUrl": "https://submit.backtrace.io/sample/99999fb147eccb0c1aa91f03499c96238ceaa3199dbb5cbdbb0a8ed57ae37191/json", | ||
} | ||
} | ||
``` | ||
In example above you can see `api` and `db` object that will be readed by `BacktraceService`, but default method won't use these objects to generate your application. | ||
### Service Layer | ||
`BacktraceService.setupServiceLayer` method will generate default service layer configuration for your application. By default this method will read from service/use service descriptor, setup identityManager and create application endpoint. | ||
Usage: | ||
``` | ||
const app = express(); | ||
BacktraceService.setupServiceLayer(app, descriptor); | ||
``` | ||
### Setup auth | ||
`Backtrace.setupAuth` method add all security middlewares to your express application - helmet, cors. | ||
Usage: | ||
``` | ||
const app = express(); | ||
BacktraceService.setupAuth(app) | ||
``` | ||
### Setup middlewares | ||
`Backtrace.setupMiddlewares` method will add all used middlewares/utilities to your express application - json, compression, pingdom. | ||
Usage: | ||
``` | ||
const app = express(); | ||
BacktraceService.setupMiddlewares(app); | ||
``` | ||
### Read configuration file | ||
`BacktraceService.readConfiguration` method allows you to read service configuration files. In addition to that (only in TypeScript), this method allows you to use strongly defined configuration file. By using generic attribute you can pass to readConfiguration method, expected configuration definition. If you want to ues default configuration object or you're JavaScript user, you can ignore generic attribute. | ||
Usage: | ||
```typescript | ||
const configuraiton = BacktraceService.readCofniguration('backtrace-service-name', 9999); | ||
``` | ||
### Bootstrap | ||
All methods above will be used by bootstrap method to extend your express application. | ||
Usage: | ||
```typescript | ||
const backtraceService = BacktraceService.bootstrap<IConfiguration>({ | ||
name: 'service-name', | ||
port: 9999, | ||
}); | ||
``` | ||
### Start | ||
You can start express applicaiton by using BacktraceService object. By doing that BacktraceService will use or not use ssl certs, descriptor port and more. | ||
``` | ||
backtraceService.start(); | ||
``` | ||
### MetricsStorage | ||
BacktraceService constructor will create new MetricsStorage instance class. By doing that our application can send data to circonus. If you bootstrap your application by using `bootstrap` method you can access metricsStorage by retrieving `backtraceService.metrics`. If you don't want to use `bootstrap` method, you can still create `MetricsStorage` object by using `MetricsStorage` construtor. | ||
```typescript | ||
const metrics = new MetricsStorage({uuid: 'uuid', secret: 'secret'}); | ||
``` | ||
In addition to that, you can define sending interval. By doing that `backtrace-service` will send data to Circonus by using your time interval, instead of default value (1000). | ||
If you pass to `MetricsStorage` construtor express application, metricsStorage will add request latency middleware to express application. By doing that `MetricsStorage` will send execution time of each action to Circonus. | ||
If you want to get more details from `metricsStorage` object, you can use `logger` object to provide default logger methods. | ||
Full sample: | ||
```typescript | ||
const app = express(); | ||
const metrics = new MetricsStorage({uuid: 'uuid', secret: 'secret', interval: 10000}, app, logger); | ||
``` |
@@ -8,3 +8,2 @@ import { randomBytes } from 'crypto'; | ||
import * as url from 'url'; | ||
import { Logger } from '../log/logger'; | ||
import { IServiceDescriptor } from '../model/serviceDescriptor'; | ||
@@ -28,31 +27,2 @@ import { ICoronerConfig } from './model/coronerConfig'; | ||
/** | ||
* getBackupConfig fetches a config file provided by the service itself. | ||
* @param serviceName - service name - for example: share/saml/comments | ||
*/ | ||
export function getBackupConfig(serviceName: string): object { | ||
try { | ||
const backupJson = readJSONFile(`${process.cwd()}/${serviceName}.conf`); | ||
return JSON.parse(backupJson); | ||
} catch (error) { | ||
Logger.getLogger().error(error); | ||
return { | ||
bind: {}, | ||
}; | ||
} | ||
} | ||
/** | ||
* getProperConfig fetches from the expected place on the machine (outside the service). | ||
* @param serviceName - service name - for example: share/saml/comments | ||
*/ | ||
export function getProperConfig(serviceName: string): object | undefined { | ||
const scPath = `/etc/backtrace/${serviceName}/${serviceName}.conf`; | ||
const serviceConfigJson = readJSONFile(scPath); | ||
if (!serviceConfigJson) { | ||
Logger.getLogger().warn(`No configuration file found at ${scPath}.\n`); | ||
} | ||
return serviceConfigJson; | ||
} | ||
function getCoronerdHostname(crcfg: ICoronerConfig): string { | ||
@@ -110,6 +80,6 @@ const alldescPath = | ||
if (cfg.json.svclayer) { | ||
if ( | ||
const descriptionExists = | ||
Array.isArray(cfg.json.svclayer.descriptorpaths) && | ||
cfg.json.svclayer.descriptorpaths.length > 0 | ||
) { | ||
cfg.json.svclayer.descriptorpaths.length > 0; | ||
if (descriptionExists) { | ||
/* | ||
@@ -199,3 +169,2 @@ * Prefer the last element as it will customarily be one that the | ||
} | ||
return { | ||
@@ -239,15 +208,1 @@ name: desc.name, | ||
} | ||
/** | ||
* getConfig fetches from the expected place on the machine (outside the service). | ||
* If configuration doesn't exists then getConfig method will try to fetch configuration from internal service path | ||
* @param serviceName - service name - for example: share/saml/comments | ||
*/ | ||
export function getConfig(serviceName: string): object { | ||
let config = getProperConfig(serviceName); | ||
if (!config) { | ||
config = getBackupConfig(serviceName); | ||
} | ||
return config; | ||
} |
@@ -5,9 +5,6 @@ import axios from 'axios'; | ||
import { NextFunction, Request, Response } from 'express'; | ||
import https from 'https'; | ||
import { ResponseResult } from '../model/responseResult'; | ||
import { IServiceDescriptor } from '../model/serviceDescriptor'; | ||
import { IAuthRequest } from './model/authRequest'; | ||
import { | ||
ICoronerRequestOption, | ||
} from './model/authRequestOptions'; | ||
import { ICoronerRequestOption } from './model/authRequestOptions'; | ||
import { IServerConfiguration } from './model/serverConfiguration'; | ||
@@ -115,5 +112,3 @@ | ||
'error', | ||
`${ | ||
request.ip | ||
}: Missing internal params token: ${token} || url: ${url}`, | ||
`${request.ip}: Missing internal params token: ${token} || url: ${url}`, | ||
); | ||
@@ -130,3 +125,2 @@ ResponseResult.badRequest( | ||
.get<IServerConfiguration>(`${url}${prefix}api/config`, { | ||
httpsAgent: new https.Agent({ rejectUnauthorized: false}), | ||
headers: { | ||
@@ -293,2 +287,3 @@ 'X-Service-Name': this.descr.name, | ||
} | ||
private readCoronerAuthInfo(request: IAuthRequest) { | ||
@@ -295,0 +290,0 @@ let data = request.coronerAuth; |
@@ -28,7 +28,4 @@ /** | ||
export { | ||
getBackupConfig, | ||
getConfig, | ||
getDescriptor, | ||
listenDescriptor, | ||
getProperConfig, | ||
} from './config/config'; | ||
@@ -35,0 +32,0 @@ export { ICoronerDescriptor } from './config/model/coronerDescriptor'; |
@@ -7,12 +7,3 @@ import { | ||
} from 'backtrace-node'; | ||
import { | ||
configure, | ||
debug, | ||
error, | ||
format, | ||
info, | ||
transports, | ||
verbose, | ||
warn, | ||
} from 'winston'; | ||
import winston, { configure, format, transports } from 'winston'; | ||
@@ -32,3 +23,7 @@ export class Logger { | ||
) { | ||
initialize(opts); | ||
try { | ||
initialize(opts); | ||
} catch (err) { | ||
this.getLogger().warn(`Cannot initialize Backtrace. Reason: ${err}`); | ||
} | ||
} | ||
@@ -38,3 +33,4 @@ | ||
// #define SD_EMERG "<0>" /* system is unusable */ | ||
// transportLevel definition that Logger class respect: | ||
// #define SD_EMERG "<0>" /* system is unusable */ | ||
// #define SD_ALERT "<1>" /* action must be taken immediately */ | ||
@@ -58,6 +54,3 @@ // #define SD_CRIT "<2>" /* critical conditions */ | ||
private transportLevelNumber!: number; | ||
constructor( | ||
private readonly logLevel: string = 'debug', | ||
private readonly sendToRemoteServer = false, | ||
) { | ||
constructor(logLevel: string = 'debug') { | ||
this.transportLevelNumber = | ||
@@ -75,13 +68,12 @@ this.transportLevel[logLevel] || this.transportLevel['debug']; | ||
public trace(message: string): void { | ||
verbose(this.formatScope(message)); | ||
winston.verbose(this.formatScope(message)); | ||
} | ||
public info(message: string): void { | ||
info(this.formatScope(message)); | ||
winston.info(this.formatScope(message)); | ||
} | ||
public warn(message: string): void { | ||
warn(this.formatScope(message)); | ||
winston.warn(this.formatScope(message)); | ||
} | ||
public debug(message: string): void { | ||
debug(this.formatScope(message)); | ||
winston.debug(this.formatScope(message)); | ||
} | ||
@@ -92,12 +84,14 @@ public error(data: string | Error): void { | ||
const client = getBacktraceClient(); | ||
if (client && this.sendToRemoteServer === true) { | ||
if (client) { | ||
client.reportSync(exception); | ||
} | ||
error(this.formatScope(exception.message), [error]); | ||
winston.error(this.formatScope(exception.message), [exception]); | ||
} | ||
private formatScope(msg: string): string { | ||
return `<${this.transportLevelNumber}> ${this.transportLevel}: | ||
[${new Date().toLocaleString()}] ${msg}`; | ||
// tslint:disable-next-line:max-line-length | ||
return `<${this.transportLevelNumber}>${ | ||
this.transportLevel | ||
}:[${new Date().toLocaleString()}] ${msg}`; | ||
} | ||
} |
@@ -7,4 +7,5 @@ import axios from 'axios'; | ||
private readonly _hostUrl = 'https://api.circonus.com/module/httptrap/'; | ||
private _url = `${this._hostUrl}${this.opts.uuid}/${this.opts.secret}`; | ||
private readonly _hostUrl: string = | ||
'https://api.circonus.com/module/httptrap/'; | ||
private _url = `${this._hostUrl}${this.uuid}/${this.secret}`; | ||
@@ -21,14 +22,17 @@ private readonly _hostName: string = os.hostname().split('.')[0]; | ||
constructor( | ||
private opts: { uuid: string; secret: string; interval?: number }, | ||
private readonly uuid: string, | ||
private readonly secret: string, | ||
private opts?: { interval?: number; hostUrl?: string }, | ||
app?: express.Express, | ||
private logger?: { warn: (msg: string) => void | Promise<void> }, | ||
) { | ||
if (!opts) { | ||
throw new Error('opts object is required'); | ||
} | ||
if (!opts.uuid || !opts.secret) { | ||
if (!this.uuid || !this.secret) { | ||
if (logger) { | ||
logger.warn( | ||
'Metrics Uuid or secret is not defined. Metrics will be disabled', | ||
'Opts (or uuid/secret) object is required. Metrics are disabled', | ||
); | ||
} else { | ||
console.warn( | ||
'Opts (or uuid/secret) object is required. Metrics are disabled', | ||
); | ||
} | ||
@@ -38,4 +42,11 @@ return; | ||
// override default circonus url. | ||
// different url might be useful in case | ||
// when we will prepare our own metrics client | ||
if (this.opts && this.opts.hostUrl) { | ||
this._hostUrl = this.opts.hostUrl; | ||
} | ||
if (app) { | ||
app.use((req, res, next) => this.registerRquestLatency(req, res, next)); | ||
app.use((req, res, next) => this.registerRequestLatency(req, res, next)); | ||
} | ||
@@ -51,3 +62,3 @@ this.addMetric('birthday', Date.now()); | ||
} | ||
}, opts.interval || 1000); | ||
}, (this.opts && this.opts.interval) || 1000); | ||
} | ||
@@ -60,2 +71,6 @@ | ||
public add(source: object) { | ||
// metrics are disabled because of missing secret and uuid parameters | ||
if (!this.uuid || !this.secret) { | ||
return; | ||
} | ||
for (const key in source) { | ||
@@ -75,2 +90,6 @@ if (source.hasOwnProperty(key)) { | ||
public addMetric(name: string, value: number) { | ||
// metrics are disabled because of missing secret and uuid parameters | ||
if (!this.uuid || !this.secret) { | ||
return; | ||
} | ||
name = `${name}|ST[hostname:${this._hostName}]`; | ||
@@ -81,3 +100,3 @@ this._data[name] = this._data[name] || { _type: 'n', _value: [] }; | ||
private registerRquestLatency( | ||
private registerRequestLatency( | ||
req: Request, | ||
@@ -84,0 +103,0 @@ res: Response, |
import { Logger } from './../log/logger'; | ||
export interface IAppOptions { | ||
name: string; | ||
port: number; | ||
confPath?: string; | ||
remoteLogger?: boolean; | ||
logLevel?: 'error' | 'warn' | 'info' | 'verbose' | 'debug'; | ||
logger?: Logger; | ||
skipServiceLayerIntegation?: boolean; | ||
} |
@@ -19,4 +19,2 @@ import { IBacktraceClientOptions } from 'backtrace-node'; | ||
export interface ILoggerOptions { | ||
remoteReport: boolean; | ||
backtraceUrl: string; | ||
logLevel: string; | ||
@@ -23,0 +21,0 @@ } |
@@ -30,17 +30,17 @@ import { json, urlencoded } from 'body-parser'; | ||
public static bootstrap<T extends {}>( | ||
name: string, | ||
port: number, | ||
opts: IAppOptions, | ||
app?: express.Express, | ||
): BacktraceService<T> { | ||
if (!opts || !opts.port || !opts.name) { | ||
throw new Error( | ||
`Invalid arguments. Name: ${opts.name}, port: ${opts.port}`, | ||
); | ||
if (!port || !name) { | ||
throw new Error(`Invalid arguments. Name: ${name}, port: ${port}`); | ||
} | ||
const service = new BacktraceService<T>(opts, app); | ||
const service = new BacktraceService<T>(name, port, opts, app); | ||
app = service.app; | ||
this.setupAuth(app); | ||
this.setupMiddlewares(app); | ||
if (opts.skipServiceLayerIntegation !== true) { | ||
this.setupServiceLayer(app, service._descriptor); | ||
} | ||
this.setupServiceLayer(app, name, port, (ip) => { | ||
service.coronerdAddress.add(ip); | ||
}); | ||
@@ -53,8 +53,12 @@ app.on('close', () => { | ||
public static readConfiguration<T>( | ||
public static readConfiguration<T = {}>( | ||
name: string, | ||
port: number, | ||
path?: string, | ||
): IConfiguration<T> { | ||
const serviceConfiguration = new ServiceConfiguration<T>(name, port); | ||
return serviceConfiguration.loadConfiguration(); | ||
if (!path) { | ||
path = serviceConfiguration.getProjectSettingsPath(); | ||
} | ||
return serviceConfiguration.loadConfiguration(path); | ||
} | ||
@@ -73,3 +77,3 @@ | ||
app.get('/', (_req, res) => { | ||
res.status(200).redirect('https://backtrace.io'); | ||
res.sendStatus(200); | ||
}); | ||
@@ -84,4 +88,9 @@ } | ||
app.use(cors()); | ||
app.options('*', cors()); | ||
app.use(helmet.noCache()); | ||
app.use(helmet()); | ||
app.use( | ||
helmet({ | ||
hsts: false, | ||
}), | ||
); | ||
} | ||
@@ -95,11 +104,19 @@ | ||
app: express.Express, | ||
descriptor?: IServiceDescriptor, | ||
name?: string, | ||
port?: number, | ||
coronerdCallback?: (param: string) => void, | ||
) { | ||
const logger = Logger.getLogger(); | ||
const serviceDescriptor = descriptor | ||
? descriptor | ||
: this._service | ||
let serviceDescriptor = this._service | ||
? this._service._descriptor | ||
: undefined; | ||
// this case handle a situation when we didn't call backtraceService constr | ||
// here we want to read service descriptor from hard drive - if descr exists | ||
// we're using here a service name and port instead of service descriptor | ||
// to avoid extra steps required by service to setup service layer. | ||
if (!serviceDescriptor && name && port) { | ||
serviceDescriptor = getDescriptor(name, port); | ||
} | ||
if (!serviceDescriptor) { | ||
@@ -118,4 +135,7 @@ throw new Error('Descriptor is undefined'); | ||
secret: serviceDescriptor.secret, | ||
coronerdCallback: (_: string) => { | ||
logger.info(`Received message from coronerd`); | ||
coronerdCallback: (address: string) => { | ||
if (coronerdCallback) { | ||
coronerdCallback(address); | ||
} | ||
logger.info(`Coronerd address: ${address}`); | ||
}, | ||
@@ -128,2 +148,4 @@ } as ICoronerRequestOption), | ||
public readonly configuration!: IConfiguration<T>; | ||
public readonly coronerdAddress = new Set<string>(); | ||
public identityManager!: IdentityManager; | ||
@@ -136,18 +158,33 @@ public readonly app: express.Express; | ||
constructor(private readonly _opts: IAppOptions, app?: express.Express) { | ||
this._logger = _opts.logger ? _opts.logger : new Logger(_opts.logLevel); | ||
constructor( | ||
private readonly name: string, | ||
private readonly port: number, | ||
private readonly _opts: IAppOptions, | ||
app?: express.Express, | ||
) { | ||
this.app = app ? app : express(); | ||
this.configuration = BacktraceService.readConfiguration<T>( | ||
_opts.name, | ||
_opts.port, | ||
this.name, | ||
this.port, | ||
this._opts.confPath, | ||
); | ||
const logLevel = | ||
this.configuration.logger && this.configuration.logger.logLevel | ||
? this.configuration.logger.logLevel | ||
: undefined; | ||
this._logger = _opts.logger ? _opts.logger : new Logger(logLevel); | ||
if ( | ||
!!_opts.remoteLogger === true || | ||
(this.configuration.backtrace && | ||
this.configuration.backtrace.remoteLog === true) | ||
this.configuration.backtrace && | ||
this.configuration.backtrace.remoteLog === true | ||
) { | ||
Logger.initializeBacktrace(this.configuration.backtrace); | ||
} | ||
this.metrics = new MetricsStorage(this.configuration.metrics, this.app); | ||
this.metrics = new MetricsStorage( | ||
this.configuration.metrics.secret, | ||
this.configuration.metrics.uuid, | ||
undefined, | ||
this.app, | ||
); | ||
@@ -184,3 +221,3 @@ this.prepareServiceLayer(); | ||
if (!this._descriptor) { | ||
const descriptor = getDescriptor(this._opts.name, this._opts.port); | ||
const descriptor = getDescriptor(this.name, this.port); | ||
this._descriptor = descriptor; | ||
@@ -187,0 +224,0 @@ } |
import fs from 'fs'; | ||
import mkdirp from 'mkdirp'; | ||
import path from 'path'; | ||
@@ -13,6 +12,26 @@ import { getDescriptor } from '..'; | ||
public loadConfiguration(): IConfiguration<T> { | ||
const configurationPath = this.getProjectSettingsPath(); | ||
this.createConfigurationIfNotExists(configurationPath); | ||
/** | ||
* Select default path for service configuraiton path in different environment | ||
* By default Backtrace expect to run service from root service directory | ||
* by using npm commands. In environment different than production | ||
* @ServiceConfiguration class will read configuration file from root dir. | ||
* Otherwise from default Backtrace configuration directory | ||
*/ | ||
public getProjectSettingsPath(): string { | ||
switch (process.env.NODE_ENV) { | ||
case 'production': | ||
return `/etc/backtrace/${this._serviceName}/${this._serviceName}.conf`; | ||
default: | ||
return path.join(process.cwd(), `${this._serviceName}.conf`); | ||
} | ||
} | ||
/** | ||
* Loads service configuration file | ||
*/ | ||
public loadConfiguration(configurationPath: string): IConfiguration<T> { | ||
if (!configurationPath) { | ||
throw new Error(`configurationPath is undefined`); | ||
} | ||
const config = JSON.parse( | ||
@@ -27,27 +46,2 @@ fs.readFileSync(configurationPath, 'utf8'), | ||
} | ||
public reload(): IConfiguration<T> { | ||
return this.loadConfiguration(); | ||
} | ||
private createConfigurationIfNotExists(conf: string) { | ||
if (!fs.existsSync(conf)) { | ||
const projPath = path.join(process.cwd(), `${this._serviceName}.conf`); | ||
if (conf !== projPath) { | ||
mkdirp.sync(path.dirname(conf)); | ||
fs.copyFileSync(projPath, conf); | ||
} else { | ||
throw new Error('Configuration file not found'); | ||
} | ||
} | ||
} | ||
private getProjectSettingsPath(): string { | ||
switch (process.env.NODE_ENV) { | ||
case 'production': | ||
return `/etc/backtrace/${this._serviceName}/${this._serviceName}.conf`; | ||
default: | ||
return path.join(process.cwd(), `${this._serviceName}.conf`); | ||
} | ||
} | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
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
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
159679
10
473
2
85
2808
- Removedshelljs@^0.8.3
- Removedbalanced-match@1.0.2(transitive)
- Removedbrace-expansion@1.1.11(transitive)
- Removedconcat-map@0.0.1(transitive)
- Removedfs.realpath@1.0.0(transitive)
- Removedglob@7.2.3(transitive)
- Removedinflight@1.0.6(transitive)
- Removedinterpret@1.4.0(transitive)
- Removedis-core-module@2.16.1(transitive)
- Removedminimatch@3.1.2(transitive)
- Removedonce@1.4.0(transitive)
- Removedpath-is-absolute@1.0.1(transitive)
- Removedpath-parse@1.0.7(transitive)
- Removedrechoir@0.6.2(transitive)
- Removedresolve@1.22.10(transitive)
- Removedshelljs@0.8.5(transitive)
- Removedsupports-preserve-symlinks-flag@1.0.0(transitive)
- Removedwrappy@1.0.2(transitive)
Updatedbacktrace-node@^1.0.5