npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

edgesync360-edgehub-logbook-nodejs-sdk

v2.0.1

Published

EdgeHub LogBook SDK for Node.js — structured audit logging for microservices (IEC 62443 / EU CRA)

Downloads

121

Readme

EdgeHub LogBook — Node.js SDK

v2.0.1 · A structured audit-log SDK for microservices, designed for IEC 62443 / EU CRA compliance.
API behaviour is consistent across Go (common-library/logbook), Java (dc-edge-sdk-logbook-java), and Node.js.


Table of Contents

  1. Installation
  2. Overview — Three API Flavours
  3. Quick Start
  4. Minimal usage
  5. Scope entry points
  6. Registering Actions
  7. resource() — Type Auto-Inference
  8. AuditTask — Multi-step Audit
  9. AuditEvent — Single-step Audit
  10. logBook — General System Log
  11. Severity Methods
  12. Cross-service MQ Trace Propagation
  13. Express Middleware
  14. Constants Reference
  15. Custom Transport
  16. i18n Action Names

Installation

npm install edgesync360-edgehub-logbook-nodejs-sdk

Peer dependency: requires mongodb >= 5 (only when using the built-in MongoTransport).


Overview — Three API Flavours

SDK 提供三種 log 記錄方式,依使用情境選擇:

| API | 用途 | 典型場景 | |-----|------|----------| | auditTask() | 多步驟稽核流程 | 使用者操作需記錄 start → progress → close | | auditEvent() | 單步驟稽核 | 一次性事件,一行鏈結即完成 | | logBook() | 系統層級 log (tp=sys) | 背景排程、流量統計等無明確操作者的紀錄 |

核心名詞

| 名詞 | 說明 | |------|------| | Scope | 上下文物件,攜帶 tenantIDaccountNametraceIDspanID | | TraceID | 32 字元小寫 hex,用來串聯同一操作在不同服務間的所有 log | | Action | 操作代碼(如 GROUP.CREATE),透過 registerActions 預先註冊 | | Resource | 被操作的目標資源(id + name),type 由註冊的 action 自動推斷 |


Quick Start

const {
  LogClient, MongoTransport,
  SERVICE, SOURCE, RESOURCETYPE
} = require("edgesync360-edgehub-logbook-nodejs-sdk");

// 1. 建立 Transport
const transport = new MongoTransport("mongodb://localhost:27017", "my_db");

// 2. 初始化 Client
const client = new LogClient(transport, {
  serviceName: SERVICE.DPM,   // 預設 service(可在鏈中覆寫)
  source: SOURCE.EDGE         // 預設 source
});

// 3. 註冊 Action(啟動時呼叫一次)
await client.registerActions({
  svc: SERVICE.DM,
  acts: [
    { code: "GROUP.CREATE",  name: "Create Group",  res_type: RESOURCETYPE.GROUP  },
    { code: "DEVICE.CREATE", name: "Create Device", res_type: RESOURCETYPE.DEVICE },
  ],
  v: "1.0"
});

// 4. 寫一筆 audit log
await client.auditEvent()
  .accountName("[email protected]")
  .tenant("t-001", "MyTenant")
  .source(SOURCE.WEB)
  .action("GROUP.CREATE")
  .resource("g-001", "MyGroup")  
  .info("Done")
  .reportSuccess();

Scope entry points

auditTask() / auditEvent() 的 scope(tenantIDaccountNametraceIDspanID 等)可用下列三種方式之一帶入,擇一即可,不必併用。

| 方式 | 行為摘要 | 何時用 | |------|----------|--------| | fromRequest(req) | 從 cookieEIToken JWT → accountNameIFPTenanttenantIDTenantName → 租戶顯示名)與 headerx-logbook-trace-id / traceparentx-span-id / x-logbook-span-id)組 scope;無則自動產生 trace / span | 標準 Web 請求,與 Go / Java SDK 解析慣例一致 | | fromScopeContext() | 從 AsyncLocalStorage 讀取目前 async 鏈上的 scope | 已掛 middleware()runWithScope(),深層程式拿不到 req 時 | | 手動鏈結 | .accountName().tenant().traceID().source().service() … 自行帶欄位 | 無 HTTP、自訂登入、或欄位來源與預設 cookie/header 不同 |

fromScopeContext() 的前提:必須在具有 ALS store 的上下文內呼叫。Express 專案通常:

// 進入點:middleware 用 fromRequest + runWithScope 包住後續處理
app.use(client.middleware());

// 深層:不需 req
const task = client.auditTask().fromScopeContext()
  .action("GROUP.CREATE")
  .resource("g-001", "MyGroup");

手動建立 ALS 上下文(不依賴 Express):可傳 plain scope 物件ScopedClient(與 middleware() 相同)。

await client.runWithScope(
  {
    tenantID: "t-001",
    tenant: "MyTenant",
    accountName: "[email protected]",
    source: SOURCE.WEB,
  },
  async () => {
    await client.auditEvent().fromScopeContext()
      .action("GROUP.CREATE")
      .resource("g-001", "MyGroup")
      .info("inside ALS")
      .reportSuccess();
  }
);

Express MiddlewareCross-service MQ 搭配時,建議先讀本節再選進入點。


Registering Actions

在服務啟動時呼叫一次,將 action 定義寫入 dc_log_action collection。
註冊後,鏈中呼叫 .action(code) 會自動帶入 namesvcres_type,不需手動輸入。

await client.registerActions({
  svc: SERVICE.DM,
  acts: [
    { code: "GROUP.CREATE",  name: "Create Group",   res_type: RESOURCETYPE.GROUP  },
    { code: "DEVICE.CREATE", name: "Create Device",  res_type: RESOURCETYPE.DEVICE },
    { code: "OTA.UPDATE",    name: "OTA Update",     res_type: RESOURCETYPE.DEVICE },
    { code: "USAGE.REPORT",  name: "Report Usage",   res_type: RESOURCETYPE.USAGE  },
  ],
  v: "1.0"
});

resource() — Type Auto-Inference

所有 API 的 resource() 統一只接受兩個參數 (id, name),resource type 由 registerActions 自動推斷:

// 先呼叫 .action(),再呼叫 .resource()
.action("DEVICE.CREATE")           // 從註冊資料帶入 res_type = DEVICE
.resource("dev-001", "My-Device")  // type 自動為 DEVICE,無需手動傳入

只要 registerActions 有定義 res_type,SDK 會自動對應。無需也無法手動指定 type。


AuditTask — Multi-step Audit

適用於需要記錄「開始 → 處理中 → 結案」的多步驟操作。

方式一:從 HTTP Request 自動取得 Scope

從 cookie(EIToken JWT / IFPTenant / TenantName)和 x-logbook-trace-id header 自動解析使用者與租戶資訊。

const task = client.auditTask().fromRequest(req)
  .source(SOURCE.WEB)
  .action("GROUP.CREATE")
  .resource("g-001", "MyGroup");

await task.info("Starting group creation").start();
await task.info("Data written to database").progress();
await task.info("Group created successfully").reportSuccess();
// 其他結案方式:.reportFail() / .reportCancel()

附帶 i18n 多語訊息:

await task.info("Starting group creation")
  .withMsgs("開始建立群組", "开始新增群组", "グループの追加を開始します")  // tw, cn, jp
  .start();

方式二:手動建構 Scope(背景任務)

未帶 source 時預設為 SOURCE.INTERNAL

const task = client.auditTask()
  .accountName("[email protected]")
  .tenant("t-worker", "BackgroundWorkerTenant")
  .action("OTA.UPDATE")
  .resource("dev-002", "Smart-Gateway-01");

await task.info("Start background OTA sync").start();
await task.info("Syncing metadata...").progress();
await task.info("OTA sync completed").reportSuccess();

方式三:從 AsyncLocalStorage 繼承 Scope

需先掛載 client.middleware()(見 Express Middleware)。
middleware 會在每個 request 進來時自動解析 scope 並存入背景,之後任何深度的程式碼都能用 fromScopeContext() 取得,不需要層層傳遞 req

// service layer — 完全不需要 req 參數
async function createDevice(device) {
  const task = client.auditTask().fromScopeContext()
    .action("DEVICE.CREATE")
    .resource(device.id, device.name);

  await task.info("Creating device").start();
  await db.insert(device);
  await task.info("Device created").reportSuccess();
}

適合 audit log 寫在較深層的 service / helper 中,不想一路傳 req 的情境。


AuditEvent — Single-step Audit

適用於一次性完成的事件,不需 start/progress,一行鏈結即結案。
resource() 是可選的 — 可以在 action() 之後直接呼叫 severity 方法。

// 帶 resource
await client.auditEvent().fromRequest(req)
  .source(SOURCE.WEB)
  .action("GROUP.CREATE")
  .resource("g-001", "MyGroup")
  .info("Group created")
  .reportSuccess();

// 不帶 resource(直接接 severity)
await client.auditEvent()
  .accountName("[email protected]")
  .tenant("t-001", "test tenant")
  .action("GROUP.CREATE")
  .info("Group created (no resource)")
  .reportSuccess();

MQ 消費端已有 ScopedClient 時:

const scopedB = client.fromMessageAttrs(mqAttrs, SOURCE.INTERNAL);

await scopedB.auditEvent()
  .action("DEVICE.CREATE")
  .resource(payload.device_id, payload.device_name)
  .info("Service B processed and logged")
  .reportSuccess();

logBook — General System Log

用於系統層級紀錄(tp = sys),例如排程回報、流量統計等無明確操作者的場景。
Tenant 為必填accountName 可省略(省略時自動帶 "-")。
resource() 是可選的。

// 最簡形式:tenant + action + severity + result
await client.logBook()
  .tenant("t-001", "MyTenant")
  .action("USAGE.REPORT")
  .info("Device reported usage")
  .reportSuccess();

// 帶 resource(type 由 registerActions 自動推斷)
await client.logBook()
  .tenant("t-001", "MyTenant")
  .action("USAGE.REPORT")
  .resource("epn-001", "Edge-Device")
  .info("EPN reported traffic")
  .reportSuccess();

// 帶 accountName
await client.logBook()
  .tenant("t-001", "MyTenant")
  .accountName("[email protected]")
  .action("USAGE.REPORT")
  .info("Usage confirmed")
  .reportSuccess();

forLogBook(scope) — 直接傳入 scope 物件:

await client.forLogBook({ tenantID: "t-001", tenant: "MyTenant" })
  .action("USAGE.REPORT")
  .info("Report complete")
  .reportSuccess();

LogBookEntryBuilder 方法

| Method | Description | |--------|-------------| | .withCtx({ key: "value" }) | 附加 key-value 上下文(value 必須為字串) | | .reportSuccess() | result = success | | .reportFail() | result = failed | | .reportCancel() | result = cancel |


Severity Methods

所有 API(AuditTask / AuditEvent / logBook)均共用以下 severity 方法:

| Method | 用途 | |--------|------| | .info(msg) | 一般資訊 | | .warn(msg) | 警告 | | .error(msg) | 錯誤 | | .critical(msg) | 嚴重錯誤 | | .debug(msg) | 除錯 |

呼叫後回傳 EntryBuilder,再以 .reportSuccess() / .reportFail() / .reportCancel() 結案。
AuditTask 額外提供 .start().progress()


Cross-service MQ Trace Propagation

跨服務時透過 MQ header 傳遞 traceID 等 scope 資訊,讓不同服務的 log 可以串聯。

MQTT 5 / RabbitMQ / AMQP

Service A(Publisher)

const scoped = client.auditTask().fromRequest(req).source(SOURCE.WEB);
const task   = scoped.action("DEVICE.CREATE").resource("dev-001", "MyDevice");

await task.info("User initiated device creation").start();

const body  = JSON.stringify(businessPayload);  // 純商業資料
const attrs = scoped.toMQHeaders();             // { "x-logbook-trace-id": ..., ... }

// MQTT 5:         msg.userProperties = scoped.toMQTT5UserProperties()
// AMQP/RabbitMQ:  message headers = attrs

await task.info("Message sent to broker").reportSuccess();

Service B(Subscriber)

const scopedB = client.fromMessageAttrs(attrs, SOURCE.INTERNAL);

await scopedB.auditEvent()
  .action("DEVICE.CREATE")
  .resource(payload.device_id, payload.device_name)
  .info("Service B completed processing")
  .reportSuccess();
// traceID 與 Service A 一致,可在 audit UI 中串聯查詢

MQTT 3 — 將 trace 嵌入 payload envelope

MQTT 3 不支援 user properties,需將 trace 資訊包進 payload。

Service A

const {
  MQTT3_ENVELOPE_KEY_LOGBOOK,
  MQTT3_ENVELOPE_KEY_DATA
} = require("edgesync360-edgehub-logbook-nodejs-sdk");

// 方式一:手動組裝 envelope
const envelope = {
  [MQTT3_ENVELOPE_KEY_LOGBOOK]: scoped.toMQHeaders(),
  [MQTT3_ENVELOPE_KEY_DATA]:    businessPayload,
};
publish(JSON.stringify(envelope));

// 方式二:SDK 自動包裝
const wrappedBuffer = scoped.wrapPayloadWithTrace(
  Buffer.from(JSON.stringify(businessPayload))
);
publish(wrappedBuffer);

Service B

const { unwrapPayloadAndScope } = require("edgesync360-edgehub-logbook-nodejs-sdk");

const { scopeMap, dataBytes } = unwrapPayloadAndScope(rawPayloadBuffer);
const scopedB = client.fromMessageAttrs(scopeMap, SOURCE.INTERNAL);
const data    = JSON.parse(dataBytes.toString());

await scopedB.auditEvent()
  .action("DEVICE.CREATE")
  .resource(data.device_id, data.device_name)
  .info("Service B completed processing (MQTT 3)")
  .reportSuccess();

Propagation Key Constants

const {
  KEY_TRACE_ID,      // "x-logbook-trace-id"
  KEY_SPAN_ID,       // "x-logbook-span-id"
  KEY_ACCOUNT_NAME,  // "x-logbook-account-name"
  KEY_TENANT_ID,     // "x-logbook-tenant-id"
  KEY_TENANT_NAME,   // "x-logbook-tenant-name"
} = require("edgesync360-edgehub-logbook-nodejs-sdk");

Express Middleware

client.middleware() 是 SDK 提供的 Express middleware,作用:

  1. 從 request 的 cookieEIToken / IFPTenant / TenantName)解析使用者與租戶資訊
  2. headerx-logbook-trace-id)取得 traceID;若沒有則自動產生
  3. 將解析出的 scope 存入 AsyncLocalStorage,讓後續程式碼可用 fromScopeContext() 取得
const express      = require("express");
const cookieParser = require("cookie-parser");
const app          = express();

app.use(cookieParser());       // 解析 cookie 字串 → req.cookies 物件
app.use(client.middleware());  // 解析 scope → 存入 AsyncLocalStorage

app.post("/groups", async (req, res) => {
  const task = client.auditTask().fromScopeContext()
    .action("GROUP.CREATE")
    .resource("g-001", "MyGroup");

  await task.info("Group creation started").start();
  // ... business logic ...
  await task.info("Group created successfully").reportSuccess();
  res.json({ ok: true });
});

服務間呼叫(無 cookie)

服務間 API 呼叫通常沒有 cookie,middleware 不會報錯,只是 accountName / tenantID 會是空的。
兩種處理方式:

方式 A:呼叫端在 header 帶 trace 資訊

// Service A → 呼叫 Service B 時帶上 trace header
const headers = scoped.toMQHeaders();
await fetch("http://service-b/internal/devices", {
  headers: { ...headers, "Content-Type": "application/json" },
  body: JSON.stringify(payload)
});
// Service B 的 middleware 會從 header 讀到 traceID,串聯兩端 log

方式 B:handler 裡手動建構 scope(不依賴 middleware)

await client.auditEvent()
  .accountName("service-a@internal")
  .tenant(req.body.tenantID, req.body.tenantName)
  .action("DEVICE.CREATE")
  .info("Internal call from Service A")
  .reportSuccess();

Constants Reference

LOG_TYPE

| Key | Value | Description | |-----|-------|-------------| | OP | "op" | 操作稽核(AuditTask / AuditEvent) | | SEC | "sec" | 安全事件 | | SYS | "sys" | 系統 log(logBook) |

SEVERITY

| Key | Value | |-----|-------| | I | "info" | | W | "warn" | | E | "error" | | C | "critical" | | D | "debug" |

RESULT

| Key | Value | |-----|-------| | SUCCESS | "success" | | FAIL | "failed" | | PARTIAL | "partial_success" | | CANCEL | "cancel" |

SOURCE

| Key | Value | Description | |-----|-------|-------------| | WEB | "web" | 來自前端操作 | | EDGE | "edge" | 來自邊緣裝置 | | INTERNAL | "internal" | 內部服務 / 背景任務 |

SERVICE

DPM / DM / UM / CC / AE / DA / TM / SS / CM / TFM

RESOURCETYPE

DEVICE / GROUP / USER / TENANT / FILE / ALARM / ROLE / CONTAINER / USAGE … (完整列表見 enums.js)


Custom Transport

如不使用內建 MongoTransport,可自行實作以下介面:

const customTransport = {
  // 必要:寫入一筆 log entry
  async writeLog(entry, dateForPartition) { /* ... */ },

  // 必要:更新 resource 索引(不需要的話傳空函式)
  async updateResourceIndex(entry) { /* ... */ },

  // 選用:前處理 entry(加 hash、轉換欄位等),省略則跳過
  async prepareLog(entry) { return entry; },

  // 選用:註冊 action 定義(無 MongoDB 時可省略)
  async registerActions(input, cache) { /* ... */ },
};

const client = new LogClient(customTransport, { version: "1.0" });

i18n Action Names

使用 Names.of(tw, cn, jp) 為 action 註冊多語顯示名稱:

const { Names } = require("edgesync360-edgehub-logbook-nodejs-sdk");

await client.registerActions({
  svc: SERVICE.DM,
  acts: [{
    code: "GROUP.CREATE",
    name: "Create Group",
    names: Names.of("建立群組", "新增群组", "グループの追加"),  // tw, cn, jp
    res_type: RESOURCETYPE.GROUP,
  }],
  v: "1.0"
});

License

MIT