如果你正在建立 JavaScript 函式庫並需要記錄功能,你可能會喜歡 LogTape

洪 民憙 (Hong Minhee) @hongminhee@hackers.pub

建立 JavaScript 函式庫是一種微妙的平衡。你希望提供有用的功能,同時尊重使用者的選擇和限制。當涉及到記錄功能(許多函式庫需要用於除錯、監控和使用者支援)時,這種平衡變得特別具有挑戰性。

JavaScript 生態系統已經發展出各種應對這一挑戰的方法,每種方法都有其自身的權衡。LogTape 提供了一條不同的路徑,這是專為函式庫作者設計的。

函式庫記錄功能的現狀

如果你之前曾建立過函式庫,你可能已經遇到過記錄功能的困境。你的函式庫會從記錄功能中受益——也許是幫助使用者除錯整合問題、追蹤內部狀態變化,或提供對效能瓶頸的洞察。但你如何負責任地添加這種功能呢?

目前,流行的函式庫通過幾種方式處理這一挑戰:

debug 方法
像 Express 和 Socket.IO 這樣的函式庫使用輕量級的 debug 套件,它允許使用者通過環境變數(DEBUG=express:*)啟用記錄功能。這種方法效果不錯,但創建了一個獨立的記錄系統,無法與使用者現有的記錄基礎設施整合。
自定義記錄系統
像 Mongoose 和 Prisma 這樣的函式庫已經建立了自己的記錄機制。Mongoose 提供 mongoose.set('debug', true),而 Prisma 使用自己的記錄配置。這些方法有效,但每個函式庫都創建了自己的記錄 API,使用者必須分別學習。
應用程式導向的函式庫
winstonPinoBunyan 是強大的記錄解決方案,但它們主要是為應用程式而非函式庫設計的。在函式庫中使用它們意味著強加重要的依賴關係,並可能與使用者現有的記錄選擇產生衝突。
完全不記錄
許多函式庫作者完全避開這種複雜性,讓他們的函式庫保持沉默,使所有相關人員的除錯工作更具挑戰性。
依賴注入
一些函式庫採用更複雜的方法,通過配置或構造函數參數接受來自應用程式的記錄器實例。這保持了關注點的清晰分離,並允許函式庫使用應用程式選擇的任何記錄系統。然而,這種模式需要更複雜的 API,並給函式庫使用者帶來額外的負擔,要求他們理解和配置記錄依賴關係。

每種方法都代表了對真實問題的合理解決方案,但沒有一種完全解決核心矛盾:如何在不強加選擇給使用者的情況下提供有價值的診斷功能?

碎片化問題

當每個函式庫都以自己的方式解決記錄問題時,會出現另一個挑戰:碎片化。考慮一個典型的 Node.js 應用程式,它可能使用 Express 作為網頁框架,Socket.IO 用於即時通訊,Axios 用於 HTTP 請求,Mongoose 用於資料庫存取,以及其他幾個專門的函式庫。

每個函式庫可能都有自己的記錄方法:

  • Express 使用 DEBUG=express:*
  • Socket.IO 使用 DEBUG=socket.io:*
  • Mongoose 使用 mongoose.set('debug', true)
  • Axios 可能使用 axios-logger 或類似套件
  • Redis 客戶端有自己的除錯配置
  • 認證函式庫通常包含自己的記錄機制

從應用程式開發者的角度來看,這造成了管理上的挑戰。他們必須學習和配置多個不同的記錄系統,每個系統都有自己的語法、功能和特性。記錄分散在不同的輸出中,格式不一致,使得難以獲得應用程式中發生的事情的統一視圖。

缺乏整合也意味著像結構化記錄、記錄關聯和集中式記錄管理等強大功能變得更難在所有使用中的函式庫中一致地實現。

LogTape 的方法

LogTape 嘗試通過所謂的"函式庫優先設計"來解決這些挑戰。核心原則簡單但潛力強大:如果沒有配置記錄功能,什麼都不會發生。沒有輸出,沒有錯誤,沒有副作用——只有完全的透明性。

這種方法允許你在函式庫中添加全面的記錄功能,而不會對不需要它的使用者產生任何影響。當使用者導入你的函式庫並運行他們的程式碼時,LogTape 的記錄呼叫本質上是無操作的,直到有人明確配置記錄功能。想要了解你的函式庫行為的使用者可以選擇加入;那些不想要的則完全不受影響。

更重要的是,當使用者確實選擇配置記錄功能時,所有啟用 LogTape 的函式庫都可以通過單一的、統一的配置系統進行管理。這意味著所有函式庫記錄都使用一個一致的 API、一種記錄格式和一個目的地,同時仍然允許對從哪些函式庫記錄什麼內容進行精細控制。

Note

這種方法並非完全創新——它從 Python 的標準 logging 函式庫汲取靈感,該函式庫已成功創建了一個統一的記錄生態系統。在 Python 中,像 Requests、SQLAlchemy 和 Django 組件等函式庫都使用標準記錄框架,允許開發者通過單一、一致的系統配置所有函式庫記錄。這已被證明既實用又強大,在整個 Python 生態系統中啟用豐富的診斷功能,同時為應用程式開發者保持簡單性。

// 在你的函式庫程式碼中 - 完全安全地包含
import { getLogger } from "@logtape/logtape";

const logger = getLogger(["my-awesome-lib", "database"]);

export function connectToDatabase(config) {
  logger.debug("嘗試資料庫連接", { config });
  // ... 你的邏輯
  logger.info("資料庫連接已建立");
}

依賴性考量

現代 JavaScript 開發涉及對依賴關係的仔細考量。雖然像 winston 和 Pino 這樣流行的記錄函式庫維護良好且廣受信任,但它們確實帶有自己的依賴樹。例如,winston 包含 17 個依賴項,而 Pino 包含 1 個。

對於函式庫作者來說,這產生了一個考量:你添加的每個依賴都會成為使用者的依賴,無論他們是否需要它。這不一定有問題(許多優秀的函式庫都有依賴),但它確實代表了你代表使用者做出的選擇。

LogTape 採用了不同的方法,零依賴。這不僅僅是一個哲學選擇——它對你的函式庫使用者有實際影響。他們不會在 node_modules 中看到額外的套件,不需要擔心與記錄相關依賴的供應鏈考量,也不會面臨你的記錄選擇與他們的潛在版本衝突。

僅 5.3KB 壓縮和 gzip 後,LogTape 對他們的套件增加了最小的重量。安裝過程變得更快,依賴樹保持更乾淨,安全審計仍然專注於直接服務於你的函式庫核心功能的依賴。

打破相容性鏈

這裡有一個可能熟悉的挑戰:你希望你的函式庫同時支援 ESMCommonJS 環境。也許你的一些使用者正在使用依賴 CommonJS 的舊版 Node.js 專案,而其他人則使用現代 ESM 設置或為瀏覽器構建。

當你有依賴關係時,挑戰變得明顯。雖然 ESM 模組可以毫無問題地導入 CommonJS 模組,但反過來則不行——CommonJS 模組無法 require ESM-only 套件(至少在 Node.js 22+ 的實驗性功能 穩定之前)。這創造了一個不對稱的相容性限制。

如果你的函式庫依賴於任何 ESM-only 套件,你的函式庫實際上也會變成 ESM-only,因為 CommonJS 環境將無法使用它。這意味著即使在你的依賴鏈中只有一個 ESM-only 依賴,也可能阻止你支援 CommonJS 使用者。

LogTape 完全支援 ESM 和 CommonJS,這意味著它不會成為強制這種限制的弱點。無論你的使用者是使用舊版 Node.js 專案、尖端 ESM 應用程式還是混合環境,LogTape 都能無縫適應他們的設置。

更重要的是,當 LogTape 提供原生 ESM 支援(而不僅僅是可以作為 CommonJS 導入)時,它在現代打包工具中啟用了樹搖優化(tree shaking)。樹搖優化允許打包工具在構建過程中消除未使用的程式碼,但它需要只有 ESM 提供的靜態 import/export 結構。雖然 CommonJS 模組可以導入到 ESM 專案中,但它們通常被視為不透明的區塊,無法優化,可能在最終打包中包含未使用的程式碼。

對於旨在產生最小影響的記錄函式庫來說,這種優化能力可能很有意義,特別是對於打包大小很重要的應用程式。

通用執行環境支援

JavaScript 生態系統如今涵蓋了令人印象深刻的執行環境範圍。你的函式庫可能在 Node.js 伺服器、Deno 腳本、Bun 應用程式、網頁瀏覽器或邊緣函數中運行。LogTape 在所有這些環境中以相同的方式工作,無需 polyfill、相容性層或特定執行環境的程式碼。

這種通用性意味著你可以專注於函式庫的核心功能,而不必擔心你的記錄選擇是否能在使用者可能遇到的每個環境中工作。無論有人將你的函式庫導入到 Cloudflare Worker、Next.js 應用程式還是 Deno CLI 工具中,記錄行為都保持一致且可靠。

不妥協的效能

函式庫作者經常擔心的一個問題是記錄對效能的影響。如果你的使用者將你的函式庫導入到高效能應用程式中怎麼辦?如果他們在記憶體受限的環境中運行怎麼辦?

當記錄功能被禁用時,LogTape 以卓越的效率解決了這個問題。未配置的 LogTape 呼叫的開銷幾乎為零——是所有可用記錄解決方案中最低的之一。這意味著你可以在整個函式庫中添加詳細的記錄,用於開發和除錯目的,而不必擔心對未啟用記錄的使用者的效能影響。

當記錄功能啟用時,LogTape 的效能始終優於其他函式庫,特別是對於控制台輸出——這通常是開發過程中最常見的記錄目的地。

避免命名空間衝突

共享同一應用程式的函式庫當它們都輸出到相同的命名空間時會造成記錄混亂。LogTape 的分層類別系統通過鼓勵函式庫使用自己的命名空間優雅地解決了這個問題。

你的函式庫可能使用像 ["my-awesome-lib", "database"]["my-awesome-lib", "validation"] 這樣的類別,確保你的記錄與其他函式庫和主應用程式明確分開。配置 LogTape 的使用者可以獨立控制不同函式庫和這些函式庫內不同組件的記錄級別。

開發者體驗的完美運作

LogTape 從頭開始就是用 TypeScript 構建的,這意味著你的基於 TypeScript 的函式庫無需額外的依賴或類型套件就能獲得完全的類型安全。API 感覺自然且現代,支援模板字面量和結構化記錄模式,這些模式與當代 JavaScript 開發實踐良好整合。

// 模板字面量風格 - 感覺自然
logger.info`使用者 ${userId} 執行了動作 ${action}`;

// 結構化記錄 - 適合監控
logger.info("使用者動作完成", { userId, action, duration });

實際整合

在你的函式庫中實際使用 LogTape 令人耳目一新地簡單。你只需導入記錄器,創建適當命名空間的類別,並在有意義的地方記錄。無需配置,無需設置,無需複雜的初始化序列。

import { getLogger } from "@logtape/logtape";

const logger = getLogger(["my-lib", "api"]);

export async function fetchUserData(userId) {
  logger.debug("正在獲取使用者資料", { userId });
  
  try {
    const response = await api.get(`/users/${userId}`);
    logger.info("使用者資料成功獲取", { 
      userId, 
      status: response.status 
    });
    return response.data;
  } catch (error) {
    logger.error("獲取使用者資料失敗", { userId, error });
    throw error;
  }
}

對於想要查看這些記錄的使用者,配置同樣簡單:

import { configure, getConsoleSink } from "@logtape/logtape";

await configure({
  sinks: { console: getConsoleSink() },
  loggers: [
    { category: ["my-lib"], lowestLevel: "info", sinks: ["console"] }
  ]
});

橋接過渡

如果你的潛在使用者已經投資於其他記錄系統,LogTape 為流行的函式庫如 winston 和 Pino 提供了適配器。這允許啟用 LogTape 的函式庫與現有的記錄基礎設施整合,通過應用程式已經使用的任何系統路由它們的記錄。

這些適配器的存在揭示了一個誠實的事實:LogTape 在 JavaScript 生態系統中尚未被廣泛採用為標準。大多數應用程式仍然圍繞著已建立的記錄函式庫構建,要求使用者完全重構他們的記錄方法將是不現實的。這些適配器代表了一種實際的妥協——它們允許函式庫作者利用 LogTape 的函式庫友好設計,同時尊重使用者現有的投資和偏好。

這種方法減少了採用的摩擦,同時仍然為函式庫作者提供了現代、零依賴的記錄 API。也許隨著時間的推移,隨著更多的函式庫採用這種模式,更多的開發者體驗到它的好處,對這類適配器的需求可能會減少。但目前,它們作為 LogTape 願景與生態系統當前現實之間的實用橋樑。

值得考慮的選擇

最終,為你的函式庫選擇 LogTape 代表了一種關於函式庫和應用程式之間關係的特定哲學。這是關於提供功能的同時保留選擇,提供洞察而避免強制。

傳統方法——無論是使用 debug 套件、應用程式導向的記錄器還是自定義解決方案——各有其優點,並且已經很好地服務於社群。LogTape 只是提供了另一個選擇:一個專為函式庫在 JavaScript 生態系統中所處的獨特位置而設計的選擇。

對於函式庫作者來說,這種方法可能提供幾個實際好處。你的函式庫獲得了用於開發、除錯和使用者支援的詳細記錄,而你的使用者保留了對是否以及如何使用這些功能的完全自主權。

更廣泛的好處可能是在 JavaScript 生態系統中提供更具凝聚力的記錄體驗——一個函式庫可以提供豐富的診斷信息,與應用程式選擇採用的任何記錄策略無縫整合。

在每個依賴決策都有影響的世界中,LogTape 提供了一種值得考慮的方法:一種增強函式庫功能的方式,同時尊重使用者的偏好和現有選擇。

7

1 comment

If you have a fediverse account, you can comment on this article from your own instance. Search https://hackers.pub/ap/articles/01979a7b-f451-7c4e-bcd1-206cf175844c on your instance and reply to it.

1