Compare commits
2 Commits
285349a481
...
63a16a3253
Author | SHA1 | Date |
---|---|---|
Marvin Preuss | 63a16a3253 | 3 years ago |
Marvin Preuss | d5a7d1d9a4 | 3 years ago |
@ -0,0 +1,21 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/spf13/cobra"
|
||||
"go.xsfx.dev/schnutibox/pkg/timer"
|
||||
)
|
||||
|
||||
// nolint:gochecknoglobals
|
||||
var timerCmd = &cobra.Command{
|
||||
Use: "timer",
|
||||
Short: "Handling timer",
|
||||
Run: timer.Run,
|
||||
PreRun: func(cmd *cobra.Command, args []string) {
|
||||
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
|
||||
initConfig(false)
|
||||
},
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package grpcclient
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
func Conn(hostname string, port int) (*grpc.ClientConn, error) {
|
||||
var conn *grpc.ClientConn
|
||||
|
||||
conn, err := grpc.Dial(fmt.Sprintf("%s:%d", hostname, port), grpc.WithInsecure())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not connect: %w", err)
|
||||
}
|
||||
|
||||
return conn, nil
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
package timer
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/golang/protobuf/ptypes/duration"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"go.xsfx.dev/schnutibox/internal/config"
|
||||
"go.xsfx.dev/schnutibox/internal/grpcclient"
|
||||
api "go.xsfx.dev/schnutibox/pkg/api/v1"
|
||||
"go.xsfx.dev/schnutibox/pkg/mpc"
|
||||
"google.golang.org/protobuf/types/known/durationpb"
|
||||
)
|
||||
|
||||
// nolint:gochecknoglobals
|
||||
var T = &Timer{}
|
||||
|
||||
type Timer struct {
|
||||
Req *api.Timer
|
||||
}
|
||||
|
||||
func (t *Timer) Handle() {
|
||||
if t.Req != nil {
|
||||
// Initialize the current object.
|
||||
if t.Req.Current == nil {
|
||||
t.Req.Current = &duration.Duration{}
|
||||
t.Req.Current.Seconds = t.Req.Duration.Seconds
|
||||
}
|
||||
|
||||
switch {
|
||||
// There is some timing going on.
|
||||
case t.Req.Duration.Seconds != 0 && t.Req.Current.Seconds != 0:
|
||||
log.Debug().
|
||||
Int64("current", t.Req.Current.Seconds).
|
||||
Int64("duration", t.Req.Duration.Seconds).
|
||||
Msg("timer is running")
|
||||
|
||||
if t.Req.Current.Seconds > 0 {
|
||||
t.Req.Current.Seconds -= 1
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// No timer is running... so setting the duration to 0.
|
||||
case t.Req.Current.Seconds == 0 && t.Req.Duration.Seconds != 0:
|
||||
log.Debug().Msg("stoping timer")
|
||||
|
||||
if err := mpc.Stop(log.Logger); err != nil {
|
||||
log.Error().Err(err).Msg("could not stop")
|
||||
}
|
||||
|
||||
t.Req.Duration.Seconds = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Run is the command line interface for triggering the timer.
|
||||
func Run(cmd *cobra.Command, args []string) {
|
||||
conn, err := grpcclient.Conn(config.Cfg.Box.Hostname, config.Cfg.Box.Port)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("could not connect")
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
c := api.NewTimerServiceClient(conn)
|
||||
|
||||
d := durationpb.New(viper.GetDuration("timer.duration"))
|
||||
|
||||
_, err = c.Create(context.Background(), &api.Timer{Duration: d})
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("could not create timer")
|
||||
}
|
||||
|
||||
log.Info().Msg("added timer")
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
package watcher
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"go.xsfx.dev/schnutibox/internal/metrics"
|
||||
"go.xsfx.dev/schnutibox/pkg/mpc"
|
||||
"go.xsfx.dev/schnutibox/pkg/timer"
|
||||
)
|
||||
|
||||
const tickerTime = time.Second
|
||||
|
||||
// Run runs actions after tickerTime is over, over again and again.
|
||||
// Right now its mostly used for setting metrics.
|
||||
func Run() {
|
||||
log.Debug().Msg("starting watch")
|
||||
|
||||
ticker := time.NewTicker(tickerTime)
|
||||
|
||||
go func() {
|
||||
for {
|
||||
<-ticker.C
|
||||
|
||||
// Timer.
|
||||
go timer.T.Handle()
|
||||
|
||||
// Metrics.
|
||||
go func() {
|
||||
m, err := mpc.Conn()
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("could not connect")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
uris, err := mpc.PlaylistURIS()
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("could not get playlist uris")
|
||||
metrics.BoxErrors.Inc()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Gettings MPD state.
|
||||
s, err := m.Status()
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("could not get status")
|
||||
metrics.BoxErrors.Inc()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Sets the metrics.
|
||||
metrics.Set(uris, s["state"])
|
||||
}()
|
||||
}
|
||||
}()
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
package main
|
||||
|
||||
func main() {}
|
@ -0,0 +1,16 @@
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, build with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
/vendor
|
||||
grpc-zerolog
|
||||
coverage.txt
|
@ -0,0 +1,14 @@
|
||||
language: go
|
||||
|
||||
go:
|
||||
- tip
|
||||
|
||||
before_install:
|
||||
- curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
|
||||
- dep ensure
|
||||
|
||||
script:
|
||||
- go test -race -coverprofile=coverage.txt -covermode=atomic
|
||||
|
||||
after_success:
|
||||
- bash <(curl -s https://codecov.io/bash)
|
@ -0,0 +1,259 @@
|
||||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||
|
||||
|
||||
[[projects]]
|
||||
digest = "1:a2c1d0e43bd3baaa071d1b9ed72c27d78169b2b269f71c105ac4ba34b1be4a39"
|
||||
name = "github.com/davecgh/go-spew"
|
||||
packages = ["spew"]
|
||||
pruneopts = "UT"
|
||||
revision = "346938d642f2ec3594ed81d874461961cd0faa76"
|
||||
version = "v1.1.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:4ba8096f8ea6ea9f7aefb98e189e0128324883f0b4e839800ce4b37c90230c5c"
|
||||
name = "github.com/golang/protobuf"
|
||||
packages = [
|
||||
"jsonpb",
|
||||
"proto",
|
||||
"ptypes",
|
||||
"ptypes/any",
|
||||
"ptypes/duration",
|
||||
"ptypes/struct",
|
||||
"ptypes/timestamp",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "d04d7b157bb510b1e0c10132224b616ac0e26b17"
|
||||
version = "v1.4.2"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:0028cb19b2e4c3112225cd871870f2d9cf49b9b4276531f03438a88e94be86fe"
|
||||
name = "github.com/pmezard/go-difflib"
|
||||
packages = ["difflib"]
|
||||
pruneopts = "UT"
|
||||
revision = "792786c7400a136282c1664665ae0a8db921c6c2"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:342baf1b227dbc8829a60dc06cd7a5e70d3de350b501e2184ec8f19fe4418d4a"
|
||||
name = "github.com/rs/zerolog"
|
||||
packages = [
|
||||
".",
|
||||
"internal/cbor",
|
||||
"internal/json",
|
||||
"log",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "7825d863376faee2723fc99c061c538bd80812c8"
|
||||
version = "v1.19.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:ac83cf90d08b63ad5f7e020ef480d319ae890c208f8524622a2f3136e2686b02"
|
||||
name = "github.com/stretchr/objx"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "477a77ecc69700c7cdeb1fa9e129548e1c1c393c"
|
||||
version = "v0.1.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:5201127841a78d84d0ca68a2e564c08e3882c0fb9321a75997ce87926e0d63ea"
|
||||
name = "github.com/stretchr/testify"
|
||||
packages = [
|
||||
"assert",
|
||||
"mock",
|
||||
"require",
|
||||
"suite",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "f654a9112bbeac49ca2cd45bfbe11533c4666cf8"
|
||||
version = "v1.6.1"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:dc1b5a4f3a84b5c8503036630dd0f245de44d9c22414ddff0e5e473849139fd3"
|
||||
name = "golang.org/x/net"
|
||||
packages = [
|
||||
"context",
|
||||
"http/httpguts",
|
||||
"http2",
|
||||
"http2/hpack",
|
||||
"idna",
|
||||
"internal/timeseries",
|
||||
"trace",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "ed29d75add3d7c4bf7ca65aac0c6df3d1420216f"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:a2ab62866c75542dd18d2b069fec854577a20211d7c0ea6ae746072a1dccdd18"
|
||||
name = "golang.org/x/text"
|
||||
packages = [
|
||||
"collate",
|
||||
"collate/build",
|
||||
"internal/colltab",
|
||||
"internal/gen",
|
||||
"internal/tag",
|
||||
"internal/triegen",
|
||||
"internal/ucd",
|
||||
"language",
|
||||
"secure/bidirule",
|
||||
"transform",
|
||||
"unicode/bidi",
|
||||
"unicode/cldr",
|
||||
"unicode/norm",
|
||||
"unicode/rangetable",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0"
|
||||
version = "v0.3.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:601e63e7d4577f907118bec825902505291918859d223bce015539e79f1160e3"
|
||||
name = "google.golang.org/genproto"
|
||||
packages = ["googleapis/rpc/status"]
|
||||
pruneopts = "UT"
|
||||
revision = "ff3583edef7de132f219f0efc00e097cabcc0ec0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:2dab32a43451e320e49608ff4542fdfc653c95dcc35d0065ec9c6c3dd540ed74"
|
||||
name = "google.golang.org/grpc"
|
||||
packages = [
|
||||
".",
|
||||
"balancer",
|
||||
"balancer/base",
|
||||
"balancer/roundrobin",
|
||||
"codes",
|
||||
"connectivity",
|
||||
"credentials",
|
||||
"encoding",
|
||||
"encoding/proto",
|
||||
"grpclog",
|
||||
"internal",
|
||||
"internal/backoff",
|
||||
"internal/channelz",
|
||||
"internal/grpcrand",
|
||||
"keepalive",
|
||||
"metadata",
|
||||
"naming",
|
||||
"peer",
|
||||
"resolver",
|
||||
"resolver/dns",
|
||||
"resolver/passthrough",
|
||||
"stats",
|
||||
"status",
|
||||
"tap",
|
||||
"transport",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "168a6198bcb0ef175f7dacec0b8691fc141dc9b8"
|
||||
version = "v1.13.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:6a351cb031761dcd668e5aca9a10bd6d1675aa5427ac1483a61c3a49b2d64962"
|
||||
name = "google.golang.org/protobuf"
|
||||
packages = [
|
||||
"encoding/protojson",
|
||||
"encoding/prototext",
|
||||
"encoding/protowire",
|
||||
"internal/descfmt",
|
||||
"internal/descopts",
|
||||
"internal/detrand",
|
||||
"internal/encoding/defval",
|
||||
"internal/encoding/json",
|
||||
"internal/encoding/messageset",
|
||||
"internal/encoding/tag",
|
||||
"internal/encoding/text",
|
||||
"internal/errors",
|
||||
"internal/fieldsort",
|
||||
"internal/filedesc",
|
||||
"internal/filetype",
|
||||
"internal/flags",
|
||||
"internal/genid",
|
||||
"internal/impl",
|
||||
"internal/mapsort",
|
||||
"internal/pragma",
|
||||
"internal/set",
|
||||
"internal/strs",
|
||||
"internal/version",
|
||||
"proto",
|
||||
"reflect/protoreflect",
|
||||
"reflect/protoregistry",
|
||||
"runtime/protoiface",
|
||||
"runtime/protoimpl",
|
||||
"types/known/anypb",
|
||||
"types/known/durationpb",
|
||||
"types/known/structpb",
|
||||
"types/known/timestamppb",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "3f7a61f89bb6813f89d981d1870ed68da0b3c3f1"
|
||||
version = "v1.25.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "v3"
|
||||
digest = "1:229cb0f6192914f518cc1241ede6d6f1f458b31debfa18bf3a5c9e4f7b01e24b"
|
||||
name = "gopkg.in/yaml.v3"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "eeeca48fe7764f320e4870d231902bf9c1be2c08"
|
||||
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
input-imports = [
|
||||
"github.com/davecgh/go-spew/spew",
|
||||
"github.com/golang/protobuf/jsonpb",
|
||||
"github.com/golang/protobuf/proto",
|
||||
"github.com/golang/protobuf/ptypes",
|
||||
"github.com/golang/protobuf/ptypes/any",
|
||||
"github.com/golang/protobuf/ptypes/duration",
|
||||
"github.com/golang/protobuf/ptypes/struct",
|
||||
"github.com/golang/protobuf/ptypes/timestamp",
|
||||
"github.com/pmezard/go-difflib/difflib",
|
||||
"github.com/rs/zerolog",
|
||||
"github.com/rs/zerolog/log",
|
||||
"github.com/stretchr/objx",
|
||||
"github.com/stretchr/testify/assert",
|
||||
"github.com/stretchr/testify/mock",
|
||||
"github.com/stretchr/testify/require",
|
||||
"github.com/stretchr/testify/suite",
|
||||
"golang.org/x/net/context",
|
||||
"golang.org/x/net/http/httpguts",
|
||||
"golang.org/x/net/http2",
|
||||
"golang.org/x/net/http2/hpack",
|
||||
"golang.org/x/net/idna",
|
||||
"golang.org/x/net/trace",
|
||||
"golang.org/x/text/collate",
|
||||
"golang.org/x/text/collate/build",
|
||||
"golang.org/x/text/language",
|
||||
"golang.org/x/text/secure/bidirule",
|
||||
"golang.org/x/text/transform",
|
||||
"golang.org/x/text/unicode/bidi",
|
||||
"golang.org/x/text/unicode/cldr",
|
||||
"golang.org/x/text/unicode/norm",
|
||||
"golang.org/x/text/unicode/rangetable",
|
||||
"google.golang.org/genproto/googleapis/rpc/status",
|
||||
"google.golang.org/grpc",
|
||||
"google.golang.org/grpc/balancer",
|
||||
"google.golang.org/grpc/balancer/base",
|
||||
"google.golang.org/grpc/balancer/roundrobin",
|
||||
"google.golang.org/grpc/codes",
|
||||
"google.golang.org/grpc/connectivity",
|
||||
"google.golang.org/grpc/credentials",
|
||||
"google.golang.org/grpc/encoding",
|
||||
"google.golang.org/grpc/encoding/proto",
|
||||
"google.golang.org/grpc/grpclog",
|
||||
"google.golang.org/grpc/keepalive",
|
||||
"google.golang.org/grpc/metadata",
|
||||
"google.golang.org/grpc/naming",
|
||||
"google.golang.org/grpc/peer",
|
||||
"google.golang.org/grpc/resolver",
|
||||
"google.golang.org/grpc/resolver/dns",
|
||||
"google.golang.org/grpc/resolver/passthrough",
|
||||
"google.golang.org/grpc/stats",
|
||||
"google.golang.org/grpc/status",
|
||||
"google.golang.org/grpc/tap",
|
||||
"google.golang.org/grpc/transport",
|
||||
]
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
@ -0,0 +1,46 @@
|
||||
# Gopkg.toml example
|
||||
#
|
||||
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
|
||||
# for detailed Gopkg.toml documentation.
|
||||
#
|
||||
# required = ["github.com/user/thing/cmd/thing"]
|
||||
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
|
||||
#
|
||||
# [[constraint]]
|
||||
# name = "github.com/user/project"
|
||||
# version = "1.0.0"
|
||||
#
|
||||
# [[constraint]]
|
||||
# name = "github.com/user/project2"
|
||||
# branch = "dev"
|
||||
# source = "github.com/myfork/project2"
|
||||
#
|
||||
# [[override]]
|
||||
# name = "github.com/x/y"
|
||||
# version = "2.4.0"
|
||||
#
|
||||
# [prune]
|
||||
# non-go = false
|
||||
# go-tests = true
|
||||
# unused-packages = true
|
||||
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/golang/protobuf"
|
||||
version = "1.1.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/rs/zerolog"
|
||||
version = "1.8.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "google.golang.org/grpc"
|
||||
version = "1.13.0"
|
||||
|
||||
[prune]
|
||||
go-tests = true
|
||||
unused-packages = true
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/stretchr/testify"
|
||||
version = "1.2.2"
|
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019 Philip Bui
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
@ -0,0 +1,17 @@
|
||||
PORT?=8000
|
||||
PACKAGE:=github.com/philip-bui/grpc-zerolog
|
||||
COVERAGE:=coverage.txt
|
||||
proto:
|
||||
protoc -I protos/ protos/*.proto --go_out=plugins=grpc:protos
|
||||
|
||||
godoc:
|
||||
echo "localhost:${PORT}/pkg/${PACKAGE}"
|
||||
godoc -http=:${PORT}
|
||||
|
||||
.PHONY: test
|
||||
|
||||
test:
|
||||
go test -race -coverprofile=${COVERAGE} -covermode=atomic
|
||||
|
||||
coverage:
|
||||
go tool cover -html=${COVERAGE}
|
@ -0,0 +1,49 @@
|
||||
# gRPC Zerolog
|
||||
|
||||
[![BuildStatus Widget]][BuildStatus Result]
|
||||
[![CodeCov Widget]][CodeCov Result]
|
||||
[![GoReport Widget]][GoReport Status]
|
||||
[![GoDoc Widget]][GoDoc]
|
||||
|
||||
[BuildStatus Result]: https://travis-ci.org/philip-bui/grpc-zerolog
|
||||
[BuildStatus Widget]: https://travis-ci.org/philip-bui/grpc-zerolog.svg?branch=master
|
||||
|
||||
[CodeCov Result]: https://codecov.io/gh/philip-bui/grpc-zerolog
|
||||
[CodeCov Widget]: https://codecov.io/gh/philip-bui/grpc-zerolog/branch/master/graph/badge.svg
|
||||
|
||||
[GoReport Status]: https://goreportcard.com/report/github.com/philip-bui/grpc-zerolog
|
||||
[GoReport Widget]: https://goreportcard.com/badge/github.com/philip-bui/grpc-zerolog
|
||||
|
||||
[GoDoc]: https://godoc.org/github.com/philip-bui/grpc-zerolog
|
||||
[GoDoc Widget]: https://godoc.org/github.com/philip-bui/grpc-zerolog?status.svg
|
||||
|
||||
Implementation of gRPC Logging Middleware, integrating [Zerolog](https://github.com/rs/zerolog) as a gRPC [Interceptor](https://github.com/grpc-ecosystem/go-grpc-middleware) to log the following fields:
|
||||
|
||||
- Request Protobufs as JSON.
|
||||
- Response Protobufs as JSON, or Errors.
|
||||
- Status Code, Duration, Timestamp, Service Name, Service Method, IP, Metadata Fields and User Agent.
|
||||
|
||||
## Usage
|
||||
|
||||
```go
|
||||
import (
|
||||
"github.com/philip-bui/grpc-zerolog"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// With global Zerolog logger.
|
||||
grpc.NewServer(
|
||||
zerolog.UnaryInterceptor(),
|
||||
)
|
||||
|
||||
// With custom Zerolog instance.
|
||||
log := zerolog.New(os.Stdout)
|
||||
grpc.NewServer(
|
||||
zerolog.UnaryInterceptorWithLogger(&log),
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
gRPC Zerolog is available under the MIT license. [See LICENSE](https://github.com/philip-bui/grpc-zerolog/blob/master/LICENSE) for details.
|
@ -0,0 +1,66 @@
|
||||
package zerolog
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
// UnaryInterceptor is a gRPC Server Option that uses NewUnaryServerInterceptor() to log gRPC Requests.
|
||||
func UnaryInterceptor() grpc.ServerOption {
|
||||
return grpc.UnaryInterceptor(NewUnaryServerInterceptor())
|
||||
}
|
||||
|
||||
func UnaryInterceptorWithLogger(log *zerolog.Logger) grpc.ServerOption {
|
||||
return grpc.UnaryInterceptor(NewUnaryServerInterceptorWithLogger(log))
|
||||
}
|
||||
|
||||
// NewUnaryServerInterceptor that logs gRPC Requests using Zerolog.
|
||||
// {
|
||||
// ServiceField: "ExampleService",
|
||||
// MethodField: "ExampleMethod",
|
||||
// DurationField: 1.00
|
||||
//
|
||||
// IpField: "127.0.0.1",
|
||||
//
|
||||
// MetadataField: {},
|
||||
//
|
||||
// UserAgentField: "ExampleClientUserAgent",
|
||||
// ReqField: {}, // JSON representation of Request Protobuf
|
||||
//
|
||||
// Err: "An unexpected error occurred",
|
||||
// CodeField: "Unknown",
|
||||
// MsgField: "Error message returned from the server",
|
||||
// DetailsField: [Errors],
|
||||
//
|
||||
// RespField: {}, // JSON representation of Response Protobuf
|
||||
//
|
||||
// ZerologMessageField: "UnaryMessageDefault",
|
||||
// }
|
||||
func NewUnaryServerInterceptor() grpc.UnaryServerInterceptor {
|
||||
return NewUnaryServerInterceptorWithLogger(&log.Logger)
|
||||
}
|
||||
|
||||
func NewUnaryServerInterceptorWithLogger(log *zerolog.Logger) grpc.UnaryServerInterceptor {
|
||||
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
|
||||
now := time.Now()
|
||||
resp, err := handler(ctx, req)
|
||||
if log.Error().Enabled() {
|
||||
if err != nil {
|
||||
logger := log.Error()
|
||||
LogIncomingCall(ctx, logger, info.FullMethod, now, req)
|
||||
LogStatusError(logger, err)
|
||||
logger.Msg(UnaryMessageDefault)
|
||||
} else if log.Info().Enabled() {
|
||||
logger := log.Info()
|
||||
LogIncomingCall(ctx, logger, info.FullMethod, now, req)
|
||||
LogResponse(logger, resp)
|
||||
logger.Msg(UnaryMessageDefault)
|
||||
}
|
||||
}
|
||||
return resp, err
|
||||
}
|
||||
}
|
@ -0,0 +1,224 @@
|
||||
package zerolog
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/golang/protobuf/jsonpb"
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/rs/zerolog"
|
||||
"google.golang.org/grpc/metadata"
|
||||
"google.golang.org/grpc/peer"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
var (
|
||||
// Marshaller of Protobuf to JSON
|
||||
Marshaller = &jsonpb.Marshaler{}
|
||||
// TimestampLog call start.
|
||||
TimestampLog = true
|
||||
// ServiceField key.
|
||||
ServiceField = "service"
|
||||
// ServiceLog gRPC service name.
|
||||
ServiceLog = true
|
||||
// MethodField key.
|
||||
MethodField = "method"
|
||||
// MethodLog gRPC method name.
|
||||
MethodLog = true
|
||||
// DurationField key.
|
||||
DurationField = "dur"
|
||||
// DurationLog gRPC call duration.
|
||||
DurationLog = true
|
||||
// IPField key.
|
||||
IPField = "ip"
|
||||
// IPLog gRPC client IP.
|
||||
IPLog = true
|
||||
// MetadataField key.
|
||||
MetadataField = "md"
|
||||
// MetadataLog gRPC call metadata.
|
||||
MetadataLog = true
|
||||
// UserAgentField key.
|
||||
UserAgentField = "ua"
|
||||
// UserAgentLog gRPC client User Agent.
|
||||
UserAgentLog = true
|
||||
// ReqField key.
|
||||
ReqField = "req"
|
||||
// ReqLog gRPC request body.
|
||||
ReqLog = true
|
||||
// RespField key.
|
||||
RespField = "resp"
|
||||
// RespLog gRPC response body.
|
||||
RespLog = true
|
||||
// MaxSize to log gRPC bodies.
|
||||
MaxSize = 2048000
|
||||
// CodeField gRPC status code response.
|
||||
CodeField = "code"
|
||||
// MsgField gRPC response message.
|
||||
MsgField = "msg"
|
||||
// DetailsField gRPC response errors.
|
||||
DetailsField = "details"
|
||||
// UnaryMessageDefault of logging messages from unary.
|
||||
UnaryMessageDefault = "unary"
|
||||
)
|
||||
|
||||
// LogIncomingCall of gRPC method.
|
||||
// {
|
||||
// ServiceField: ExampleService,
|
||||
// MethodField: ExampleMethod,
|
||||
// DurationField: 1.00,
|
||||
// }
|
||||
func LogIncomingCall(ctx context.Context, logger *zerolog.Event, method string, t time.Time, req interface{}) {
|
||||
LogTimestamp(logger, t)
|
||||
LogService(logger, method)
|
||||
LogMethod(logger, method)
|
||||
LogDuration(logger, t)
|
||||
LogRequest(logger, req)
|
||||
LogIncomingMetadata(ctx, logger)
|
||||
}
|
||||
|
||||
// LogTimestamp of call.
|
||||
// {
|
||||
// TimestampField: Timestamp,
|
||||
// }
|
||||
func LogTimestamp(logger *zerolog.Event, t time.Time) {
|
||||
if TimestampLog {
|
||||
*logger = *logger.Time(zerolog.TimestampFieldName, t)
|
||||
}
|
||||
}
|
||||
|
||||
// LogService of gRPC name.
|
||||
// {
|
||||
// ServiceField: gRPCServiceName,
|
||||
// }
|
||||
func LogService(logger *zerolog.Event, method string) {
|
||||
if ServiceLog {
|
||||
*logger = *logger.Str(ServiceField, path.Dir(method)[1:])
|
||||
}
|
||||
}
|
||||
|
||||
// LogMethod of gRPC call.
|
||||
// {
|
||||
// MethodField: gRPCMethodName,
|
||||
// }
|
||||
func LogMethod(logger *zerolog.Event, method string) {
|
||||
if MethodLog {
|
||||
*logger = *logger.Str(MethodField, path.Base(method))
|
||||
}
|
||||
}
|
||||
|
||||
// LogDuration in seconds of gRPC call.
|
||||
// {
|
||||
// DurationField: Timestamp,
|
||||
// }
|
||||
func LogDuration(logger *zerolog.Event, t time.Time) {
|
||||
if DurationLog {
|
||||
*logger = *logger.Dur(DurationField, time.Since(t))
|
||||
}
|
||||
}
|
||||
|
||||
// LogIP address of gRPC client, if assigned.
|
||||
// {
|
||||
// IpField: 127.0.0.1
|
||||
// }
|
||||
func LogIP(ctx context.Context, logger *zerolog.Event) {
|
||||
if IPLog {
|
||||
if p, ok := peer.FromContext(ctx); ok {
|
||||
*logger = *logger.Str(IPField, p.Addr.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// LogRequest in JSON of gRPC Call, given Request is smaller than MaxSize (Default=2MB).
|
||||
// {
|
||||
// ReqField: {}
|
||||
// }
|
||||
func LogRequest(e *zerolog.Event, req interface{}) {
|
||||
if ReqLog {
|
||||
if b := GetRawJSON(req); b != nil {
|
||||
*e = *e.RawJSON(ReqField, b.Bytes())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// LogResponse in JSON of gRPC Call, given Response is smaller than MaxSize (Default=2MB).
|
||||
// {
|
||||
// RespField: {}
|
||||
// }
|
||||
func LogResponse(e *zerolog.Event, resp interface{}) {
|
||||
if RespLog {
|
||||
if b := GetRawJSON(resp); b != nil {
|
||||
*e = *e.RawJSON(RespField, b.Bytes())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetRawJSON converts a Protobuf message to JSON bytes if less than MaxSize.
|
||||
func GetRawJSON(i interface{}) *bytes.Buffer {
|
||||
if pb, ok := i.(proto.Message); ok {
|
||||
b := &bytes.Buffer{}
|
||||
if err := Marshaller.Marshal(b, pb); err == nil && b.Len() < MaxSize {
|
||||
return b
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// LogIncomingMetadata or UserAgent field of incoming gRPC Request, if assigned.
|
||||
// {
|
||||
// MetadataField: {
|
||||
// MetadataKey1: MetadataValue1,
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// {
|
||||
// UserAgentField: "Client-assigned User-Agent",
|
||||
// }
|
||||
func LogIncomingMetadata(ctx context.Context, e *zerolog.Event) {
|
||||
if md, ok := metadata.FromIncomingContext(ctx); ok {
|
||||
if MetadataLog {
|
||||
*e = *e.Dict(MetadataField, LogMetadata(&md))
|
||||
return
|
||||
} else if UserAgentLog {
|
||||
LogUserAgent(e, &md)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// LogMetadata of gRPC Request
|
||||
// {
|
||||
// MetadataField: {
|
||||
// MetadataKey1: MetadataValue1,
|
||||
// }
|
||||
// }
|
||||
func LogMetadata(md *metadata.MD) *zerolog.Event {
|
||||
dict := zerolog.Dict()
|
||||
for i := range *md {
|
||||
dict = dict.Str(i, strings.Join(md.Get(i), ","))
|
||||
}
|
||||
return dict
|
||||
}
|
||||
|
||||
// LogUserAgent of gRPC Client, if assigned.
|
||||
// {
|
||||
// UserAgentField: "Client-assigned User-Agent",
|
||||
// }
|
||||
func LogUserAgent(logger *zerolog.Event, md *metadata.MD) {
|
||||
if ua := strings.Join(md.Get("user-agent"), ""); ua != "" {
|
||||
*logger = *logger.Str(UserAgentField, ua)
|
||||
}
|
||||
}
|
||||
|
||||
// LogStatusError of gRPC Error Response.
|
||||
// {
|
||||
// Err: "An unexpected error occurred",
|
||||
// CodeField: "Unknown",
|
||||
// MsgField: "Error message returned from the server",
|
||||
// DetailsField: [Errors],
|
||||
// }
|
||||
func LogStatusError(logger *zerolog.Event, err error) {
|
||||
statusErr := status.Convert(err)
|
||||
*logger = *logger.Err(err).Str(CodeField, statusErr.Code().String()).Str(MsgField, statusErr.Message()).Interface(DetailsField, statusErr.Details())
|
||||
}
|
@ -0,0 +1,119 @@
|
||||
package zerolog
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
"google.golang.org/grpc/grpclog"
|
||||
)
|
||||
|
||||
// GrpcLogSetNewZeroLogger sets grpclog to a new GrpcZeroLogger.
|
||||
func GrpcLogSetNewZeroLogger() {
|
||||
grpclog.SetLoggerV2(NewGrpcZeroLogger(zerolog.Logger{}))
|
||||
}
|
||||
|
||||
// GrpcLogSetZeroLogger sets grpclog to a GrpcZeroLogger.
|
||||
func GrpcLogSetZeroLogger(logger GrpcZeroLogger) {
|
||||
grpclog.SetLoggerV2(logger)
|
||||
}
|
||||
|
||||
// GrpcZeroLogger transforms grpc log calls to Zerolog logger.
|
||||
type GrpcZeroLogger struct {
|
||||
log zerolog.Logger
|
||||
}
|
||||
|
||||
// NewGrpcZeroLogger creates a new GrpcZeroLogger
|
||||
func NewGrpcZeroLogger(logger zerolog.Logger) GrpcZeroLogger {
|
||||
return GrpcZeroLogger{log: logger}
|
||||
}
|
||||
|
||||
// Fatal fatals arguments.
|
||||
func (l GrpcZeroLogger) Fatal(args ...interface{}) {
|
||||
l.log.Fatal().Msg(fmt.Sprint(args...))
|
||||
}
|
||||
|
||||
// Fatalf fatals formatted string with arguments.
|
||||
func (l GrpcZeroLogger) Fatalf(format string, args ...interface{}) {
|
||||
l.log.Fatal().Msgf(format, args...)
|
||||
}
|
||||
|
||||
// Fatalln fatals and new line.
|
||||
func (l GrpcZeroLogger) Fatalln(args ...interface{}) {
|
||||
l.Fatal(args...)
|
||||
}
|
||||
|
||||
// Error errors arguments.
|
||||
func (l GrpcZeroLogger) Error(args ...interface{}) {
|
||||
l.log.Error().Msg(fmt.Sprint(args...))
|
||||
}
|
||||
|
||||
// Errorf errors formatted string with arguments.
|
||||
func (l GrpcZeroLogger) Errorf(format string, args ...interface{}) {
|
||||
l.log.Error().Msgf(format, args...)
|
||||
}
|
||||
|
||||
// Errorln errors and new line.
|
||||
func (l GrpcZeroLogger) Errorln(args ...interface{}) {
|
||||
l.Error(args...)
|
||||
}
|
||||
|
||||
// Info infos arguments.
|
||||
func (l GrpcZeroLogger) Info(args ...interface{}) {
|
||||
l.log.Info().Msg(fmt.Sprint(args...))
|
||||
}
|
||||
|
||||
// Infof infos formatted string with arguments.
|
||||
func (l GrpcZeroLogger) Infof(format string, args ...interface{}) {
|
||||
l.log.Info().Msgf(format, args...)
|
||||
}
|
||||
|
||||
// Infoln infos and new line.
|
||||
func (l GrpcZeroLogger) Infoln(args ...interface{}) {
|
||||
l.Info(args...)
|
||||
}
|
||||
|
||||
// Warning warns arguments.
|
||||
func (l GrpcZeroLogger) Warning(args ...interface{}) {
|
||||
l.log.Warn().Msg(fmt.Sprint(args...))
|
||||
}
|
||||
|
||||
// Warningf warns formatted string with arguments.
|
||||
func (l GrpcZeroLogger) Warningf(format string, args ...interface{}) {
|
||||
l.log.Warn().Msgf(format, args...)
|
||||
}
|
||||
|
||||
// Warningln warns and new line.
|
||||
func (l GrpcZeroLogger) Warningln(args ...interface{}) {
|
||||
l.Warning(args...)
|
||||
}
|
||||
|
||||
// Print logs arguments.
|
||||
func (l GrpcZeroLogger) Print(args ...interface{}) {
|
||||
l.Info(args...)
|
||||
}
|
||||
|
||||
// Printf logs formatted string with arguments.
|
||||
func (l GrpcZeroLogger) Printf(format string, args ...interface{}) {
|
||||
l.Infof(format, args...)
|
||||
}
|
||||
|
||||
// Println logs with new line.
|
||||
func (l GrpcZeroLogger) Println(args ...interface{}) {
|
||||
l.Infoln(args...)
|
||||
}
|
||||
|
||||
// V determines Verbosity Level.
|
||||
func (l GrpcZeroLogger) V(level int) bool {
|
||||
switch level {
|
||||
case 0:
|
||||
return zerolog.InfoLevel <= zerolog.GlobalLevel()
|
||||
case 1:
|
||||
return zerolog.WarnLevel <= zerolog.GlobalLevel()
|
||||
case 2:
|
||||
return zerolog.ErrorLevel <= zerolog.GlobalLevel()
|
||||
case 3:
|
||||
return zerolog.FatalLevel <= zerolog.GlobalLevel()
|
||||
default:
|
||||
panic("unhandled gRPC logger level")
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue