Log SDK
@parallelsandbox/log 是一個很小的瀏覽器 SDK。掛在任何你在箱子裡(或其他地方)跑的網頁上,console 輸出與錯誤就會進到你自己的 log project。你的 agent 之後用 logs_search、logs_errors、logs_tail 讀,不用再叫你貼 console。
它只在瀏覽器裡跑:它掛的是頁面的 console 與 window 事件,log 服務也只收 project 允許的 origin 送來的批次。它不是伺服器端的 logger。箱子裡直接跑的程式,直接讀它們的輸出(見不用 log 服務時)。
Project 與 key
log project 用 REST 建,帶 token(API key 或 OAuth access token,key 從哪來);從你自己的機器建,不要在箱子裡:
curl -fsS -X POST https://log.parallelsandbox.com/v1/projects \
-H "Authorization: Bearer $PSBX_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "name": "example-web", "origins": ["http://localhost:8080", "https://*.box.parallelsandbox.com"] }'
回應是 {"project": {"id": "prj_...", ...}, "writeKey": "psw_..."}。每個 project 有:
- project id,
prj_...,每次查詢都帶(工具也接受 project 的名稱); - write key,
psw_...,瀏覽器每批 log 都帶。只在這次回應裡出現。它只能寫這個 project,所以放在網頁上沒關係。POST /v1/projects/{id}/write-key換一把新的,舊的立刻失效; - origin 白名單:其他來源送的一律拒收。沒給
origins時是https://*.box.parallelsandbox.com,涵蓋經箱子網址(sceneUrl與它 web 服務的網址)打開的頁面。你給的清單會取代這個預設,兩種都要就一起列。每一項是scheme://host[:port]、https://*.example.com、null或*(任何來源,連沒帶 origin 的請求也收),最多 32 項。用PATCH /v1/projects/{id}帶{"origins": [...]}修改; - 每個 project 與每個送出位址的限流。
GET /v1/projects 列出你的 project;DELETE /v1/projects/{id} 連同它的 log 與 source map 一起刪掉。每個 project 自己一個 log group。log 不設保留期限,會一直留到刪掉 project;刪掉之後 log 與 source map 的保存費用就停了。
安裝
請用 0.1.2 以上。它從箱子任何一個網址的 host 讀箱子 id:<id>-<key>.box.parallelsandbox.com(sceneUrl,第一個宣告的服務是 web 時也是它的 url)與 <id>-<key>-<targetPort>.box.parallelsandbox.com(之後的 web 服務的 url),而且不送 key:訊息、stack、route 與 extra 裡的每個 <id>-<key> 在送出頁面之前都換成 <id>,所以 https://<id>-<key>-5173.box.parallelsandbox.com/app.js 記成 https://<id>-5173.box.parallelsandbox.com/app.js。log 服務收到時也會再換一次。更早的版本不會從 web 服務的網址讀箱子 id。
用 bundler 的話從 npm 裝:
npm install @parallelsandbox/log@^0.1.2
不用 bundler 的話用 import map(@0.1 是 0.1.x 的最新版):
<script type="importmap">
{ "imports": { "@parallelsandbox/log": "https://cdn.jsdelivr.net/npm/@parallelsandbox/log@0.1/+esm" } }
</script>
初始化
import { init } from '@parallelsandbox/log';
const log = init({
writeKey: 'psw_...', // project 的 write key;它本身就指明是哪個 project
release: 'a1b2c3d', // build id;要跟上傳 source map 時用的 release 一樣
});
呼叫一次,越早越好。writeKey 不是 psw_ 開頭時 init 會丟錯;再呼叫一次會換掉前一個 logger。選項:
| 選項 | 預設 | 說明 |
|---|---|---|
writeKey |
必填 | project 的 write key |
endpoint |
https://log.parallelsandbox.com |
log 服務 |
release |
空 | build id;跟上傳的 source map 對得上,stack 才會還原 |
boxId |
從頁面 host 讀 | 箱子 id;從箱子網址的 host(<id>-<key>.box.parallelsandbox.com 或 <id>-<key>-<targetPort>.box.parallelsandbox.com)讀,其他地方是空的 |
boxDomain |
box.parallelsandbox.com |
箱子 host 的網域 |
captureConsole |
true |
掛上 console.debug、log、info、warn、error |
captureErrors |
true |
收 window 的錯誤與未處理的 promise rejection |
levels |
['info', 'warn', 'error'] |
要送的等級;要送 console.debug 就加上 'debug' |
flushIntervalMs |
2000 |
多久送一次 |
batchSize |
50 |
每個請求幾筆,最多 200 |
maxBuffer |
1000 |
記憶體裡最多留幾筆,超過的新 log 會丟掉 |
platform |
'web' |
每批都帶的標籤 |
init 之後:
console.log以info送,其他 console 方法用各自的等級;未處理的錯誤會變成[uncaught] <訊息> at <檔案>:<行>:<欄>並附 stack,未處理的 rejection 變成[unhandledrejection] <訊息>。- 每隔
flushIntervalMs批次送出,頁面隱藏或卸載時把剩下的送掉。 - 每批都帶
release、platform、user agent、頁面的路徑與 query(route),以及箱子 id。頁面經箱子的網址供應時,箱子 id 從它的 host 取,key 不會離開頁面:這批 log 裡的每個<id>-<key>都換成<id>;log 服務收到時也會再換一次,並從頁面的 origin 取箱子 id。在箱子裡用localhost打開的頁面沒有箱子 id,除非你給boxId。
手動呼叫
init 回傳 logger:
log.info('checkout started', { cart: 3 });
log.warn('slow response', { ms: 2400 });
try {
riskyThing();
} catch (err) {
log.error(err, { step: 'riskyThing' }); // Error 會保留 stack
}
await log.flush(); // 測試裡要離開頁面之前
debug、info、warn 接一個訊息與選填的 extra 物件;error 接一個 Error 或任何值,加選填的 extra。extra 是任何能轉成 JSON 的物件,可以用 logs_search 搜。logger 另外有 setBox(id)、setRelease(release)、boxId 與 uninstall()(把 console 與 window 的事件還原)。
Source map
壓縮過的 stack 對 agent 沒有用。每次 build 都用你給 init 的同一個 release 上傳 source map,logs_errors 回的 stack 就會還原到原始檔案與行號。
curl -fsS -X POST https://log.parallelsandbox.com/v1/projects/prj_.../sourcemaps \
-H "Authorization: Bearer $PSBX_TOKEN" \
-F "release=a1b2c3d" \
-F "assets/app-ALQGRFWG.js.map=@dist/assets/app-ALQGRFWG.js.map;type=application/json" \
-F "assets/log-init-K6FPZHJN.js.map=@dist/assets/log-init-K6FPZHJN.js.map;type=application/json"
每個 map 是一個檔案欄位,欄位名稱就是 map 在網站上的路徑,相對於頁面的 origin(stack 裡那支 script 再加 .map);一個請求可以帶同一個 release 的所有 map。上傳用帳號的 token,不是 write key。GET /v1/projects/{id}/sourcemaps?release= 列出傳過的。example-web-with-logs 有一支腳本,整個 dist/ 目錄一次傳完。
stack 只在錯誤送到的那一刻還原一次,而且只還原有 stack、有 release 的 error 等級。先傳 source map 再讓頁面跑;在那之前送到的錯誤會維持壓縮過的 stack。
從 agent 查
logs_errors { "project": "prj_...", "since": "30m", "boxId": "<box id>" }
logs_search { "project": "prj_...", "since": "1h", "query": "checkout" }
logs_tail { "project": "prj_...", "boxId": "<box id>" }
每一列有 ts、ingestedAt、level、box、release、platform、route、msg、stack、stackResolved、extra 與 stream。logs_search 與 logs_errors 由舊到新回,附 nextCursor;logs_tail 是長輪詢,有新的列或等到 waitSec 就回,附給下一次呼叫用的 cursor。參數見工具參考。
費用
log 的寫入與保存跟其他東西一樣計量:
| 用量 | 點數 |
|---|---|
| 寫入 | 每 GB 760 點 |
| 保存 | 每 GB 每月 33 點 |
一個每天回報幾百個錯誤的網頁,一個月遠低於一點。