wazero is a zero-dependency WebAssembly runtime implemented in pure Go. It enables Go applications to embed and execute WebAssembly modules without CGo, system dependencies, or a separate runtime process — the entire Wasm execution environment is compiled into your Go binary. Teams using wazero embed it to run user-defined logic, plugins, sandboxed code, or language runtimes inside their Go services. When wazero module compilation fails, a guest function panics, or the host application becomes unresponsive after a Wasm execution, there is no external process to monitor — only the application itself. Vigilmon gives you external monitoring that watches the application health endpoints you expose and validates that Wasm execution is completing on time.
What You'll Build
- Vigilmon HTTP monitors for your wazero-embedded application's health and readiness endpoints
- A heartbeat monitor that validates Wasm guest function execution from inside the application
- An SSL monitor for TLS certificate validity on your application's HTTPS endpoint
- Alert channels routed to your backend engineering team
Prerequisites
- A Go application using wazero (
go.moddependency ongithub.com/tetratelabs/wazero) - The application deployed and accessible over HTTP/HTTPS
- A free account at vigilmon.online
Why Monitoring wazero Matters
wazero runs entirely inside your Go process. Its failure modes are application-level rather than daemon-level, and they are harder to observe externally than failures in process-per-module runtimes:
Wasm module compilation failures at startup are fatal but may be silently swallowed by application-level error handling. If a .wasm binary is corrupt, incompatible with the wazero version, or references imports that are not provided, wazero.Runtime.CompileModule() returns an error. Applications that log this error and continue start serving requests — but every call into the unloaded module panics or returns a stub response. External health monitoring that validates the compilation result from the application's perspective catches this class of failure.
Guest function panics trap silently. wazero recovers from Wasm traps (unreachable instructions, out-of-bounds memory access, call stack exhaustion) and returns them as Go errors from api.Function.Call(). An application that does not surface these as health degradation continues returning HTTP 200 responses while every Wasm execution is failing internally. A probe that calls a lightweight guest health function and validates the response catches this.
Memory growth and module instance leaks accumulate in long-running services that compile or instantiate modules per request. wazero does not garbage-collect module instances that are not explicitly closed. A service that creates wazero.Runtime.InstantiateModule() per request without calling module.Close() leaks memory at a rate proportional to request volume — typically manifesting as OOM kill hours later. Monitoring the application's memory usage and request latency catches this before the node goes down.
wazero version upgrades may silently reject previously valid modules. wazero's WebAssembly spec compliance evolves with Go releases. A module that compiled and ran correctly under wazero 1.7 may fail compilation under 1.8 due to stricter validation of extended proposals (e.g., multi-value, SIMD). An upgrade pipeline that does not run a Wasm module smoke test misses this regression until production traffic hits the broken path.
Step 1: Expose Health and Readiness Endpoints from Your Application
Add standard health endpoints to your wazero-embedded Go application:
package main
import (
"context"
"encoding/json"
"log"
"net/http"
"sync/atomic"
"time"
"github.com/tetratelabs/wazero"
"github.com/tetratelabs/wazero/api"
)
var (
wasmReady atomic.Bool
lastExecTime atomic.Value // stores time.Time
wasmMod api.Module
wasmRuntime wazero.Runtime
)
func healthHandler(w http.ResponseWriter, r *http.Request) {
status := map[string]interface{}{
"status": "ok",
"wasm_ready": wasmReady.Load(),
}
if t, ok := lastExecTime.Load().(time.Time); ok {
status["last_wasm_exec"] = t.Format(time.RFC3339)
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(status)
}
func readinessHandler(w http.ResponseWriter, r *http.Request) {
if !wasmReady.Load() {
http.Error(w, `{"status":"not_ready","reason":"wasm_module_not_loaded"}`, http.StatusServiceUnavailable)
return
}
w.Header().Set("Content-Type", "application/json")
w.Write([]byte(`{"status":"ready"}`))
}
func wasmExecProbeHandler(w http.ResponseWriter, r *http.Request) {
if !wasmReady.Load() || wasmMod == nil {
http.Error(w, `{"status":"error","reason":"module_not_loaded"}`, http.StatusServiceUnavailable)
return
}
ctx, cancel := context.WithTimeout(r.Context(), 5*time.Second)
defer cancel()
// Call a lightweight health function exported by your Wasm module
fn := wasmMod.ExportedFunction("health_check")
if fn == nil {
http.Error(w, `{"status":"error","reason":"health_check_not_exported"}`, http.StatusServiceUnavailable)
return
}
results, err := fn.Call(ctx)
if err != nil || (len(results) > 0 && results[0] != 1) {
http.Error(w, `{"status":"error","reason":"wasm_exec_failed"}`, http.StatusServiceUnavailable)
return
}
lastExecTime.Store(time.Now())
w.Header().Set("Content-Type", "application/json")
w.Write([]byte(`{"status":"ok","wasm_exec":"ok"}`))
}
func main() {
// ... initialize wazero runtime and compile/instantiate modules ...
// wasmReady.Store(true) after successful instantiation
http.HandleFunc("/healthz", healthHandler)
http.HandleFunc("/readyz", readinessHandler)
http.HandleFunc("/wasm-probe", wasmExecProbeHandler)
log.Fatal(http.ListenAndServe(":8080", nil))
}
Export a health_check function from your Wasm module (example in WAT):
(module
;; ... your module's actual exports ...
(func (export "health_check") (result i32)
i32.const 1 ;; return 1 = healthy
)
)
Test the endpoints:
curl http://localhost:8080/healthz
# {"status":"ok","wasm_ready":true,"last_wasm_exec":"2026-07-12T10:00:00Z"}
curl http://localhost:8080/readyz
# {"status":"ready"}
curl http://localhost:8080/wasm-probe
# {"status":"ok","wasm_exec":"ok"}
Step 2: Monitor the Application Health Endpoint
In the Vigilmon dashboard:
- Go to Add Monitor → HTTP
- Configure:
| Field | Value |
|---|---|
| Name | wazero application health |
| URL | https://your-app.example.com/healthz |
| Method | GET |
| Expected status | 200 |
| Response body contains | "wasm_ready":true |
| Check interval | 1 minute |
| Timeout | 10 seconds |
| Alert after | 2 consecutive failures |
Add a second monitor for the readiness endpoint:
| Field | Value |
|---|---|
| Name | wazero application readiness |
| URL | https://your-app.example.com/readyz |
| Expected status | 200 |
| Response body contains | "status":"ready" |
| Check interval | 1 minute |
| Alert after | 2 consecutive failures |
Step 3: Heartbeat Monitor for Wasm Guest Function Execution
The /wasm-probe endpoint validates that wazero is executing Wasm code successfully. Set up a Vigilmon heartbeat monitor that the application calls on a schedule from within:
// Add to your application — call this from a goroutine on a timer
func startWasmExecutionHeartbeat(heartbeatURL string, interval time.Duration) {
ticker := time.NewTicker(interval)
defer ticker.Stop()
for range ticker.C {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
fn := wasmMod.ExportedFunction("health_check")
if fn == nil {
cancel()
log.Printf("wasm heartbeat: health_check not exported")
continue
}
results, err := fn.Call(ctx)
cancel()
if err != nil || (len(results) > 0 && results[0] != 1) {
log.Printf("wasm heartbeat: execution failed: %v", err)
continue
}
// Send heartbeat to Vigilmon
resp, err := http.Get(heartbeatURL)
if err != nil {
log.Printf("wasm heartbeat: failed to send: %v", err)
continue
}
resp.Body.Close()
lastExecTime.Store(time.Now())
}
}
// In main():
go startWasmExecutionHeartbeat(
"https://vigilmon.online/heartbeat/YOUR_WASM_EXEC_TOKEN",
5*time.Minute,
)
Create the heartbeat monitor in Vigilmon:
- Add Monitor → Heartbeat
- Name:
wazero guest function execution - Expected interval: 5 minutes
- Grace period: 3 minutes
Step 4: Monitor TLS Certificate for Your HTTPS Endpoint
Add an SSL monitor for your application's certificate:
- In Vigilmon: Add Monitor → SSL Certificate
- URL:
https://your-app.example.com - Alert when: certificate expires in less than 14 days
- Name:
wazero app TLS certificate
Step 5: Configure Alert Channels
Route wazero application alerts to your backend team:
- In Vigilmon: Alert Channels → Add Channel → Slack Webhook
- Add your
#backend-alertsor#wasm-opswebhook URL - Assign to all wazero monitors
For Wasm execution failures (guest function returning errors means core functionality is broken):
- Alert Channels → Add Channel → PagerDuty
- Route the
wazero guest function executionheartbeat to on-call - A Wasm execution failure in a production service warrants immediate investigation — the failure may affect all requests that route through the Wasm path
What You're Now Monitoring
| Component | Monitor Type | What It Detects | |---|---|---| | Application health | HTTP GET | App down, Wasm module not loaded, compilation failure | | Application readiness | HTTP GET | Service not ready to handle traffic, module initialization incomplete | | Guest function execution | Heartbeat (5 min) | Wasm trap/panic, guest function removed, execution timeout | | TLS certificate | SSL | Certificate expiry before HTTPS breaks |
Conclusion
wazero's zero-dependency design means there is no external process, daemon, or service to monitor directly — only your Go application. This makes it easy to overlook until a Wasm module fails to load, a guest function starts trapping, or a version upgrade silently breaks compilation. By exposing structured health and readiness endpoints and using an in-process heartbeat, you give Vigilmon the hooks it needs to observe wazero's operational state from the outside. The health endpoint detects module loading failures, the execution heartbeat catches runtime errors, and the readiness probe validates that the application is ready to serve Wasm-backed requests.
Get started free at vigilmon.online. The health endpoint probe and execution heartbeat together give you the two most critical wazero signals in under fifteen minutes of setup.