Golang に関する投稿を表示しています

CREATE TABLE 文から Markdown のテーブル表記をつくる

ドキュメントにテーブルの説明を書くのがちょっと苦しかったので CREATE TABLE 文から Markdown のテーブル表記を作れるようにした。

GitHub - sters/createtable2Markdown

CREATE TABLE 文をパースできれば勝てるので別に MySQL 限定とかではない。特殊すぎる記載じゃなければ動くと思う。

GitHub - xwb1989/sqlparser: SQL Parser implemented in Go

これがすごくて、若干の挙動が怪しい不要な要素をスルーするようにしてるけれども、あとはパース結果を組み立てる以外なにもしてない。

標準入力で SQL な文字列を受け取って、標準出力で Markdown が出せるようにしているので、ダンプしたものをそのまま流したり、クリップボードを活用したり、コマンドラインでサクサク使うのにめちゃいい。
Readme にも書いたけどこういうことができる。

mysqldump -u database_user -p -d -n --compact database_name | createtable2Markdown

Google Cloud Build + kaniko を試す

Cloud Build のドキュメントを見ていたら、ビルドを高速化するのに kaniko を使うソリューションがあるよ、と書かれていたので、試してみる。

Kaniko キャッシュの使用 | Cloud Build | Google Cloud
GitHub - GoogleContainerTools/kaniko: Build Container Images In Kubernetes

kaniko っていうのはすごいざっくりと理解したのは Docker なしに k8s 上で Docker イメージをビルドできるよ、というもの。
で、そのとき対象の Dockerfile に関して、レイヤーごとに全部キャッシュを取ってくれるので、差分があった部分以降をビルドするだけになり docker build よりも高速化できる、という話っぽい。通常 docker build するときも、レイヤーごとの情報を持っていて、変わった部分以降をビルドすることができる。できるが、そのキャッシュのバックエンドとか、マルチステージにしたときのビルドステップの進め方とかをいい感じにしたっていう話なんじゃあないかなあ。(それ以上の理解はできていない)

さて、ビルドする対象となる Dockerfile はこういう具合。
マルチステージで go で書かれたアプリケーションサーバのビルド、からのそのアプリケーションコンテナのイメージを作る。

FROM golang:1.12 as builder
ARG GITHUB_TOKEN
ARG VERSION
ENV GO111MODULE=on

WORKDIR /go/src/github.com/foo/bar

COPY go.mod go.sum ./
RUN echo "machine github.com login ${GITHUB_TOKEN}" > ~/.netrc
RUN go mod download

COPY . ./
RUN CGO_ENABLED=0 GOOS=linux go install -v -ldflags "-X main.version=${VERSION}" github.com/foo/bar/cmd/server

FROM alpine:latest

RUN apk add --update --no-cache ca-certificates tzdata
COPY --from=builder /go/bin/server /bin/server
CMD ["/bin/server"]

それでもとの cloudbuild.yaml はこうなっている。
変数をバケツリレーしていく。

steps:
- name: gcr.io/cloud-builders/docker
  args:
  - build
  - -t
  - $_IMAGE
  - --build-arg
  - GITHUB_TOKEN=$_GITHUB_TOKEN
  - --build-arg
  - VERSION=$_VERSION
  - -f
  - $_DOCKERFILE_PATH
  - .
images:
  - $_IMAGE

で、これを kaniko に対応させるとこうなる。

steps:
- name: 'gcr.io/kaniko-project/executor:latest'
  args:
  - --destination=$_IMAGE
  - --dockerfile=$_DOCKERFILE_PATH
  - --cache=true
  - --cache-ttl=6h
  - --build-arg=GITHUB_TOKEN=$_GITHUB_TOKEN
  - --build-arg=VERSION=$_VERSION

その他 GCP 設定や Dockerfile などなどはそのままで実行することはできた。

できたが結果はちょっと微妙で、めっちゃ早くなったかというとそうでもない。。。

ビルド時、主に時間がかかるのが go mod download と go install (実際は go build が時間かかる)のふたつで、アプリケーションのコードを変えただけの場合は go mod のキャッシュが効いてくれるが、レイヤーを持ってくる(?)のにやっぱり時間がかかるので、プラスマイナスみて、ややマイナス、みたいな状態。。

具体的な時間としては kaniko を使う前の元々ので 5 分くらい。 kaniko してフルキャッシュ状態(再実行しただけ)で 1-2 分。 go mod のキャッシュが効いてて 2-4 分くらい。キャッシュがまったくなし = go.mod が変更された場合で 5-7 分くらい。ちゃんと測ってなくて数回ためした程度で、実はもっと早いかもしれない。そこは試す余地がある。

あとは単純にお財布で戦って、マシンサイズを上げて強い子にすると早くなるんじゃないかなあ~。


そもそも、コンテナイメージの作成を早くするのが目的なら CI 上でクロスコンパイルしたバイナリを Docker にいれちゃうのがよさそう。
というか CI 上でも golang:1.12 なイメージでビルドするならば別にクロスコンパイルでもないのか。

マルチステージな Dockerfile でアプリケーションのビルドもまるっと!と Cloud Build でビルドすると、余計なゴミが入りにくい、という良い点はありそう。

とはいえ、ワークスペースをアタッチしない、かつ go mod だけ共有してビルドしちゃえば Cloud Build でやるのとほぼ同じ環境になりそうな気がする。そうしてできたバイナリをコンテナ内に COPY して、コンテナレジストリに登録しちゃえばいいんじゃないかな。

grpc-go の interceptor を理解した

元実装はここ
GitHub - grpc/grpc-go: The Go language implementation of gRPC. HTTP/2 based RPC

Interceptor の例としてはここ
GitHub - grpc-ecosystem/go-grpc-middleware: Golang gRPC Middlewares: interceptor chaining auth logging retries and more.

要はなにかというと Laravel をやっていたときに使っていた Middleware と自分では理解した。
Middleware - Laravel - The PHP Framework For Web Artisans

リクエストを処理する前後に、任意の処理を差し込めるもの。

リクエストを中断したり、ウェイトしたり、前処理したり。
あるいはリクエスト処理の後に、かかった時間やステータスについてのメトリクスを取ったり、といったことができる。

これはサーバだけでなく、クライアント側にも差し込めるので、クライアントリクエストする前に何処に対してリクエストしようとしているか、とか、なんか色々できそうではある。

自分でも書いてみる。

func AccessLogUnaryServerInterceptor() grpc.UnaryServerInterceptor {
	return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
		clientIP := "unknown"
		if p, ok := peer.FromContext(ctx); ok {
			clientIP = p.Addr.String()
		}

		ua := ""
		if md, ok := metadata.FromIncomingContext(ctx); ok {
			if u, ok := md["user-agent"]; ok {
				ua = strings.Join(u, ",")
			}
		}

		ctxzap.AddFields(
			ctx,
			zap.String("access.clientip", clientIP),
			zap.String("access.useragent", ua),
		)

		return handler(ctx, req)
	}
}

これは UnaryServerInterceptor で、つまりは単一リクエスト用のもの。
peer を使ってリクエスト元 IP と Metadata に入っている UserAgent を取得し、それを ctxzap を使って追加フィールドとして設定しておくもの。

なので go-grpc-middleware の grpc_zap.UnaryServerInterceptor が設定されていないと使えない。(そこで ctxzap が設定されるので)
go-grpc-middleware/logging/zap at master · grpc-ecosystem/go-grpc-middleware · GitHub

peer はここ。リクエスト元についての情報が簡単に入っている。
grpc-go/peer at master · grpc/grpc-go · GitHub

Metadata についてはここ。ざっくりとは HTTP Header みたいなやつ、と理解したけど大体あってそう。
grpc-go/grpc-metadata.md at master · grpc/grpc-go · GitHub

ctxzap は先に出した go-grpc-middleware で定義されるもの。
go-grpc-middleware/logging/zap/ctxzap at master · grpc-ecosystem/go-grpc-middleware · GitHub

gRPC リクエストの context に zap.Logger を出し入れできるもので、リクエストごとに Logger が設定されているような状態にできる。
ので、例えばリクエストの認証情報を詰めたりとかできて便利ちゃん。

実際に組み込もうとするとこんな感じ。

func NewGRPCServer() *grpc.Server {
	return grpc.NewServer(
		grpc_middleware.WithUnaryServerChain(
			grpc_zap.UnaryServerInterceptor(zap.L()),
			interceptor.AccessLogUnaryServerInterceptor(),
		),
	)
}

で、この interceptor を使った gRPC サーバに対してリクエストを送ってみると、こんな感じにログがでる。よさそう

{
  "level": "info",
  "ts": 1562940752.853247,
  "caller": "zap/server_interceptors.go:40",
  "msg": "finished unary call with code OK",
  "grpc.start_time": "2019-07-12T23:12:32+09:00",
  "system": "grpc",
  "span.kind": "server",
  "grpc.service": "helloworld.Greeter",
  "grpc.method": "SayHello",
  "access.clientip": "127.0.0.1:65516",
  "access.useragent": "my-user-agent/1.0.0 grpc-go/1.19.1",
  "grpc.code": "OK",
  "grpc.time_ms": 0.3019999861717224
}

ちなみにクライアントから UserAgent をどうやって設定するかはこうする。

client, err := grpc.DialContext(ctx, address, grpc.WithUserAgent("my-user-agent/1.0.0"), ...)

後ろに grpc-go/1.19.1 的な文言がついてしまうのはそうなんだけど、まあ、いいんじゃないかな。


あと余談なのだけど grpc-go と go-grpc-middleware とで grpc と go が入れ替わってていつもどっちだ!?!?となる


ワンソースでドッと試したのはこんなかんじ。
example にある helloworld な proto をそのまま利用するとお手軽簡単に gRPC 周辺の様子みれて便利。

package main

import (
	"context"
	"net"
	"strings"
	"time"

	grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
	grpc_zap "github.com/grpc-ecosystem/go-grpc-middleware/logging/zap"
	"github.com/grpc-ecosystem/go-grpc-middleware/logging/zap/ctxzap"
	"go.uber.org/zap"
	"google.golang.org/grpc"
	pb "google.golang.org/grpc/examples/helloworld/helloworld"
	"google.golang.org/grpc/metadata"
	"google.golang.org/grpc/peer"
)

// server is used to implement helloworld.GreeterServer.
type server struct{}

// SayHello implements helloworld.GreeterServer
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
	ctxzap.Extract(ctx).Info("Received", zap.Any("in.name", in.Name))
	return &pb.HelloReply{Message: "Hello " + in.Name}, nil
}

func main() {
	port := "127.0.0.1:13000"

	l, _ := zap.NewDevelopment()
	zap.ReplaceGlobals(l)

	// server
	go func() {
		logger := zap.L().Named("Server")

		listener, err := net.Listen("tcp", port)
		if err != nil {
			logger.Error("failed to listen: %v", zap.Error(err))
		}
		defer listener.Close()

		s := grpc.NewServer(
			grpc_middleware.WithUnaryServerChain(
				grpc_zap.UnaryServerInterceptor(logger),
				accessLogUnaryServerInterceptor(),
			),
		)
		defer s.Stop()

		pb.RegisterGreeterServer(s, &server{})

		if err := s.Serve(listener); err != nil {
			logger.Error("failed to serve: %v", zap.Error(err))
		}
	}()

	// client
	go func() {
		time.Sleep(2 * time.Second)
		ctx := context.Background()
		logger := zap.L().Named("Client")

		client, err := grpc.DialContext(
			ctx,
			port,
			grpc.WithUserAgent("my-user-agent/1.0.0"),
			grpc.WithInsecure(),
		)
		defer client.Close()

		if err != nil {
			logger.Error("failed create client", zap.Error(err))
		}

		greeterClient := pb.NewGreeterClient(client)

		logger.Info("Do request")

		response, err := greeterClient.SayHello(ctx, &pb.HelloRequest{Name: "sters"})

		logger.Info("", zap.Any("response.Message", response.Message), zap.Error(err))
	}()

	time.Sleep(5 * time.Second)
	zap.L().Info("Shutdown")
}

func accessLogUnaryServerInterceptor() grpc.UnaryServerInterceptor {
	return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
		clientIP := "unknown"
		if p, ok := peer.FromContext(ctx); ok {
			clientIP = p.Addr.String()
		}

		useragent := ""
		if md, ok := metadata.FromIncomingContext(ctx); ok {
			if ua, ok := md["user-agent"]; ok {
				useragent = strings.Join(ua, ",")
			}
		}

		ctxzap.AddFields(
			ctx,
			zap.String("access.clientip", clientIP),
			zap.String("access.useragent", useragent),
		)

		return handler(ctx, req)
	}
}

最近パッケージ読み込み出来るようになったって聞いたので The Go Playground で実行できる。
ココ にある

Confuluence を Markdown で書く

Confuluence の世界に Markdown で書いてたものを持っていくとか Markdown でサクッと書きたいなあ、などというのが微妙に不便しているので、誰かベストソリューションを教えてほしい。

Markdown for Confluence | Atlassian Marketplace

これは Confuluence のページに Markdown なコンテンツを埋め込める、というもの。
Markdown で Confuluence のページを書けるといえばそう。けど、これだと Markdown の世界と Confuluence の世界がつながらないので、ちょっと微妙…、かも?

Confluence Wiki マークアップ - アトラシアン製品ドキュメント

マークアップ機能はでっかい Markdown や、やや処理にクセがあるっぽく、うまく出来ないときの修正の仕方がいまいちわからない。
ただ Confuluence Wiki 記法はうまく処理されるっぽい。
というわけで Markdown から Confuluence Wiki 記法にするそれっぽいのを書いてみて、手元だけはすこし楽できるようにしてみた。

GitHub - sters/md2cw: Markdown syntax to Confluence Wiki syntax

converter を見てもらうとわかるけれど Markdowm のパーサーやレンダラーとして動いてくれる blackfriday というのがある。

GitHub - russross/blackfriday: Blackfriday: a Markdown processor for Go

ぼくはそれぞれの要素が Confuluence でいうどれにあたるかを書いたくらいしかしていないくらいに、世の中は便利。


git(というよりは svn が近いか)のそれみたいに、プル、プッシュ、マージが出来るようなクライアントとかあってもいいかもなーとか思った。
そうなるともはや Confuluence である必要はそんなにないかもしれないけれども。。


それはそうとして、このリポジトリ、go getでうまくいれられないくない?(日本語崩壊)
go modulesとgo getのお気持ちがちょっとまだわからない…

grpc のリクエストをガシガシ送るツールghz

gRPC なエンドポイントのパフォーマンスをみたり、負荷試験をしたいなあという話をしたら ghz というツールがあるよと教えてもらったので、これを使ってみる。

ghz · Simple gRPC benchmarking and load testing tool

公式にはリリースページからインストールしてくれやと書かれている。
が go get でとれるのでこれでいいのでは… latest を使うことになるのでリリースされてるものと微妙に差異があるかも。

go get github.com/bojand/ghz/cmd/ghz/

ab ライクに使えるので、ヘルプを見たらまあ問題なく使えるはず。
Usage · ghz

Kubernetes 上で動くアプリケーションに対して実施するとこんな感じ。
ポートフォワードしてから、

kubectl port-forward svc/foo 5000

※Service に向けてポートフォワードしてますが kubectl コマンド実行時に宛先 Pod を解決してしまうので、ロードバランスしないです。

別ターミナルで、こういう具合 proto の指定がないときは Server Reflection して勝手にやってくれる、超便利。
そっちの話題はここにある。 grpc/server-reflection.md at master · grpc/grpc · GitHub

$ ghz \
    --call foo.bar.Service.RPC \
    -d "$(jo foo='bar')" \
    -m "{\"Authorization\":\"BEARER $TOKEN\"}" \
    -q 4 \
    -c 2 \
    -n 10 \
    --insecure \
    localhost:5000

Summary:
  Count:    10
  Total:    1.37 s
  Slowest:    243.03 ms
  Fastest:    111.49 ms
  Average:    149.98 ms
  Requests/sec:    7.29

Response time histogram:
  111.491 [1]    |∎∎∎∎∎∎∎∎
  124.645 [5]    |∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎
  137.799 [0]    |
  150.953 [2]    |∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎
  164.107 [0]    |
  177.261 [0]    |
  190.415 [0]    |
  203.569 [0]    |
  216.723 [0]    |
  229.877 [0]    |
  243.031 [2]    |∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎

Latency distribution:
  10% in 118.75 ms
  25% in 119.66 ms
  50% in 123.21 ms
  75% in 243.01 ms
  90% in 243.03 ms
  0% in 0 ns
  0% in 0 ns

Status code distribution:
  [OK]   10 responses

リクエストパラメータつくるのに jo を使っている。
GitHub - jpmens/jo: JSON output from a shell

上記の例だと 最大 4 req/sec になるように、 2 並列でリクエストし、 10 リクエスト送ったら終わる、というオプションになっている。
なので、レスポンスタイム次第ではあるが、サクッとレスポンスがあれば 1 秒ちょっとで完了する。

レスポンスタイムの分布であったり、パーセンタイルもでたり、サクッと負荷かけて様子を見るのに便利そう。

もちろん、オプションの指定が必要だけど 1 リクエストだけ送ることもできるので、普段使いにもいいんじゃなかろうか。

また Go のパッケージとしても使えるっぽい。
Package · ghz
ちょっと用途が思い浮かんでないけど、例えば、複数の RPC があって、順番にテストしたいんだよね〜〜〜みたいなときとか CI 的に負荷をかけたいんだよね〜〜とか、(シェルスクリプトでさくっと ghz ラッパーでもいいのでは感)

前後の記事

Next:
Prev:

Go で 定期的に Ping する

疎通確認であったり、コネクションのkeepalive的な用途として、定期的に "ping" をしたいんだよなあ、という話があり
ぐるぐる回しっぱなしでよきに使えそうな感じのコードを書いてみた。

GitHub - sters/pinger: Ping somethings for use warmup and standby or others.

いまは土台と、HTTPしかないけど、MySQLとかなんかまあ他にもできそう

使い方はここを見てもらうのがわかりやすいかも
pinger/http_test.go at master · sters/pinger · GitHub

実践的にはこういう感じになるのかな。

ctx := context.Background()

fooPinger := pinger.NewWorker(...)
barPinger := pinger.NewWorker(...)

eg, ctx := errgroup.WithContext(ctx)
eg.Go(func() error { return fooPinger.Run(ctx) })
eg.Go(func() error { return barPinger.Run(ctx) })

if err := eg.Wait(); err != nil {
    ...
}

すくなくとも、いま主に触っているソレだとこういう感じで書くことになると思う。

余談だけど...

  • type Hoge interface{}を書いて使ったりしていると、これがダックタイピングかとどっしり理解したので、やっばりやってみないとわからんなという気持ち
  • sync パッケージ、使うものの並列にアクセスされる可能性あるやろなあ、ロックするか、くらいの感覚しかまだわかってないので要修行
  • goroutine はちょっとわかってきた感じがある。おしごとでもぼちぼち書いたりしてる
  • これまで簡単なツールをサクッとつくるの Node.js か PHP 使ってたことが多いけどもう Go 言語でよいのでは、と思えるくらいには書けるようになってきた感じ。
  • よく使うようなライブラリとかツール群はまだイマイチなので都度 :innocent:

前後の記事

Next:
Prev:

Withなんちゃらなオプション( Functional Option Pattern )

Go 文化なのかわからないけど New とか構造体作れる関数や初期化する関数へのオプションを設定するためのコールバック関数を設定するキメ文句をよく見るような気がする。

func WithInsecure() func(*Config) {
    return func(cfg *Config) {
        cfg.insecure = true
    }
}

func WithUserAgent(ua String) func(*Config) {
    return func(cfg *Config) {
        cfg.userAgent = ua
    }
}

func main() {
    req := NewRequest(
        WithInsecure(),
        WithUserAgent(),
    )
    req.Send()
}

こういう感じのやつ。

オプションを外のパッケージに対して隠せるっていうメリットがあるのかな。
流れに乗っておくと、雰囲気で利用できて便利そうだなーって思った。


追記:2019年3月4日
これは Functional Option Pattern だ、という話をまったく違う流れで社の Slack で知った。

Functional Option Pattern

Config 構造体でがんばって持つのもいいけど、文量増えてきたり、外に公開するような API であった場合にちょっと不便かなあと思っていたので、このデザインはいいなと思った。

envconfig の

kelseyhightower/envconfig: Golang library for managing configuration data from environment variables

ざっくり説明

環境変数を読み込んで構造体におとせるやつ。
構造体のタグに環境変数名をいれると、任意の環境変数をパースしたときに展開してくれる。
書かなくてもよしなに動く(すごい)

構造体は入れ子にできる

タグに必須有無やデフォルト値をかける。

使い方

リードミーに書いてあるので読むべし。
envconfig/README.md at master · kelseyhightower/envconfig

よしなにしてくれるが、毎回タグを書くようにしたほうが事故防止や分かりやすさからもいいと思う。

type (
    // Config of the app.
    Config struct {
        // Service config
        Environment string `envconfig:"ENVIRONMENT" required:"true"`
        LogLevel    string `envconfig:"LOG_LEVEL" default:"ERROR"`
        Version     string `envconfig:"VERSION"`
    }
)

なぜ使うか

ツール類なら flag パッケージで十分だと思う。
アプリケーションとしてだと環境変数に値をいれておいて設定を変えたい場合があるのでは。
DB の接続先とか。

Laravel のときにお世話になってた dotenv もそういう感じよね。
Configuration - Laravel - The PHP Framework For Web Artisans

Go でも dotenv が出来るやつある。
joho/godotenv: A Go port of Ruby's dotenv library (Loads environment variables from .env.)

これは構造体にシュッとならないので map 経由してよしなにやる必要ありそう。
envconfig は構造体にシュッとなって便利そうではある。

どうやって実装されているのか

これは完全に興味で羽を伸ばしてみる。

このソースだけで完結できる。意外と短い?
envconfig/envconfig.go at master · kelseyhightower/envconfig

Process がエントリポイントで、すぐに gatherInfo を呼ぶ。
この中では、引数のバリデーションと、渡された構造体を reflect パッケージを使って、タグや形、キーたる名前をパースしてる。要は構造体の情報から必要なものだけ集約したもの。それが varInfo のスライスになる。
このとき、構造体のタグに split_words がついていると、ベストエフォートでキャメルケースを、スネークケースに変換してキーを設定する。

構造体を持っている場合、それがデコード出来ない形式なら再帰的にパースを試みる。
がてことは割と雑多に構造体を放り込んでもよしなに動いてくれそうではある。

このようにして、読み込んだデータを格納する構造体についての分析が終わったら、いよいよ環境変数から値を読み込む。

すでに確認されているキー名で lookupEnv(os.getenv にあたる)をして、環境変数から読み込み、なければデフォルト値を試みて required の判定をする。
読み込んだ値は文字列でしかないので、構造体の形情報に合わせて変換していく。それを processField でやっている。

このとき、対象が Decoder か Setter を実装していると、それを使って構造体のフィールドを戻そうとする。あるいは TextUnmarshar,BinaryUnmarshaler でも。

サンプルだと IP アドレスな文字列から net.Ip 構造体に落としている。
たとえばだけど暗号化した値を渡して復号化することもできし json を入れて構造体に戻すこともできるし DB 接続の情報をいれて sql.DB のクライアントを作ったり API の接続情報から http.Client を作ることもできる。
もはや設定値から作るようなものなら何でも作れる。
やりすぎるとコード読む側が苦になりそうだけど…。

話をコードに戻すと、あとはエラーの場合がある程度で、以上。

Process がエントリポイントと最初に書いたけど MustProcess というのもある。
こっちはエラーになったら panic する。

regexp.MustCompile なんかと同じ。

おわり。

dd-trace-go を使って Datadog の Span をモリモリ作る

import "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer"

func hoge(ctx context.Context) (err error) {
    span, ctx := tracer.StartSpanFromContext(
        ctx,
        "do hoge",
    )
    defer func() { span.Finish(tracer.WithError(err)) }()

    err = somethings1(ctx)
    if err != nil {
        return
    }

    err = somethings2(ctx)
    if err != nil {
        return
    }

    return
}

こういう具合に tracer.StartSpanFromContext してあげるとよい。
StartSpanFormContext を使うと context に実行時の span を保持してくれて、親子関係を構築してくれる。

で、これをサードパーティなパッケージが絡んだところや、その他いろいろにモリモリ適用しようとすると大変そうだなあと思ったので、クロージャでシュッと StartSpanFormContext を書いてみた。

sters/wrapspan: for https://github.com/DataDog/dd-trace-go/

別に何も難しいことはやってない。


dd-trace-go に contrib パッケージがあるように、対象のソレをラップした構造体なり関数なりを作るほうが、そういうパッケージとして切り出せるしオプションの勝手もよいだろうしコードの見通しもよくなりそうだ、と、この記事を書いているときに思った。

Readme に書いた例だとこういう感じ。

func Xsomething1(ctx context.Context) (err error) {
    span, ctx := tracer.StartSpanFromContext(
        ctx,
        "span-1",
    )
    defer func() { span.Finish(tracer.WithError(err)) }()

    return X.something1(ctx)
}

func Xsomething2(ctx context.Context) (err error) {
    span, ctx := tracer.StartSpanFromContext(
        ctx,
        "span-2",
    )
    defer func() { span.Finish(tracer.WithError(err)) }()

    return X.something2(ctx)
}

func Xsomethings(ctx) (err error) {
    span, ctx := tracer.StartSpanFromContext(
        ctx,
        "span-somethings",
    )
    defer func() { span.Finish(tracer.WithError(err)) }()

    err = X.something1(ctx)
    if err != nil {
        return
    }

    err = X.something2(ctx)
    if err != nil {
        return
    }
}

よさそう。

Windows で dep ensure したときに File name too long が出るのを解決した

どうやら Windows はパスの文字数が 260 文字という制限があるらしい。

が、それをレジストリで設定変更できる。ここを 1 にしたらよいとのこと。

\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\FileSystem

LongPathsEnabled = 1

Pro やら Enterprise やらだとグループポリシーの設定が必要そう。

Local Computer Policy > Computer Configuration > Administrative Templates > System > Filesystem

Enable Win32 Long Paths

see: Long File Names?


そしてさらに git の設定が必要そう。

git config --system core.longpaths true

see: Sorce Tree で"Filename too long"のエラーが出た時の対処 - Qiita

1 2