偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

前端日志回撈系統(tǒng)的性能優(yōu)化實踐

開發(fā) 前端
在現(xiàn)代前端應用中,日志回撈系統(tǒng)是排查線上問題的重要工具。然而,傳統(tǒng)的日志系統(tǒng)往往面臨著包體積過大、存儲無限膨脹、性能影響用戶體驗等問題。本文將深入分析我們在@dw/log和@dw/log-upload兩個庫中實施的關(guān)鍵性能優(yōu)化,以及改造過程中遇到的技術(shù)難點和解決方案。

一、前言

二、核心性能優(yōu)化

1. 優(yōu)化一:智能化數(shù)據(jù)庫清理機制

2. 優(yōu)化二:上傳模塊的異步加載架構(gòu)

3. 優(yōu)化三:JSZip 庫的動態(tài)引入

4. 優(yōu)化四:日志隊列與性能優(yōu)化

三、打包構(gòu)建中的技術(shù)難點與解決方案

1. 難點一:異步加載 import() 打包失敗問題

2. 難點二:process 對象未定義問題

3. 難點三:第三方依賴的 ESM/CJS 兼容性問題

四、性能測試與效果對比

五、總結(jié)

一、前言

在現(xiàn)代前端應用中,日志回撈系統(tǒng)是排查線上問題的重要工具。然而,傳統(tǒng)的日志系統(tǒng)往往面臨著包體積過大、存儲無限膨脹、性能影響用戶體驗等問題。本文將深入分析我們在@dw/log和@dw/log-upload兩個庫中實施的關(guān)鍵性能優(yōu)化,以及改造過程中遇到的技術(shù)難點和解決方案。

核心優(yōu)化策略概覽:

我們的優(yōu)化策略主要圍繞三個核心問題:

  • 存儲膨脹問題 - 通過智能清理策略控制本地存儲大小
  • 包體積問題 - 通過異步模塊加載實現(xiàn)按需引入
  • 性能影響問題 - 通過隊列機制和節(jié)流策略提升用戶體驗

二、核心性能優(yōu)化

優(yōu)化一:智能化數(shù)據(jù)庫清理機制

問題背景

傳統(tǒng)日志系統(tǒng)的一個重大痛點是本地存儲無限膨脹。用戶長期使用后,IndexedDB 可能積累數(shù)萬條日志記錄,不僅占用大量存儲空間,更拖慢了所有數(shù)據(jù)庫查詢和寫入操作。

解決方案:雙重清理策略

我們實現(xiàn)了一個智能清理機制,它結(jié)合了兩種策略,并只在瀏覽器空閑時執(zhí)行,避免影響正常業(yè)務。

  • 雙重清理:

按時間清理: 刪除N天前的所有日志。

按數(shù)量清理: 當日志總數(shù)超過閾值時,刪除最舊的日志,直到數(shù)量達標。

/**
 * 綜合清理日志(同時處理過期和數(shù)量限制)
 * @param retentionDays 保留天數(shù)
 * @param maxLogCount 最大日志條數(shù)
 */
async cleanupLogs(retentionDays?: number, maxLogCount?: number): Promise<void> {
  if (!this.db) {
    throw new Error('Database not initialized')
  }
  
  try {
    // 先清理過期日志
    if (retentionDays && retentionDays > 0) {
      await this.clearExpiredLogs(retentionDays)
    }
    
    // 再清理超出數(shù)量限制的日志
    if (maxLogCount && maxLogCount > 0) {
      await this.clearExcessLogs(maxLogCount)
    }
  } catch (error) {
    // 日志清理失敗不應該影響主流程
    console.warn('日志清理失敗:', error)
  }
}
  • 智能調(diào)度:

節(jié)流: 保證清理操作在短時間內(nèi)(如5分鐘)最多執(zhí)行一次。

空閑執(zhí)行: 將清理任務調(diào)度到瀏覽器主線程空閑時執(zhí)行,確保不與用戶交互或頁面渲染爭搶資源。

/**
 * 檢查并執(zhí)行清理(節(jié)流版本,避免頻繁清理)
 */
private checkAndCleanup = (() => {
  let lastCleanup = 0
  const CLEANUP_INTERVAL = 5 * 60 * 1000 // 5分鐘最多清理一次
  
  return () => {
    const now = Date.now()
    if (now - lastCleanup > CLEANUP_INTERVAL) {
      lastCleanup = now
      executeWhenIdle(() => {
        this.performCleanup()
      }, 1000)
    }
  }
})()

優(yōu)化二:上傳模塊的異步加載架構(gòu)

圖片

問題背景

日志上傳功能涉及 OSS 上傳、文件壓縮等重型依賴,如果全部打包到主庫中,會顯著增加包體積。更重要的是,大部分用戶可能永遠不會觸發(fā)日志上傳功能。

解決方案:動態(tài)模塊加載

189KB 的包體積是不可接受的。分析發(fā)現(xiàn),包含文件壓縮(JSZip)和OSS上傳的 @dw/log-upload模塊是體積元兇,但99%的用戶在正常瀏覽時根本用不到它。

我們采取了“核心功能+插件化”的設計思路,將非核心的上傳功能徹底分離。

  • 上傳模塊分離: 將上傳邏輯拆分為獨立的@dw/log-upload庫,并通過CDN托管。
  • 動態(tài)加載實現(xiàn): 僅在用戶手動觸發(fā)“上傳日志”時,才通過動態(tài)創(chuàng)建script標簽的方式,從CDN異步加載上傳模塊。我們設計了一個單例加載器確保模塊只被請求一次。
/**
 * OSS 上傳模塊的遠程 URL
 */
const OSS_UPLOADER_URL = 'https://cdn-jumper.dewu.com/sdk-linker/dw-log-upload.js'


/**
 * 動態(tài)加載遠程模塊
 * 使用單例模式確保模塊只加載一次
 */
const loadRemoteModule = async (): Promise<LogUploadModule> => {
  if (!moduleLoadPromise) {
    moduleLoadPromise = (async () => {
      try {
        await loadScript(OSS_UPLOADER_URL)
        return window.DWLogUpload
      } catch (error) {
        moduleLoadPromise = null
        throw error
      }
    })()
  }
  return moduleLoadPromise
}


/**
 * 上傳文件到 OSS
 */
export const uploadToOss = async (file: File, curEnv?: string, appId?: string): Promise<string> => {
  try {
    // 懶加載上傳函數(shù)
    if (!ossUploader) {
      const module = await loadRemoteModule()
      ossUploader = module.uploadToOss
    }
    
    const result = await ossUploader(file, curEnv, appId)
    return result
  } catch (error) {
    console.info('Failed to upload file to OSS:', error)
    return ''
  }
}

優(yōu)化三:JSZip庫的動態(tài)引入

我們避免將 JSZip 打包到主庫中,從主包中移除,改為在上傳模塊內(nèi)部動態(tài)引入,優(yōu)先使用業(yè)務側(cè)可能已加載的全局window.JSZip。

/**
 * 獲取 JSZip 實例
 */
export const getJSZip = async (): Promise<JSZip | null> => {
  try {
    if (!JSZipCreator) {
      const module = await loadRemoteModule()
      JSZipCreator = module.JSZipCreator
    }
    
    zipInstance = new window.JSZip()
    return zipInstance
  } catch (error) {
    console.info('Failed to create JSZip instance:', error)
    return null
  }
}


// 在上傳模塊中實現(xiàn)靈活的 JSZip 加載
export const JSZipCreator = async () => {
  // 優(yōu)先使用全局 JSZip(如果頁面已經(jīng)加載了)
  if (window.JSZip) {
    return window.JSZip
  }
  return JSZip
}

優(yōu)化四:日志隊列與性能優(yōu)化

圖片圖片

在某些異常場景下,日志會短時間內(nèi)高頻觸發(fā)(如循環(huán)錯誤),密集的IndexedDB.put()操作會阻塞主線程,導致頁面卡頓。

我們引入了一個日志隊列,將所有日志寫入請求“緩沖”起來,再由隊列控制器進行優(yōu)化處理。

  • 限流: 設置每秒最多處理的日志條數(shù)(如50條),超出部分直接丟棄。錯誤(Error)級別的日志擁有最高優(yōu)先級,不受此限制,確保關(guān)鍵信息不丟失。
  • 批處理與空閑執(zhí)行: 將隊列中的日志打包成批次,利用requestIdleCallback在瀏覽器空閑時一次性寫入數(shù)據(jù)庫,極大減少了 I/O 次數(shù)和對主線程的占用。
export class LogQueue {
  private readonly MAX_LOGS_PER_SECOND = 50
  
  /**
   * 檢查限流邏輯
   */
  private checkRateLimit(entry: LogEntry): boolean {
    // 錯誤日志總是被接受
    if (entry.level === 'error') {
      return true
    }
    
    const now = Date.now()
    if (now - this.lastResetTime > 1000) {
      this.logCount = 0
      this.lastResetTime = now
    }
    
    if (this.logCount >= this.MAX_LOGS_PER_SECOND) {
      return false
    }
    
    this.logCount++
    return true
  }
}

空閑時間處理機制:

export function executeWhenIdle(callback: () => void, timeout: number = 2000): void {
  if (typeof window !== 'undefined' && 'requestIdleCallback' in window) {
    window.requestIdleCallback(() => {
      callback()
    }, { timeout })
  } else {
    setTimeout(callback, 50)
  }
}

三、打包構(gòu)建中的技術(shù)難點與解決方案

在改造過程中,我們遇到了許多與打包構(gòu)建相關(guān)的技術(shù)難題。這些問題往往隱藏較深,但一旦出現(xiàn)就會阻塞整個開發(fā)流程。以下是我們遇到的主要問題和解決方案:

難點一:異步加載 import() 打包失敗問題

問題描述

await import('./module')語法在 Rollup 打包為 UMD 格式時會直接報錯,因為 UMD 規(guī)范本身不支持代碼分割。

// 這樣的代碼會導致 UMD 打包失敗
const loadModule = async () => {
  const module = await import('./upload-module')
  return module
}

錯誤信息:

Error: Dynamic imports are not supported in UMD builds
[!] (plugin commonjs) RollupError: "import" is not exported by "empty.js"

解決方案:inlineDynamicImports 配置

通過在 Rollup 配置中設置inlineDynamicImports: true來解決這個問題:

// rollup.config.js
export default {
  input: 'src/index.ts',
  output: [
    {
      file: 'dist/umd/dw-log.js',
      format: 'umd',
      name: 'DwLog',
      // 關(guān)鍵配置:內(nèi)聯(lián)動態(tài)導入
      inlineDynamicImports: true,
    },
    {
      file: 'dist/cjs/index.js',
      format: 'cjs',
      // CJS 格式也需要這個配置
      inlineDynamicImports: true,
    }
  ],
  plugins: [
    typescript(),
    resolve({ browser: true }),
    commonjs(),
  ]
}

配置說明

  • inlineDynamicImports: true會將所有動態(tài)導入的模塊內(nèi)聯(lián)到主包中
  • 這解決了 UMD 格式不支持動態(tài)導入的問題

難點二:process對象未定義問題

問題描述

打包后的代碼在瀏覽器環(huán)境中運行時出現(xiàn)process is not defined錯誤:

ReferenceError: process is not defined
    at Object.<anonymous> (dw-log.umd.js:1234:56)

這通常是因為某些 Node.js 模塊或工具庫在代碼中引用了process對象,而瀏覽器環(huán)境中并不存在。

解決方案:插件注入 process 對象

我們使用@rollup/plugin-inject插件,在打包時向代碼中注入一個模擬的process 對象,以滿足這些庫的運行時需求。

  • 創(chuàng)建process-shim.js文件提供瀏覽器端的process實現(xiàn)。
  • 在rollup.config.js中配置插件:
// rollup.config.js
import inject from '@rollup/plugin-inject'
import path from 'path'


export default {
  // ... 其他配置
  plugins: [
    // 注入 process 對象
    inject({
      // 使用文件導入方式注入 process 對象
      process: path.join(__dirname, 'process-shim.js'),
    }),
    typescript(),
    resolve({ browser: true }),
    commonjs(),
  ]
}

創(chuàng)建 process-shim.js 文件:

// process-shim.js
// 為瀏覽器環(huán)境提供 process 對象的基本實現(xiàn)
export default {
  env: {
    NODE_ENV: 'production'
  },
  browser: true,
  version: '',
  versions: {},
  platform: 'browser',
  argv: [],
  cwd: function() { return '/' },
  nextTick: function(fn) {
    setTimeout(fn, 0)
  }
}

高級解決方案:條件注入

為了更精確地控制注入,我們還可以使用條件注入:

inject({
  // 只在需要的地方注入 process
  process: {
    id: path.join(__dirname, 'process-shim.js'),
    // 可以添加條件,只在特定模塊中注入
    include: ['**/node_modules/**', '**/src/utils/**']
  },
  // 同時處理 global 對象
  global: 'globalThis',
  // 處理 Buffer 對象
  Buffer: ['buffer', 'Buffer'],
})

難點三:第三方依賴的ESM/CJS兼容性問題

問題描述

某些第三方庫(如 JSZip、@poizon/upload)在不同模塊系統(tǒng)下的導入方式不同,導致打包后出現(xiàn)導入錯誤:

TypeError: Cannot read property 'default' of undefined

解決方案:混合導入處理

// 處理 JSZip 的兼容性導入
let JSZipModule: any
try {
  // 嘗試 ESM 導入
  JSZipModule = await import('jszip')
  // 檢查是否有 default 導出
  JSZipModule = JSZipModule.default || JSZipModule
} catch {
  // 降級到全局變量
  JSZipModule = (window as any).JSZip || require('jszip')
}


// 處理 @poizon/upload 的導入
import PoizonUploadClass from '@poizon/upload'


// 兼容不同的導出格式
const PoizonUpload = PoizonUploadClass.default || PoizonUploadClass

在 Rollup 配置中加強兼容性處理:

export default {
  plugins: [
    resolve({
      browser: true,
      preferBuiltins: false,
      // 解決模塊導入問題
      exportConditions: ['browser', 'import', 'module', 'default']
    }),
    commonjs({
      // 處理混合模塊
      dynamicRequireTargets: [
        'node_modules/jszip/**/*.js',
        'node_modules/@poizon/upload/**/*.js'
      ],
      // 轉(zhuǎn)換默認導出
      defaultIsModuleExports: 'auto'
    }),
  ]
}

四、性能測試與效果對比

打包優(yōu)化效果對比:

圖片

五、總結(jié)

通過解決這些打包構(gòu)建中的技術(shù)難點,我們不僅成功完成了日志系統(tǒng)的性能優(yōu)化,還積累了工程化經(jīng)驗。這些實踐不僅帶來了日志系統(tǒng)本身的輕量化與高效化,其經(jīng)驗對于任何追求高性能和穩(wěn)定性的前端項目都有部分參考價值。

責任編輯:武曉燕 來源: 得物技術(shù)
相關(guān)推薦

2021-09-17 18:40:55

京東mPaaS移動端

2014-02-26 11:01:28

日志優(yōu)化系統(tǒng)日志

2022-06-27 09:48:15

H5移動互聯(lián)網(wǎng)頁面性能

2020-03-23 15:15:57

MySQL性能優(yōu)化數(shù)據(jù)庫

2019-11-01 14:00:58

前端性能優(yōu)化代碼

2020-10-16 09:00:12

前端開發(fā)技術(shù)

2022-11-16 12:03:13

性能優(yōu)化前端

2020-10-16 10:40:39

前端性能可視化

2022-05-17 09:02:30

前端性能優(yōu)化

2020-07-17 19:55:50

Vue前端性能優(yōu)化

2010-07-06 09:07:09

2025-06-12 02:22:00

Netflix前端系統(tǒng)

2021-07-05 14:55:28

前端優(yōu)化圖片

2022-03-02 11:13:50

Web前端開發(fā)

2022-09-13 12:56:28

前端優(yōu)化

2019-07-29 10:39:39

前端性能優(yōu)化緩存

2021-09-24 14:02:53

性能優(yōu)化實踐

2019-08-02 11:28:45

HadoopYARN調(diào)度系統(tǒng)

2019-05-21 09:40:47

Elasticsear高性能 API

2022-10-28 13:41:51

字節(jié)SDK監(jiān)控
點贊
收藏

51CTO技術(shù)棧公眾號