Pular para o conteúdo

Logs

Este conteúdo não está disponível em sua língua ainda.

In-memory runtime log collection with a fixed ring buffer, real-time SSE streaming, level/text filters, and a built-in UI at /logs. It’s the visual tail -f of Buntime — focused on transient diagnosis, not long-term retention.

This plugin is a consumer of the runtime’s logging pipeline. For the general logger system (@buntime/shared/logger, console/file transports, child loggers, ctx.logger), see Runtime Logging. For the plugin model (provides, getPlugin, manifest), see Plugin System.

plugin-logs solves a specific problem: quick inspection of recent events without having to install Loki/Elasticsearch or open kubectl logs. Other plugins push entries via the service registry, the UI consumes the SSE stream, and everything disappears on the next restart — by design.

CapabilityHow it works
Ring bufferFixed-size structure (maxEntries); O(1) insertion; automatic eviction of the oldest entry
Filterslevel (debug/info/warn/error) + search (free text in the message) + limit
StatisticsAggregated counters by level since the last clearLogs
SSE streamingPush of the full payload (logs + stats) every sseInterval ms
Service registryaddLog/getLogs/getStats/clearLogs exposed via provides()
Built-in UIReact SPA at /logs — viewer with filters, stats dashboard, and clear action
Other plugins (ctx.getPlugin → addLog) ──┐
POST /api/ ──────────────────────────────┤
Log service (ring buffer + stats)
Ring buffer (maxEntries entries)
┌─────────────────────────┼──────────────────────────┐
▼ ▼ ▼
GET /api/, GET /api/stats GET /api/sse (loop every sseInterval)
┌───────────────┴────────────────┐
▼ ▼
React SPA (/logs) External EventSource
ConfigurationDefaultNotes
enabledfalsePlugin is opt-in; manifest needs enabled: true to start
base/logsRouting prefix (routes + UI)
injectBasetrueUI receives the base path for reverse proxy
maxEntries1000Ring buffer capacity (~500 KB of RAM)
sseInterval1000ms between SSE events (1 push per second)
PersistencenoneEverything lost on restart — by design

For persistent retention, export to an external aggregator (see Limitations and troubleshooting).

All configuration lives in manifest.yaml. There is no runtime API to change maxEntries/sseInterval at execution time.

name: "@buntime/plugin-logs"
base: "/logs"
enabled: true # default false
injectBase: true
entrypoint: dist/client/index.html
pluginEntry: dist/plugin.js
menus:
- icon: lucide:scroll-text
path: /logs
title: Logs
maxEntries: 1000
sseInterval: 1000
OptionTypeDefaultUseful rangeDescription
maxEntriesnumber100010010000Ring buffer size; when full, evicts the oldest entry in O(1)
sseIntervalnumber (ms)10002505000SSE push frequency; lower values = more CPU/bandwidth
maxEntriesEstimated RAMScenario
100~50 KBMinimal footprint, only the most recent events
1000~500 KBDefault — suitable for most setups
5000~2.5 MBHigh-traffic environments
10000~5 MBExtended retention during active diagnosis
ValueUpdate rateCPU/networkTypical use
2504/sHighReal-time dashboard, active debugging
5002/sModerateIncident monitoring
10001/sLow (default)General monitoring
50000.2/sMinimalBackground, multiple open tabs

All routes are under {base}/api/* (default /logs/api/*). No authentication by default; protect via plugin-authn if needed.

MethodEndpointResponseUse
GET/api/{ logs, stats }Listing with filters
GET/api/statsLogStatsCounters only (lightweight)
GET/api/ssetext/event-streamReal-time streaming
POST/api/{ added: true }Add an entry
POST/api/clear{ cleared: true }Empty the ring buffer and stats
ParameterTypeDefaultDescription
levelstringFilter by debug, info, warn, error
searchstringSubstring in message
limitnumber100Maximum entries returned
Terminal window
curl "/logs/api/?level=error&limit=50"
curl "/logs/api/?search=timeout"
curl "/logs/api/?level=warn&search=deprecated&limit=20"

Response:

{
"logs": [
{
"timestamp": "2024-01-23T10:30:00.000Z",
"level": "error",
"message": "Connection timeout after 5000ms",
"source": "proxy",
"meta": { "target": "https://api.example.com", "duration": 5000 }
}
],
"stats": { "total": 1543, "debug": 200, "info": 1100, "warn": 193, "error": 50 }
}
Terminal window
curl -X POST /logs/api/ \
-H "Content-Type: application/json" \
-d '{
"level": "info",
"message": "Deployment complete",
"source": "deployer",
"meta": { "version": "1.2.3", "duration": 4200 }
}'

Fields: level (required), message (required), source (optional), meta (optional). Timestamp is generated by the server.

The GET /api/sse endpoint keeps a text/event-stream connection open and pushes the full payload every sseInterval ms.

Client ──> Server: GET /logs/api/sse
Server ──> Client: HTTP 200 (text/event-stream)
loop every sseInterval ms:
Server ──> Client: data: {logs: [...], stats: {...}}
Client --x Server: disconnect
(Server performs automatic cleanup)
BehaviorDetail
PayloadFull array + stats in each event (non-incremental)
Concurrent connectionsSupported; each receives the same snapshot
ReconnectionEventSource reconnects automatically; no cursor — current buffer is re-sent
CleanupConnection is dropped when the client disconnects
BandwidthGrows linearly with maxEntries and number of clients
const source = new EventSource("/logs/api/sse");
source.onmessage = (event) => {
const { logs, stats } = JSON.parse(event.data);
console.log(`Total ${stats.total}, errors ${stats.error}`);
};
source.onerror = () => source.close();

Service registry — using from other plugins

Section titled “Service registry — using from other plugins”

provides() exposes four methods. Other plugins consume them via ctx.getPlugin("@buntime/plugin-logs").

MethodSignatureDescription
addLog(entry: Omit<LogEntry, "timestamp">) => voidAdds an entry; timestamp is generated
getLogs(filters?: { level?, search?, limit? }) => LogEntry[]Returns filtered entries (same semantics as GET /api/)
getStats() => LogStatsSnapshot of counters
clearLogs() => voidEmpties buffer + stats
import type { PluginImpl, PluginContext } from "@buntime/shared/types";
export default function myPlugin(): PluginImpl {
let logs: ReturnType<PluginContext["getPlugin"]> | null = null;
return {
onInit(ctx) {
logs = ctx.getPlugin("@buntime/plugin-logs");
logs?.addLog({
level: "info",
message: "My plugin initialized",
source: "my-plugin",
});
},
};
}
export type LogLevel = "debug" | "info" | "warn" | "error";
export interface LogEntry {
timestamp: string; // ISO 8601, server-generated
level: LogLevel;
message: string;
source?: string; // producer identifier
meta?: Record<string, unknown>; // arbitrary structured metadata
}
export interface LogStats {
total: number;
debug: number;
info: number;
warn: number;
error: number;
}
export interface LogsConfig {
maxEntries?: number;
sseInterval?: number;
}
HookAction
onInitCaptures ctx.logger, calls configure({ maxEntries, sseInterval }) on the service layer, and exposes provides()

There is no onShutdown — the buffer is GC’d with the process.

The built-in UI at /logs is registered via menus in the manifest and appears as a navigation item in the CPanel shell when the plugin is enabled.

AspectDetail
RoutingTanStack Router; route / consumes GET /api/sse for a live feed
FiltersUI mirrors the REST query params (level, search)
AuthInherits authentication from the shell when plugin-authn is enabled
Base pathHonors injectBase: true for reverse-proxy setups (Helm, NGINX)

The SPA is served by index.ts (worker entrypoint) — entrypoint: dist/client/index.html. REST/SSE routes are defined in plugin.ts and run on the main thread (the worker entrypoint only serves the UI).

SymptomLikely causeMitigation
Logs disappear after restartRing buffer is in-memory by designConfigure FileTransport on the global logger or push to an external aggregator (Loki/Elastic/Datadog)
maxEntries appears smaller than expectedUI uses limit=100 on the initial fetch; actual buffer is largerAdjust limit in the query param or use SSE (sends everything)
SSE consumes too much CPUsseInterval too low + many clientsRaise to 1000+ ms; consolidate tabs; reduce maxEntries
stats.total > maxEntriesExpected behavior: stats count all entries addedUse clearLogs() to reset; use getLogs() if you only want what’s in the buffer
Memory growingmeta with large payloads (e.g. serialized stack traces)Limit meta size at the producer; reduce maxEntries
Logs do not appear in the UIPlugin disabled or nothing is calling addLogCheck enabled: true in manifest; instrument producers; check GET /api/stats
404 on /logs/api/*Plugin did not load (invalid manifest, build not run)Check runtime logs; run bun run build in the plugin
Terminal window
# Stats — confirms the plugin is alive
curl /logs/api/stats
# Add a test entry
curl -X POST /logs/api/ -H "Content-Type: application/json" \
-d '{"level":"info","message":"sanity check"}'
# Stream in terminal
curl -N /logs/api/sse
  • Audit/compliance — logs are transient; use the logger with FileTransport + an aggregator.
  • High persistent volume — in-memory ring buffer does not scale; use Loki/Elastic.
  • Logs from other processes — scope is the current runtime; does not consume stdout from neighboring containers.

For these cases, keep @buntime/shared/logger sending to JSON/file and use the platform aggregator — see Runtime Logging.

plugins/plugin-logs/
├── manifest.yaml # config + menus
├── plugin.ts # routes, provides(), lifecycle (main thread)
├── index.ts # worker entrypoint — serves the UI SPA
├── server/
│ ├── api.ts # Hono routes (SSE, CRUD)
│ └── services.ts # ring buffer + stats + configure
├── client/ # React + TanStack Router
└── dist/ # build output
  • Runtime Logging — central logger (@buntime/shared/logger), transports, child loggers, ctx.logger.
  • Plugin System — plugin model, provides/getPlugin, manifest, lifecycle.
  • Metrics — companion plugin for pool and worker metrics.