@tracing/core
Version:
323 lines (310 loc) • 12.2 kB
JavaScript
/*!
* @tracing/core v0.0.3
* (c) 2024 wermdany <https://github.com/wermdany>
* license ISC
* hash f6fbb5bcb01c00afbd6fb6f5826e891202a721b1
*/
import { transProfile, hasOwn } from '@tracing/shared';
function createStore(name, initStore, logger) {
let store = Object.assign({}, initStore);
return {
name,
get() {
return transProfile(store);
},
set(key, value) {
(process.env.NODE_ENV !== 'production') &&
logger &&
hasOwn(store, key) &&
logger.warn("store:".concat(name), `You are trying to overwrite an existing property: ${key}`);
store[key] = value;
},
getOrigin() {
return Object.assign({}, store);
},
remove(key) {
delete store[key];
},
clear() {
store = {};
}
};
}
function createLogger(name, options) {
const { isLogger = true } = options || {};
const genPrefix = () => {
return name ? "[".concat("tracing", "/", name, "]") : "[".concat("tracing", "]");
};
return {
warn(...args) {
isLogger && console.warn(genPrefix(), ...args);
},
info(...args) {
isLogger && console.info(genPrefix(), ...args);
},
error(...args) {
isLogger && console.error(genPrefix(), ...args);
},
throwError(base) {
if (!(base instanceof Error)) {
base = new Error(base);
Object.defineProperty(base, "name", {
value: "".concat(titleToUpperCase("tracing"), titleToUpperCase(name), "Error")
});
}
throw base;
}
};
}
function titleToUpperCase(str) {
return str.charAt(0).toUpperCase() + str.slice(1);
}
/******************************************************************************
Copyright (c) Microsoft Corporation.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
***************************************************************************** */
function __awaiter(thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
}
function PluginDriver(plugins, options, logger) {
const sortedPlugins = new Map();
const pluginContexts = new Map(plugins.map(plugin => [plugin, createContext(plugin, options)]));
function destroyPluginDriver() {
sortedPlugins.clear();
pluginContexts.clear();
}
function runHook(hookName, args, plugin) {
return __awaiter(this, void 0, void 0, function* () {
const hook = plugin[hookName];
const handler = typeof hook === "object" ? hook.handler : hook;
const context = pluginContexts.get(plugin);
return Promise.resolve()
.then(() => {
const hookResult = handler.apply(context, args);
if (!(hookResult === null || hookResult === void 0 ? void 0 : hookResult.then)) {
return hookResult;
}
return hookResult;
})
.catch(err => logger.throwError(err));
});
}
function runHookSync(hookName, args, plugin) {
const hook = plugin[hookName];
const handler = typeof hook === "object" ? hook.handler : hook;
const context = pluginContexts.get(plugin);
try {
return handler.apply(context, args);
}
catch (err) {
return logger.throwError(err);
}
}
function getSortedValidatePlugins(hookName, plugins, validateHandler = validateFunctionPluginHandler) {
const pre = [];
const normal = [];
const post = [];
for (const plugin of plugins) {
const hook = plugin[hookName];
if (hook) {
if (typeof hook === "object") {
validateHandler(hook.handler, hookName, plugin);
if (hook.order == "pre") {
pre.push(plugin);
continue;
}
if (hook.order == "post") {
post.push(plugin);
continue;
}
normal.push(plugin);
}
else {
validateHandler(hook, hookName, plugin);
normal.push(plugin);
}
}
}
return pre.concat(normal, post);
}
function validateFunctionPluginHandler(handler, hookName, plugin) {
if (typeof handler !== "function") {
logger.throwError(`Error running plugin hook "${hookName}" for plugin "${plugin.name}", expected a function hook or an object with a "handler" function.`);
}
}
function getSortedPlugins(hookName) {
return getOrCreate(sortedPlugins, hookName, () => getSortedValidatePlugins(hookName, plugins));
}
return {
runHook,
runHookSync,
getSortedValidatePlugins,
destroyPluginDriver,
hookParallel(hookName, args) {
return __awaiter(this, void 0, void 0, function* () {
const parallelPromises = [];
for (const plugin of getSortedPlugins(hookName)) {
parallelPromises.push(runHook(hookName, args, plugin));
}
yield Promise.all(parallelPromises);
});
},
hookSequentialSync(hookName, args) {
const effects = [];
for (const plugin of getSortedPlugins(hookName)) {
const effect = runHookSync(hookName, args, plugin);
if (effect && typeof effect == "function") {
effects.push(effect);
}
}
return effects;
},
hookBail(hookName, args) {
for (const plugin of getSortedPlugins(hookName)) {
const result = runHookSync(hookName, args, plugin);
// 只有在明确返回 false 才会熔断,这个是考虑避免用户写过于复杂判断,没有 return 而导致熔断
if (result === false) {
return false;
}
}
return true;
},
hookChainMergeSync(hookName, args, initialValue) {
let returnValue = initialValue;
for (const plugin of getSortedPlugins(hookName)) {
const result = runHookSync(hookName, args, plugin);
returnValue = mergeConfig(returnValue, result);
}
return returnValue;
}
};
}
function createContext(plugin, options) {
return {
logger: createLogger(plugin.name, options),
meta: {
version: "0.0.3"
}
};
}
function getOrCreate(plugins, key, init) {
const existing = plugins.get(key);
if (existing) {
return existing;
}
const value = init();
plugins.set(key, value);
return value;
}
function mergeConfig(defaults, overrides) {
const merged = Object.assign({}, defaults);
for (const key in overrides) {
if (!Object.prototype.hasOwnProperty.call(overrides, key)) {
continue;
}
const value = overrides[key];
if (value == null) {
continue;
}
const existing = merged[key];
if (existing == null) {
merged[key] = value;
continue;
}
if (Array.isArray(existing) || Array.isArray(value)) {
merged[key] = [...toArray(existing !== null && existing !== void 0 ? existing : []), ...toArray(value !== null && value !== void 0 ? value : [])];
continue;
}
if (isObject(existing) && isObject(value)) {
merged[key] = mergeConfig(existing, value);
continue;
}
merged[key] = value;
}
return merged;
}
function toArray(target) {
return Array.isArray(target) ? target : [target];
}
function isObject(value) {
return Object.prototype.toString.call(value) === "[object Object]";
}
const initConfig = {
isLogger: (process.env.NODE_ENV !== 'production'),
sendLog: (process.env.NODE_ENV !== 'production'),
plugins: []
};
function createInitConfig(inputConfig) {
return Object.assign(Object.assign({}, initConfig), inputConfig);
}
class TracingCore {
constructor(config) {
this.initialized = false;
this.destroyed = false;
this.effects = [];
this.config = createInitConfig(config);
this.logger = createLogger("core", this.config);
this.pluginDriver = PluginDriver(this.config.plugins.flat(), this.config, this.logger);
this.pluginDriver.hookSequentialSync("setup", [this.config]);
}
init() {
if (this.initialized) {
(process.env.NODE_ENV !== 'production') && this.logger.warn('"init" has been initialized, please do not call again');
return;
}
this.header = createStore("header", {});
this.initialized = true;
this.effects = this.pluginDriver.hookSequentialSync("init", [this]);
}
report(event, record, meta) {
if (!this.initialized) {
(process.env.NODE_ENV !== 'production') && this.logger.warn('"TracingCore" Not initialized, please execute after initialization');
return;
}
if (this.destroyed) {
(process.env.NODE_ENV !== 'production') && this.logger.warn('"TracingCore" has been destroyed and you can\'t do anything now');
return;
}
this.build(event, record, meta);
}
build(event, record, meta) {
const build = this.pluginDriver.hookChainMergeSync("build", [event, record, meta], {});
this.send(event, build);
}
send(event, build) {
const isBail = this.pluginDriver.hookBail("beforeSend", [event, build]);
if (isBail === false)
return;
if ((process.env.NODE_ENV !== 'production') && this.config.isLogger && this.config.sendLog) {
console.log(JSON.stringify(build, null, 2));
}
this.pluginDriver.hookBail("send", [event, build]);
}
destroy() {
return __awaiter(this, void 0, void 0, function* () {
yield Promise.all(this.effects.map(effect => effect()));
this.effects.length = 0;
yield this.pluginDriver.hookParallel("beforeDestroy", [this]);
this.pluginDriver.hookSequentialSync("destroy", [this]);
this.header.clear();
this.pluginDriver.destroyPluginDriver();
this.destroyed = true;
});
}
}
const version = "0.0.3";
export { TracingCore, createLogger, createStore, version };