|
|
@@ -4,77 +4,77 @@ class MonitorSDK {
|
|
|
constructor(options = {}) {
|
|
|
this.config = {
|
|
|
// 基础配置
|
|
|
- appId: options.appId || '',
|
|
|
- apiUrl: options.apiUrl || '',
|
|
|
+ appId: options.appId || "",
|
|
|
+ apiUrl: options.apiUrl || "",
|
|
|
enableErrorTracking: options.enableErrorTracking !== false,
|
|
|
enablePerformanceTracking: options.enablePerformanceTracking !== false,
|
|
|
enableUserBehaviorTracking: options.enableUserBehaviorTracking !== false,
|
|
|
enableResourceTracking: options.enableResourceTracking !== false,
|
|
|
sampleRate: options.sampleRate || 1.0, // 采样率,默认100%
|
|
|
reportInterval: options.reportInterval || 5000, // 上报间隔,默认5秒
|
|
|
- ...options
|
|
|
+ ...options,
|
|
|
};
|
|
|
-
|
|
|
+
|
|
|
this.init();
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
init() {
|
|
|
- console.log('MonitorSDK initialized');
|
|
|
-
|
|
|
+ console.log("MonitorSDK initialized");
|
|
|
+
|
|
|
// 初始化各种监控模块
|
|
|
if (this.config.enableErrorTracking) {
|
|
|
this.initErrorTracking();
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
if (this.config.enablePerformanceTracking) {
|
|
|
this.initPerformanceTracking();
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
if (this.config.enableUserBehaviorTracking) {
|
|
|
this.initUserBehaviorTracking();
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
if (this.config.enableResourceTracking) {
|
|
|
this.initResourceTracking();
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 定期上报数据
|
|
|
this.startReporting();
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 错误监控
|
|
|
initErrorTracking() {
|
|
|
// 监控JavaScript错误
|
|
|
- window.addEventListener('error', (event) => {
|
|
|
+ window.addEventListener("error", (event) => {
|
|
|
this.reportError({
|
|
|
- type: 'javascript_error',
|
|
|
+ type: "javascript_error",
|
|
|
message: event.message,
|
|
|
filename: event.filename,
|
|
|
lineno: event.lineno,
|
|
|
colno: event.colno,
|
|
|
stack: event.error ? event.error.stack : null,
|
|
|
- timestamp: Date.now()
|
|
|
+ timestamp: Date.now(),
|
|
|
});
|
|
|
});
|
|
|
-
|
|
|
+
|
|
|
// 监控未处理的Promise错误
|
|
|
- window.addEventListener('unhandledrejection', (event) => {
|
|
|
+ window.addEventListener("unhandledrejection", (event) => {
|
|
|
this.reportError({
|
|
|
- type: 'promise_rejection',
|
|
|
+ type: "promise_rejection",
|
|
|
message: event.reason && event.reason.message ? event.reason.message : String(event.reason),
|
|
|
stack: event.reason && event.reason.stack ? event.reason.stack : null,
|
|
|
- timestamp: Date.now()
|
|
|
+ timestamp: Date.now(),
|
|
|
});
|
|
|
});
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 性能监控
|
|
|
initPerformanceTracking() {
|
|
|
// 页面加载性能
|
|
|
if (performance && performance.timing) {
|
|
|
setTimeout(() => {
|
|
|
const perfData = {
|
|
|
- type: 'performance',
|
|
|
+ type: "performance",
|
|
|
navigationStart: performance.timing.navigationStart,
|
|
|
// DNS查询耗时
|
|
|
dnsTime: performance.timing.domainLookupEnd - performance.timing.domainLookupStart,
|
|
|
@@ -85,172 +85,170 @@ class MonitorSDK {
|
|
|
// DOM解析耗时
|
|
|
domParseTime: performance.timing.domComplete - performance.timing.domLoading,
|
|
|
// DOM内容加载完成耗时
|
|
|
- domContentLoadedTime: performance.timing.domContentLoadedEventEnd - performance.timing.navigationStart,
|
|
|
+ domContentLoadedTime:
|
|
|
+ performance.timing.domContentLoadedEventEnd - performance.timing.navigationStart,
|
|
|
// 页面加载完成总耗时
|
|
|
loadCompleteTime: performance.timing.loadEventEnd - performance.timing.navigationStart,
|
|
|
- timestamp: Date.now()
|
|
|
+ timestamp: Date.now(),
|
|
|
};
|
|
|
this.reportData(perfData);
|
|
|
}, 0);
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 监控Web Vitals
|
|
|
this.measureWebVitals();
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 用户行为监控
|
|
|
initUserBehaviorTracking() {
|
|
|
// 监控页面点击事件
|
|
|
- document.addEventListener('click', (event) => {
|
|
|
+ document.addEventListener("click", (event) => {
|
|
|
const target = event.target;
|
|
|
this.reportData({
|
|
|
- type: 'user_behavior',
|
|
|
- action: 'click',
|
|
|
+ type: "user_behavior",
|
|
|
+ action: "click",
|
|
|
element: target.tagName,
|
|
|
elementId: target.id,
|
|
|
elementClass: target.className,
|
|
|
pageUrl: window.location.href,
|
|
|
- timestamp: Date.now()
|
|
|
+ timestamp: Date.now(),
|
|
|
});
|
|
|
});
|
|
|
-
|
|
|
+
|
|
|
// 监控页面停留时间
|
|
|
let pageVisibleTime = Date.now();
|
|
|
- document.addEventListener('visibilitychange', () => {
|
|
|
- if (document.visibilityState === 'hidden') {
|
|
|
+ document.addEventListener("visibilitychange", () => {
|
|
|
+ if (document.visibilityState === "hidden") {
|
|
|
this.reportData({
|
|
|
- type: 'page_stay',
|
|
|
+ type: "page_stay",
|
|
|
duration: Date.now() - pageVisibleTime,
|
|
|
pageUrl: window.location.href,
|
|
|
- timestamp: Date.now()
|
|
|
+ timestamp: Date.now(),
|
|
|
});
|
|
|
} else {
|
|
|
pageVisibleTime = Date.now();
|
|
|
}
|
|
|
});
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 资源加载监控
|
|
|
initResourceTracking() {
|
|
|
if (performance && performance.getEntriesByType) {
|
|
|
// 监控资源加载
|
|
|
- const resources = performance.getEntriesByType('resource');
|
|
|
- resources.forEach(resource => {
|
|
|
+ const resources = performance.getEntriesByType("resource");
|
|
|
+ resources.forEach((resource) => {
|
|
|
this.reportData({
|
|
|
- type: 'resource_load',
|
|
|
+ type: "resource_load",
|
|
|
name: resource.name,
|
|
|
duration: resource.duration,
|
|
|
entryType: resource.entryType,
|
|
|
- timestamp: Date.now()
|
|
|
+ timestamp: Date.now(),
|
|
|
});
|
|
|
});
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 上报错误
|
|
|
reportError(errorData) {
|
|
|
// 检查采样率
|
|
|
if (Math.random() > this.config.sampleRate) {
|
|
|
return; // 根据采样率决定是否上报
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
this.reportData({
|
|
|
- type: 'error',
|
|
|
- ...errorData
|
|
|
+ type: "error",
|
|
|
+ ...errorData,
|
|
|
});
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 上报数据
|
|
|
reportData(data) {
|
|
|
// 检查采样率
|
|
|
if (Math.random() > this.config.sampleRate) {
|
|
|
return; // 根据采样率决定是否上报
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 添加通用信息
|
|
|
data.sessionId = this.getSessionId();
|
|
|
data.userAgent = navigator.userAgent;
|
|
|
data.url = window.location.href;
|
|
|
data.timestamp = Date.now();
|
|
|
-
|
|
|
+
|
|
|
// 发送到服务端
|
|
|
if (this.config.apiUrl) {
|
|
|
try {
|
|
|
// 使用navigator.sendBeacon发送数据,即使页面关闭也能发送
|
|
|
if (navigator.sendBeacon) {
|
|
|
- navigator.sendBeacon(
|
|
|
- this.config.apiUrl,
|
|
|
- JSON.stringify(data)
|
|
|
- );
|
|
|
+ navigator.sendBeacon(this.config.apiUrl, JSON.stringify(data));
|
|
|
} else {
|
|
|
// 备用方案:使用fetch
|
|
|
fetch(this.config.apiUrl, {
|
|
|
- method: 'POST',
|
|
|
+ method: "POST",
|
|
|
body: JSON.stringify(data),
|
|
|
headers: {
|
|
|
- 'Content-Type': 'application/json'
|
|
|
- }
|
|
|
- }).catch(err => {
|
|
|
- console.error('MonitorSDK: Failed to send data:', err);
|
|
|
+ "Content-Type": "application/json",
|
|
|
+ },
|
|
|
+ }).catch((err) => {
|
|
|
+ console.error("MonitorSDK: Failed to send data:", err);
|
|
|
});
|
|
|
}
|
|
|
} catch (error) {
|
|
|
- console.error('MonitorSDK: Error sending data:', error);
|
|
|
+ console.error("MonitorSDK: Error sending data:", error);
|
|
|
}
|
|
|
} else {
|
|
|
// 开发模式下打印到控制台
|
|
|
- console.log('MonitorSDK: Data to send:', data);
|
|
|
+ console.log("MonitorSDK: Data to send:", data);
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 开始定期上报
|
|
|
startReporting() {
|
|
|
setInterval(() => {
|
|
|
// 这里可以实现批量上报逻辑
|
|
|
}, this.config.reportInterval);
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 获取会话ID
|
|
|
getSessionId() {
|
|
|
if (!this.sessionId) {
|
|
|
- this.sessionId = 'session_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
|
|
|
+ this.sessionId = "session_" + Date.now() + "_" + Math.random().toString(36).substr(2, 9);
|
|
|
}
|
|
|
return this.sessionId;
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 测量Web Vitals
|
|
|
measureWebVitals() {
|
|
|
// 这里可以集成web-vitals库来测量核心Web指标
|
|
|
// 如CLS (Cumulative Layout Shift), FID (First Input Delay), LCP (Largest Contentful Paint)
|
|
|
- if ('PerformanceObserver' in window) {
|
|
|
+ if ("PerformanceObserver" in window) {
|
|
|
// LCP (Largest Contentful Paint)
|
|
|
new PerformanceObserver((entryList) => {
|
|
|
const entries = entryList.getEntries();
|
|
|
const lastEntry = entries[entries.length - 1];
|
|
|
if (lastEntry) {
|
|
|
this.reportData({
|
|
|
- type: 'web_vitals',
|
|
|
- metric: 'LCP',
|
|
|
+ type: "web_vitals",
|
|
|
+ metric: "LCP",
|
|
|
value: lastEntry.startTime,
|
|
|
- timestamp: Date.now()
|
|
|
+ timestamp: Date.now(),
|
|
|
});
|
|
|
}
|
|
|
- }).observe({ entryTypes: ['largest-contentful-paint'] });
|
|
|
-
|
|
|
+ }).observe({ entryTypes: ["largest-contentful-paint"] });
|
|
|
+
|
|
|
// FID (First Input Delay)
|
|
|
new PerformanceObserver((entryList) => {
|
|
|
const firstInput = entryList.getEntries()[0];
|
|
|
if (firstInput) {
|
|
|
const fid = firstInput.processingStart - firstInput.startTime;
|
|
|
this.reportData({
|
|
|
- type: 'web_vitals',
|
|
|
- metric: 'FID',
|
|
|
+ type: "web_vitals",
|
|
|
+ metric: "FID",
|
|
|
value: fid,
|
|
|
- timestamp: Date.now()
|
|
|
+ timestamp: Date.now(),
|
|
|
});
|
|
|
}
|
|
|
- }).observe({ entryTypes: ['first-input'] });
|
|
|
-
|
|
|
+ }).observe({ entryTypes: ["first-input"] });
|
|
|
+
|
|
|
// CLS (Cumulative Layout Shift)
|
|
|
let clsValue = 0;
|
|
|
new PerformanceObserver((entryList) => {
|
|
|
@@ -260,25 +258,25 @@ class MonitorSDK {
|
|
|
}
|
|
|
}
|
|
|
this.reportData({
|
|
|
- type: 'web_vitals',
|
|
|
- metric: 'CLS',
|
|
|
+ type: "web_vitals",
|
|
|
+ metric: "CLS",
|
|
|
value: clsValue,
|
|
|
- timestamp: Date.now()
|
|
|
+ timestamp: Date.now(),
|
|
|
});
|
|
|
- }).observe({ entryTypes: ['layout-shift'] });
|
|
|
+ }).observe({ entryTypes: ["layout-shift"] });
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 初始化监控SDK
|
|
|
const monitor = new MonitorSDK({
|
|
|
- appId: 'monitor-app',
|
|
|
- apiUrl: '/api/monitor', // 这里应该是实际的API地址
|
|
|
+ appId: "monitor-app",
|
|
|
+ apiUrl: "/api/monitor", // 这里应该是实际的API地址
|
|
|
enableErrorTracking: true,
|
|
|
enablePerformanceTracking: true,
|
|
|
enableUserBehaviorTracking: true,
|
|
|
enableResourceTracking: true,
|
|
|
- sampleRate: 1.0
|
|
|
+ sampleRate: 1.0,
|
|
|
});
|
|
|
|
|
|
// 将monitor实例暴露到全局,方便调用
|