Golang製軽量WebフレームワークechoのアクセスログをLTSV形式で出力する

# これはなに
Golangでお手軽にWebサーバを作成する場合、軽量Webフレームワークである [echo](https://echo.labstack.com/) を選択する方は多いと思います。

いざ可動させよう、といったときにLogフォーマットが揃っていると後々便利になりますし、
ある程度こなれた人であればそうそうにフォーマットを整えることでしょう。

本記事では、広く普及している[LTSV](http://ltsv.org/)フォーマットで出力するサンプルを紹介します。
（LTSVがなにでどう嬉しいかは説明しません）

# LTSVフォーマットでアクセスログを出力する
HelloWorldのサンプルでは、`e.Use(middleware.Logger())`でデフォルトのロガー使っています。
`middleware.LoggerWithConfig()`を使ってアクセスログをカスタマイズすることができます。

```golang
logger := middleware.LoggerWithConfig(middleware.LoggerConfig{
                Format: logFormat(),
                Output: os.Stdout,
        })
e.Use(logger)
```

Formatはパラメータが多く、長くなりがちなため別関数にしています。




ログフォーマットは

```golang
"keyname:${tag}\tkey2name:${tag2}\n"
```

という形のフォーマットを定義することでLTSV形式で出力できます。
（最後`\n`で改行を忘れないようにしましょう）

参照できるログのタグは[こちら](https://github.com/labstack/echo/blob/ba1891b3dc1fc5548330e580c51157ea5eb6604f/middleware/logger.go#L23)で確認します。

以下はアクセスログプロファイラ [alp](https://github.com/tkuchiki/alp) のパラメータを参考にしたLTSVフォーマットです。

```golang
func logFormat() string {
        // Refer to https://github.com/tkuchiki/alp
        var format string
        format += "time:${time_rfc3339}\t"
        format += "host:${remote_ip}\t"
        format += "forwardedfor:${header:x-forwarded-for}\t"
        format += "req:-\t"
        format += "status:${status}\t"
        format += "method:${method}\t"
        format += "uri:${uri}\t"
        format += "size:${bytes_out}\t"
        format += "referer:${referer}\t"
        format += "ua:${user_agent}\t"
        format += "reqtime_ns:${latency}\t"
        format += "cache:-\t"
        format += "runtime:-\t"
        format += "apptime:-\t"
        format += "vhost:${host}\t"
        format += "reqtime_human:${latency_human}\t"
        format += "x-request-id:${id}\t"
        format += "host:${host}\n"
        return format
}
```

nginxなどと違ってレスポンスヘッダに対応していなかったり、レスポンスタイムがナノ秒単位固定だったりと、alp互換を作ることは困難でしたがLTSVで出力するという目標は達成できました。

# スタートバナーを消す

デフォルトの設定では次のようにスタートバナーが表示されます。

```shell-session
$ go run main.go
   ____    __
  / __/___/ /  ___
 / _// __/ _ \/ _ \
/___/\__/_//_/\___/ v3.3.dev
High performance, minimalist Go web framework
https://echo.labstack.com
____________________________________O/_______
                                    O\
⇨ http server started on [::]:1323
```

最終的にLTSV以外のログは除外する必要が出てきそうなので消してしまいます。

[HideBannerおよびHidePort](https://github.com/labstack/echo/blob/ba1891b3dc1fc5548330e580c51157ea5eb6604f/echo.go#L80-L81)がそれに当たるオプションですのでechoインスタンスの作成時に無効にします。

```golang
// Echo instance
e := echo.New()
e.HideBanner = true
e.HidePort = true
```

# 実行例
```console
$ go run main.go
time:2018-11-01T22:53:59+09:00  host:::1        forwardedfor:   req:-   status:200      method:GET      uri:/   size:14 referer:        ua:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36  reqtime_ns:21333        cache:- runtime:-       apptime:-     vhost:localhost:1323    reqtime_human:21.333µs  x-request-id:   host:localhost:1323
```

# 作成したHelloWorldサンプル
```golang
package main

import (
        "net/http"
        "os"

        "github.com/labstack/echo"
        "github.com/labstack/echo/middleware"
)

func main() {
        // Echo instance
        e := echo.New()
        e.HideBanner = true
        e.HidePort = true

        // Middleware
        logger := middleware.LoggerWithConfig(middleware.LoggerConfig{
                Format: logFormat(),
                Output: os.Stdout,
        })
        e.Use(logger)
        e.Use(middleware.Recover())

        // Route => handler
        e.GET("/", func(c echo.Context) error {
                return c.String(http.StatusOK, "Hello, World!\n")
        })

        // Start server
        e.Logger.Fatal(e.Start(":1323"))
}

func logFormat() string {
        // Refer to https://github.com/tkuchiki/alp
        var format string
        format += "time:${time_rfc3339}\t"
        format += "host:${remote_ip}\t"
        format += "forwardedfor:${header:x-forwarded-for}\t"
        format += "req:-\t"
        format += "status:${status}\t"
        format += "method:${method}\t"
        format += "uri:${uri}\t"
        format += "size:${bytes_out}\t"
        format += "referer:${referer}\t"
        format += "ua:${user_agent}\t"
        format += "reqtime_ns:${latency}\t"
        format += "cache:-\t"
        format += "runtime:-\t"
        format += "apptime:-\t"
        format += "vhost:${host}\t"
        format += "reqtime_human:${latency_human}\t"
        format += "x-request-id:${id}\t"
        format += "host:${host}\n"

        return format
}
```