Why Does SeaLights Use Static Instrumentation for Go?

SeaLights uses static instrumentation for Go applications, how it works, and how to deploy it safely without impacting production environments.

TL;DR

  • Go does not support dynamic runtime instrumentation, so SeaLights uses build-time static instrumentation.

  • This approach adds a registration call at the start of each method to track coverage during test execution.

  • The functional method code remains unchanged, and no repository files are modified.

  • SeaLights’ instrumentation in Go is equivalent in effect to dynamic agents in Java/.NET — it just happens earlier in the lifecycle.

  • Safe deployment options ensure instrumentation is only active in testing environments and never impacts production.

For implementation guidance or questions about your specific environment, please don't hesitate to contact your SeaLights support representative or solutions engineer.

Why doesn't Go support runtime instrumentation like Java or .NET?

Java and .NET rely on intermediate runtime environments (JVM and CLR), which allow dynamic instrumentation using agents or profilers. These tools can modify bytecode in memory at runtime to track method execution and collect coverage data.

Go, by contrast, compiles directly to native machine code and does not expose hooks or an intermediate representation during execution. This means runtime instrumentation is not possible, and coverage instrumentation must happen earlier — at build time.

What does SeaLights do instead?

To support coverage collection in Go, SeaLights performs static instrumentation during the build process:

  • A lightweight method-entry call is prepended to the beginning of each method, just before the functional code.

  • These instructions record that a method was executed during test runs, enabling method-level coverage reporting.

  • The method’s logic and control flow remain unchanged.

  • Most importantly, the source code in your repository is never touched — instrumentation is applied only to a temporary local copy before compilation.

When instrumentation is not explicitly enabled (e.g., in production), these registration calls are inert and introduce no runtime overhead.

How can we deploy the instrumented binaries safely?

SeaLights suggests three deployment strategies to ensure instrumentation is active only in test environments, with no risk of affecting production behavior:

Deployment Option
Description
Activation
Operational Notes

Single Binary

One binary built with instrumentation, deployed across all environments

Controlled via environment variable (e.g., SEALIGHTS_DISABLE_ON_INIT=false)

Simplest setup; instrumentation is inactive unless explicitly enabled

Separate Images

Two distinct container images: one for test (instrumented), one for production (original)

CI/CD selects the appropriate image

Strongest separation; instrumented image can be deleted after testing to reduce storage (e.g., after 48 hours)

Dual Binary in One Container

Both original and instrumented binaries are included in one image, with a symbolic link used to select the active binary

Pre-deployment script modifies the symlink based on the environment

Maintains clear separation of binaries; requires slight deployment logic

Examples for Deployment Options

The examples below demonstrate how to activate or switch SeaLights instrumentation in different environments using Kubernetes manifests, as Kubernetes is a widely adopted standard for containerized applications. These examples can be easily adapted to fit your existing Continuous Deployment setup, regardless of the specific tools or orchestration platform you use.

Single Binary – Enabling SeaLights via Environment Variable

After scanning the application with the --disable-on-init true parameter, you can use the same container image across all environments and selectively enable SeaLights only in test environments by adding an environment variable:

apiVersion: v1
kind: Pod
metadata:
  name: myapp-test
spec:
  containers:
    - name: myapp
      image: myorg/myapp:instrumented
      env:
        - name: SEALIGHTS_DISABLE_ON_INIT
          value: "false"

In production, either omit the SEALIGHTS_DISABLE_ON_INIT variable or set it to "true" to ensure instrumentation remains disabled.


Separate Images – Switching via Image Tag and Environment Variables

This approach uses two distinct container images:

  • myorg/myapp:latestuninstrumented (regular) version for production or performance environments

  • myorg/myapp-sealights:latestSeaLights-instrumented version for test environments

Tools like Kustomize can be used to cleanly inherit a base deployment and override just the image and environment variables for test environments.

Base Deployment (prod/default environments):

apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
spec:
  replicas: 1
  template:
    spec:
      containers:
        - name: myapp
          image: myorg/myapp:latest

Test Deployment (SeaLights-enabled):

apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
spec:
  replicas: 1
  template:
    spec:
      containers:
        - name: myapp
          image: myorg/myapp-sealights:latest
          env:
            - name: SEALIGHTS_LAB_ID
              value: "test-lab"

In environments where SeaLights instrumentation is not required, simply deploy the standard image (myorg/myapp:latest) without any need for declaring the SEALIGHTS_LAB_ID variable.


In this approach, the container image includes two versions of the application binary:

  • /app/myapp-original – the uninstrumented (regular) version

  • /app/sl-myapp-executable – the SeaLights-instrumented version

At runtime, an InitContainer replaces the default binary with the instrumented version by updating the symbolic link:

apiVersion: v1
kind: Pod
metadata:
  name: myapp-test
spec:
  initContainers:
    - name: switch-to-sealights
      image: busybox
      command: ["sh", "-c"]
      args:
        - |
          mv /app/myapp-executable /app/myapp-original
          ln -s /app/sl-myapp-executable /app/myapp-executable
      volumeMounts:
        - name: app-bin
          mountPath: /app
  containers:
    - name: myapp
      image: myorg/myapp:combined
      volumeMounts:
        - name: app-bin
          mountPath: /app
  volumes:
    - name: app-bin
      emptyDir: {}

In environments where SeaLights instrumentation is not required, you can simply omit the InitContainer or adjust its logic to preserve the default binary (/app/myapp-executable pointing to the original version).

Last updated

Was this helpful?