Compare commits

...

2 Commits

Author SHA1 Message Date
Marvin Preuss 63a16a3253 modifies the command line interface handling / timer work
continuous-integration/drone/push Build was killed Details
continuous-integration/drone Build was killed Details
3 years ago
Marvin Preuss d5a7d1d9a4 wip: timer 3 years ago

@ -33,12 +33,13 @@ steps:
- name: swap
path: /SWAP
commands:
- apk add --no-cache openssh-client
- wget https://raw.githubusercontent.com/xsteadfastx/docker-qemu-alpine/main/ssh
- chmod 600 ssh
- apk add --no-cache openssh-client curl
- mkdir ~/.ssh
- curl https://raw.githubusercontent.com/xsteadfastx/docker-qemu-alpine/main/ssh -o ~/.ssh/id_rsa
- chmod 600 ~/.ssh/id_rsa
- wget -O /usr/local/bin/don https://git.xsfx.dev/attachments/8f8f4dbb-8254-448a-a549-552f8b96cb26
- chmod +x /usr/local/bin/don
- don -t 15m -r 15s -c "ssh -i ssh -o StrictHostKeyChecking=no -o ConnectTimeout=10 root@qemu-alpine"
- don -t 15m -r 15s -c "ssh -o StrictHostKeyChecking=no -o ConnectTimeout=10 root@qemu-alpine"
- cat scripts/rpi-image-test/build.sh | ssh -i ssh -o StrictHostKeyChecking=no root@qemu-alpine
- ssh -i ssh -o StrictHostKeyChecking=no root@qemu-alpine poweroff
- ls -lah /SWAP
@ -55,8 +56,6 @@ steps:
- make test-integration
depends_on:
- test
- docker
- qemu-alpine
- create-image
- name: create-torrent
@ -108,8 +107,6 @@ steps:
depends_on:
- test
- lint
- prepare-image
- docker
when:
event:
- tag

@ -2,6 +2,7 @@ syntax = "proto3";
package schnutibox.v1;
option go_package = "go.xsfx.dev/schnutibox/pkg/api/v1";
import "google/api/annotations.proto";
import "google/protobuf/duration.proto";
service IdentifierService {
rpc Identify (IdentifyRequest) returns (IdentifyResponse) {
@ -20,3 +21,25 @@ message IdentifyResponse {
string name = 1;
repeated string uris = 2;
}
service TimerService {
rpc Create(Timer) returns (Timer) {
option (google.api.http) = {
post: "/v1/timer/set"
body: "*"
};
}
rpc Get(TimerEmpty) returns (Timer) {
option (google.api.http) = {
get: "/v1/timer/get"
};
}
}
message Timer {
google.protobuf.Duration duration = 1;
google.protobuf.Duration current = 2;
}
message TimerEmpty {}

@ -7,6 +7,9 @@
"tags": [
{
"name": "IdentifierService"
},
{
"name": "TimerService"
}
],
"consumes": [
@ -47,6 +50,60 @@
"IdentifierService"
]
}
},
"/v1/timer/get": {
"get": {
"operationId": "TimerService_Get",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/v1Timer"
}
},
"default": {
"description": "An unexpected error response.",
"schema": {
"$ref": "#/definitions/rpcStatus"
}
}
},
"tags": [
"TimerService"
]
}
},
"/v1/timer/set": {
"post": {
"operationId": "TimerService_Create",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/v1Timer"
}
},
"default": {
"description": "An unexpected error response.",
"schema": {
"$ref": "#/definitions/rpcStatus"
}
}
},
"parameters": [
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/v1Timer"
}
}
],
"tags": [
"TimerService"
]
}
}
},
"definitions": {
@ -101,6 +158,17 @@
}
}
}
},
"v1Timer": {
"type": "object",
"properties": {
"duration": {
"type": "string"
},
"current": {
"type": "string"
}
}
}
}
}

@ -4,6 +4,6 @@ deps:
owner: beta
repository: googleapis
branch: main
commit: 2e73676eef8642dfba4ed782b7c8d6fe
digest: b1-vB11w98W2vFtEP4Veknm56Pi6DU6MpOuocESiOzvbqw=
create_time: 2021-04-26T14:55:30.644663Z
commit: 1c473ad9220a49bca9320f4cc690eba5
digest: b1-unlhrcI3tnJd0JEGuOb692LZ_tY_gCGq6mK1bgCn1Pg=
create_time: 2021-06-23T20:16:47.788079Z

@ -1,15 +1,17 @@
//nolint:exhaustivestruct,gochecknoglobals,gochecknoinits
//nolint:exhaustivestruct,gochecknoglobals,gochecknoinits,gomnd
package cmd
import (
"fmt"
"os"
"strings"
"time"
"github.com/fsnotify/fsnotify"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/spf13/viper"
"go.xsfx.dev/schnutibox/internal/config"
"go.xsfx.dev/schnutibox/pkg/prepare"
@ -27,7 +29,12 @@ var rootCmd = &cobra.Command{
}
// init initializes the command line interface.
//
// nolint:funlen
func init() {
// Root.
rootCmd.PersistentFlags().Bool("pprof", false, "Enables pprof for debugging")
// Run.
rootCmd.AddCommand(runCmd)
runCmd.Flags().StringVarP(&cfgFile, "config", "c", "", "config file")
@ -36,6 +43,8 @@ func init() {
log.Fatal().Err(err).Msg("missing flag")
}
runCmd.Flags().Bool("ignore-reader", false, "Ignoring that the reader is missing")
// Prepare.
rootCmd.AddCommand(prepareCmd)
prepareCmd.Flags().BoolVar(&prepare.Cfg.ReadOnly, "read-only", false, "Setup read-only system")
@ -57,20 +66,26 @@ func init() {
if err := webCmd.MarkFlagRequired("config"); err != nil {
log.Fatal().Err(err).Msg("missing flag")
}
}
// initConfig loads the config file.
// fatal defines if config parsing should end in a fatal error or not.
func initConfig(fatal bool) {
logger := log.With().Str("config", cfgFile).Logger()
// Timer.
rootCmd.AddCommand(timerCmd)
timerCmd.Flags().String("hostname", "localhost", "Hostname of schnutibox")
timerCmd.Flags().Int("port", 6600, "Port of schnutibox")
timerCmd.Flags().DurationP("duration", "d", time.Minute, "Duration until the timer stops the playback")
if err := timerCmd.MarkFlagRequired("duration"); err != nil {
log.Fatal().Err(err).Msg("missing flag")
}
// Defaults.
viper.SetDefault("box.hostname", "localhost")
viper.SetDefault("box.port", 9999)
viper.SetDefault("box.grpc", 9998)
viper.SetDefault("mpd.hostname", "localhost")
viper.SetDefault("mpd.port", 6600)
viper.SetDefault("reader.dev", "/dev/hidraw0")
viper.SetDefault("reader.ignore", false)
viper.SetDefault("debug.pprof", false)
viper.SetDefault("timer.duration", time.Minute)
// Environment handling.
viper.SetEnvPrefix("SCHNUTIBOX")
@ -78,23 +93,45 @@ func initConfig(fatal bool) {
viper.AutomaticEnv()
// Flags.
if err := viper.BindPFlag("reader.dev", prepareCmd.Flags().Lookup("rfid-reader")); err != nil {
logger.Fatal().Err(err).Msg("could not bind flag")
for k, v := range map[string]*pflag.Flag{
"debug.pprof": rootCmd.PersistentFlags().Lookup("pprof"),
"reader.dev": prepareCmd.Flags().Lookup("rfid-reader"),
"reader.ignore": runCmd.Flags().Lookup("ignore-reader"),
"box.hostname": timerCmd.Flags().Lookup("hostname"),
"box.port": timerCmd.Flags().Lookup("port"),
"timer.duration": timerCmd.Flags().Lookup("duration"),
} {
if err := viper.BindPFlag(k, v); err != nil {
log.Fatal().Err(err).Msg("could not bind flag")
}
}
}
// initConfig loads the config file.
// fatal defines if config parsing should end in a fatal error or not.
func initConfig(fatal bool) {
logger := log.With().Str("config", cfgFile).Logger()
// Parse config file.
if cfgFile != "" {
viper.SetConfigFile(cfgFile)
parseConfig(logger, fatal)
} else {
if cfgFile == "" && fatal {
logger.Fatal().Msg("missing config file")
} else {
logger.Warn().Msg("missing config file")
}
viper.WatchConfig()
viper.OnConfigChange(func(e fsnotify.Event) {
logger.Info().Msg("config file changed")
parseConfig(logger, false)
})
// Dont mind if there is no config file... viper also should populate
// flags and environment variables.
viper.SetConfigFile(cfgFile)
parseConfig(logger, fatal)
// Configfile changes watch only enabled if there is a config file.
if cfgFile != "" {
viper.WatchConfig()
viper.OnConfigChange(func(e fsnotify.Event) {
logger.Info().Msg("config file changed")
parseConfig(logger, false)
})
}
}
// parseConfig parses the config and does some tests if required fields are there.
@ -106,8 +143,6 @@ func parseConfig(logger zerolog.Logger, fatal bool) {
}
logger.Error().Err(err).Msg("error loading config file")
return
}
if err := viper.Unmarshal(&config.Cfg); err != nil {
@ -116,16 +151,22 @@ func parseConfig(logger zerolog.Logger, fatal bool) {
}
logger.Error().Err(err).Msg("could not unmarshal config")
return
}
if err := config.Cfg.Require(); err != nil {
if fatal {
logger.Fatal().Err(err).Msg("missing config parts")
}
// Disabling require check if no config is set.
// Not sure about this!
if cfgFile != "" {
if err := config.Cfg.Require(); err != nil {
if fatal {
logger.Fatal().Err(err).Msg("missing config parts")
}
logger.Error().Err(err).Msg("missing config parts")
logger.Error().Err(err).Msg("missing config parts")
return
}
} else {
logger.Warn().Msg("doesnt do a config requirement check")
return
}

@ -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)
},
}

@ -8,14 +8,17 @@ require (
github.com/containerd/continuity v0.0.0-20210208174643-50096c924a4e // indirect
github.com/fhs/gompd/v2 v2.2.0
github.com/fsnotify/fsnotify v1.4.7
github.com/golang/protobuf v1.5.2
github.com/grpc-ecosystem/grpc-gateway/v2 v2.4.0
github.com/helloyi/go-sshclient v1.0.0
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/moby/term v0.0.0-20201216013528-df9cb8a40635 // indirect
github.com/ory/dockertest/v3 v3.6.3
github.com/philip-bui/grpc-zerolog v1.0.1
github.com/prometheus/client_golang v0.9.3
github.com/rs/zerolog v1.22.0
github.com/spf13/cobra v1.1.3
github.com/spf13/pflag v1.0.5
github.com/spf13/viper v1.7.0
github.com/tmc/scp v0.0.0-20170824174625-f7b48647feef
go.xsfx.dev/don v1.0.0

@ -274,6 +274,8 @@ github.com/ory/dockertest/v3 v3.6.3/go.mod h1:EFLcVUOl8qCwp9NyDAcCDtq/QviLtYswW/
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/philip-bui/grpc-zerolog v1.0.1 h1:EMacvLRUd2O1K0eWod27ZP5CY1iTNkhBDLSN+Q4JEvA=
github.com/philip-bui/grpc-zerolog v1.0.1/go.mod h1:qXbiq/2X4ZUMMshsqlWyTHOcw7ns+GZmlqZZN05ZHcQ=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
@ -336,6 +338,7 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An
github.com/spf13/viper v1.7.0 h1:xVKxvI7ouOI5I+U9s2eeiUfMaWBVoXA3AWskkrqK0VM=
github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=

@ -11,6 +11,10 @@ import (
var Cfg Config
type Config struct {
Debug struct {
PPROF bool `mapstructure:"PPROF"`
}
// Reader is used to configure the RFID Reader.
Reader struct {
Dev string `mapstructure:"Dev"`

@ -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
}

@ -31,6 +31,15 @@ var Play = promauto.NewGaugeVec(
[]string{"rfid", "name"},
)
// Seconds tracks the seconds a play was played.
var Seconds = promauto.NewCounterVec(
prometheus.CounterOpts{
Name: "schnutibox_play_seconds_total",
Help: "play seconds metrics",
},
[]string{"rfid", "name"},
)
// BoxErrors counts schnutibox errors.
var BoxErrors = promauto.NewCounter(
prometheus.CounterOpts{
@ -56,10 +65,12 @@ func tracksEqual(a, b []string) bool {
}
// Set sets `1` on play gauge if item is playing, a `0` on every other play.
// It also raises the counter for played seconds of a play.
func Set(uris []string, state string) {
for r, p := range Plays {
if tracksEqual(uris, p.Uris) && state == "play" {
Play.WithLabelValues(r, p.Name).Set(1)
Seconds.WithLabelValues(r, p.Name).Inc()
} else {
Play.WithLabelValues(r, p.Name).Set(0)
}

@ -7,6 +7,7 @@
package v1
import (
duration "github.com/golang/protobuf/ptypes/duration"
_ "google.golang.org/genproto/googleapis/api/annotations"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
@ -123,30 +124,144 @@ func (x *IdentifyResponse) GetUris() []string {
return nil
}
type Timer struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Duration *duration.Duration `protobuf:"bytes,1,opt,name=duration,proto3" json:"duration,omitempty"`
Current *duration.Duration `protobuf:"bytes,2,opt,name=current,proto3" json:"current,omitempty"`
}
func (x *Timer) Reset() {
*x = Timer{}
if protoimpl.UnsafeEnabled {
mi := &file_schnutibox_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Timer) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Timer) ProtoMessage() {}
func (x *Timer) ProtoReflect() protoreflect.Message {
mi := &file_schnutibox_proto_msgTypes[2]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Timer.ProtoReflect.Descriptor instead.
func (*Timer) Descriptor() ([]byte, []int) {
return file_schnutibox_proto_rawDescGZIP(), []int{2}
}
func (x *Timer) GetDuration() *duration.Duration {
if x != nil {
return x.Duration
}
return nil
}
func (x *Timer) GetCurrent() *duration.Duration {
if x != nil {
return x.Current
}
return nil
}
type TimerEmpty struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *TimerEmpty) Reset() {
*x = TimerEmpty{}
if protoimpl.UnsafeEnabled {
mi := &file_schnutibox_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *TimerEmpty) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*TimerEmpty) ProtoMessage() {}
func (x *TimerEmpty) ProtoReflect() protoreflect.Message {
mi := &file_schnutibox_proto_msgTypes[3]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use TimerEmpty.ProtoReflect.Descriptor instead.
func (*TimerEmpty) Descriptor() ([]byte, []int) {
return file_schnutibox_proto_rawDescGZIP(), []int{3}
}
var File_schnutibox_proto protoreflect.FileDescriptor
var file_schnutibox_proto_rawDesc = []byte{
0x0a, 0x10, 0x73, 0x63, 0x68, 0x6e, 0x75, 0x74, 0x69, 0x62, 0x6f, 0x78, 0x2e, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x12, 0x0d, 0x73, 0x63, 0x68, 0x6e, 0x75, 0x74, 0x69, 0x62, 0x6f, 0x78, 0x2e, 0x76,
0x31, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x61, 0x6e,
0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22,
0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a,
0x1e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66,
0x2f, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22,
0x21, 0x0a, 0x0f, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02,
0x69, 0x64, 0x22, 0x3a, 0x0a, 0x10, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x79, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01,
0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x75, 0x72,
0x69, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x04, 0x75, 0x72, 0x69, 0x73, 0x32, 0x79,
0x0a, 0x11, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x53, 0x65, 0x72, 0x76,
0x69, 0x63, 0x65, 0x12, 0x64, 0x0a, 0x08, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x79, 0x12,
0x1e, 0x2e, 0x73, 0x63, 0x68, 0x6e, 0x75, 0x74, 0x69, 0x62, 0x6f, 0x78, 0x2e, 0x76, 0x31, 0x2e,
0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
0x1f, 0x2e, 0x73, 0x63, 0x68, 0x6e, 0x75, 0x74, 0x69, 0x62, 0x6f, 0x78, 0x2e, 0x76, 0x31, 0x2e,
0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x22, 0x17, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x11, 0x22, 0x0c, 0x2f, 0x76, 0x31, 0x2f, 0x69, 0x64,
0x65, 0x6e, 0x74, 0x69, 0x66, 0x79, 0x3a, 0x01, 0x2a, 0x42, 0x23, 0x5a, 0x21, 0x67, 0x6f, 0x2e,
0x78, 0x73, 0x66, 0x78, 0x2e, 0x64, 0x65, 0x76, 0x2f, 0x73, 0x63, 0x68, 0x6e, 0x75, 0x74, 0x69,
0x62, 0x6f, 0x78, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x62, 0x06,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
0x69, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x04, 0x75, 0x72, 0x69, 0x73, 0x22, 0x73,
0x0a, 0x05, 0x54, 0x69, 0x6d, 0x65, 0x72, 0x12, 0x35, 0x0a, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74,
0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67,
0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61,
0x74, 0x69, 0x6f, 0x6e, 0x52, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x33,
0x0a, 0x07, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32,
0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,
0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x07, 0x63, 0x75, 0x72, 0x72,
0x65, 0x6e, 0x74, 0x22, 0x0c, 0x0a, 0x0a, 0x54, 0x69, 0x6d, 0x65, 0x72, 0x45, 0x6d, 0x70, 0x74,
0x79, 0x32, 0x79, 0x0a, 0x11, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x53,
0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x64, 0x0a, 0x08, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69,
0x66, 0x79, 0x12, 0x1e, 0x2e, 0x73, 0x63, 0x68, 0x6e, 0x75, 0x74, 0x69, 0x62, 0x6f, 0x78, 0x2e,
0x76, 0x31, 0x2e, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x73, 0x63, 0x68, 0x6e, 0x75, 0x74, 0x69, 0x62, 0x6f, 0x78, 0x2e,
0x76, 0x31, 0x2e, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x22, 0x17, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x11, 0x22, 0x0c, 0x2f, 0x76, 0x31,
0x2f, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x79, 0x3a, 0x01, 0x2a, 0x32, 0xad, 0x01, 0x0a,
0x0c, 0x54, 0x69, 0x6d, 0x65, 0x72, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x4e, 0x0a,
0x06, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x12, 0x14, 0x2e, 0x73, 0x63, 0x68, 0x6e, 0x75, 0x74,
0x69, 0x62, 0x6f, 0x78, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x72, 0x1a, 0x14, 0x2e,
0x73, 0x63, 0x68, 0x6e, 0x75, 0x74, 0x69, 0x62, 0x6f, 0x78, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69,
0x6d, 0x65, 0x72, 0x22, 0x18, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x12, 0x22, 0x0d, 0x2f, 0x76, 0x31,
0x2f, 0x74, 0x69, 0x6d, 0x65, 0x72, 0x2f, 0x73, 0x65, 0x74, 0x3a, 0x01, 0x2a, 0x12, 0x4d, 0x0a,
0x03, 0x47, 0x65, 0x74, 0x12, 0x19, 0x2e, 0x73, 0x63, 0x68, 0x6e, 0x75, 0x74, 0x69, 0x62, 0x6f,
0x78, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x72, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a,
0x14, 0x2e, 0x73, 0x63, 0x68, 0x6e, 0x75, 0x74, 0x69, 0x62, 0x6f, 0x78, 0x2e, 0x76, 0x31, 0x2e,
0x54, 0x69, 0x6d, 0x65, 0x72, 0x22, 0x15, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0f, 0x12, 0x0d, 0x2f,
0x76, 0x31, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x72, 0x2f, 0x67, 0x65, 0x74, 0x42, 0x23, 0x5a, 0x21,
0x67, 0x6f, 0x2e, 0x78, 0x73, 0x66, 0x78, 0x2e, 0x64, 0x65, 0x76, 0x2f, 0x73, 0x63, 0x68, 0x6e,
0x75, 0x74, 0x69, 0x62, 0x6f, 0x78, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76,
0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
@ -161,19 +276,28 @@ func file_schnutibox_proto_rawDescGZIP() []byte {
return file_schnutibox_proto_rawDescData
}
var file_schnutibox_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
var file_schnutibox_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
var file_schnutibox_proto_goTypes = []interface{}{
(*IdentifyRequest)(nil), // 0: schnutibox.v1.IdentifyRequest
(*IdentifyResponse)(nil), // 1: schnutibox.v1.IdentifyResponse
(*IdentifyRequest)(nil), // 0: schnutibox.v1.IdentifyRequest
(*IdentifyResponse)(nil), // 1: schnutibox.v1.IdentifyResponse
(*Timer)(nil), // 2: schnutibox.v1.Timer
(*TimerEmpty)(nil), // 3: schnutibox.v1.TimerEmpty
(*duration.Duration)(nil), // 4: google.protobuf.Duration
}
var file_schnutibox_proto_depIdxs = []int32{
0, // 0: schnutibox.v1.IdentifierService.Identify:input_type -> schnutibox.v1.IdentifyRequest
1, // 1: schnutibox.v1.IdentifierService.Identify:output_type -> schnutibox.v1.IdentifyResponse
1, // [1:2] is the sub-list for method output_type
0, // [0:1] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
4, // 0: schnutibox.v1.Timer.duration:type_name -> google.protobuf.Duration
4, // 1: schnutibox.v1.Timer.current:type_name -> google.protobuf.Duration
0, // 2: schnutibox.v1.IdentifierService.Identify:input_type -> schnutibox.v1.IdentifyRequest
2, // 3: schnutibox.v1.TimerService.Create:input_type -> schnutibox.v1.Timer
3, // 4: schnutibox.v1.TimerService.Get:input_type -> schnutibox.v1.TimerEmpty
1, // 5: schnutibox.v1.IdentifierService.Identify:output_type -> schnutibox.v1.IdentifyResponse
2, // 6: schnutibox.v1.TimerService.Create:output_type -> schnutibox.v1.Timer
2, // 7: schnutibox.v1.TimerService.Get:output_type -> schnutibox.v1.Timer
5, // [5:8] is the sub-list for method output_type
2, // [2:5] is the sub-list for method input_type
2, // [2:2] is the sub-list for extension type_name
2, // [2:2] is the sub-list for extension extendee
0, // [0:2] is the sub-list for field type_name
}
func init() { file_schnutibox_proto_init() }
@ -206,6 +330,30 @@ func file_schnutibox_proto_init() {
return nil
}
}
file_schnutibox_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Timer); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_schnutibox_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*TimerEmpty); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
@ -213,9 +361,9 @@ func file_schnutibox_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_schnutibox_proto_rawDesc,
NumEnums: 0,
NumMessages: 2,
NumMessages: 4,
NumExtensions: 0,
NumServices: 1,
NumServices: 2,
},
GoTypes: file_schnutibox_proto_goTypes,
DependencyIndexes: file_schnutibox_proto_depIdxs,

@ -65,6 +65,58 @@ func local_request_IdentifierService_Identify_0(ctx context.Context, marshaler r
}
func request_TimerService_Create_0(ctx context.Context, marshaler runtime.Marshaler, client TimerServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq Timer
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.Create(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_TimerService_Create_0(ctx context.Context, marshaler runtime.Marshaler, server TimerServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq Timer
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.Create(ctx, &protoReq)
return msg, metadata, err
}
func request_TimerService_Get_0(ctx context.Context, marshaler runtime.Marshaler, client TimerServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq TimerEmpty
var metadata runtime.ServerMetadata
msg, err := client.Get(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_TimerService_Get_0(ctx context.Context, marshaler runtime.Marshaler, server TimerServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq TimerEmpty
var metadata runtime.ServerMetadata
msg, err := server.Get(ctx, &protoReq)
return msg, metadata, err
}
// RegisterIdentifierServiceHandlerServer registers the http handlers for service IdentifierService to "mux".
// UnaryRPC :call IdentifierServiceServer directly.
// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906.
@ -97,6 +149,61 @@ func RegisterIdentifierServiceHandlerServer(ctx context.Context, mux *runtime.Se
return nil
}
// RegisterTimerServiceHandlerServer registers the http handlers for service TimerService to "mux".
// UnaryRPC :call TimerServiceServer directly.
// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906.
// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterTimerServiceHandlerFromEndpoint instead.
func RegisterTimerServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux, server TimerServiceServer) error {
mux.Handle("POST", pattern_TimerService_Create_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/schnutibox.v1.TimerService/Create")
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_TimerService_Create_0(rctx, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
ctx = runtime.NewServerMetadataContext(ctx, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
forward_TimerService_Create_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_TimerService_Get_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/schnutibox.v1.TimerService/Get")
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_TimerService_Get_0(rctx, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
ctx = runtime.NewServerMetadataContext(ctx, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
forward_TimerService_Get_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
return nil
}
// RegisterIdentifierServiceHandlerFromEndpoint is same as RegisterIdentifierServiceHandler but
// automatically dials to "endpoint" and closes the connection when "ctx" gets done.
func RegisterIdentifierServiceHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) {
@ -165,3 +272,96 @@ var (
var (
forward_IdentifierService_Identify_0 = runtime.ForwardResponseMessage
)
// RegisterTimerServiceHandlerFromEndpoint is same as RegisterTimerServiceHandler but
// automatically dials to "endpoint" and closes the connection when "ctx" gets done.
func RegisterTimerServiceHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) {
conn, err := grpc.Dial(endpoint, opts...)
if err != nil {
return err
}
defer func() {
if err != nil {
if cerr := conn.Close(); cerr != nil {
grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr)
}
return
}
go func() {
<-ctx.Done()
if cerr := conn.Close(); cerr != nil {
grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr)
}
}()
}()
return RegisterTimerServiceHandler(ctx, mux, conn)
}
// RegisterTimerServiceHandler registers the http handlers for service TimerService to "mux".
// The handlers forward requests to the grpc endpoint over "conn".
func RegisterTimerServiceHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error {
return RegisterTimerServiceHandlerClient(ctx, mux, NewTimerServiceClient(conn))
}
// RegisterTimerServiceHandlerClient registers the http handlers for service TimerService
// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "TimerServiceClient".
// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "TimerServiceClient"
// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in
// "TimerServiceClient" to call the correct interceptors.
func RegisterTimerServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux, client TimerServiceClient) error {
mux.Handle("POST", pattern_TimerService_Create_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateContext(ctx, mux, req, "/schnutibox.v1.TimerService/Create")
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_TimerService_Create_0(rctx, inboundMarshaler, client, req, pathParams)
ctx = runtime.NewServerMetadataContext(ctx, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
forward_TimerService_Create_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_TimerService_Get_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateContext(ctx, mux, req, "/schnutibox.v1.TimerService/Get")
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_TimerService_Get_0(rctx, inboundMarshaler, client, req, pathParams)
ctx = runtime.NewServerMetadataContext(ctx, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
forward_TimerService_Get_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
return nil
}
var (
pattern_TimerService_Create_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "timer", "set"}, ""))
pattern_TimerService_Get_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "timer", "get"}, ""))
)
var (
forward_TimerService_Create_0 = runtime.ForwardResponseMessage
forward_TimerService_Get_0 = runtime.ForwardResponseMessage
)

@ -97,3 +97,123 @@ var IdentifierService_ServiceDesc = grpc.ServiceDesc{
Streams: []grpc.StreamDesc{},
Metadata: "schnutibox.proto",
}
// TimerServiceClient is the client API for TimerService service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type TimerServiceClient interface {
Create(ctx context.Context, in *Timer, opts ...grpc.CallOption) (*Timer, error)
Get(ctx context.Context, in *TimerEmpty, opts ...grpc.CallOption) (*Timer, error)
}
type timerServiceClient struct {
cc grpc.ClientConnInterface
}
func NewTimerServiceClient(cc grpc.ClientConnInterface) TimerServiceClient {
return &timerServiceClient{cc}
}
func (c *timerServiceClient) Create(ctx context.Context, in *Timer, opts ...grpc.CallOption) (*Timer, error) {
out := new(Timer)
err := c.cc.Invoke(ctx, "/schnutibox.v1.TimerService/Create", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *timerServiceClient) Get(ctx context.Context, in *TimerEmpty, opts ...grpc.CallOption) (*Timer, error) {
out := new(Timer)
err := c.cc.Invoke(ctx, "/schnutibox.v1.TimerService/Get", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// TimerServiceServer is the server API for TimerService service.
// All implementations should embed UnimplementedTimerServiceServer
// for forward compatibility
type TimerServiceServer interface {
Create(context.Context, *Timer) (*Timer, error)
Get(context.Context, *TimerEmpty) (*Timer, error)
}
// UnimplementedTimerServiceServer should be embedded to have forward compatible implementations.
type UnimplementedTimerServiceServer struct {
}
func (UnimplementedTimerServiceServer) Create(context.Context, *Timer) (*Timer, error) {
return nil, status.Errorf(codes.Unimplemented, "method Create not implemented")
}
func (UnimplementedTimerServiceServer) Get(context.Context, *TimerEmpty) (*Timer, error) {
return nil, status.Errorf(codes.Unimplemented, "method Get not implemented")
}
// UnsafeTimerServiceServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to TimerServiceServer will
// result in compilation errors.
type UnsafeTimerServiceServer interface {
mustEmbedUnimplementedTimerServiceServer()
}
func RegisterTimerServiceServer(s grpc.ServiceRegistrar, srv TimerServiceServer) {
s.RegisterService(&TimerService_ServiceDesc, srv)
}
func _TimerService_Create_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(Timer)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(TimerServiceServer).Create(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/schnutibox.v1.TimerService/Create",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(TimerServiceServer).Create(ctx, req.(*Timer))
}
return interceptor(ctx, in, info, handler)
}
func _TimerService_Get_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(TimerEmpty)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(TimerServiceServer).Get(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/schnutibox.v1.TimerService/Get",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(TimerServiceServer).Get(ctx, req.(*TimerEmpty))
}
return interceptor(ctx, in, info, handler)
}
// TimerService_ServiceDesc is the grpc.ServiceDesc for TimerService service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var TimerService_ServiceDesc = grpc.ServiceDesc{
ServiceName: "schnutibox.v1.TimerService",
HandlerType: (*TimerServiceServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "Create",
Handler: _TimerService_Create_Handler,
},
{
MethodName: "Get",
Handler: _TimerService_Get_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "schnutibox.proto",
}

@ -7,13 +7,11 @@ import (
"github.com/fhs/gompd/v2/mpd"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"go.xsfx.dev/schnutibox/internal/config"
"go.xsfx.dev/schnutibox/internal/metrics"
)
const (
tickerTime = time.Second
timeout = 5 * time.Second
timeoutWait = time.Second / 2
)
@ -30,7 +28,7 @@ func Conn() (*mpd.Client, error) {
default:
c, err := mpd.Dial("tcp", fmt.Sprintf("%s:%d", config.Cfg.MPD.Hostname, config.Cfg.MPD.Port))
if err != nil {
log.Error().Err(err).Msg("could not connect")
// log.Error().Err(err).Msg("could not connect")
time.Sleep(timeoutWait)
@ -71,45 +69,6 @@ func PlaylistURIS() ([]string, error) {
return uris, nil
}
func Watcher() {
log.Debug().Msg("starting watch")
ticker := time.NewTicker(tickerTime)
go func() {
for {
<-ticker.C
m, err := Conn()
if err != nil {
log.Error().Err(err).Msg("could not connect")
continue
}
uris, err := PlaylistURIS()
if err != nil {
log.Error().Err(err).Msg("could not get playlist uris")
metrics.BoxErrors.Inc()
continue
}
// Gettings MPD state.
s, err := m.Status()
if err != nil {
log.Error().Err(err).Msg("could not get status")
metrics.BoxErrors.Inc()
continue
}
// Sets the metrics.
metrics.Set(uris, s["state"])
}
}()
}
func Stop(logger zerolog.Logger) error {
logger.Info().Msg("trying to stop playback")

@ -3,9 +3,11 @@ package run
import (
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"go.xsfx.dev/schnutibox/internal/config"
"go.xsfx.dev/schnutibox/pkg/mpc"
"go.xsfx.dev/schnutibox/pkg/rfid"
"go.xsfx.dev/schnutibox/pkg/watcher"
"go.xsfx.dev/schnutibox/pkg/web"
)
@ -16,50 +18,57 @@ func Run(cmd *cobra.Command, args []string) {
r := rfid.NewRFID(config.Cfg, idChan)
if err := r.Run(); err != nil {
log.Fatal().Err(err).Msg("could not start RFID reader")
if !viper.GetBool("reader.ignore") {
log.Fatal().Err(err).Msg("could not start RFID reader")
}
log.Warn().Err(err).Msg("could not start RFID reader. ignoring...")
}
// Stating watcher.
mpc.Watcher()
watcher.Run()
go func() {
var id string
// nolint:nestif
if !viper.GetBool("reader.ignore") {
go func() {
var id string
for {
// Wating for a scanned tag.
id = <-idChan
logger := log.With().Str("id", id).Logger()
logger.Info().Msg("received id")
for {
// Wating for a scanned tag.
id = <-idChan
logger := log.With().Str("id", id).Logger()
logger.Info().Msg("received id")
// Check of stop tag was detected.
if id == config.Cfg.Meta.Stop {
logger.Info().Msg("stopping")
// Check of stop tag was detected.
if id == config.Cfg.Meta.Stop {
logger.Info().Msg("stopping")
if err := mpc.Stop(logger); err != nil {
logger.Error().Err(err).Msg("could not stop")
}
if err := mpc.Stop(logger); err != nil {
logger.Error().Err(err).Msg("could not stop")
}
if err := mpc.Clear(logger); err != nil {
logger.Error().Err(err).Msg("could not clear")
}
if err := mpc.Clear(logger); err != nil {
logger.Error().Err(err).Msg("could not clear")
}
continue
}
continue
}
// Check if there is a track for the ID.
tracks, ok := config.Cfg.Tracks[id]
if !ok {
logger.Error().Msg("could not find track for ID")
// Check if there is a track for the ID.
tracks, ok := config.Cfg.Tracks[id]
if !ok {
logger.Error().Msg("could not find track for ID")
continue
}
continue
}
// Try to play track.
if err := mpc.Play(logger, id, tracks.Name, tracks.Uris); err != nil {
logger.Error().Err(err).Msg("could not play track")
// Try to play track.
if err := mpc.Play(logger, id, tracks.Name, tracks.Uris); err != nil {
logger.Error().Err(err).Msg("could not play track")
}
}
}
}()
}()
}
// Running web interface. Blocking.
web.Run(cmd, args)

@ -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"])
}()
}
}()
}

@ -5,9 +5,11 @@ import (
"fmt"
"html/template"
"net/http"
"net/http/pprof"
"strings"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
zerolog "github.com/philip-bui/grpc-zerolog"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
@ -16,6 +18,7 @@ import (
"go.xsfx.dev/schnutibox/internal/config"
api "go.xsfx.dev/schnutibox/pkg/api/v1"
"go.xsfx.dev/schnutibox/pkg/sselog"
"go.xsfx.dev/schnutibox/pkg/timer"
"golang.org/x/net/http2"
"golang.org/x/net/http2/h2c"
"google.golang.org/grpc"
@ -53,11 +56,11 @@ func grpcHandlerFunc(grpcServer *grpc.Server, otherHandler http.Handler) http.Ha
}), &http2.Server{})
}
type server struct{}
type identifyServer struct{}
// Identify searches in tracks config for entries and returns them.
// nolint:goerr113
func (s server) Identify(ctx context.Context, in *api.IdentifyRequest) (*api.IdentifyResponse, error) {
func (i identifyServer) Identify(ctx context.Context, in *api.IdentifyRequest) (*api.IdentifyResponse, error) {
r := &api.IdentifyResponse{}
if in.Id == "" {
@ -75,11 +78,27 @@ func (s server) Identify(ctx context.Context, in *api.IdentifyRequest) (*api.Ide
return r, nil
}
type timerServer struct{}
func (t timerServer) Create(ctx context.Context, req *api.Timer) (*api.Timer, error) {
timer.T.Req = req
return timer.T.Req, nil
}
// Get just returns the status of the timer.
func (t timerServer) Get(ctx context.Context, req *api.TimerEmpty) (*api.Timer, error) {
return timer.T.Req, nil
}
func gw(s *grpc.Server, conn string) *runtime.ServeMux {
ctx := context.Background()
gopts := []grpc.DialOption{grpc.WithInsecure()}
api.RegisterIdentifierServiceServer(s, server{})
api.RegisterIdentifierServiceServer(s, identifyServer{})
api.RegisterTimerServiceServer(s, timerServer{})
// Adds reflections.
reflection.Register(s)
gwmux := runtime.NewServeMux()
@ -95,7 +114,9 @@ func Run(command *cobra.Command, args []string) {
lh := fmt.Sprintf("%s:%d", config.Cfg.Box.Hostname, config.Cfg.Box.Port)
// Create grpc server.
grpcServer := grpc.NewServer()
grpcServer := grpc.NewServer(
zerolog.UnaryInterceptor(),
)
// Define http handlers.
mux := http.NewServeMux()
@ -118,6 +139,11 @@ func Run(command *cobra.Command, args []string) {
mux.Handle("/api/", http.StripPrefix("/api", gw(grpcServer, lh)))
// PPROF.
if config.Cfg.Debug.PPROF {
mux.HandleFunc("/debug/pprof/", pprof.Index)
}
// Serving http.
log.Info().Msgf("serving HTTP on %s...", lh)

@ -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")
}
}

@ -156,6 +156,7 @@ github.com/golang/glog
# github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e
github.com/golang/groupcache/lru
# github.com/golang/protobuf v1.5.2
## explicit
github.com/golang/protobuf/descriptor
github.com/golang/protobuf/jsonpb
github.com/golang/protobuf/proto
@ -268,6 +269,9 @@ github.com/ory/dockertest/v3/docker/types/strslice
github.com/ory/dockertest/v3/docker/types/versions
# github.com/pelletier/go-toml v1.2.0
github.com/pelletier/go-toml
# github.com/philip-bui/grpc-zerolog v1.0.1
## explicit
github.com/philip-bui/grpc-zerolog
# github.com/pkg/errors v0.9.1
github.com/pkg/errors
# github.com/pkg/profile v1.5.0
@ -306,6 +310,7 @@ github.com/spf13/cobra
# github.com/spf13/jwalterweatherman v1.0.0
github.com/spf13/jwalterweatherman
# github.com/spf13/pflag v1.0.5
## explicit
github.com/spf13/pflag
# github.com/spf13/viper v1.7.0
## explicit

Loading…
Cancel
Save