Diary of a Perpetual Student

Perpetual Student: A person who remains at university far beyond the normal period

Mackerelカスタムダッシュボードおまかせ生成機能の裏側

このエントリはMackerel Advent Calendar 2025 3日目の記事です。まだ空き枠がありますので、ご利用のみなさまぜひご参加ください。

qiita.com


こんにちは、Mackerel開発チームサブディレクター・テックリードのid:arthur-1です。

Mackerelにはカスタムダッシュボードおまかせ生成機能というものがあります。従来カスタムダッシュボードを作成するには、ユーザーが1つ1つウィジェットを設定して並べていく必要がありました。おまかせ生成機能では、パラメータとして指定したロールのホストで使われているプラグインやインテグレーションを検出して、いい感じのダッシュボードを生成することができます。

mackerel.io

arthur-1のVPSでカスタムダッシュボードおまかせ生成機能を利用した様子。nginx pluginの利用が検出されnginxに関するメトリックグラフが並べられている

id:arthur-1は当時この機能の企画やPoC開発を担当していました。このエントリでは、Mackerelカスタムダッシュボードおまかせ生成機能が生まれるに至った裏側をご紹介します。

Mackerelは、ちょっとしたスクリプトでmackerel-agentを入れるだけですぐにホストページやサービスページでいい感じの表示ができるところに良さを感じていただいている方が多い監視サービスです。その体験と比較して、カスタムダッシュボード機能は自力でグラフやマークダウンといったウィジェットを1つ1つ配置して作成する必要があり、構築やメンテナンスが大変というユーザーの声を聞いていました。

カスタムダッシュボードを自分で構築するのが面倒というユーザーの課題を解決するために、まずはカスタムダッシュボードのテンプレートという概念があると捗ると考えました。参照したいホストやロールをパラメータとして指定し、テンプレートを元にカスタムダッシュボードの定義を出力するようなツールがあれば良いと思ったのです。

そこで、MackerelのWebコンソールからアクセスできるものとして作るより前に、テンプレートからカスタムダッシュボードを作るようなCLIツールを開発しました。テンプレートにパラメータを注入してカスタムダッシュボードの定義を得るメインの機能とは別に、以下の2つの要件が求められると考えました:

  • 条件を評価し、ウィジェットをダッシュボードに載せるか載せないかを分岐できること
  • ウィジェットの配置を絶対指定することなくHTMLのように半自動で配置できること

1つ目の「条件を評価し、ウィジェットをダッシュボードに載せるか載せないかを分岐できること」は、ユーザーの利用環境の違いをテンプレートである程度吸収できるようにすることが目的です。システムメトリックを表示するだけでも、mackerel-agentを利用している場合とクラウドインテグレーションを利用している場合では表示するグラフが変わってきます。こうしたバリアントが増えるたびにテンプレートを増やしていては組合せ爆発を起こしてしまいますから、テンプレートとしてちょっとした違いを吸収できる表現力が必要と考えました。

これを言い換えれば、ダッシュボードに載せたいグラフの数やレイアウトはユーザーが指定したパラメータや利用状況によって変化するということです。しかし、ここでMackerelのAPIにおけるダッシュボード定義において絶対的な座標指定を求められることが障害になります。そこで2つ目の「ウィジェットの配置を絶対指定することなくHTMLのように半自動で配置できること」が機能要件として上がってくるわけです。

これを実現するようなCLIツールをGo言語でサクッと作って動作イメージを確認し、有用そうであることを確認しました。その後、MackerelのWebコンソールでアクセスできる機能として再度設計し直し開発を進めました。また、メインのユースケースとしてWebサービスをシンプルに提供する3層構造のシステムを想定し、テンプレートも作り込みました。その結果完成したのがこの「カスタムダッシュボードおまかせ生成機能」です。

現在は私たちが用意した3層構造のWebサービスに特化したテンプレートからのおまかせ生成しかできませんが、その他のシステム向けのテンプレートが欲しいとか、ユーザーが独自にテンプレートを定義したい、といったケースもあるでしょう。ぜひお近くのMackerelの中の人に要望を伝えてみてください。

あなたのOpenTelemetry CollectorのBatch Processorはもう不要かもしれない

これはOpenTelemetry Advent Calendar 2025 2日目のエントリです。2週間に1回欠かさずOpenTelemetery Collectorのアップデートを追っている身として、「意外と知られていないのでは?」と個人的に思っているネタを取り上げます。


Batch Processorの利用推奨について

OpenTelemetry Collectorをお使いのみなさん。Batch Processorを利用していますか?

Batch Processorは複数のテレメトリーデータを1つのbatchにまとめる役割を果たします。受け取った・生成されたテレメトリーを即時にexportする手法と比較して、オブザーバビリティバックエンドにデータを送信するときの接続数が減らせたり、データの圧縮がより効くようになったりして、結果データ転送が最適化されます。

さて、OpenTelemetry Collectorをある程度の期間使っている人は、configファイルに以下のようにBatch Processorを仕込んでいることでしょう:

receivers:
  otlp:
    protocols:
      grpc:
      http:

processors:
  batch:

exporters:
  otlp:
    endpoint: your-observability-backend.example:4317

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [batch]
      exporters: [otlp]

なぜなら、Batch Processorを使用することが以下のように推奨されてきており、ドキュメントでもBatch Processorをパイプラインに含めた設定例が掲載されてきたからです。

It is highly recommended to configure the batch processor on every collector.

しかしこれはかつての話であり、現在ではBatch Processorの使用の推奨が取り止められていることをご存知でしょうか?

github.com

exporterのsending_queue設定

Batch Processorの利用が推奨されなくなったからといって、exportの最適化のためにテレメトリーデータをまとめるニーズがなくなったわけでもありません。この目的を満たす代わりに利用できるExporterの設定があります。

OTLP ExporterOTLP HTTP Exporterにはsending_queueという設定があり、この中でbatchを有効化することができます。

簡単には、以下のようにsending_queue: batch: {}という設定を加えるだけです。デフォルトでbatchが有効化されているわけではないことに注意してください。

 processors:
-  batch: 

 exporters:
   otlp:
     endpoint: your-observability-backend.example:4317
+    sending_queue:
+      batch:

元々Batch Processorでバッチサイズやタイムアウト時間を調整して使っていた場合には、例えば以下のように詳細に記述すると良いでしょう。

 processors:
-  batch:
-    timeout: 1s
-    send_batch_size: 4096
-    send_batch_max_size: 4096

 exporters:
   otlp:
     endpoint: your-observability-backend.example:4317
+    sending_queue:
+      batch:
+        flush_timeout: 1s
+        min_size: 4096
+        max_size: 4096

あとはserviceのpipelines設定から不要になったbatch processorを外してあげれば移行完了です!

最新のドキュメントのOpenTelemetry Collector設定例を見ても、otlp exporterのconfig部分にsending_queue: batch:と書かれており、パイプラインにbatch processorが含まれていないことがわかります。

exporters:
  otlp:
    endpoint: otelcol:4317
    sending_queue:
      batch:

service:
  pipelines:
    traces:
      receivers: [otlp]
      exporters: [otlp]

🔗 https://opentelemetry.io/docs/collector/configuration/#basics

また、推奨されるProcessorやその適用順序が書かれているドキュメントでも、「Batch Processorの代わりにExporterに付随するバッチ設定を利用するのがおすすめです」という内容の記述が書かれています。

batch, although prefer using the exporter's batching capabilities

🔗 https://github.com/open-telemetry/opentelemetry-collector/tree/main/processor#recommended-processors

Mackerel OTLP Exporterのデフォルト設定

公式のOTLP Exporterに限らず、go.opentelemetry.io/collector/exporter/exporterhelperが提供するQueueBatchのヘルパーを利用して実装されているExporterでは同様の設定が利用できるはずです。

github.com

先日、Mackerel向けのOpenTelemetry CollectorのディストリビューションならびにMackerel OTLP Exporterを公開しました。

github.com

Mackerel OTLP Exporterもexporterhelperの設定定義や関数を利用しており、sending_queueによりexporterのbatchを設定できます。

Mackerelへのトレース投稿時にはリクエストサイズを6MB以下に抑えなければならないという制約があります。Mackerel OTLP Exporterが提供するデフォルト設定をそのまま使うと、何も書かずにbatchが有効になるだけでなく、この制約に合わせてbatchサイズを調整してくれるようになっています。

queueConfig := exporterhelper.NewDefaultQueueConfig()

// overrides default exporter queue batch config
// because Vaxila endpoint does not accept requests larger than 6MB.
queueBatchConfig := queueConfig.Batch.GetOrInsertDefault()
queueBatchConfig.Sizer = exporterhelper.RequestSizerTypeBytes
queueBatchConfig.MaxSize = defaultBatchMaxSizeBytes

queueConfig.Batch = configoptional.Default(*queueBatchConfig)

🔗 https://github.com/mackerelio/opentelemetry-collector-mackerel/blob/578431ae63b7fc137c8f898af5b8410db17fcf7d/exporter/mackerelotlpexporter/factory.go#L32-L40

29歳

になった。1週間前に。

前回:

blog.arthur1.dev

今年の誕生日も普通に仕事をしていた。なんならオフィスに物理出勤していた。

昼休みに友人からDIORのコンシーラーと花束をもらって表参道を散歩した。嬉しすぎる。

近所のミュージックバーに飲みに行ったら、サックスでHappy Birthday to Youの曲を吹いてもらった。あとは、他のお客さんにこの曲演奏して〜と言われまくったのでリクエストに応えてピアノを弾いていたらチップをもらった。

その後は朝までガールズバーで飲み歌歌って牛丼食って帰宅。

翌日も午前中から大学の友人がうちに遊びにきて、FF14TRPGをやったり鍋を作ったりケーキを食べたりした。

ここまでちゃんと祝ってもらったの17歳の誕生日ぶりな気がして嬉しかった。11月はハードワークだったのも相まってより嬉しく感じたのかもしれない。

ここに書{か,け}ないようなものもいろいろもらった。誕生日プレゼントはいつでも待ってます。

ラスト20代なわけだけど、だからと言って何か大きな決断を早まるのは良くないと勝間和代が言っていたような気がする。じゃあ、どうする?志村正彦と津野米咲が死んだ29歳をどう生きるか。

それはともかく、最近はエアライダーを楽しんでやってます。

最近聴いてる曲: Nov. 2025

prev:

blog.arthur1.dev

1年ぶりですね。

曲紹介

RUN / Doona

曲名通りの疾走感。サビのポルタメントがいい味出してる。

www.youtube.com

Farewell / Azami

良いシャウトだからこそサビのクリーンが映える。

www.youtube.com

キケンナアソビ / WurtS

クリープハイプトリビュートアルバムから。気怠げな色気。

www.youtube.com

Missing / Vaundy

こちらはELLEGARDENのトリビュートアルバムから。30代で学生の頃バンドやってた人全員この曲好きだから。そうかな?

www.youtube.com

丸の内ミゼラブル / Royz

「死んでほしい」からのラスサビの悲壮感。秒速5センチメートルのラストの展開を良いと思える人は好きだと思う。

www.youtube.com

Kid / シンガーズハイ

ゆるおもぐらいのロックが自分の耳に馴染む。

www.youtube.com

暴露 / ポップしなないで

キャッチーなメロディーライン。\モンスター!/

www.youtube.com

こっから / SixTONES

ミクスチャーロックっていうのかな。ワウギターが良すぎる。

ジャニーズ、、、じゃなくてSTARTO全然わかんないのでもっと布教してください。

www.youtube.com

鏡面に映す哀の容 / RETEMPEST

メタルサウンドへのピアノとストリングスの合わせ方が勉強になった。

www.youtube.com

花一匁 / スキュラ

↑のライブ観に行ったら対バンでたまたま聞いた。某高収入広告の音楽をMIXしたくなるイントロ。

毒蛇ニゲロの表情が良すぎるのでまた行きたいグループ。

www.youtube.com

芒に月 / 椎名林檎

2025年も一番良かったのは椎名林檎になりそう。3:50あたりから終盤にかけての盛り上がりの作り方がすごい。

www.youtube.com

DESPERADO feat. J-REXXX / ALI

呪術廻戦でお馴染み(?)なLost in Paradiseのカップリング。レゲエ好きなのでラテンな感じが好み。

www.youtube.com

モエチャッカファイア / 弌誠

カラオケで歌ったらtiktokの曲!って言われた。

www.youtube.com

哀悼、そして日常は続く / 宮下遊

卯花ロク ft.裏命による曲の歌ってみたver.。聴きすぎてメンヘラ悪化した。

www.youtube.com

地球の裏 / いよわ feat.裏命

これも裏命。変拍子・テンポ変化大好き。

www.youtube.com

この前DJで流した:

blog.arthur1.dev

最後の歌 / ドラッグオンドラグーン3

こういう曲好き(語彙力)

フローターランド / MARIO KART WORLD

マリオギャラクシーの時は壮大な楽曲だったけど、激カッコいいロックに変身。アレンジが良すぎる。

聴きたい人はゲーム買って聴いてください。

まとめ

これだけ紹介して2025年出た曲を5つしか載せられてないの、digり不足を感じる。

泥酔夜会 Vol. 1/Vol. 2 @ RIME セットリスト(おまけあり)

恵比寿RIMEというシーシャバーで最近「泥酔夜会」という名のDJイベントを開催していて、機材提供と演者をやっています。今回はその振り返りエントリです。

Vol. 1 2025-09-28

初開催なので自己紹介も兼ねて、自分の生き方の指針であるPop×Cool×Little Bit Sexyをイメージしてエレクトロサウンドの曲から選びました。

BPM128っていいですよね。ITエンジニア的にはそこそこキリの良い数字だし。

シャクシャイン→WORLD OF FANTASYの繋ぎが個人的には一番綺麗に決まったかなあと思ってます。

お店のエントリも出てるのでよかったらどうぞ:

note.com

Vol. 2 2025-11-09

PHPカンファレンス福岡2025の当日スタッフやった翌日に福岡から直行したのでだいぶ疲れていました。

今回はXでやって欲しいテーマをアンケートで募集した結果Vocaloidになったので、以下のようなセットリストで持って行きました。

音声はこんな分布でした。色々拾おうと思ったけど結局初音ミク多いがち:

  • 初音ミク 6
  • 鏡音リン 1
  • KAITO 1
  • GUMI 1
  • 重音テト 2
  • flower 2(Ci flower / v flower)
  • 足立レイ 1
  • 可不 1
  • 裏命 1

まず、お店の名前であるRIMEに掛けて音楽的同位体 裏命(RIME)の曲を選びました。これを最後に流すぞと決めて、そこに合うような、具体的には人間の声でないからこそ表現できるような世界観の曲を多く選んで構成しました。もちろん、みんなが知ってそうな曲も序盤はほどほどに混ぜました。

たまたま次のDJ妹子が流したのがr-906とかLeaFとかで似た雰囲気だったので、いい感じに繋がりました。

普段はBPM128固定することが多いので、BPMが違う曲の繋ぎ方を色々研究できたのがかなりいい勉強になりました。

Next…?

私は企画者じゃないのでなんとも言えないけど、次開催したらぜひ来てください。

おまけ

先週月曜日から禁酒を始めました。

始めて1週間の成績はこんな感じです:

  • 3(月) 🍺
  • 4(火) 🍺
  • 5(水) 🍺
  • 6(木) ❌
  • 7(金) 🍺🥃
  • 8(土) 🍺
  • 9(日) 🥃

1勝6敗

GoReleaserをgoreleaser-action経由で使うか、go tool経由で使うか

様々な環境(OS・CPUアーキテクチャ)向けのインストールパッケージやDockerイメージを提供しなければならない、例えばCLIツールのようなものをGo言語で作るとき、多くの人がGoReleaserを利用しているでしょう。

goreleaser.com

GoReleaserは動作検証のために手元で動かすこともありつつ、ほとんどのユースケースではGitHub ActionsなどのCIツール上で動かすでしょう。(リリースが手動の場合には手元で動かすのがメインの人もいるかもしれませんね。)

GitHub Actions上でGoReleaserを利用するとき、ドキュメントに従うとgoreleaser/goreleaser-actionというaction経由でGoReleaserをインストール・起動することになります。

一方、Go 1.24でgo.modにtoolディレクティブが追加されたことで、generate.goファイルを利用していたワークアラウンドに代わって、開発に利用するGo製ツールを管理できるようになりました。これでGoReleaserもgoのToolsの管理下に入れて呼び出す選択肢も浮上しました。

このエントリではGitHub Actions上でGoReleaserを利用する2つの手法を比較します。

goreleaser-action経由で使う

ドキュメント通りに以下のようなGitHub Actionsのワークフロー定義を構築するだけです。

jobs:
  goreleaser:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5
        with:
          fetch-depth: 0
      - uses: actions/setup-go@v5
      - name: Run GoReleaser
        uses: goreleaser/goreleaser-action@v6
        with:
          version: '~> v2'
          args: release --clean

メリットは、公式で案内されている手法で安心感があることです。コンパイル済みのバイナリを持ってくるので、セットアップにかかる時間も比較的高速です。

デメリットは、利用するGoReleaserのバージョンの宣言がGitHub Actions側に寄ることです。CIで利用しているGoReleaserのバージョンと手元のバージョンを仕組みで揃えるのが厳しくなります。

go tool経由で使う

go.modのtoolディレクティブに以下のように記述します。GoReleaserは現在v2なのでメジャーバージョンの指定も必要なことに注意してください。

tool github.com/goreleaser/goreleaser/v2

go toolコマンド経由でGoReleaserを呼び出せます。

$ go tool goreleaser --version
  ____       ____      _
 / ___| ___ |  _ \ ___| | ___  __ _ ___  ___ _ __
| |  _ / _ \| |_) / _ \ |/ _ \/ _` / __|/ _ \ '__|
| |_| | (_) |  _ <  __/ |  __/ (_| \__ \  __/ |
 \____|\___/|_| \_\___|_|\___|\__,_|___/\___|_|
goreleaser: Release engineering, simplified.
https://goreleaser.com

GitVersion:    v2.12.7
GitCommit:     unknown
GitTreeState:  unknown
BuildDate:     unknown
BuiltBy:       unknown
GoVersion:     go1.25.3
Compiler:      gc
ModuleSum:     h1:C2EgMDSQQvFpaZwA4L7JuopZzrvvNuVvdHA52AyCT0M=
Platform:      darwin/arm64

あとは、CIで実際に利用したいGoReleaserの(go toolを経由した)コマンド呼び出しを、GitHub Actionsのワークフロー定義に入れていきます。

jobs:
  goreleaser:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5
        with:
          fetch-depth: 0
      - uses: actions/setup-go@v5
      - name: Run GoReleaser
        run: go tool goreleaser release --clean

メリットは、GitHub Actionsでも手元でも同じGoReleaserのバージョンに揃えられることです。

GoReleaserは2024年にメジャーバージョンがv2となり、このタイミングで破壊的な変更が入っています。また、現在も継続的に開発が続けられており、新たな設定が利用可能になったり、元々使えていた設定が非推奨になったりする変化があります。GoReleaserのバージョンをgo.modで宣言・集約的に管理できることには一定の価値があります。

また、GitHub Actionsに依存しないやり方なので、GitLabなど他のCIツールでも同じように利用できるはずです。

デメリットは、CI環境でのGoReleaserインストールが遅いことです。setup-go actionのキャッシュがツールのバイナリにも効くと期待していたのですが、そうではなさそうです。GitHub Actions上で実行したところ、installフェーズに1分30秒ぐらいかかりました。

さらにもう一つ致命的なデメリットがあります。go.modのtoolディレクティブで管理しているツールのgo.modに書かれているgoディレクティブは、自分たちのモジュールのgo.modにも影響を及ぼします。例えば、元々の自分たちのgo.modにgo 1.24と書かれていても、現時点で最新のGoReleaser v2を依存に加えてしまうとgo 1.25.1に勝手に上げられてしまいます。

これは、バイナリを提供しつつライブラリとしての機能も果たすようなプロジェクトで特に困ります。なぜなら、そのモジュールに依存したソースコードをビルド可能なGoのバージョンが引き上げられてしまうからです。Go言語では直近2つのメジャーリリースがサポートされていますが、先ほどの例だと、そのモジュールはサポート下のGo 1.24の環境では利用できなくなるということです。

go.modのセマンティクスとしては、goディレクティブはコンパイル可能な最低限のバージョン、toolchainディレクティブは推奨されるツールチェーンバージョンを表します。しかし、ツール提供だけを目的としたモジュールではこの区別をせず、単にgoディレクティブを最新に近いバージョンに設定していることがあります。GoReleaserもその一つです。setup-go actionでgo-version-fileにgo.modを指定するとgoディレクティブを読んでバージョンを決める機能があることもこの状況を助長していると感じています。

所感

特にこだわりがなければ、公式が案内しているgoreleaser-actionを利用するやり方が無難で良いと思います。

CIが多少遅くてもよく、ビルド可能なGoバージョンの下限が上がっても構わないなら、手元でもCIでも同じGoReleaserを使えるというメリットを取って、go tool経由で利用するのもアリかもしれません。

tools.goを利用したツール管理の慣習をgo.modのtoolディレクティブに置き換えるツールとその実装

Go言語のtools.goを利用したツール管理の慣習を、go.modのtoolディレクティブに置き換えるツールを作ったのでご紹介します。

前提知識

Go言語にはGoで書かれたバイナリをインストールできるgo installコマンドという仕組みがあります。golangci-lintなど開発に利用するCLIツールをバージョン固定して各開発環境でインストールさせるために、ビルドタグをつけたtools.goというファイルを作ってblack importし、go.modの依存管理に加えるというハックがありました*1

//go:build tools

package tools

import (
    _ "golang.org/x/tools/cmd/stringer"
)

github.com

Go 1.24からはgo.modにtoolディレクティブが登場しました。これにより、ハックに頼らずGo Modulesで正式に開発ツールを管理できるようになりました。

go.dev

先ほどのtools.goの例を置き換えるとすると、go.modに以下のようにtoolディレクティブの記述をすることになります。

 module sample
 
 go 1.24.0
 
+tool golang.org/x/tools/cmd/stringer

 require golang.org/x/tools v0.37.0

 require (
    golang.org/x/mod v0.28.0 // indirect
    golang.org/x/sync v0.17.0 // indirect
    golang.org/x/sys v0.36.0 // indirect
    golang.org/x/telemetry v0.0.0-20250908211612-aef8a434d053 // indirect
 )

また、登録したツールはgo tool stringerのような形で呼び出すことができ、バージョン管理とは別にインストールのためにgo installしていたのは不要になります。

tools.goからtoolディレクティブへの置き換えを自動化

以下のようなXのポストを見かけました。

この置き換えはそこまで大変なものではないのですが、自動化するツールがあったら面白いだろうなと思って作ってみました。なお、私は世の中に他のソリューションがあるかどうかを確認していません。

github.com

使い方としては、まずはgo-tools-migratorをgo installで導入してください*2

go install github.com/Arthur1/go-tools-migrator/cmd/go-tools-migrator@latest

go.modとtools.goがあるフォルダで、以下のようにコマンドを呼び出すと、tools.goの中身を見て、必要なものをgo.modのtoolディレクティブに追加してくれます。また、不要になったtools.goは削除します。

$ go-tools-migrator
✅ Succeeded to migrate.

ファイルが別の場所にある場合など、発展的な使い方をしたい人は以下のヘルプを見てください。

$ go-tools-migrator -h
Usage: go-tools-migrator [flags]

go-tools-migrator: a CLI tool that replaces tools management via tools.go with go.mod tool directive.

Flags:
  -h, --help                        Show context-sensitive help.
  -v, --version                     Print version and quit
      --dryrun                      Output the contents of the new go.mod without modifying existing files.
      --tools-go-file="tools.go"    tools.go file path (default: tools.go)
      --go-mod-file="go.mod"        go.mod file path (default: go.mod)

実装

まずはtools.goを読んで静的解析し、importしているものを取り出します。

📄 internal/gotool/gotool.go#L49-L70

import文の静的解析については以前もこのブログで取り上げています。

blog.arthur1.dev

ちゃんと実装するなら、importしているものが単なるpackageでなく実行可能なprogramなのかを判定できたら堅牢になるかもしれません。golang.org/x/tools/go/packagesを使ってpackage名を取得して、それがmainだったら実行可能である、という判定ができそうな気がしますが、未実装です。

次に、go.modを読んで、足りないtoolディレクティブを足していきます。go.modの構文を守って書き込む時にはどうしたらいいかというと、golang.org/x/mod/modfileという便利packageがあるので使います。

📄 internal/gotool/gotool.go#L72-L88

読み込んだgo.modをmodfile.File構造体に変換して、用意されているAPIを使ってgo.modを編集していきます。toolディレクティブの内容を追加するFile.AddTool関数は、すでに存在するものを再度渡しても何も起こらないとドキュメントに書いてあるので、細かいことを考えずにtools.goから得た依存関係を順にAddToolに渡すだけで良いです。

AddToolなどを呼んでも元のgo.modファイルが直接編集されるわけではありません。File.Formatを呼ぶことで、編集後のgo.modの内容を[]byte型で得ることができます。

あとは、go.modファイルの中身をFormatで得た新たなgo.modの内容に置き換えられれば良いです。しかし、go.modをtruncateした後にファイルの書き込みに失敗するとgo.modの内容が失われてしまいます。そこで、シェルの>リダイレクトのような操作をatomicに行えるライブラリを利用しています。

github.com

📄 internal/gotool/gotool.go#L31-L43

他にも、Immutable Releaseを採用したり、CI周りでこだわっていたりするので、本体の実装以外のコードも眺めてみてください。

最後に宣伝

2025-10-31(金)、弊社でGo言語の勉強会やるのでぜひいらしてください。公募LT枠もありますよ〜。

connpass.com

*1:ビルドタグ名もファイル名もパッケージ名も本質的にはなんでも良いはずなのですが、慣習として多くの人がこういう命名をしているという事実はあったはずです。go.devのWikiにも載っていた手法ですし。

*2:もちろんtoolディレクティブで導入してもらっても構いません。