Skip to content

Adding metrics

In this lab, we'll add a metric (counter) that increments each time the incoming requests include a header called hello.

Configuring the pluginContext

First, let's open main.go and update the pluginContext to include the helloHeaderCounter:

type pluginContext struct {
  // Embed the default plugin context here,
  // so that we don't need to reimplement all the methods.
  types.DefaultPluginContext
  additionalHeaders  map[string]string
  contextID          uint32
  helloHeaderCounter proxywasm.MetricCounter
}

Creating the metric

With the metric counter in the struct, we can now create it in the NewPluginContext function. We'll call the metric hello_header_counter.

func (*vmContext) NewPluginContext(contextID uint32) types.PluginContext {
  return &pluginContext{contextID: contextID, additionalHeaders: map[string]string{}, helloHeaderCounter: proxywasm.DefineCounterMetric("hello_header_counter")}
}

Since we need to check the incoming request headers to decide whether to increment the counter, we need to add the helloHeaderCounter to the httpContext struct as well:

type httpContext struct {
  // Embed the default http context here,
  // so that we don't need to reimplement all the methods.
  types.DefaultHttpContext
  contextID          uint32
  additionalHeaders  map[string]string
  helloHeaderCounter proxywasm.MetricCounter
}

Also, we need to get the counter from the pluginContext and set it when we're creating the new HTTP context:

// Override types.DefaultPluginContext.
func (ctx *pluginContext) NewHttpContext(contextID uint32) types.HttpContext {
  return &httpContext{contextID: contextID, additionalHeaders: ctx.additionalHeaders, helloHeaderCounter: ctx.helloHeaderCounter}
}

Incrementing the counter

Now that we've piped the helloHeaderCounter all the way through to the httpContext, we can use it in the OnHttpRequestHeaders function:

func (ctx *httpContext) OnHttpRequestHeaders(numHeaders int, endOfStream bool) types.Action {
  proxywasm.LogInfo("OnHttpRequestHeaders")

  _, err := proxywasm.GetHttpRequestHeader("hello") // (1)
  if err != nil {
    // Ignore if header is not set
    return types.ActionContinue
  }

  ctx.helloHeaderCounter.Increment(1) // (2)
  proxywasm.LogInfo("hello_header_counter incremented")
  return types.ActionContinue
}
  1. Retrieving the header called hello and ignoring the errors if the header is not set

  2. Incrementing the helloHeaderCounter if the hello header was present in the request

Here, we're checking if the "hello" request header is defined (note that we don't care about the header value), and if it's defined, we call the Increment function on the counter instance. Otherwise, we'll ignore it and return ActionContinue if we get an error from the GetHttpRequestHeader call.

Complete main.go
main.go
package main

import (
    "github.com/valyala/fastjson"

    "github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm"
    "github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm/types"
)

const (
    sharedDataKey                 = "my_key"
    sharedDataInitialValue uint64 = 1
)

func main() {
    proxywasm.SetVMContext(&vmContext{})
}

// Override types.DefaultPluginContext.
func (ctx pluginContext) OnPluginStart(pluginConfigurationSize int) types.OnPluginStartStatus {
    data, err := proxywasm.GetPluginConfiguration()
    if err != nil {
        proxywasm.LogCriticalf("error reading plugin configuration: %v", err)
    }

    var p fastjson.Parser
    v, err := p.ParseBytes(data)
    if err != nil {
        proxywasm.LogCriticalf("error parsing configuration: %v", err)
    }

    obj, err := v.Object()
    if err != nil {
        proxywasm.LogCriticalf("error getting object from json value: %v", err)
    }

    obj.Visit(func(k []byte, v *fastjson.Value) {
        ctx.additionalHeaders[string(k)] = string(v.GetStringBytes())
    })

    return types.OnPluginStartStatusOK
}

type vmContext struct {
    // Embed the default VM context here,
    // so that we don't need to reimplement all the methods.
    types.DefaultVMContext
}

// Override types.DefaultVMContext.
func (*vmContext) NewPluginContext(contextID uint32) types.PluginContext {
    return &pluginContext{contextID: contextID, additionalHeaders: map[string]string{}, helloHeaderCounter: proxywasm.DefineCounterMetric("hello_header_counter")}
}

type pluginContext struct {
    // Embed the default plugin context here,
    // so that we don't need to reimplement all the methods.
    types.DefaultPluginContext
    additionalHeaders  map[string]string
    contextID          uint32
    helloHeaderCounter proxywasm.MetricCounter
}

// Override types.DefaultPluginContext.
func (ctx *pluginContext) NewHttpContext(contextID uint32) types.HttpContext {
    proxywasm.LogInfo("NewHttpContext")
    return &httpContext{contextID: contextID, additionalHeaders: ctx.additionalHeaders, helloHeaderCounter: ctx.helloHeaderCounter}
}

type httpContext struct {
    // Embed the default http context here,
    // so that we don't need to reimplement all the methods.
    types.DefaultHttpContext
    contextID          uint32
    additionalHeaders  map[string]string
    helloHeaderCounter proxywasm.MetricCounter
}

func (ctx *httpContext) OnHttpResponseHeaders(numHeaders int, endOfStream bool) types.Action {
    proxywasm.LogInfo("OnHttpResponseHeaders")

    for key, value := range ctx.additionalHeaders {
        if err := proxywasm.AddHttpResponseHeader(key, value); err != nil {
            proxywasm.LogCriticalf("failed to add header: %v", err)
            return types.ActionPause
        }
        proxywasm.LogInfof("header set: %s=%s", key, value)
    }

    return types.ActionContinue
}

// Override types.DefaultHttpContext.
func (ctx *httpContext) OnHttpRequestHeaders(numHeaders int, endOfStream bool) types.Action {
    _, err := proxywasm.GetHttpRequestHeader("hello")
    if err != nil {
        return types.ActionContinue
    }

    ctx.helloHeaderCounter.Increment(1)
    proxywasm.LogInfo("hello_header_counter incremented")
    return types.ActionContinue
}

Let's rebuild the extension again:

tinygo build -o main.wasm -scheduler=none -target=wasi main.go

We can remove the configuration for the second-extension from the Envoy config and then re-run the Envoy proxy using func-e run -c envoy.yaml &.

Make a couple of requests like this:

curl -H "hello: something" localhost:18000

You'll notice the log Envoy log entry like this one:

wasm log: hello_header_counter incremented

You can also use the admin address on port 8001 to check that Envoy tracks the metric:

curl localhost:8001/stats/prometheus | grep hello
# TYPE envoy_hello_header_counter counter
envoy_hello_header_counter{} 1