本文へスキップ

マルチモジュールプロジェクト

複数のGoモジュールで構成されたプロジェクトでwhatap-go-instを使用する方法を説明します。

用語整理

用語説明
モジュールgo.modがある単位module mycompany/user-api
パッケージディレクトリレベルのコードグループmycompany/user-api/pkg/auth
外部モジュールgo getで取得した依存関係github.com/gin-gonic/gin
ローカルモジュール同じプロジェクト内の別モジュールreplace ../shared-lib

パッケージタイプ別の処理

whatap-go-instはパッケージの位置によって計測有無を決定します。

toolexecスキップルール

// 1. Go標準ライブラリ → スキップ
if strings.HasPrefix(path, os.Getenv("GOROOT")) {
return true // 変換しない
}

// 2. 外部パッケージ (go get) → スキップ
if strings.HasPrefix(path, os.Getenv("GOMODCACHE")) {
return true // 変換しない
}

// 3. その他 (自分のコード) → 変換
return false

要約

パッケージタイプパス計測有無
Go標準ライブラリ$GOROOT/src/...スキップ
外部モジュール (go get)$GOMODCACHE/...スキップ
自分のプロジェクトコードローカルパス計測
replaceローカルモジュールローカルパス (../)計測 (fastモードのみ)

マルチモジュールシナリオ

シナリオ: 3つの分離されたモジュール

C:/projects/
├── db-lib/ # モジュールA: DBロジックライブラリ
│ ├── go.mod # module mycompany/db-lib
│ ├── connection.go # sql.Open()使用
│ └── query.go

├── web-lib/ # モジュールB: Webサーバーライブラリ
│ ├── go.mod # module mycompany/web-lib
│ ├── server.go # gin.Default()使用
│ └── handler.go

└── main-app/ # モジュールC: メインアプリ
├── go.mod # module mycompany/main-app
└── main.go # A、Bをimport

モジュールCのgo.mod

module mycompany/main-app

go 1.21

require (
mycompany/db-lib v1.0.0
mycompany/web-lib v1.0.0
)

推奨アプローチ

方式1: モジュール別個別inject (デプロイ用)

本番環境デプロイに推奨します。

Step 1: 各モジュールをinject

# db-libを計測
cd db-lib
whatap-go-inst inject -s . -o ../db-lib-instrumented

# web-libを計測
cd web-lib
whatap-go-inst inject -s . -o ../web-lib-instrumented

# main-appを計測
cd main-app
whatap-go-inst inject -s . -o ../main-app-instrumented

Step 2: 計測されたバージョンに置換

// main-app-instrumented/go.mod
replace mycompany/db-lib => ../db-lib-instrumented
replace mycompany/web-lib => ../web-lib-instrumented

Step 3: ビルド

cd main-app-instrumented
go build ./...

方式2: Monorepo構造 (新規プロジェクト)

新規プロジェクトなら単一モジュール構造が最もシンプルです。

myproject/
├── go.mod # 単一モジュール mycompany/myproject
├── cmd/
│ └── main/
│ └── main.go # mainパッケージ
├── internal/
│ ├── db/
│ │ └── connection.go
│ └── web/
│ └── server.go
└── pkg/
└── shared/
└── utils.go
# ビルド
whatap-go-inst go build ./cmd/main

モード別動作比較

--wrapモード

項目--wrapモード
mainモジュール計測される
replaceモジュール計測されない
外部モジュール (GOMODCACHE)スキップ
事前条件なし

replace処理

// --wrapモードはreplaceパスのみ調整
replace mycompany/db-lib => ../db-lib

// 一時ディレクトリにコピー後:
replace mycompany/db-lib => /original/path/to/db-lib // 元を参照!

結果: replace対象モジュールはコピーされず、元の(非計測)コードを参照

replaceモジュールも計測するには: 各モジュールを個別にinjectする必要があります。

# 各モジュール個別inject
whatap-go-inst inject -s ../db-lib -o ../db-lib-instrumented
whatap-go-inst inject -s . -o ./instrumented

# instrumented/go.modでreplaceパスを修正
# replace mycompany/db-lib => ../db-lib-instrumented

注意事項

1. mainがないライブラリinject

main関数がないライブラリもinjectできます。

項目処理
trace.Init()追加されない (正常)
sql.Open()whatapsql.Open()変換される
gin.Default() + ミドルウェア追加される
# ライブラリモジュールinject
cd db-lib
whatap-go-inst inject -s . -o ./instrumented

# 結果: whatapsql.Open()に変換される
# trace.Init()は追加されない (mainでのみ呼び出す必要がある)

2. 依存関係の伝播

計測されたライブラリを使用するモジュールもwhatap依存関係が必要です。

// db-lib-instrumented/connection.go
import "github.com/whatap/go-api/instrumentation/.../whatapsql"

// main-appでビルドするにはこの依存関係が必要
cd main-app
go get github.com/whatap/go-api@latest

FAQ

Q1: replaceモジュールも計測されますか?

A: いいえ、wrapモードはreplaceモジュールを計測しません。replaceパスのみ調整し、元を参照します。replaceモジュールも計測するには、各モジュールを個別にinjectする必要があります。

各モジュール個別inject

whatap-go-inst inject -s ../db-lib -o ../db-lib-inst
whatap-go-inst inject -s ../web-lib -o ../web-lib-inst

Q2: 外部ライブラリ(gin、gormなど)を計測できますか?

A: いいえ。外部ライブラリは$GOMODCACHEにあるためスキップされます。これは意図された動作です。

  • 外部ライブラリ修正時、他のプロジェクトに影響
  • Goモジュールシステムの不変性原則

代わりに、ユーザーコードでの外部ライブラリ呼び出しが変換されます:

  • sql.Open()whatapsql.Open()
  • gin.Default() → ミドルウェア自動追加

Q3: Monorepo vs マルチモジュール、どちらが良いですか?

構造長所短所
Monorepo計測が簡単、依存関係管理が容易モジュール再利用が困難
マルチモジュール独立したモジュールデプロイが可能計測設定が複雑

推奨:

  • 新規プロジェクト → Monorepo
  • 既存マルチモジュール → モジュール別個別inject

Q4: CI/CDでマルチモジュールをどのようにビルドしますか?

下記のCI/CD例セクションを参照してください。


CI/CD例

GitHub Actions

# .github/workflows/build.yml
name: Build with WhaTap Instrumentation

jobs:
build:
runs-on: ubuntu-latest
steps:
# 1. すべてのモジュールをチェックアウト
- uses: actions/checkout@v3
with:
path: main-app
- uses: actions/checkout@v3
with:
repository: mycompany/db-lib
path: db-lib
- uses: actions/checkout@v3
with:
repository: mycompany/web-lib
path: web-lib

# 2. Goインストール
- uses: actions/setup-go@v4
with:
go-version: '1.21'

# 3. whatap-go-instインストール
- run: go install github.com/whatap/go-api-inst/cmd/whatap-go-inst@latest

# 4. ビルド
- run: |
cd main-app
whatap-go-inst go build -o myapp ./...

# 5. アーティファクトアップロード
- uses: actions/upload-artifact@v3
with:
name: myapp
path: main-app/myapp

GitLab CI

# .gitlab-ci.yml
stages:
- build

build:
stage: build
image: golang:1.21
before_script:
# whatap-go-instインストール
- go install github.com/whatap/go-api-inst/cmd/whatap-go-inst@latest
# 他のモジュールをクローン
- git clone https://gitlab.com/mycompany/db-lib.git ../db-lib
- git clone https://gitlab.com/mycompany/web-lib.git ../web-lib
script:
- whatap-go-inst go build -o myapp ./...
artifacts:
paths:
- myapp

Dockerマルチステージビルド

# Dockerfile
FROM golang:1.21 AS builder

WORKDIR /workspace

# whatap-go-instインストール
RUN go install github.com/whatap/go-api-inst/cmd/whatap-go-inst@latest

# ソースコピー (すべてのモジュール)
COPY db-lib/ ./db-lib/
COPY web-lib/ ./web-lib/
COPY main-app/ ./main-app/

# 初期化および依存関係を追加
WORKDIR /workspace/main-app

# ビルド
RUN whatap-go-inst go build -o /app/myapp ./...

# ランタイムイメージ
FROM alpine:latest
COPY --from=builder /app/myapp /app/myapp
CMD ["/app/myapp"]