20200120のGoに関する記事は7件です。

1時間でできるMP3音楽を再生するCLIツールを作成する

はじめに

Go言語によって1時間でMP3を再生するCLIツールを作ります!

hello worldしたから次、何か成果物を作りたい!
CLIツールの作り方を知りたい!
みたいな人にオススメです!

完成品

ターミナルで自作したコマンドを実行して、音楽を流しましょう!!

作成!!

STEP:1 簡単にmp3の音楽を流してみる

今回,mp3の音声を流すために使うライブラリはこちらです!

main.go
package main

import (
    "github.com/faiface/beep"
    "github.com/faiface/beep/mp3"
    "github.com/faiface/beep/speaker"

    "os"
    "time"
)

func main(){
        filepath := "~/Desktop/test.mp3"
        Sound(filepath)
}

func Sound(path string) error {
    f, _ := os.Open(path)
    s, format, err := mp3.Decode(f)
    if err != nil {
        return err
    }
    err = speaker.Init(format.SampleRate, format.SampleRate.N(time.Second/10))
    if err != nil {
        return err
    }
    done := make(chan struct{})
    speaker.Play(beep.Seq(s, beep.Callback(func() {
        close(done)
    })))
    <-done

    return nil
}

コードの解説

(1) mp3ファイルを読み込む

f, _ := os.Open(path)
s, format, err := mp3.Decode(f)

ここで、ファイルパス(path)から、mp3を読み込む処理を入れています。
mp3ファイルがあるpathを指定してください!

(2)ファイルを再生する

err = speaker.Init(format.SampleRate, format.SampleRate.N(time.Second/10))
done := make(chan struct{})
speaker.Play(beep.Seq(s, beep.Callback(func() {
    close(done)
})))
<-done

Init()で、音声を再生する速度を設定しています。こちらは通常再生の設定です。

Play()は再生する設定をしています。beep.Callback()は再生が終了する時に呼ばれる関数です。再生が終了した時に、doneチャネルに値を送信します。

<-done この部分はチャネルから値が入るまでブロックします。なので、音楽が流れている間は、この部分で止まっています。終了して値が入ってくると、次の処理に移ります。
チャネルはgo特有の仕組みです。とりあえず、再生が終了するまで、ここでブロックされていると思ってください

(3) 実行する

$ go run main.go

音楽がなりますね!簡単にmp3を流すことができます。

Step2:cliツールを作る

cliツールを簡単に作れるパッケージはこちらです。

このパッケージの使い方を簡単にご紹介します。

example(コード)

example
package main

import (
    "fmt"
    "os"

    "github.com/urfave/cli"
)

func main() {
    app := cli.NewApp()
    app.Name = "test"
    app.Usage = "play music"
    app.Version = "1.0.0"
    app.Commands = []cli.Command{
        {
            Name:   "play",
            Usage:  "play music",
            Action: play,
        },
    }

    app.Run(os.Args)
}

func play(c *cli.Context) {
    for _, v := range c.Args() {
        fmt.Println(v)
    }
}

example解説

(1) ツールの初期化、設定出力

    app := cli.NewApp()
    app.Name = "test"
    app.Usage = "play music"
    app.Version = "1.0.0"
    app.Commands = []cli.Command{
        {
            Name:   "play",
            Usage:  "play music",
            Action: play,
        },
    }
  • Name:アプリの名前
  • Usage:アプリの使い方
  • Version:cliのバージョン
  • Commands:コマンドを登録する です!

自由に設定できます。設定した情報は、コマンドツールでよくある、-h オプションで表示できます。

試しに $go run cli.go -hと打ってみましょう

NAME:
   test - play music

USAGE:
   cli [global options] command [command options] [arguments...]

VERSION:
   1.0.0

COMMANDS:
   play     play music
   help, h  Shows a list of commands or help for one command

GLOBAL OPTIONS:
   --help, -h     show help
   --version, -v  print the version

簡単にクオリティの高いhelpが作れます!

(2)アクションを追加
次に
cli.Commandスライスには、アクション(command)を登録できます

  • Name:コマンド名
  • Usage:コマンドの使い方
  • Action:コマンドの実際の動作を設定

そして、このアクションに、

func (c *cli.Context){}

という関数を設定します。
試しにコマンドの後ろに繋げた文字を表示するアクションを作ってみます。

func play(c *cli.Context) {
    for _, v := range c.Args() {
        fmt.Println(v)
    }
}

(3) 実行してみる

$go run cli.go play test1 test2 test3
test1 
test2
test3

しっかり引数が表示されてますね

次がこれらの複合です!

Step3: Step1,2を組み合わせてMp3を実行するCLIツールを実装する

ディレクトリ構成

.
├── mcli
│   ├── action.go
│   └── cli.go
├── cmd
│   └── music-cli.go
├── go.mod
├── go.sum
└── sound.go

補足情報
今回、mainパッケージをcmd/に追加しています。これによって、コマンドを利用するときは、
$ go get github.com/ryomak/music-cli/cmd
でインポートできます。

CLIツールではなく音楽を再生するパッケージだけを使いたいときは

$ go get github.com/ryomak/music-cli
/*
コード上では
import(
    sound "github.com/ryomak/music-cli"
)
*/

で利用できます。
自分で作成した音楽を再生するパッケージ(sound.go)を他のプロジェクトでも使う時に、見やすくなります。

1:mcliパッケージ

mcliパッケージは、musicを再生するcliパッケージです!
cliの役割を記述します

mcli/cli.go
package mcli

import (
    "github.com/urfave/cli"
)

func New(name, usage, version string) *cli.App {
    app := cli.NewApp()
    app.Name = name
    app.Usage = usage
    app.Version = version
    app.Commands = getCommands()
    return app
}

func getCommands() []cli.Command {
    return []cli.Command{
        {
            Name:   "play",
            Usage:  "play music",
            Action: play,
        },
    }
}

次にcliのアクションを記述したaction.go です!

mcli/action.go
package mcli

import (
    "fmt"
    "path/filepath"

    "github.com/ryomak/musicbox/sound"
    "github.com/urfave/cli"
)

func play(c *cli.Context) {
    for _, v := range c.Args() {
        _, filename := filepath.Split(v)
        fmt.Printf("start %s... \n", filename)
        err := sound.Sound(v)
        if err != nil {
            fmt.Printf("cannot start skipping %s \n", filename)
        }
    }
}

補足情報
actionとcli設定で分けた理由としては、これからコマンドが増える時に

actionのみの設定を記述するファイルに分けておくと、後でで改修がしやすいからです。

また、filepath.Splitはとても便利で、入力されたファイルパスを
ディレクトリとファイル名に分けてくれます。
~/Desktop/test.go => (~/Desktop) と (test.go)

2:Mainパッケージ

cli/main.go
package main

import (
    "os"

    "github.com/ryomak/music-cli/mcli"
)

func main() {
  app := mcli.New("music-cli", "BGM", "1.0.0")
  app.Run(os.Args)
}

先ほど作った、mcliパッケージを読み込んで実行しています。

3:soundパッケージ

sound.go
package sound

import (
    "github.com/faiface/beep"
    "github.com/faiface/beep/mp3"
    "github.com/faiface/beep/speaker"

    "os"
    "time"
)

func Sound(path string) error {
    f, _ := os.Open(path)
    s, format, err := mp3.Decode(f)
    if err != nil {
        return err
    }
    done := make(chan struct{})
    err = speaker.Init(format.SampleRate, format.SampleRate.N(time.Second/10))
    if err != nil {
        return err
    }
    speaker.Play(beep.Seq(s, beep.Callback(func() {
        close(done)
    })))
    <-done

    return nil
}

実行

$ go run cmd/music-cli play ~/Desktop/music.mp3
start music.mp3...

まとめ

今回はmp3を再生するcliツールを作成してみました。
簡単に作成できて、これから派生もたくさんできるので、ぜひ改良していってください!

少しでもプログラミングのお役に立てれば幸いです!

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

GoのGinでハンドラーに引数を渡したいとき

検索してたどり着いたみなさん、Gin やってますか?私は最近Go X Gin X LINE bot なサービスを作ろうとして結構悩みました。この経験を共有できたらいいな、と記事を書いています。

困ったこと:router.GET("/", handler.Index) に引数がない!

Goを始めてWebアプリを作ろうとしてGinに手を出した人には共感してもらえる(かもしれない)と思うんですが、私にはとても不思議に思えることがありました。アプリのルーティングを設定する場所に、Tour of Goとかでは見たことがない書き方が現れるのです。たとえば、こんな感じですね。

main.go
func main() {
    router := gin.Defualt()
    ...
    router.GET("/", handler.Index)
    ...
}

素人の視点からすると、handler.Indexってなんなんだ!なんでカッコが付いてないんだ!とうならざるを得ません。そしてさらに悪いことに、LINE botを使おうとしていた私はドツボにハマることになります。LINE botはGoのためのSDKを用意していて、そのドキュメント(から類推するところ)によると最初に bot client instanceを作る必要があります。インスタンスは連発するとメモリ食いそうだから最初に呼び出したものを使いたい...

main.go
func main() {
    bot, err := linebot.New(<channel secret>, <channel access token>)
    ...
}

うーん、困った。ハンドラーでbotのメソッドを使いたいのに、ハンドラーには引数がないからbotインスタンスを渡せない...

整理すると、

main.go
func main() {
    router := gin.Default()
    bot, err := linebot.New(~~~, ~~~)
    ...
    router.GET("/", handler.Index) //ここにbotを渡したい!!!!!
}

(本筋に関係ない部分は適当に書いてます。また、router.GET("/", handler.Index)にbotを渡すことないだろう、というツッコミは甘んじて受け入れます。メソッドはGETでもPOSTでもなんでもいいです。ルーティングも /callbackとかの方が適切かもしれません。一つの具体例として多めに見てください。ハンドラーの名前も他にあるでしょう)←長い言い訳ですが、さて、この問題どう解決すればいいでしょうか?

ハンドラーの型に注目する

router.GET("/", handler.Index)の部分は、パッケージを分けて書いてあるわけですが、一方で関数リテラルで書く方法もあります。

main.go
router.GET("/", func (c *gin.Context) { ... })

一体どうなっているんだろう... ここで GETメソッドの中身はどうなっているんだ、と調べてみます。とくにハンドラーの型がどうなっているんでしょう。

routergruoup.go
...
// GET is a shortcut for router.Handle("GET", path, handle).
func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
    return group.handle("GET", relativePath, handlers)
}
...

ハンドラーの部分はhandlers ...HandlerFuncとなっていますね。...が前につく型はN個(Nは0を含む自然数)の引数をとることができるというルールです。詳しくは言語仕様を読んでみましょう。

では、型HandlerFuncとはなんなのか?その定義にさかのぼってみます。

gin.go
...
// HandlerFunc defines the handler used by gin middleware as return value.
type HandlerFunc func(*Context)
...

簡潔な定義です。ここで明らかになるのは、HandlerFuncの型は関数型func(*Context)であるということです。(型Contextはgin.goで定義される構造体です。他のパッケージから参照する場合はgin.Contextと書きますので、この記事でも2つの書き方が現れます)。どうりで、ハンドラーを関数リテラルで書くときは必ず

main.go
router.GET("/", func (c *gin.Context) { ... })

というふうに書くわけです。ここからわかることは、ハンドラーの型はfunc(*Context)でなければならない、ということです。

さて、私たちが書きたい関数Xxxはどんな形をしているでしょう。以下のようになって欲しいですね。

main.go
func main() {
    ...
    bot, err := linebot.New()
    ...
    router.GET("/", handler.Xxx(bot))
    ...
}

関数Xxxの型を考えると、引数がbot *linebot.Clientであり、戻り値がfunc(*gin.Context)でなければならない。つまり、関数Xxxの宣言は次のようになるでしょう。

handler.go
func Xxx(bot *linebot.Client) func(*gin.Context) {
    ...
}

型で言うとfunc(*linebot.Client) func(*gin.Context)というふうになります。へんてこりんですね。

ハンドラーの中身を考える

さて、ハンドラーの外側がわかってきたので、いよいよ中身を考えましょう。なんやこらー、とパソコンをぶん投げたくなりますが、順番に考えましょう。関数に戻り値があるということは、当然ですが、ブロックの中でreturnされるものがあります。簡単な関数の例を考えましょう。

func succeess( x int) int {
    var y int
    y = x + 1
    return y
}

少し大げさに書きましたが、この場合は戻り値はint型であり、returnされるのはyです。宣言されている通り、yint型です。

同じように考えて、関数Xxxの戻り値はfunc(*gin.Context)型であるから、次のようなものがreturnされるはずです。

handler.go
func Xxx(bot *linebot.Client) func(*gin.Context) {
    ...
    return func(c *gin.Context){ ... }
    ...
}

こうなれば簡単ですね。 return func(c *gin.Context){ ... }のブロックの内部でbotを使うことができます。

おわりに

LINE公式のGithubリポジトリーのサンプルはパッケージが1つしかありませんでしたため、パッケージを分ける技術を模索しました。次のサイトを参考にさせていただきました。最後ですがお礼申し上げます。

Handlerに引数を追加したい時 - Qiita

Ginのコードは公式リポジトリーから引用しました。
https://github.com/gin-gonic/gin

LINE bot 公式リポジトリー
https://github.com/line/line-bot-sdk-go

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

標準入力のあるプログラムを delve でデバッグしたい

Goで開発していて delve は便利なデバッグツールですが、標準入力のあるプログラムはそのままではデバッグができません
標準入力をプログラム本体に入力することができないためです

対処法

まず、プログラムを一度コンパイルし、
コンパイルしたプログラムを実行します

$ go build  -gcflags="-N -l" hoge.go
$ ./hoge

で、 新しい ターミナルを開き
このプロセスに対してdlvをアタッチします

$ dlv attach $(pgrep -fn hoge)

dlv attach コマンドは、引数に pid を渡すことで、その Go プログラムのデバッグを開始します

これで無事にデバッグができるようになります。
プログラム本体側のターミナルで標準入力ができますし、dlv側のターミナルでデバッグコマンドが使えます

めでたしめでたし

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

golangでバイナリ操作 - ヌル文字が出てくるまで取得したい

課題: 任意の文字列が与えられたとき、ヌル文字まで取得したい

え、普通にSplit使えばいいじゃんて?それに気づいたのは遥か先だった。。

  • 方法1: 直接ゴリゴリ
  • 方法2: バッファに入れてNextで読み出し
  • 方法2: バッファに入れてReadで読み出し
  • 方法3: バッファに入れてencoding/binaryのReadで1バイトずつ読み出し
  • 方法4: splitsで分けちゃえ

試したもの

package main

import (
    "bytes"
    "crypto/rand"
    "encoding/binary"
    "io"
    "testing"
)

func generateRandomBytes() []byte {
    token := make([]byte, 1000000)
    rand.Read(token)
    return token
}

func BenchmarkSplitByNullByte_Direct(b *testing.B) {
    token := generateRandomBytes()
    b.ResetTimer()

    var bytes [][]byte
    bytes = append(bytes, []byte{})

    for _, v := range token{
        bytes[len(bytes)-1] = append(bytes[len(bytes)-1], v)
        if v == 0x00 {
            bytes = append(bytes, []byte{})
        }
    }
}

func BenchmarkSplitByNullByte_BytesNext(b *testing.B) {
    token := generateRandomBytes()
    b.ResetTimer()

    buf := bytes.NewBuffer(token)
    var bytes [][]byte
    bytes = append(bytes, []byte{})

    v := buf.Next(1)
    for ; ; v = buf.Next(1) {
        if len(v) == 0 {
            break
        }
        bytes[len(bytes)-1] = append(bytes[len(bytes)-1], v[0])
        if v[0] == 0x00 {
            bytes = append(bytes, []byte{})
        }
    }
}

func BenchmarkSplitByNullByte_BytesRead(b *testing.B) {
    token := generateRandomBytes()
    b.ResetTimer()

    buf := bytes.NewBuffer(token)
    var bytes [][]byte
    bytes = append(bytes, []byte{})

    var n int
    v := make([]byte, 1)
    n, _ = buf.Read(v)
    for ; ; n, _ = buf.Read(v) {
        if n == 0 {
            break
        }
        bytes[len(bytes)-1] = append(bytes[len(bytes)-1], v[0])
        if v[0] == 0x00 {
            bytes = append(bytes, []byte{})
        }
    }
}

func BenchmarkSplitByNullByte_EncodingBinaryRead(b *testing.B) {
    token := generateRandomBytes()
    b.ResetTimer()

    buf := bytes.NewBuffer(token)
    var bytes [][]byte
    bytes = append(bytes, []byte{})

    var err error
    v := make([]byte, 1)
    binary.Read(buf, binary.BigEndian, v)
    for ; ; err = binary.Read(buf, binary.BigEndian, v) {
        if err == io.EOF {
            break
        }
        bytes[len(bytes)-1] = append(bytes[len(bytes)-1], v[0])
        if v[0] == 0x00 {
            bytes = append(bytes, []byte{})
        }
    }
}

func BenchmarkSplitByNullByte_Split(b *testing.B) {
    token := generateRandomBytes()
    b.ResetTimer()

    bytes.Split(token, []byte("\x00"))
}

結果

goos: linux
goarch: amd64
pkg: github.com/gpioblink/sandbox
BenchmarkSplitByNullByte_Direct-8                   1000000000           0.00876 ns/op
BenchmarkSplitByNullByte_BytesNext-8                1000000000           0.0106 ns/op
BenchmarkSplitByNullByte_BytesRead-8                1000000000           0.0142 ns/op
BenchmarkSplitByNullByte_EncodingBinaryRead-8       1000000000           0.101 ns/op
BenchmarkSplitByNullByte_Split-8                    1000000000           0.000196 ns/op
PASS

うん、やっぱり普通にSplit使うのが一番早いよね。。
NextよりReadの方が遅いのはちょっと意外だったかな。コード見ると内部でコピーとかやってるからかな?

ゴリ押しのコードも、頑張れば絶対もっと早く出来ると思います。何か知っていたら教えてください!

結論

普通にSplit使え

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

GoのInterfaceを理解する

はじめに

この記事は私と同じようにGoを勉強し始めたものの
interfaceを理解できない方へ向けたものとなっております。
interfaceは2個の意味が存在しています。

  • なんでも入れられる型 interface{}
  • 構造体などに紐づけ出来る書き方

今回は後者のほうを説明していきます。
ちなみに関数名などは適当なので申し訳ございません。

コード

package main

import (
    "fmt"
)

type Fish interface {
    Place()string       //住んでいる場所 
}

type Sanma struct {}
type Aji struct {}

func (s *Sanma)Place()string{
    return "Japan"
}

func (a *Aji)Place()string{
    return "Europe"
}

type Sea struct {}

func(s *Sea)seafish(fish Fish){
    fmt.Println("どこに住んでるの?:" + fish.Place())
}

func main(){
    sanma := &Sanma{}
    aji := &Aji{}
    sea := Sea{}
    fmt.Println(sanma.Place())
    fmt.Println(aji.Place())

    sea.seafish(sanma)
}

解説

上記のサンプルコードはinterfaceを使ったものとなっています。
Fishというinterfaceを宣言し、そこにPlaceという関数を宣言します。
SanmaとAjiという構造体を宣言します。作成した構造体のメソッドとして
Place関数を宣言します。interfaceにあるすべての関数を宣言するだけで紐づけすることができます。
この事によりSea構造体のseafishメソッドの引数にFishと紐づいている構造体を引数として渡すことができます。

interfaceの重要性

もしinterfaceを使わない場合はseafish関数の引数が各構造体を引数とし
Aji用のseafishとSanma用のseafishを宣言しなければいけなくなり、コードが長くなってしまいます。

終わりに

godoc見ていくといろいろとinterfaceで宣言されているので見てみると面白いかもしれません。
最後まで読んでいただきありがとうございました。

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

template.ParseFilesの省略

自分用に、いちいちtemplateを呼び出すときに2,3行かくのめんどいので1行で書こうとした。

func handleIndex(w http.ResponseWriter, r *http.Request) {
    v := 1
    render("index").Execute(w, v) 

//t :=template.ParseFiles("index")  t.Execute(w, v) を省略した。()の中身を変えるだけでfileが呼び出せる。
//変更するのは()の引数とvの値のみでOK!!わざわざtemplate.Parsefile書くのめんどかった。
}
func render(name string) *template.Template {
    t, _ := template.ParseFiles(name + ".html")
    return t
}
//引数にfile名を指定し活用
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Golang logrusによるロギング

概要

golangのロギングパッケージである、logrusを用いて、以下のようにログを出力させる方法について記載する。

[INFO]:2020-01-20 03:06:19 [main.go:19]  - info msg1

出力内容は、レベル、時刻、ファイル名、ライン位置、メッセージである。

また、以下についても記載する。

  • ログファイルへの出力
  • ログレベルの変更

Git: k-washi/gologger

インストール

go get github.com/sirupsen/logrus

logrusの設定ファイル

まず、設定に使用する関数を作成する。
詳細はプログラム内に記載。

utils.go
package gologger

import (
    "os"
    "strings"
)

//openFile ログを出力するファイルを設定する。
//ファイルが存在する場合、ファイルにログを追記。
//ファイルが存在しない場合、ファイルを作成し、ログを出力。
func openFile(fileName string) (*os.File, error) {
    if exists(fileName) {
        f, err := os.OpenFile(fileName, os.O_WRONLY|os.O_APPEND, 0777)
        return f, err
    }
    f, err := os.OpenFile(fileName, os.O_WRONLY|os.O_CREATE, 0777)
    return f, err
}

//formatFilePath ログに記載するファイル名の抽出
func formatFilePath(path string) string {
    arr := strings.Split(path, "/")
    return arr[len(arr)-1]

}

//exists ファイルが存在するか確認する。
func exists(name string) bool {
    _, err := os.Stat(name)
    return !os.IsNotExist(err)
}

logrusのformatを参考にして、ロギングフォーマットを設定。
詳細はプログラム内に記載。

log.go
package gologger

import (
    "bytes"
    "fmt"
    "io"
    "os"
    "strings"

    "github.com/sirupsen/logrus"
)

type logFormat struct {
    TimestampFormat string
}

//Format ログの形式を設定
func (f *logFormat) Format(entry *logrus.Entry) ([]byte, error) {
    var b *bytes.Buffer

    if entry.Buffer != nil {
        b = entry.Buffer
    } else {
        b = &bytes.Buffer{}
    }

    b.WriteByte('[')
    b.WriteString(strings.ToUpper(entry.Level.String()))
    b.WriteString("]:")
    b.WriteString(entry.Time.Format(f.TimestampFormat))

    b.WriteString(" [")
    b.WriteString(formatFilePath(entry.Caller.File))
    b.WriteString(":")
    fmt.Fprint(b, entry.Caller.Line)
    b.WriteString("] ")

    if entry.Message != "" {
        b.WriteString(" - ")
        b.WriteString(entry.Message)
    }

    if len(entry.Data) > 0 {
        b.WriteString(" || ")
    }
    for key, value := range entry.Data {
        b.WriteString(key)
        b.WriteByte('=')
        b.WriteByte('{')
        fmt.Fprint(b, value)
        b.WriteString("}, ")
    }

    b.WriteByte('\n')
    return b.Bytes(), nil
}

//init パッケージ読み込み時に実行される。
func init() {
    logrus.SetReportCaller(true) //Caller(実行ファイル(ex. main.go)を扱うため)
    formatter := logFormat{}
    formatter.TimestampFormat = "2006-01-02 15:04:05" //時刻設定

    logrus.SetFormatter(&formatter)

    //ログ出力ファイルの設定
    f, err := openFile("log.txt")
    if err != nil {
        logrus.Fatal(err)
    }
    logrus.SetOutput(io.MultiWriter(os.Stdout, f))

    //ログレベルの設定
    logrus.SetLevel(logrus.InfoLevel)

}

//SetLevelDebug Debugレベルに設定
func SetLevelDebug() {
    logrus.SetLevel(logrus.DebugLevel)
}

//SetLevelInfo Set Infoレベルに設定
func SetLevelInfo() {
    logrus.SetLevel(logrus.InfoLevel)
}

ロギング

以下のように、main.goで設定パッケージを読み込むことで、logrusの設定が読み込まれる。
もし、ログレベルを変更したい場合は、コメントアウトした関数を使用する。

#出力結果
[INFO]:2020-01-20 03:32:53 [main.go:22]  - info msg1
[INFO]:2020-01-20 03:32:53 [subpkg.go:9]  - sub info msg1
main.go
package main

import (
    "github.com/k-washi/gologger/test/subpkg"

    _ "github.com/k-washi/gologger"
    log "github.com/sirupsen/logrus"
)

func main() {
    //logSetter.SetLevelDebug()
    log.Info("info msg1")
    log.Debug("debug msg1")

    subpkg.SubPrint()

}
subpkg.go
package subpkg

import (
    log "github.com/sirupsen/logrus"

)

func SubPrint() {

    log.Info("sub info msg1")
    log.Debug("sub debug msg1")

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