You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@skywalking.apache.org by wu...@apache.org on 2026/10/18 08:59:58 UTC

(skywalking-go) 01/01: Add AGENTS.md with CLAUDE.md as a symlink, and build and plugin-e2e skills

This is an automated email from the ASF dual-hosted git repository.

wu-sheng pushed a commit to branch docs/agents-md-and-skills
in repository https://gitbox.apache.org/repos/asf/skywalking-go.git

commit 837c97584df7e187c5c9b62ad10c50b1a88584d7
Author: Wu Sheng <wu...@foxmail.com>
AuthorDate: Sun Oct 18 16:59:47 2026 +0800

    Add AGENTS.md with CLAUDE.md as a symlink, and build and plugin-e2e skills
    
    AGENTS.md is the guide for coding agents: the repository layout, the Go
    version policy, how to build, test, and lint, the rules that follow from
    how the agent copies and rewrites plugin code (instrument selection, the
    direct-import rule for copied files, native mirrors, receiver-only
    matching, version-split plugins), the plugin test harness semantics, and
    the changelog, docs, and CI conventions. CLAUDE.md is a symlink to it.
    
    The build skill builds the agent, runs tests and lint, checks the Go 1.26
    floor, and compiles an instrumented application to verify copied plugin
    code at both ends of a version range. The plugin-e2e skill runs plugin
    scenarios and the e2e suite locally and maps common failures to causes.
    
    Claude worktrees and local settings are ignored.
    
    Co-Authored-By: Claude Opus 5.5 <no...@anthropic.com>
    Claude-Session: https://claude.ai/code/session_01Ej1HubxqebgChKygA5TTHP
---
 .claude/skills/build/SKILL.md      | 121 +++++++++++++++++++++++++++++++
 .claude/skills/plugin-e2e/SKILL.md | 115 +++++++++++++++++++++++++++++
 .gitignore                         |   2 +
 AGENTS.md                          | 144 +++++++++++++++++++++++++++++++++++++
 CLAUDE.md                          |   1 +
 5 files changed, 383 insertions(+)

diff --git a/.claude/skills/build/SKILL.md b/.claude/skills/build/SKILL.md
new file mode 100644
index 0000000..d1df880
--- /dev/null
+++ b/.claude/skills/build/SKILL.md
@@ -0,0 +1,121 @@
+---
+name: build
+description: Build the SkyWalking Go agent binary, run unit/race tests and lint, check the Go 1.26 floor, and compile an instrumented application to verify plugin code at both ends of a framework version range
+user-invocable: true
+allowed-tools: Bash, Read, Glob, Grep
+---
+
+# Build and Verify the SkyWalking Go Agent
+
+Infer what the user wants from the request or recent changes, then run the
+matching steps. Run the smallest relevant step first, and report each command
+with its result.
+
+## Prerequisites
+
+```bash
+go version                      # Go 1.26 or newer; CI uses 1.26 and 1.27
+git rev-parse --short HEAD      # the build version
+ls VERSION 2>/dev/null && cat VERSION   # see "Agent binary"
+```
+
+## Agent binary
+
+`make build` creates the gitignored `VERSION` file the first time and keeps using
+it, so the binary name can drift from the current commit. Pass the version
+explicitly:
+
+```bash
+make -C tools/go-agent build VERSION=$(git rev-parse --short HEAD)   # linux, darwin, windows
+make -C tools/go-agent darwin VERSION=$(git rev-parse --short HEAD)  # one platform
+ls bin/skywalking-go-agent-*
+```
+
+## Tests
+
+`make test` walks every `go.mod` in the tree, so first remove the leftovers of
+scenario runs:
+
+```bash
+rm -rf test/plugins/workspace
+make test             # every module except the plugin scenarios
+make test-race        # TestRace* regression tests under the race detector
+(cd plugins/<name> && go test ./...)   # one module
+```
+
+Check the floor: a dependency that needs a newer Go fails here instead of in CI.
+
+```bash
+GOTOOLCHAIN=go1.26.<latest patch> go test ./...
+```
+
+## Lint and consistency
+
+`make lint` reuses any `golangci-lint` in `$(go env GOPATH)/bin`, so check its
+version first. CI uses v2.15.0, and the install URL matters: older copies of the
+install script fail the checksum check for releases with SBOMs.
+
+```bash
+golangci-lint version
+curl -sSfL https://golangci-lint.run/install.sh | sh -s -- -b "$(go env GOPATH)/bin" v2.15.0
+make lint
+(cd <module> && golangci-lint run --config "$(git rev-parse --show-toplevel)/.golangci.yml" ./...)
+make check            # go mod tidy must leave no changes
+```
+
+`license-eye header check` (apache/skywalking-eyes) verifies the license headers.
+Markdown files are exempt.
+
+## Instrumented compile check
+
+Run this check after changing any code that the agent copies or injects into a
+framework: plugin interceptors, native mirrors, or the logger and runtime
+templates. Unit tests do not cover it. Copied files compile inside the framework
+package, where only that package's direct imports are available.
+
+Use a dedicated `GOCACHE`. Instrumented builds otherwise leave instrumented
+package objects in the shared build cache, which later plain builds reuse.
+
+```bash
+REPO=$(git rev-parse --show-toplevel)
+WORK=$(mktemp -d)
+export GOWORK=off GOFLAGS=-mod=mod GOCACHE="$WORK/gocache"
+(cd "$REPO/tools/go-agent" && go build -o "$WORK/agent" ./cmd)
+
+mkdir "$WORK/app" && cd "$WORK/app"
+cat > main.go <<'EOF'
+package main
+
+import (
+	_ "github.com/apache/skywalking-go"
+	// import and use the framework packages that the plugin hooks
+)
+
+func main() {}
+EOF
+go mod init app
+go mod edit -replace=github.com/apache/skywalking-go="$REPO" \
+  -require=google.golang.org/genproto@v0.0.0-20230530153820-e85fd2cbaebc
+go get <framework module>@<version>
+go mod tidy
+go build -toolexec="$WORK/agent" -a -o app .
+go tool nm app | grep -c 'Interceptor).BeforeInvoke'   # the interceptors are linked
+SW_AGENT_REPORTER_GRPC_BACKEND_SERVICE=127.0.0.1:1 ./app
+```
+
+Repeat at the lowest and highest version in the plugin's range. For a version
+split, also check the boundary versions on both sides.
+
+To inspect the rewritten sources, add `-work` to the build and look in the
+printed `WORK=` directory.
+
+Common failures:
+- `could not import X (open : no such file or directory)`: a copied file imports
+  a package that the hooked package does not import directly in that version.
+- `undefined: skywalking_operator...`: an identifier in a copied or injected file
+  was rewritten. In logger templates, prefer unnamed types to framework types.
+
+## Report
+
+Summarize the commands you ran and their pass or fail results. For failures,
+include the first compiler or test error, with its file and line.
diff --git a/.claude/skills/plugin-e2e/SKILL.md b/.claude/skills/plugin-e2e/SKILL.md
new file mode 100644
index 0000000..c7aadfd
--- /dev/null
+++ b/.claude/skills/plugin-e2e/SKILL.md
@@ -0,0 +1,115 @@
+---
+name: plugin-e2e
+description: Run SkyWalking Go plugin scenario tests and the end-to-end suite locally the way CI does, narrow a run to some framework versions, and diagnose build, dependency, runtime, and validation failures
+user-invocable: true
+allowed-tools: Bash, Read, Glob, Grep, Edit
+---
+
+# Run Plugin Scenario Tests and E2E Locally
+
+Ask which scenario to run if the user did not say, or infer it from the changed
+plugins: `plugins/<name>` is tested by `test/plugins/scenarios/<name>`. A run of
+several cases takes minutes, so run it in the background and report when it
+ends.
+
+## Prerequisites
+
+```bash
+docker info >/dev/null && echo docker ok
+docker-compose version --short    # the scenario scripts call docker-compose
+yq --version                      # mikefarah yq v4, used by run.sh
+go version
+```
+
+## 1. Build the test tools
+
+Rebuild after every change to the agent, a plugin, or the runner. This builds the
+linux agent for the host architecture, the runner-helper, and the validator
+image. Pass `VERSION`, because a stale gitignored `VERSION` file would make the
+build look for a binary name that does not exist.
+
+```bash
+make -C test/plugins build VERSION=$(git rev-parse --short HEAD)
+```
+
+## 2. Run a scenario
+
+```bash
+bash test/plugins/run.sh <scenario>           # every case in plugin.yml
+bash test/plugins/run.sh --debug <scenario>   # keep the workspace and the actual data
+bash test/plugins/run.sh --clean             # remove the test images and the workspace only
+bash test/plugins/run.sh --clean <scenario>  # clean, then run the scenario
+```
+
+Each `support-version` entry in `plugin.yml` produces one case per framework
+version, `go<version>-<framework version>`. The run stops at the first failing
+case. Success ends with `Scenarios: <name>, Testcases: N`.
+
+To run only some versions, edit the scenario's `plugin.yml` temporarily, then
+restore it. Never commit a narrowed matrix.
+
+```bash
+git checkout -- test/plugins/scenarios/<scenario>/plugin.yml
+```
+
+## 3. Diagnose failures
+
+Logs are kept for a failed case in
+`test/plugins/workspace/<scenario>/<case>/logs/`. The files are `scenarios.log`
+(the image build, `go mod tidy`, and the instrumented build), `service.log` (the
+application), and `runner-helper.log`.
+
+| Symptom | Usual cause |
+|---|---|
+| `failed to solve: ... go mod tidy` | The case's framework version does not resolve with the scenario's other requirements, or a dependency needs a newer Go. Reproduce it in the workspace with `GOWORK=off go mod tidy`. |
+| `could not import X (open : no such file or directory)` | A copied plugin file imports a package that the hooked package does not import directly in that version. See the `build` skill and `AGENTS.md`. |
+| `undefined: skywalking_operator...` | An identifier in copied or injected code was rewritten. |
+| `panic` in `service.log` | The application fails at startup, for example a protobuf `file "x.proto" is already registered` conflict. Give the scenario's proto file a unique name. |
+| `[ERROR] <case>, validate actual data failed!` | The reported data differs from `config/excepted.yml`. The `reason:` line names the first mismatch, such as a component id, or `ActualSegmentRefIsEmptyException` when context propagation is missing. |
+
+When a framework version legitimately reports different data, add an
+expectation file and set `excepted-file: config/<file>.yml` on that version's
+`support-version` entry. Do not loosen the shared expectation file.
+
+## 4. Check what a case actually builds
+
+Cases build with `GOWORK=off`. `run.sh` rewrites the `framework:` requirement in
+the scenario `go.mod` to the case version. The scenario's own requirements can
+still lift it, so check the selected version before claiming a lower bound:
+
+```bash
+cd test/plugins/workspace/<scenario>/<case>   # kept after a failure or --debug
+GOWORK=off go list -m <framework module>
+```
+
+`framework:` must be the module path exactly as written in `go.mod`, including a
+`/vN` suffix. Otherwise every case builds the version pinned in `go.mod`.
+
+## 5. Adding or changing versions
+
+1. Add the version to `plugin.yml`: the oldest Go entry for the floor, plus a
+   newest-Go entry for the latest version.
+2. Run the scenario.
+3. Update `docs/en/agent/support-plugins.md` so the range is the tested min..max,
+   and add a `CHANGES.md` entry at the end of the matching section.
+4. List a new scenario in the `case` matrix of
+   `.github/workflows/plugin-tests.yaml`.
+
+CI runs on ubuntu amd64. Local runs on arm64 do not exercise amd64-only code paths
+in frameworks, so CI is the final check.
+
+## 6. End-to-end suite
+
+`test/e2e` runs the reporter cases (`grpc`, `grpc-multi-backend`, `kafka`) with
+skywalking-infra-e2e. Use the same commit as `.github/workflows/e2e.yaml`:
+
+```bash
+make -C test/e2e build VERSION=$(git rev-parse --short HEAD)
+go install github.com/apache/skywalking-infra-e2e/cmd/e2e@7e4b5b68716fdb7b79b21fa8908f9db497e1b115
+e2e run -c test/e2e/case/grpc/e2e.yaml        # from the repository root
+```
+
+## Report
+
+List each case with pass or fail. For a failure, give the case name, the log file,
+the first relevant error or `reason:` line, and the likely cause from the table.
diff --git a/.gitignore b/.gitignore
index 1ca07e2..f8fb3f1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,3 +11,5 @@ coverage.txt
 /tools/protocols/temp/
 vendor/
 VERSION
+.claude/worktrees/
+.claude/settings.local.json
diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644
index 0000000..0e70c56
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1,144 @@
+# SkyWalking Go Agent Guide
+
+Apache SkyWalking Go is a compile-time auto-instrumentation agent. The
+`skywalking-go-agent` binary runs as `go build -toolexec`, rewrites the
+framework packages an application uses, and links the agent runtime into the
+application. Follow nearby code and tests, and keep changes focused on the
+requested behavior.
+
+## Repository layout
+
+- The root module `github.com/apache/skywalking-go` (`agent/`, `log/`,
+  `imports.go`) is what applications import with
+  `_ "github.com/apache/skywalking-go"`. Its `go` directive becomes the minimum Go
+  version of every application that imports it.
+- `plugins/core` holds the tracing core and the plugin API (`instrument`,
+  `operator`, `tracing`). It is compiled into the application.
+- `plugins/<name>`: one Go module per framework plugin. Some frameworks have
+  version-split plugins, such as `grpc` and `grpc-v184`, or `dubbo` and
+  `dubbo-v331`.
+- `tools/go-agent` builds the `skywalking-go-agent` toolexec binary. Plugins are
+  registered in `tools/go-agent/instrument/plugins/register.go`. The runtime
+  hook (`instrument/runtime`) and the logrus/zap integration
+  (`instrument/logger`) live here too.
+- `toolkit` is the public toolkit API. `test/plugins` holds the plugin scenario
+  tests, `test/e2e` the end-to-end suite, and `docs/` the documentation.
+- `go.work` lists every module, including the scenario modules. Plugin scenario
+  cases are built with `GOWORK=off` (see below).
+
+## Go versions
+
+- The floor is Go 1.26: every project module declares `go 1.26.0`. CI builds and
+  tests with Go 1.26 and Go 1.27.
+- Keep the floor at the oldest Go release that is still supported upstream,
+  because raising the root module's `go` line forces every user application to
+  that Go version.
+- A dependency must not require a newer Go than the floor. Run `go get` with
+  `GOTOOLCHAIN=go1.26.<latest>` so that such a dependency fails locally.
+
+## Build, test, and lint
+
+See `.claude/skills/build/SKILL.md` for the details. In short:
+
+- `make build` builds `bin/skywalking-go-agent-<VERSION>-{linux,darwin,windows}-<arch>`.
+- `make test` and `make test-race` run every module's tests.
+- `make lint` runs golangci-lint v2.15.0 in every module, using the repository
+  `.golangci.yml`.
+- `make check` verifies that `go mod tidy` leaves no changes.
+
+## Writing plugins
+
+These rules come from how the agent rewrites code. Ignoring them breaks user
+builds, sometimes only at one end of a version range.
+
+- **Selection.** For each compiled package, the agent uses the first registered
+  instrument whose `BasePackage()` is a prefix of the package and whose
+  `VersionChecker(version)` accepts the module version. The version comes from
+  the module cache path. It is empty when the framework is replaced by a local
+  module.
+- **Copied files.** Every non-test `.go` file of the plugin package directory is
+  copied into the framework package and compiled there. Files containing
+  `//skywalking:nocopy`, such as `instrument.go`, are not copied.
+- **Imports.** A copied file can only import packages that the hooked package
+  imports directly, standard library packages included, in every supported
+  version. `github.com/apache/skywalking-go/...` imports are exempt, because the
+  agent rewrites them.
+- **Native mirrors.** `//skywalking:native <pkg> <Type>` declares a mirror struct
+  that is rewritten to the real framework type. Its field names and types must
+  exist in every version the plugin accepts.
+- **Matching.** For receiver methods, matching compares only the method name and
+  receiver type; `WithArgType`/`WithResultType` filters apply to static
+  functions only. A signature change therefore still matches, and fails at
+  runtime in the interceptor's `invocation.Args()[i]` type assertions. Use
+  checked type assertions.
+- **Version splits.** When a framework release changes the internals the
+  interceptors compile against, add a separate plugin folder rather than
+  version branches in one folder:
+  - give the two plugins disjoint `VersionChecker`s and the same `Name()`, so
+    that plugin exclusion keeps working;
+  - let the legacy plugin accept unparseable versions.
+- **Plugin `go.mod`.** It requires the newest verified framework version. It does
+  not affect user builds or scenario cases, but it is linked into the agent
+  binary, so keep it free of vulnerable dependencies (`govulncheck` on
+  `tools/go-agent`).
+- **Logger code.** Code in `tools/go-agent/instrument/logger/frameworks` is
+  injected into logrus/zap by AST rewriting. References to the framework package,
+  such as `logrus.Fields`, are rewritten, so prefer an unnamed equivalent type
+  such as `map[string]interface{}`.
+
+### Bumping a framework version
+
+1. Check that every hook point of the plugin exists with the same full signature
+   at the lowest and highest supported version.
+2. Compile a small instrumented application at both ends:
+   `go build -toolexec=<agent> -a` with `GOWORK=off` and
+   `replace github.com/apache/skywalking-go => <repo>`.
+3. Run the scenario (`.claude/skills/plugin-e2e/SKILL.md`).
+4. Update the scenario `plugin.yml`, the plugin `go.mod`,
+   `docs/en/agent/support-plugins.md` (the ranges must equal the tested
+   min..max), and `CHANGES.md`.
+
+## Plugin tests
+
+See `.claude/skills/plugin-e2e/SKILL.md` for running them. Key semantics:
+
+- Each `test/plugins/scenarios/<name>/plugin.yml` lists `support-version`
+  entries. Each entry has a Go version, framework versions, and an optional
+  `excepted-file` when a version changes the reported data.
+- `framework:` must be the module path exactly as written in the scenario
+  `go.mod`, including a `/vN` suffix. Otherwise the per-case version is silently
+  not applied.
+- Each case builds with `GOWORK=off`, with the agent replaced by the local code.
+  Set `toolkit: true` when the test code imports the toolkit. Older versions can
+  still be lifted by the scenario's own dependencies, so check the selected
+  version (`go list -m`) when adding a lower bound.
+- New scenarios must be added to the matrix in
+  `.github/workflows/plugin-tests.yaml`.
+
+## Changes, docs, and pull requests
+
+- Add `CHANGES.md` entries at the end of the matching section of the unreleased
+  version. When dropping support for library versions, list them, with the
+  reason, in the version's "Unsupported Library Versions" table.
+- Markdown is exempt from the license header check. Every other new file needs
+  the Apache license header (`license-eye`, configured in `.licenserc.yaml`).
+- In `.github/workflows`, `apache/*`, `actions/*`, and `github/*` actions are
+  allowed. Third-party actions must be pinned to a commit SHA that is on the ASF
+  allow-list (apache/infrastructure-actions).
+- Pull requests are squash-merged. Keep one focused change per pull request, and
+  never commit `bin/`, `test/plugins/workspace/`, `VERSION`, or `.claude/worktrees/`.
+
+## Pitfalls
+
+- `make build` creates a gitignored `VERSION` file once and keeps using it, so
+  the binary name can drift from the commit that `test/plugins` and `test/e2e`
+  expect. Delete `VERSION`, or pass `VERSION=$(git rev-parse --short HEAD)` to
+  make.
+- `-toolexec` builds can leave instrumented package objects in the shared Go
+  build cache, which later plain builds and tests pick up. Run instrumented
+  builds with a dedicated `GOCACHE`. If a plain test's stack trace shows
+  `skywalking_operator...` symbols, run `go clean -cache`.
+- `make test` and `make lint` walk every `go.mod` in the tree. Remove
+  `test/plugins/workspace/` (left by `--debug` or failed scenario runs) first.
+- `make lint` reuses any `golangci-lint` already in `$(go env GOPATH)/bin`, so
+  check `golangci-lint version` first.
diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 120000
index 0000000..47dc3e3
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1 @@
+AGENTS.md
\ No newline at end of file