istio envoyfilter - logging using lua filter

In this blog, we will be setting envoyfilter that uses lua to intercept and log payload, 

Ground work

We will be using httpbin that is shipped as part of istio samples for our sidecar setup. 

kubectl apply -f gateway.yaml

kubectl apply -f httpbin.yaml

And then we can create our envoyfilter. This is what our envoyfilter looks like this. Points to note : we are enabling buffering. Without buffering, the lua won't log and there won't be any output in the istio-proxy container log. 

Before apply the script below ensure you have set the logging level - otherwise you might not see the logs details. You need to configure the logging level with the following command:-


istioctl proxy-config log httpbin-65bbd9c89d-n449w --level lua:info

Next we will apply the following script


apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
  name: log-payload
  namespace: default
spec:
  workloadSelector:
    labels:
      app: httpbin

  configPatches:

  - applyTo: HTTP_FILTER
    match:
      context: SIDECAR_INBOUND
      listener:
        filterChain:
          filter:
            name: envoy.filters.network.http_connection_manager
            subFilter:
              name: envoy.filters.http.router
    patch:
      operation: INSERT_BEFORE
      value:
        name: envoy.filters.http.lua
        typed_config:
          "@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua
          #inlineCode: |
          default_source_code:
            inline_string: |
              function envoy_on_request(request_handle)
                request_handle:logInfo("[Lua] Intercepted a request.")
                request_handle:headers():add("dm3ch-test", "dm3ch wins")
              end

              function envoy_on_response(response_handle)
               response_handle:headers():add("dm3ch-test", "dm3ch wins")
               response_handle:logInfo("[Lua] Intercepted a response.")
              end
     


A mmore advance example

apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
  name: log-payload
  namespace: default
spec:
  workloadSelector:
    labels:
      app: httpbin
  configPatches:
  - applyTo: HTTP_FILTER
    match:
      context: SIDECAR_INBOUND
      listener:
        filterChain:
          filter:
            name: envoy.filters.network.http_connection_manager
            subFilter:
              name: envoy.filters.http.router
    patch:
      operation: INSERT_BEFORE
      value:
        name: envoy.filters.http.lua
        typed_config:
          "@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua
          inlineCode: |
            function envoy_on_request(request_handle)
              -- Get HTTP method
              local method = request_handle:headers():get(":method")
              request_handle:logInfo("[Lua] HTTP Method: " .. (method or "unknown"))
             
              -- Get other headers
              local authority = request_handle:headers():get(":authority")
              local path = request_handle:headers():get(":path")
             
              request_handle:logInfo("[Lua] Authority: " .. (authority or "unknown"))
              request_handle:logInfo("[Lua] Path: " .. (path or "unknown"))
             
              -- Get request body (requires buffering)
              local body = request_handle:body()
              local body_length = 0
              if body then
                body_length = body:length()
                request_handle:logInfo("[Lua] Request body length: " .. body_length)
               
                -- Log first 1024 characters of body to avoid huge logs
                if body_length > 0 then
                  local body_string = body:getBytes(0, math.min(1024, body_length))
                  request_handle:logInfo("[Lua] Request body (first 1KB): " .. body_string)
                end
              else
                request_handle:logInfo("[Lua] No request body")
              end
             
              -- Add custom header
              request_handle:headers():add("dm3ch-test", "dm3ch wins - Method: " .. (method or "unknown"))
            end

            function envoy_on_response(response_handle)
              -- Get response status
              local status = response_handle:headers():get(":status")
              response_handle:logInfo("[Lua] Response Status: " .. (status or "unknown"))
             
              -- Get response body
              local body = response_handle:body()
              local body_length = 0
              if body then
                body_length = body:length()
                response_handle:logInfo("[Lua] Response body length: " .. body_length)
               
                -- Log first 512 characters of response body
                if body_length > 0 then
                  local body_string = body:getBytes(0, math.min(512, body_length))
                  response_handle:logInfo("[Lua] Response body (first 512B): " .. body_string)
                end
              end
             
              -- Add custom header
              response_handle:headers():add("dm3ch-test", "dm3ch wins - Status: " .. (status or "unknown"))
            end

Important to note:- 

1. Outputs is coming from istio-proxy container not on the application log and it should look like this:


2. Envoy filter does take about 1-2 minutes to gets applied or sync to your sidecar - depending on how many envoyfilter you hav in the cluster. If this is a local cluster with 1 envoyfilter, then it should takes 5 seconds. You don't have to restart your pod. 

3. default_source_code vs  inline_code?

Both can be used for inlining your Lua code. 

4. Script parameter name matching. Lua parameter for a method needs to be matched as well. For example 

Having 'handle' as a parameter name will cause issue and an error message - "index global response_handle" is nil. This refers to the fact that we're not using a matching parameter called 'request_handle'

function envoy_on_request(handle)
     handle:logInfo("request")
     handle:headers():add("dm3ch-test", "dm3ch wins")
     handle:headers().get
end

To fix it,

function envoy_on_request(request_handle)
   request_handle:logInfo("[Lua] Intercepted a request.")
   request_handle:headers():add("dm3ch-test", "dm3ch wins")
end

For response, the parameter name needs to match as well. For example

function envoy_on_response(response_handle)
    response_handle:headers():add("dm3ch-test", "dm3ch wins")
    response_handle:logInfo("[Lua] Intercepted a response.")
end
 


Comments

Popular posts from this blog

gemini cli getting file not defined error

NodeJS: Error: spawn EINVAL in window for node version 20.20 and 18.20

vllm : Failed to infer device type