Important
Active development for serviceradar-sdk-go has moved to Forgejo: https://code.carverauto.dev/carverauto/serviceradar-sdk-go
GitHub remains available for visibility and historical context, but new issues, pull requests, and source changes should go to Forgejo.
ServiceRadar plugin SDK for Go (TinyGo/WASM).
This SDK lets you write ServiceRadar plugin checkers in Go without handling low-level WASM host calls. It handles:
- Config decoding from the host
- Result builder for
serviceradar.plugin_result.v1 - Logging bridge
- HTTP/TCP/UDP proxy wrappers
- Support for Websockets
- Event emission + alert promotion hints
go get github.com/carverauto/serviceradar-sdk-gopackage main
import (
"context"
"fmt"
"github.com/carverauto/serviceradar-sdk-go/sdk"
)
type Config struct {
URL string `json:"url"`
WarnMS float64 `json:"warn_ms"`
CritMS float64 `json:"crit_ms"`
}
//export run_check
func run_check() {
_ = sdk.Execute(func() (*sdk.Result, error) {
var cfg Config
if err := sdk.LoadConfig(&cfg); err != nil {
return nil, err
}
resp, err := sdk.HTTP.GetContext(context.Background(), cfg.URL)
if err != nil {
return nil, fmt.Errorf("http request failed: %w", err)
}
latency := float64(resp.Duration.Milliseconds())
thresholds := sdk.Thresholds(cfg.WarnMS, cfg.CritMS)
return sdk.NewResult().
WithSummary(fmt.Sprintf("http %d in %.0fms", resp.Status, latency)).
WithThresholds(latency, thresholds.Warn, thresholds.Crit).
WithMetric("latency_ms", latency, "ms", thresholds).
WithStatCard("Latency", fmt.Sprintf("%.0fms", latency), "success"), nil
})
}
func main() {}examples/http-check: HTTP latency check with thresholds and eventsexamples/tcp-check: TCP connectivity check with optional write/readexamples/udp-check: UDP send check with bytes-sent metricexamples/widgets-check: HTTP check demonstrating stat card, table, sparkline, and markdown widgets
Execute accepts a function that returns (*Result, error) and itself returns an error:
err := sdk.Execute(func() (*sdk.Result, error) {
// ...
return sdk.Ok("ok"), nil
})
if err != nil {
// Optional: handle submit/serialize errors (logging is already done by the SDK)
}If your function returns a non-nil error, the SDK auto-generates a critical result (or upgrades your result to critical) and records the error details in the payload. This keeps the happy path concise while still surfacing failures.
Defaults are applied at the edge (right before serialization) so Serialize does not mutate the original object:
SchemaVersiondefaults to1Statusdefaults toUNKNOWNSummarydefaults to the status stringObservedAtdefaults to “now” in RFC3339Nano
This means var r sdk.Result is safe; serialization produces a valid payload without altering r.
Result has both conventional setters (SetSummary, AddMetric, etc.) and fluent builders (WithSummary, WithMetric, etc.) so you can choose style:
return sdk.NewResult().
WithSummary("all good").
WithMetric("cpu", 10, "%", nil).
WithLabel("version", "1.2.3"), nilUse ThresholdSpec for warning/critical thresholds and Thresholds(warn, crit) to build one without helper functions:
thresholds := sdk.Thresholds(50, 100)
res.WithMetric("latency_ms", 10, "ms", thresholds)
res.WithThresholds(10, thresholds.Warn, thresholds.Crit)Context variants exist for host I/O to match Go expectations:
- HTTP:
HTTP.DoContext,HTTP.GetContext,HTTP.PostContext - TCP:
TCPDialContext,(*TCPConn).ReadContext,(*TCPConn).WriteContext - UDP:
UDPSendToContext - WebSocket:
WebSocketDialContext,(*WebSocketConn).SendContext,(*WebSocketConn).RecvContext
These currently check ctx.Err() before the host call (TinyGo/Wasm is synchronous), but give you a stable API if cancellation support is added later.
The SDK provides WebSocket client capabilities for plugins that need to communicate with WebSocket servers:
// Dial a WebSocket endpoint
conn, err := sdk.WebSocketDialContext(ctx, "ws://localhost:8080/ws", 10*time.Second)
if err != nil {
return nil, fmt.Errorf("websocket dial failed: %w", err)
}
defer conn.Close()
// Send a message
if err := conn.SendContext(ctx, []byte(`{"method": "getInfo"}`), 10*time.Second); err != nil {
return nil, fmt.Errorf("websocket send failed: %w", err)
}
// Read response
buf := make([]byte, 4096)
n, err := conn.RecvContext(ctx, buf, 10*time.Second)
if err != nil {
return nil, fmt.Errorf("websocket recv failed: %w", err)
}
data := buf[:n]WebSocket connections are mediated by the host runtime, which enforces:
- Domain allowlists: Only permitted domains can be connected to
- Port restrictions: Only allowed ports can be accessed
- Connection limits: Maximum concurrent connections per plugin
The plugin must have the following capabilities in its manifest:
websocket_connect: Permission to establish WebSocket connectionswebsocket_send: Permission to send messageswebsocket_recv: Permission to receive messageswebsocket_close: Permission to close connections
To include headers (for example Authorization) on the initial WebSocket handshake:
headers := map[string]string{
"Authorization": "Basic <base64-user-pass>",
}
conn, err := sdk.WebSocketConnectWithHeaders("wss://camera.local/vapix/ws-data-stream?sources=events", headers, 10*time.Second)LoadConfig is an alias of GetConfig for more idiomatic naming in user code:
if err := sdk.LoadConfig(&cfg); err != nil {
return nil, err
}For policy-driven plugin assignments, decode and validate the typed input payload:
var payload sdk.PluginInputsPayload
if err := sdk.LoadConfig(&payload); err != nil {
return nil, err
}
if err := payload.Validate(); err != nil {
return nil, err
}
// Iterate all resolved items (devices/interfaces/etc.)
err := payload.EachItem(func(item sdk.PluginInputItem) error {
// item.Entity: "devices" | "interfaces" | ...
// item.Item: map with resolved fields (uid/ip/if_name/etc.)
return nil
})
if err != nil {
return nil, err
}
devices := payload.ItemsByEntity("devices")
_ = devicesHelpers also include:
sdk.ParsePluginInputsJSON([]byte)sdk.ParsePluginInputsMap(map[string]any)(*PluginInputsPayload).FlattenItems()(*PluginInputsPayload).ItemsByEntity(string)(*PluginInputsPayload).ItemsByName(string)
# Requires TinyGo
cd examples/http-check
tinygo build -o plugin.wasm -target=wasi ./The agent imports host functions from the env module:
get_configlogsubmit_resulthttp_requesttcp_connect/tcp_read/tcp_write/tcp_closeudp_sendtowebsocket_connect/websocket_send/websocket_recv/websocket_closecamera_media_open/camera_media_write/camera_media_heartbeat/camera_media_close
The SDK wraps these functions and exports alloc/dealloc for host memory access.
The SDK emits optional fields in the result payload to support event promotion:
events: list of OCSF Event Log Activity objectsalert_hint: boolean flag for immediate promotioncondition_id: string used for de-duplication and auto-clear logic
These fields are ignored safely by older control-plane builds.