Neovim + DAP + Go

TL;DR DAP(Debug Adapter Protocol)の力を借りれば、Neovim上でも視覚的にGoのデバッグが出来ました。 ここでは下記については話しません。 DAPの詳細 packer.nvimでのインストール方法 vscode-goのdebugAdapter.jsの使用方法 Neovimでデバッグするモチベーション v0.2.2から色々変わったNeovimを久々に触ってみようかと思いinit.luaへ以降して遊んでいました。 起動が早いだけでなく、LSP系の動作もサクサク動いてここ2ヶ月程快適に過ごしています。 ただ、VSCodeでのデバッグ機能がとても便利なため、デバッガ使用時のみこの快適な環境を離れていました。 どうにかNeovim上で完結させられないか…。 DAP DAPの説明はリンク先や他HPをご参照ください。 イメージを持ってもらうためLSPを引き合いに出すと、LSPがテキスト編集時に定義や変更内容や補完候補等を返すのに対して、DAPはデバッガとやりとりしてデバッグ情報を返す位のイメージを持ってもらえれば大丈夫だと思います。 Neovim + DAP + Go 使うプラグインは下記になります。 mfussenegger/nvim-dap : NeovimのDAPクライアント。VSCodeのlaunch.jsonも使用可 rcarriga/nvim-dap-ui : nvim-dapを視覚的に操作しやすいUI leoluz/nvim-dap-go : DAPのGo用設定(delve dap起動) nvim-treesitter/nvim-treesitter nvim-dap-goでテストをデバッグする際、近くのテストを見つけるのに必要 packer.nvimの設定 VSCodeのlaunch.jsonを使用しない場合、require('dap.ext.vscode').load_launchjs()は不要です。 launch.jsonを読み込めた場合、デバッグ方法の選択時にlaunch.jsonの内容が追加表示されて選択可能になります。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 -- dap plugin installation with packer.nvim ... -- dap use { "rcarriga/nvim-dap-ui", requires = { "mfussenegger/nvim-dap", "leoluz/nvim-dap-go", "nvim-treesitter/nvim-treesitter" }, config = function () vim.fn.sign_define('DapBreakpoint', {text='⛔', texthl='', linehl='', numhl=''}) vim.fn.sign_define('DapStopped', {text='👉', texthl='', linehl='', numhl=''}) require('dapui').setup() require('dap-go').setup() require('dap.ext.vscode').load_launchjs() end } ... GoでDAPを起動する際の設定 Goの場合、vscode-goのdebugAdapter.jsを使用することも出来ます。ここでは、delveを待ち構えさせます。 ...

2021年11月24日 · 3 分

gin + gorm v2でのセッションやトランザクション

TL;DR ginでの各リクエストを1セッションで管理しつつ、トランザクションも管理したい。 ginのmiddlewareで、gin.Contextにgorm.WithContextを用いてGormのセッションを持たせる。 ginの各HandlerFuncの中では、gin.Context内のGormセッションからDBへアクセスする。 モチベーション Gormはv2になり色々と変わった。 Gorm 2.0 Release Note 色々な記事が書かれているがTransactoinに関してあまり書かれていない。 下記疑問を払拭して正しい使い方を自分なりに確かめたかったので試した。 なんとなく動くがこれで正しいのか? ドキュメントを読んでいてSkipDefaultTransaction:trueだと30%もパフォーマンスが上がるというがその設定はtrueにしていいいのか? っていうか、DefaultTransactionって何?ロールバックの単位とかどうやって指定するの? 確認&検証 検証時に作成したソースはgithub.com/lunarxlark/gorm2-tx Context Gorm 2.0 #Context GormはContextサポートを提供し、それを使いたかったらWithContext使ってくれ。 また、Sessionには単セッションモードと継続セッションモードがある。 普通は、継続セッションモードを使って、複数オペレーションをまとめるよ。 …GormのSessionってどんなことできるの? Session Gorm 2.0 #Session Gormは`Session`メソッドをを通して、新しいセッションを提供するよ。 新しいセッションを作る場合、設定がたくさんあるよ。DryRunやLoggerとかね。 …新しいセッションを一々作るのは望んでない。 Contextサポートしてくれるってことなのでgin.Contextへ埋め込む時にセッションを作成してそれを使い回したい。 gorm.WithContextのreturnはdb.Sessionとなっている(下記はgormのソースから抜粋)ので、gin.Contextへ埋め込むだけで新たにセッションを作る必要はない。 1 2 3 4 // WithContext change current instance db's context to ctx func (db *DB) WithContext(ctx context.Context) *DB { return db.Session(&Session{Context: ctx}) } ここまでで下記みたいな感じになる。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 func main() { //... r := gin.New() r.Use(DBSession()) //... } func DBSession() gin.HandlerFunc { return func(c *gin.Context) { c.Set("DB", infra.RDB.WithContext(c)) c.Next() } } var RDB *gorm.DB var dsnWriter = "host=writer user=postgres port=5432 dbname=testDB password=pass sslmode=disable" var dsnReader = "host=reader user=postgres port=5432 dbname=testDB password=pass sslmode=disable" func DbOpen() error { var err error RDB, err = gorm.Open( postgres.New(postgres.Config{ DSN: dsnWriter, }), ) if err != nil { return err } RDB.Use(dbresolver.Register(dbresolver.Config{ Sources: []gorm.Dialector{postgres.Open(dsnWriter)}, Replicas: []gorm.Dialector{postgres.Open(dsnReader)}, })) return nil } Transaction Gorm 2.0 #Transaction ...

2021年9月12日 · 2 分

Go Code Review Comments (2021年時点)

原典 Go Code Review Comments 動機 新しいチームメンバーに、Go の文化を知ってもらう一環として。 (GitHub や Twitter で指摘してもらえると直します。) 以下、訳。 Go Code Review Comments 略語で一つの詳細な説明まで辿り着けるように、このページでは Go のコードをレビューしている間に作られた多くの意見を集めました。 このページはよくある間違えのリストであって、包括的なスタイルガイドではありません。 Effective Goを補足として参照するとよいです。 小さな変更であっても、このページを編集する際は[変更内容を議論してください](https://github.com/golang/go/wiki/CodeReviewComments)。 多くの人が意見を持っていて、このページは編集戦争をする場ではありません。 Gofmt Comment Sentences Contexts Copying Crypto Rand Declaring Empty Slices Doc Comments Don’t Panic Error Strings Examples Goroutine Lifetimes Handle Errors Imports Import Blank Import Dot In-Band Errors Indent Error Flow Initialisms Interfaces Line Length Mixed Caps Named Result Parameters Naked Returns Package Comments Package Names Pass Values Receiver Names Receiver Type Synchronous Functions Useful Test Failures Variable Names Gofmt

2021年8月23日 · 1 分

2021年版 vim + goplsの設定

goplsが出てから, vimでも定義ジャンプやシンボル検索、ドキュメント参照等が行えるようになった。 たまにVSCodeを触りvimでの作業を改善できないか考える中で、自身の設定が古いことに気付いた。また、ググってもなかなか出てこなかったのでメモとして記述する。 cf. GitHub dotfiles いきなりだが、vimrcとvim-lsp-settings/settings.jsonを抜粋して貼り付ける。 以前、GoではLspCodeAction, LspCodeLens等をサポートしていなかったが、今では使えるようになっている。 キーマップに設定している関数は全てGoで使用出来る。 ただし、カーソルがどこにいても実行出来るわけではないので注意が必要。 LspCodeActionはカーソルの位置によって実行内容が変わるのでそこも注意。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 ... Plug 'prabirshrestha/vim-lsp' Plug 'mattn/vim-lsp-settings' Plug 'prabirshrestha/asyncomplete-lsp.vim' Plug 'mattn/vim-gomod' ... " ------------------------------------------------------------------------------ " vim-lsp " ------------------------------------------------------------------------------ function! s:on_lsp_buffer_enabled() abort setlocal omnifunc=lsp#complete setlocal signcolumn=yes if exists('+tagfunc') | setlocal tagfunc=lsp#tagfunc | endif nmap <buffer> <leader>ac <plug>(lsp-code-action) nmap <buffer> <leader>cl <plug>(lsp-code-lens) nmap <buffer> <leader>df <plug>(lsp-definition) nmap <buffer> <leader>dd <plug>(lsp-document-diagnostics) nmap <buffer> <leader>im <plug>(lsp-implementation) nmap <buffer> <leader>pdf <plug>(lsp-peek-definition) nmap <buffer> <leader>sm <plug>(lsp-document-symbol-search) nmap <buffer> <leader>Sm <plug>(lsp-workspace-symbol-search) nmap <buffer> <leader>rf <plug>(lsp-references) nmap <buffer> <leader>td <plug>(lsp-type-definition) nmap <buffer> <leader>rn <plug>(lsp-rename) nmap <buffer> <leader>en <plug>(lsp-next-error) nmap <buffer> <leader>ep <plug>(lsp-previous-error) nmap <buffer> <leader>ho <plug>(lsp-hover) let g:lsp_format_sync_timeout = 500 autocmd! BufWritePre *.rs,*.go call execute('LspDocumentFormatSync') " refer to doc to add more commands endfunction augroup lsp_install au! " call s:on_lsp_buffer_enabled only for languages that has the server registered. autocmd User lsp_buffer_enabled call s:on_lsp_buffer_enabled() augroup END 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 // vim-lsp-settings/settings.json { "go": { "gopls": { "initialization_options": { "analyses" : {"fillstruct":true}, "staticcheck": true, "directoryFilters": [ "-debug" ], "completeUnimported": true, "usePlaceholders": true, "matcher": "fuzzy", "codelenses": { "gc_details": false, "generate": true, "test": true, "tidy": true, "vendor": false }, "hoverKind": "SynopsisDocumentation" } } } } vim + goplsで新しく設定したこと LspCodeAction vim-lspのhelpにあるように、LspCodeActionが実行出来る行では A> が表示される。 ...

2021年6月16日 · 2 分

load test with dynamic payload by tsenart/vegeta

負荷テストツールvegetaのコマンドでの使い方ぐはググるとたくさん出てくるが、ライブラリとして仕様している記事が少なく手こずったのでメモ代わりに記事にする。 やりたいこと リクエスト毎にRequestの内容を動的に変えて、負荷をかけたかった。 Headerに持つタイムスタンプがリクエスト時のタイムスタンプになるように。 userIdはuniqueになるように。(同じユーザだと重複リクエストを弾く処理があったため。) 具体的には下記のような感じでリクエストを送りたい。 1 2 3 4 5 {"header": {"timestamp":"1234"}, "body": {"userId": "lunar-1"}} {"header": {"timestamp":"1234"}, "body": {"userId": "lunar-2"}} {"header": {"timestamp":"1235"}, "body": {"userId": "lunar-3"}} ... {"header": {"timestamp":"1240"}, "body": {"userId": "lunar-n"}} vegeta.Targeterは func(*vegeta.Target) error である。 また、vegeta.Attackは、vegeta.Targeterをgoroutineで都度呼び出して、Targeter内からTargetに基づきリクエストしていることから、都度Targetが変わるようなTargeterを返す関数を作成し、それをAttack時に呼び出せば良さそう。 ってことが、 [QUESTION] Sending requests with dynamic body #330に書いてあって何度も行き来した結果やっと理解できた…。 上記を拝借して以下のようにすることで目的を達成できた。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 type yourRequestBody struct { UserID string } targeter := func(id uint64) vegeta.Targeter { return func(t *vegeta.Target) error { execTime := time.Now() requestTimestamp := execTime.Unix() req := &http.Request{ Method: "POST", URL: &url.URL{ Scheme: "http", Host: "localhost", Path: "api/v1/yourendpoint/", }, Header: map[string][]string{ "X-RequestTimestamp": []string{strconv.Itoa(int(requestTimestamp))}, }, } t.Header = req.Header t.Method = req.Method t.URL = req.URL.String() t.Body, _ = json.Marshal( &yourRequestBody{ UserID: fmt.Sprintf("lunar-%d-%d", requestTimestamp, atomic.AddUint64(&id, 1)), }, ) return nil } }(0) rate := vegeta.Rate{Freq: 10, Per: time.Second} duration := 10 * time.Second var metrics vegeta.Metrics attacker := vegeta.NewAttacker() for res := range attacker.Attack(targeter, rate, duration, "Big Bang!") { metrics.Add(res) } 結構、苦戦した…。

2021年3月11日 · 1 分

Custome Logger using LoggerWithConfig in Echo

WebFramework EchoのLoggerを弄っていて気付いた点があったのでメモ。 sample code 箇条書き responseログにHeader X-Any-Headerを出力したい場合、ログフォーマットに"any_header":${header:x-any-header}を指定すればいいよ responseログにQueryParamerter ?anyparam=xxxを出力したい場合、ログフォーマットに"any_query":${query:anyparam}を指定すればいいよ デフォルトで出力されるカラム’id’はHeader X-Request-Id を指定すると出力されるよ Skipperを設定することで、path毎にログ出力する/しない等を設定出来るよ 1, 2, 3はEchoのそういう機能だってことで特にありません。 4は結構便利だなと思いました。以下のサンプルコードでは、/healthcheckに来た場合とCI/CD等での自動テスト時にはresponseログを出力しないようにする設定例です。 sample 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 // Middleware e.Use(middleware.LoggerWithConfig(customLogger())) // Handler e.GET("/greet", greet) e.GET("/healthcheck", healthcheck) var ResponseLogForamt = `{` + `"time":"${time_custom}",` + `"id":"${id}",` + `"remote_ip":"${remote_ip}",` + `"host":"${host}",` + `"method":"${method}",` + `"uri":"${uri}",` + `"user_agent":"${user_agent}",` + `"status":${status},` + `"error":"${error}",` + `"latency":${latency},` + `"latency_human":"${latency_human}",` + `"bytes_in":${bytes_in},` + `"bytes_out":${bytes_out},` + `"forwarded-for":"${header:x-forwarded-for}",` + `"same-as-id":${header:X-Request-Id},` + `"query":${query:lang}` + `}` func customLogger() middleware.LoggerConfig { cl := middleware.DefaultLoggerConfig cl.Skipper = customeSkipper cl.Format = ResponseLogForamt cl.CustomTimeFormat = "2006/01/02 15:04:05.00000" return cl } func customeSkipper(c echo.Context) bool { if c.Path() == "/healthcheck" { return true } if os.Getenv("ENV") == "auto-test" { return true } else { return false } } func greet(c echo.Context) error { switch c.QueryParam("lang") { case "jp": return c.String(http.StatusOK, "こんにちは\n") case "en": return c.String(http.StatusOK, "Hello World\n") default: return c.String(http.StatusOK, "ウホホイ!!\n") } } func healthcheck(c echo.Context) error { return c.String(http.StatusOK, "I'm fine\n") } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 -- request $ curl \ -X GET \ -H "X-Forwarded-For: achoo" \ -H "X-Request-Id: requestid123" \ "http://localhost/greet?lang=en" -- response log { "time":"2020/11/10 00:17:09.28534", "id":"requestid123", "remote_ip":"achoo", "host":"localhost", "method":"GET", "uri":"/greet?lang=en", "user_agent":"curl/7.64.1", "status":200, "error":"", "latency":18699, "latency_human":"18.699µs", "bytes_in":0, "bytes_out":12, "forwarded-for":"achoo", "same-as-id":requestid123, "query":en }

2020年11月10日 · 2 分

自分用日本語訳 Go Module

(この記事は、執筆時点でのGo Modulesをもとにした私のメモです。Go Moduleの理解のために、元記事と記載の順番が入れ替わっている項目もあります。) ...

2020年5月14日 · 2 分

My Favorite Usage urfave/cli

参加しているプロジェクトでurfave/cliを使っている。使い方はExampleにあるのと同じ書き方で使っている。 大半のバッチ処理をコマンドとして記述しているので、Exampleの書き方だとだいぶ見辛くなってきた。縦長のコマンド定義とオプション説明で目的の処理を探すのにもページ送りを何度もする。なんとかしたい。 ...

2020年2月6日 · 1 分

Error 'undefined' when go run

x…motemen/ghqを写経している時、 go run main.go 出来ないことに気付いた。 下記が実行時のエラーになる。ちなみに、 go build は出来る。 1 2 3 4 ~/d/s/g/x/ghq >>> go run main.go # command-line-arguments ./main.go:38:17: undefined: commands ~/d/s/g/x/ghq >>> commands が見つからない?同じ階層の commands.go には下記記述がちゃんとあるのに、どうして見つからない…。 1 2 3 4 5 6 var commands = []*cli.Command{ commandGet, commandList, commandRoot, commandCreate, } 理由は、go runの引数に指定したファイル(+ importされるパッケージ)しか読み込まないから。 ...

2020年2月4日 · 1 分