Chapter 11

ent に zerolog を組み込むよ

Spiegel
Spiegel
2022.04.03に更新

自動生成したコードを眺めてみると

ent/client.go
// NewClient creates a new client configured with the given options.
func NewClient(opts ...Option) *Client {
    cfg := config{log: log.Println, hooks: &hooks{}}
    cfg.options(opts...)
    client := &Client{config: cfg}
    client.init()
    return client
}

とか書いてある。「なんじゃこらー」って思わなかった? 思ったよね。更に config 構造体を定義しているところを見ると

ent/config.go
// Config is the configuration for the client and its builder.
type config struct {
    // driver used for executing database requests.
    driver dialect.Driver
    // debug enable a debug logging.
    debug bool
    // log used for logging on debug mode.
    log func(...interface{})
    // hooks to execute on mutations.
    hooks *hooks
}

とあって,さらに

ent/config.go
// Options applies the options on the config object.
func (c *config) options(opts ...Option) {
    for _, opt := range opts {
        opt(c)
    }
    if c.debug {
        c.driver = dialect.Debug(c.driver, c.log)
    }
}

// Debug enables debug logging on the ent.Driver.
func Debug() Option {
    return func(c *config) {
        c.debug = true
    }
}

// Log sets the logging function for debug mode.
func Log(fn func(...interface{})) Option {
    return func(c *config) {
        c.log = fn
    }
}

と書かれている。ふむむー。つまり func(...interface{}) 関数型にマッチした関数であれば自前で logger を組み込めるわけだ。これなら zerolog もなんとか組み込み可能だよね。しかし,凄い雑な構造だな。まっ楽でいいけど(笑)

以上を踏まえて ent 用の PostgreSQL サービス接続関数を書いてしまおう。こんな感じでどうだろうか。

dbconn/entclient.go
package dbconn

import (
    "fmt"
    "sample/ent"
    "sample/env"

    "entgo.io/ent/dialect"
    "entgo.io/ent/dialect/sql"
    "github.com/rs/zerolog"
    "github.com/goark/errs"
)

type EntContext struct {
    Client *ent.Client
    Logger *zerolog.Logger
}

func NewEnt() (*EntContext, error) {
    pgxCtx, err := NewPgx()
    if err != nil {
        return nil, errs.Wrap(err)
    }
    entCtx := &EntContext{
        Logger: pgxCtx.GetLogger(),
    }
    entCtx.Client = ent.NewClient(
        ent.Driver(
            sql.OpenDB(dialect.Postgres, pgxCtx.GetDb()),
        ),
        ent.Log(func(v ...interface{}) {
            entCtx.Logger.Debug().Msg(fmt.Sprint(v...))
        }),
    )
    if env.LogLevel() >= env.LevelDebug {
        entCtx.Client = entCtx.Client.Debug()
    }
    return entCtx, nil
}

func (entCtx *EntContext) GetClient() *ent.Client {
    if entCtx == nil {
        return nil
    }
    return entCtx.Client
}

func (entCtx *EntContext) GetLogger() *zerolog.Logger {
    if entCtx == nil {
        lggr := zerolog.Nop()
        return &lggr
    }
    return entCtx.Logger
}

func (entCtx *EntContext) Close() error {
    if client := entCtx.GetClient(); client != nil {
        return errs.Wrap(client.Close())
    }
    return nil
}

これを使って

sample1.go
//go:build run
// +build run

package main

import (
    "context"
    "fmt"
    "os"
    "sample/dbconn"

    "github.com/goark/errs"
    "github.com/goark/gocli/exitcode"
)

func Run() exitcode.ExitCode {
    // get ent context
    entCtx, err := dbconn.NewEnt()
    if err != nil {
        fmt.Fprintln(os.Stderr, err)
        return exitcode.Abnormal
    }
    defer entCtx.Close()

    // query
    if _, err := entCtx.GetClient().User.Get(context.TODO(), 1); err != nil {
        entCtx.GetLogger().Error().Interface("error", errs.Wrap(err)).Send()
        return exitcode.Abnormal
    }

    return exitcode.Normal
}

func main() {
    Run().Exit()
}

と書いて実行すれば

$ go run sample/sample1.go 
0:00AM DBG driver.Query: query=SELECT DISTINCT "users"."id", "users"."username", "users"."created_at", "users"."updated_at" FROM "users" WHERE "users"."id" = $1 LIMIT 2 args=[1]
0:00AM INF Dialing PostgreSQL server host=hostname module=pgx
0:00AM ERR Query args=[1] err="ERROR: relation \"users\" does not exist (SQLSTATE 42P01)" module=pgx pid=28859 sql="SELECT DISTINCT \"users\".\"id\", \"users\".\"username\", \"users\".\"created_at\", \"users\".\"updated_at\" FROM \"users\" WHERE \"users\".\"id\" = $1 LIMIT 2"
0:00AM ERR  error={"Context":{"function":"main.Run"},"Err":{"Msg":"ERROR: relation \"users\" does not exist (SQLSTATE 42P01)","Type":"*pgconn.PgError"},"Type":"*errs.Error"}
0:00AM INF closed connection module=pgx pid=28859

と表示された(データベースは ent の調査用に初期化したのでエラーになる)。うんうん。上から下まで統一した logger だと気持ちがいいねぇ。