
Security News
Axios Maintainer Confirms Social Engineering Attack Behind npm Compromise
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.
console-capturer
Advanced tools
It can be used to catch all errors in the system and provide performance monitoring
请优先下载最新版本库
errorCaptured 是一个用于捕获和管理前端错误、日志、网络请求、性能数据等信息的工具函数。它可以帮助开发者监控应用的运行状态,并在需要时将数据持久化存储
QQ 讨论群:324710217
npm i console-capturer
<script src="https://cdn.jsdelivr.net/npm/console-capturer/dist/index.min.js"></script>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>errorCaptured Demo</title>
<style>
body {
font-family: "Arial", sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
button {
margin: 5px;
padding: 8px 12px;
cursor: pointer;
}
pre {
background-color: #f5f5f5;
padding: 10px;
border-radius: 5px;
overflow-x: auto;
}
.log-container {
margin-top: 20px;
border: 1px solid #ddd;
padding: 10px;
height: 300px;
overflow-y: auto;
}
.log-entry {
margin-bottom: 5px;
padding: 5px;
border-bottom: 1px solid #eee;
}
.log-type {
font-weight: bold;
margin-right: 10px;
}
.log-time {
color: #666;
font-size: 0.9em;
}
.log-message {
margin-top: 5px;
}
.ERROR,
.WINDOW_ERROR,
.PROMISE_REJECTION,
.NETWORK_ERROR {
background-color: #ffebee;
}
.WARN {
background-color: #fff8e1;
}
.INFO {
background-color: #e1f5fe;
}
</style>
<script src="https://cdn.jsdelivr.net/npm/console-capturer/dist/index.min.js"></script>
</head>
<body>
<h1>errorCaptured 演示</h1>
<div>
<h3>测试操作</h3>
<button id="logBtn">打印日志</button>
<button id="warnBtn">打印警告</button>
<button id="errorBtn">打印错误</button>
<button id="throwErrorBtn">抛出错误</button>
<button id="promiseErrorBtn">Promise错误</button>
<button id="fetchBtn">网络请求</button>
<button id="reportErrorLogBtn">测试 reportError 内打印</button>
<button id="lookAllLog">查看所有的日志信息</button>
</div>
<div>
<h3>捕获的日志</h3>
<div id="logOutput" class="log-container"></div>
</div>
<script>
const errorCapture = errorCaptured({
captureConsole: true,
captureErrors: true,
captureNetwork: true,
maxLogs: 100,
reportError: (newItems, beforePush, afterPush, originalConsole) => {
// 这个回调中的日志和错误不会被记录
originalConsole.log("⚡ 报告新日志:", newItems);
// 如果是错误类型的日志,可以在这里进行上报
if (
newItems.some((item) =>
[
"ERROR",
"WINDOW_ERROR",
"PROMISE_REJECTION",
"NETWORK_ERROR",
].includes(item.type)
)
) {
originalConsole.warn("⚠️ 检测到错误,可以在这里发送到服务器");
}
},
});
// 显示日志到页面
function renderLogs() {
const logOutput = document.getElementById("logOutput");
logOutput.innerHTML = "";
errorCapture.logs.forEach((log) => {
const logEntry = document.createElement("div");
logEntry.className = `log-entry ${log.type}`;
// 显示日志类型和时间
const logHeader = document.createElement("div");
const logType = document.createElement("span");
logType.className = "log-type";
logType.textContent = log.type;
logHeader.appendChild(logType);
const logTime = document.createElement("span");
logTime.className = "log-time";
logTime.textContent = new Date(log.timestamp).toLocaleTimeString();
logHeader.appendChild(logTime);
logEntry.appendChild(logHeader);
// 显示日志消息
const logMessage = document.createElement("div");
logMessage.className = "log-message";
if (log.type === "NETWORK" || log.type === "NETWORK_ERROR") {
logMessage.textContent = `${log.subtype} ${log.url} - Status: ${log.status}`;
if (log.duration) {
logMessage.textContent += ` (${log.duration}ms)`;
}
} else if (log.messages) {
logMessage.textContent = log.messages
.map((msg) =>
typeof msg === "object" ? JSON.stringify(msg) : msg
)
.join(" ");
}
logEntry.appendChild(logMessage);
// 如果有堆栈信息,添加一个折叠的详情部分
if (log.stack) {
const details = document.createElement("details");
const summary = document.createElement("summary");
summary.textContent = "Stack trace";
details.appendChild(summary);
const pre = document.createElement("pre");
pre.textContent = log.stack;
details.appendChild(pre);
logEntry.appendChild(details);
}
logOutput.appendChild(logEntry);
});
// 滚动到底部
logOutput.scrollTop = logOutput.scrollHeight;
}
// 添加按钮事件
document.getElementById("logBtn").addEventListener("click", () => {
console.log("这是一条普通日志", { data: "some data" });
renderLogs();
});
document.getElementById("warnBtn").addEventListener("click", () => {
console.warn("这是一条警告信息");
renderLogs();
});
document.getElementById("errorBtn").addEventListener("click", () => {
console.error("这是一条错误信息");
renderLogs();
});
document.getElementById("throwErrorBtn").addEventListener("click", () => {
try {
throw new Error("手动抛出的错误");
} catch (error) {
console.error("捕获到错误:", error);
}
renderLogs();
});
document
.getElementById("promiseErrorBtn")
.addEventListener("click", () => {
// 创建一个会被拒绝的 Promise
new Promise((resolve, reject) => {
setTimeout(() => {
reject("Promise 被拒绝了");
}, 100);
}).catch((err) => {
console.log("Promise错误被捕获:", err);
});
// 一个未捕获的 Promise 错误
setTimeout(() => {
new Promise((resolve, reject) => {
reject("未捕获的 Promise 错误");
});
setTimeout(renderLogs, 200);
}, 200);
});
document
.getElementById("fetchBtn")
.addEventListener("click", async () => {
try {
// 成功的请求
const response = await fetch(
"https://jsonplaceholder.typicode.com/todos/1"
);
const data = await response.json();
console.log("请求成功:", data);
// 失败的请求
setTimeout(async () => {
try {
await fetch("https://non-existent-domain-123456.com");
} catch (error) {
console.error("请求失败:", error);
}
renderLogs();
}, 500);
} catch (error) {
console.error("请求出错:", error);
}
setTimeout(renderLogs, 1000);
});
document
.getElementById("reportErrorLogBtn")
.addEventListener("click", () => {
console.log("测试 reportError 内的打印不被捕获");
console.error(
"这个错误会被 reportError 处理,里面的打印不应该被捕获"
);
renderLogs();
});
document.getElementById("lookAllLog").addEventListener("click", () => {
alert("日志已打印在控制台,请打开控制台查看");
const { originalConsole, logs } = errorCapture;
originalConsole.log("捕获到的所有日志:",logs);
});
// 初始渲染
renderLogs();
</script>
</body>
</html>
[
{
type: "LOG",
timestamp: "2023-10-01T12:00:00.000Z",
messages: ["Hello, World!"],
},
{
type: "NETWORK",
subtype: "FETCH",
timestamp: "2023-10-01T12:00:01.000Z",
url: "https://jsonplaceholder.typicode.com/todos/1",
status: 200,
duration: 150,
},
{
type: "ERROR",
timestamp: "2023-10-01T12:00:02.000Z",
messages: ["ReferenceError: foo is not defined"],
stack: "Error stack trace...",
},
];
import errorCaptured from "./errorCaptured";
const { logs, currentlogs, disableAllCaptures } = errorCaptured({
captureConsole: true, // 是否捕获 console 日志
captureErrors: true, // 是否捕获全局错误
captureNetwork: true, // 是否捕获网络请求
capturePerformance: false, // 是否捕获性能数据
enableStorage: true, // 是否启用持久化存储
storageType: "local", // 存储类型(可选 'local' 或 'session')
storageName: "console_logs", // 存储名称
storageMaxItems: 100, // 存储的最大条目数
storageInterval: 5000, // 存储间隔时间(毫秒)
maxLogs: 1000, // 日志的最大条数
reportError: (newItems, beforePush, afterPush, originalConsole) => {
/**
* newItems: 本次新增的日志数据
* beforePush: 添加本次新增日志之前的数据,即上一次的日志数据
* afterPush: 添加本次日志之后调用的数据,即最新的日志数据
* originalConsole: 原始的 console 对象,使用该方法打印,不会被日志监控所记录
*/
// 自定义错误报告逻辑
// 这里建议使用 originalConsole.log 打印输出,可以避免打印被监听
originalConsole.log("New logs:", newItems);
},
});
errorCaptured 返回一个对象,包含以下属性和方法:
/**
* 日志捕获器的配置和状态信息。
* @property {Array} logs - 这里面存储了所有捕获到的日志信息。
* @property {Array} currentlogs - 本次新增的日志。
* @property {Object} originalConsole - 原始的console对象,可以用于控制台打印日志,但是不会被监听。
* @property {Object} cleanupFunctions - 包含各个监控功能的撤销方法的,可以用于撤销单独的监控功能。
* @property {function} disableAllCaptures - 撤销所有日志监控功能。
*
* @example
const { logs,
currentlogs,
originalConsole,
cleanupFunctions,
disableAllCaptures
} = errorCaptured({
// 你需要传入的配置项
});
*/
每条日志是一个对象,包含以下字段(具体数据结构可参见上方 捕获日志结构展示):
/**
* 日志数据库字段说明。
* @typedef {Object} LogDatabaseFields
* @property {string} type - 日志类型,如"LOG","NETWORK","ERROR"等
* @property {string} timestamp - 日志时间戳,格式为ISO8601。记录日志打印的时间
* @property {Array} messages - 日志内容,为本次日志的输出内容和报错信息
* @property {string} [subtype] - 网络日志类型(仅适用于网络日志),如"FETCH","XHR"等,表示网络请求类型
* @property {string} [url] - 网络请求的URL(仅适用于网络日志),表示网络请求的URL
* @property {number} [status] - 网络请求的状态码(仅适用于网络日志),表示网络请求的状态码
* @property {number} [duration] - 请求持续时间,表示调用此次接口或者加载此次资源所耗费的时间
* @property {string} [stack] - 错误堆栈信息(存在于错误日志)。如果该字段是网络日志,则表示堆栈跟踪信息。
*
* @example
* const logEntry = {
* type: 'NETWORK',
* subtype: 'FETCH',
* timestamp: '2023-10-05T14:48:00.000Z',
* messages: ['Fetch request sent', 'Response received'],
* url: 'https://example.com/api/data',
* status: 200,
* duration: 1234, // in milliseconds
* stack: null // No error stack for network logs
* };
*
* // For a log entry that is an error
* const errorEntry = {
* type: 'ERROR',
* subtype: null,
* timestamp: '2023-10-05T14:49:00.000Z',
* messages: ['Uncaught TypeError: Cannot read property 'x' of undefined'],
* url: null,
* status: null,
* duration: null,
* stack: 'TypeError: Cannot read property 'x' of undefined\n at ...'
* };
*/
撤销监控:如果需要恢复原始状态,撤销所有日志监控,可以调用 disableAllCaptures 方法。
持久化存储:启用持久化存储后,日志会被定时保存到 localStorage 或 sessionStorage 中。
性能开销:捕获大量日志可能会影响性能,建议根据实际需求配置 maxLogs 和 storageMaxItems 参数。
originalConsole:如果你不想此次输出被监听,可以设置 originalConsole.log()进行打印,此次打印结构不会被监听。
reportError:在 reportError 回调函数中,建议使用 originalConsole.log 打印输出,可以避免打印被监听
如果对此工具包有更好的建议或需要支持新的功能,欢迎提 issue 或者加本人的 QQ:1844119859。
FAQs
It can be used to catch all errors in the system and provide performance monitoring
We found that console-capturer demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.

Security News
The Axios compromise shows how time-dependent dependency resolution makes exposure harder to detect and contain.