20210105のGoに関する記事は4件です。

2021年Go言語フレームワークスター数比較

Go言語のフレームワーク(マイクロもフルスタックも含む)についてGithubの星比較です。(2021/01/05集計)
2021年版が見つからなかったので。

image.png

ginが強いですねー


参考

Go言語Webフレームワークランキング
https://qiita.com/loftkun/items/1a9951d1864bebdc51e1

Golang フレームワーク比較
https://qiita.com/yumin/items/5de33b068ead564ebcbf

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Go言語インストール後の実行環境テスト

最終確認

インストール、Version確認後
VSCordでフォルダを作成し、期待する結果が出力されるまでテストする
Go言語の環境構築はこちら

実行環境テスト

# テスト用ディレクトリを作成
$ mkdir -p ~/go/src/test

# ディレクトリへ移動
$ cd ~/go/src/test

# テスト用のファイルを作成
$ touch test.go

# テスト用のファイルを編集
$ vi test.go
//test.go

package main

import "fmt"

func main() {
    fmt.Printf("test, go\n")
}
# 作成したディレクトリへ移動
$ cd ~/go/src/hello

# ビルドしてバイナリファイルを生成
$ go build

:wqで保存し、ビルドしてバイナリファイルを生成

実行する

$ ./test
test, go

※エラーが出てしまう場合

$ go env

Go言語関連の環境変数を確認し、PATHあたりを確認してみると良いのかな?

※出力結果を適応

テストで出力した内容を変更した場合は上書きが必要なので、再度ビルドする

$ go build

テストまでクリアしたので、これからGoの勉強を開始します!!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Golangで自作Logger中でzapライブラリを使う際、CallerにLoggerより上の階層を出す方法

この記事を参考に、zapというログライブラリを使ってみました。
その時にログ出力されるCallerの情報についてハマったことをメモとして残します。

2021/1/6追記
conf.Build(zap.AddCallerSkip(x)) のように指定することで変更できることを知りました。

おさらい

  • よく見かけるmainに記載するコーディング例で実行した場合を記載します。
  • 出力例からわかる通りmain/main.go:30 という内容が出力されます。
  • これはzapのログを出力したファイルのディレクトリ、ファイル、行が出力されており、トレースしやすい内容となっています。
package main

import (
    "go.uber.org/zap"
    "go.uber.org/zap/zapcore"
    "time"
)

func main() {
    conf := zap.Config{
        Level:            zap.NewAtomicLevelAt(zap.DebugLevel),
        Development:      true,
        Encoding:         "console",
        OutputPaths:      []string{"stdout"},
        ErrorOutputPaths: []string{"stdout"},
        EncoderConfig: zapcore.EncoderConfig{
            LevelKey:     "level",
            TimeKey:      "time",
            MessageKey:   "msg",
            CallerKey:    "caller",
            EncodeTime:   zapcore.ISO8601TimeEncoder,
            EncodeLevel:  zapcore.LowercaseLevelEncoder,
            EncodeCaller: zapcore.ShortCallerEncoder,
        },
    }
    l, err := conf.Build()
    if err != nil {
        panic(err)
    }
    l.Info("Zap Start.", zap.Time("time", time.Now()))
}

// Output:
// 2021-01-04T23:46:14.963+0900    info    main/main.go:30 Zap Start.      {"time": "2021-01-04T23:46:14.963+0900"}

// フォルダ構成はこんな感じ。
// main
// ├── go.mod
// ├── go.sum
// └── main.go

困ったこと

  • Webアプリで作るために、一度生成したzapインスタンスを使い回したいと思い、loggerパッケージを自作しました。
  • これで問題なし!と思いきや、logがlogger/logger.go:41になっちゃった。
  • 確かにzapのメソッドを呼び出しているのはlogger/logger.goだけど、それよりもひとつ上のCallerを知りたいのよ。
  • ソースを読む限り、zapのConfigではひとつ上のCallerを指定することはできないみたい。
  • runtimeパッケージで別で出力するしかないのか。。。

2021/1/6追記
conf.Build(zap.AddCallerSkip(x)) のように指定することで変更できることを知りました。

// フォルダ構成はこんな感じ(Goのスタンダードから外れるけど今回は説明のため簡略化)。
// main
// ├── go.mod
// ├── go.sum
// ├── main.go
// └── logger
//     └── logger.go
logger.go
package logger

import (
    "go.uber.org/zap"
    "go.uber.org/zap/zapcore"
)


var (
    l *zap.Logger
)

func SetUp() error {

    conf := zap.Config{
        Level:            zap.NewAtomicLevelAt(zap.DebugLevel),
        Development:      true,
        Encoding:         "console",
        OutputPaths:      []string{"stdout"},
        ErrorOutputPaths: []string{"stdout"},
        EncoderConfig: zapcore.EncoderConfig{
            LevelKey:     "level",
            TimeKey:      "time",
            MessageKey:   "msg",
            CallerKey:    "caller",
            EncodeTime:   zapcore.ISO8601TimeEncoder,
            EncodeLevel:  zapcore.LowercaseLevelEncoder,
            EncodeCaller: zapcore.ShortCallerEncoder,
        },
    }
    var err error
    l, err = conf.Build()
    if err != nil {
        return err
    }

    return nil
}

func Info(msg string, fields ...zap.Field){
    l.Info(msg,fields...)
}

main.go
package main

import (
    "go.uber.org/zap"
    "main/logger"
    "time"
)

func main() {
    err := logger.SetUp()
    if err != nil {
        panic(err)
    }
    logger.Info("Zap Start.", zap.Time("time", time.Now()))
}
// Output:
// 2021-01-05T00:12:44.683+0900    info    logger/logger.go:41     Zap Start.      {"time": "2021-01-05T00:12:44.683+0900"}
//                                          ↑ここをlogger/logger.goじゃなく、main/main.goにしたい。

解決策

  • ラップするのではなく、zapの関数をそのままloggerのPublicな変数にぶちこむ!
logger.go
package logger

import (
    "go.uber.org/zap"
    "go.uber.org/zap/zapcore"
)

var (
    l    *zap.Logger
    Info func(msg string, fields ...zap.Field) //ここが変更点1
)

func SetUp() error {

    conf := zap.Config{
        Level:            zap.NewAtomicLevelAt(zap.DebugLevel),
        Development:      true,
        Encoding:         "console",
        OutputPaths:      []string{"stdout"},
        ErrorOutputPaths: []string{"stdout"},
        EncoderConfig: zapcore.EncoderConfig{
            LevelKey:     "level",
            TimeKey:      "time",
            MessageKey:   "msg",
            CallerKey:    "caller",
            EncodeTime:   zapcore.ISO8601TimeEncoder,
            EncodeLevel:  zapcore.LowercaseLevelEncoder,
            EncodeCaller: zapcore.ShortCallerEncoder,
        },
    }
    var err error
    l, err = conf.Build()
    if err != nil {
        return err
    }

    Info = l.Info //ここが変更点2
    return nil
}

main.goはさっきと一緒なので省略。
無事、main/main.goが取れました。
↓出力結果

2021-01-05T00:19:47.157+0900    info    main/main.go:14 Zap Start.      {"time": "2021-01-05T00:19:47.157+0900"}

最終的なLoggerの形

以下の方針のもと、最終的に以下の形にしました。

  • 開発生産性を考慮し、基本的に zap.SugaredLogger でLoggingする方針にしました(zap.field書くのはめんどくさい)。
  • なんども出力して速度がどうしても気になる時にだけ、zap.Loggerを使う。
  • zap.Logger は以下の理由からInfoとWarnのみ。
    • エラー出力が出る時なんて速度より別問題。
    • Debugは本番で出力しない=速度気にしなくていい。
これで進めようと思うlogger.go
package logger

import (
    "go.uber.org/zap"
    "go.uber.org/zap/zapcore"
)

var (
    l *zap.Logger
    s *zap.SugaredLogger

    Debug     func(args ...interface{})
    Info      func(args ...interface{})
    Warn      func(args ...interface{})
    Error     func(args ...interface{})
    Debugf    func(tmpl string, args ...interface{})
    Infof     func(tmpl string, args ...interface{})
    Warnf     func(tmpl string, args ...interface{})
    Errorf    func(tmpl string, args ...interface{})
    InfoQuick func(msg string, fields ...zap.Field)
    WarnQuick func(msg string, fields ...zap.Field)
)

func SetUp() error {

    //TODO: 環境変数つかって設定できるようにする
    conf := zap.Config{
        Level:            zap.NewAtomicLevelAt(zap.DebugLevel),
        Development:      true,
        Encoding:         "console",
        OutputPaths:      []string{"stdout"},
        ErrorOutputPaths: []string{"stdout"},
        EncoderConfig: zapcore.EncoderConfig{
            LevelKey:     "level",
            TimeKey:      "time",
            MessageKey:   "msg",
            CallerKey:    "caller",
            EncodeTime:   zapcore.ISO8601TimeEncoder,
            EncodeLevel:  zapcore.LowercaseLevelEncoder,
            EncodeCaller: zapcore.ShortCallerEncoder,
        },
    }
    var err error
    l, err = conf.Build()
    if err != nil {
        return err
    }
    s = l.Sugar()

    //LogのCallerを正しく表示させるために、ファンクションをDIする形にする
    //例えば、別ファンクション func Debug(...){ s.Debug(...) } のように定義した場合、
    //出力されるログのcallerが全てlogger/logger.goになってしまう。
    Debug = s.Debug
    Info = s.Info
    Warn = s.Warn
    Error = s.Error
    Debugf = s.Debugf
    Infof = s.Infof
    Warnf = s.Warnf
    Errorf = s.Errorf
    InfoQuick = l.Info
    WarnQuick = l.Warn

    return nil
}

  • 思考錯誤しながらも最終的にはいい形になったのではないかと思っています。
  • これを元に開発するのはこれからなので変わるかもしれませんが。
  • loggerの実装自体、デファクトスタンダードがありそう、もっといい方法あれば教えてほしいです!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

LeetCodeに毎日挑戦してみた 168. Excel Sheet Column Title(Python、Go)

Leetcodeとは

leetcode.com
ソフトウェア開発職のコーディング面接の練習といえばこれらしいです。
合計1500問以上のコーデイング問題が投稿されていて、実際の面接でも同じ問題が出されることは多いらしいとのことです。

golang入門+アルゴリズム脳の強化のためにgoとPythonで解いていこうと思います。

38問目(問題168)

168. Excel Sheet Column Title

問題内容

Given a positive integer, return its corresponding column title as appear in an Excel sheet.

For example:

    1 -> A
    2 -> B
    3 -> C
    ...
    26 -> Z
    27 -> AA
    28 -> AB 
    ...

Example 1:

Input: 1
Output: "A"

Example 2:

Input: 28
Output: "AB"

Example 3:

Input: 701
Output: "ZY"

考え方

  1. 数字を文字に変換する方針で解いていきます。ASCIIを知っているとわかりやすいです

  2. 26文字で一周なので26で割りながら0になったらループを終了します

  3. 最終的にresが逆順になっているので反転して返します。

解答コード

class Solution:
    def convertToTitle(self, n: int) -> str:
        res = ""
        while n > 0:
            n -= 1  # 26 -> "Z"
            res += chr(n % 26 + ord('A'))
            n //= 26
        return res[::-1]
  • Goでも書いてみます!
func convertToTitle(n int) string {
    result := ""
    for {
        if n <= 26 {
            result = string(n+64) + result
            break
        } else {
            result = string((n-1)%26+1+64) + result
            n = (n - 1) / 26
        }
    }
    return result
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む