mirror of
https://github.com/xaionaro-go/streamctl.git
synced 2025-10-23 23:44:11 +08:00
Initial commit, pt. 36
This commit is contained in:
@@ -12,10 +12,14 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/facebookincubator/go-belt"
|
||||
"github.com/facebookincubator/go-belt/tool/experimental/errmon"
|
||||
errmonsentry "github.com/facebookincubator/go-belt/tool/experimental/errmon/implementation/sentry"
|
||||
"github.com/facebookincubator/go-belt/tool/logger"
|
||||
"github.com/facebookincubator/go-belt/tool/logger/implementation/zap"
|
||||
"github.com/facebookincubator/go-belt/tool/logger/implementation/logrus"
|
||||
"github.com/getsentry/sentry-go"
|
||||
"github.com/spf13/pflag"
|
||||
"github.com/xaionaro-go/streamctl/cmd/streamd/ui"
|
||||
"github.com/xaionaro-go/streamctl/pkg/observability"
|
||||
"github.com/xaionaro-go/streamctl/pkg/streamd"
|
||||
"github.com/xaionaro-go/streamctl/pkg/streamd/config"
|
||||
"github.com/xaionaro-go/streamctl/pkg/streamd/grpc/go/streamd_grpc"
|
||||
@@ -36,7 +40,7 @@ func main() {
|
||||
cpuProfile := pflag.String("go-profile-cpu", "", "file to write cpu profile to")
|
||||
heapProfile := pflag.String("go-profile-heap", "", "file to write memory profile to")
|
||||
pflag.Parse()
|
||||
l := zap.Default().WithLevel(loggerLevel)
|
||||
l := logrus.Default().WithLevel(loggerLevel)
|
||||
|
||||
if *cpuProfile != "" {
|
||||
f, err := os.Create(*cpuProfile)
|
||||
@@ -129,8 +133,29 @@ func main() {
|
||||
l.Fatalf("unable to initialize the streamd instance: %v", err)
|
||||
}
|
||||
|
||||
l := l
|
||||
if streamD.Config.SentryDSN != "" {
|
||||
l.Infof("setting up Sentry at DSN '%s'", streamD.Config.SentryDSN)
|
||||
sentryClient, err := sentry.NewClient(sentry.ClientOptions{
|
||||
Dsn: streamD.Config.SentryDSN,
|
||||
})
|
||||
if err != nil {
|
||||
l.Fatal(err)
|
||||
}
|
||||
sentryErrorMonitor := errmonsentry.New(sentryClient)
|
||||
ctx = errmon.CtxWithErrorMonitor(ctx, sentryErrorMonitor)
|
||||
|
||||
l = l.WithPreHooks(observability.NewErrorMonitorLoggerHook(
|
||||
sentryErrorMonitor,
|
||||
))
|
||||
ctx = logger.CtxWithLogger(ctx, l)
|
||||
logger.Default = func() logger.Logger {
|
||||
return l
|
||||
}
|
||||
}
|
||||
|
||||
if err = streamD.Run(ctx); err != nil {
|
||||
l.Fatalf("streamd exited with error: %v", err)
|
||||
l.Errorf("streamd returned an error: %v", err)
|
||||
}
|
||||
|
||||
listener, err := net.Listen("tcp", *listenAddr)
|
||||
|
@@ -1,79 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"runtime"
|
||||
|
||||
"github.com/DataDog/gostackparse"
|
||||
xruntime "github.com/facebookincubator/go-belt/pkg/runtime"
|
||||
errmontypes "github.com/facebookincubator/go-belt/tool/experimental/errmon/types"
|
||||
"github.com/facebookincubator/go-belt/tool/logger"
|
||||
)
|
||||
|
||||
func getGoroutines() ([]errmontypes.Goroutine, int) {
|
||||
// TODO: consider pprof.Lookup("goroutine") instead of runtime.Stack
|
||||
|
||||
// getting all goroutines
|
||||
stackBufferSize := 65536 * runtime.NumGoroutine()
|
||||
if stackBufferSize > 10*1024*1024 {
|
||||
stackBufferSize = 10 * 1024 * 1024
|
||||
}
|
||||
stackBuffer := make([]byte, stackBufferSize)
|
||||
n := runtime.Stack(stackBuffer, true)
|
||||
goroutines, errs := gostackparse.Parse(bytes.NewReader(stackBuffer[:n]))
|
||||
if len(errs) > 0 { //nolint:staticcheck
|
||||
// TODO: do something
|
||||
}
|
||||
|
||||
// convert goroutines for the output
|
||||
goroutinesConverted := make([]errmontypes.Goroutine, 0, len(goroutines))
|
||||
for _, goroutine := range goroutines {
|
||||
goroutinesConverted = append(goroutinesConverted, *goroutine)
|
||||
}
|
||||
|
||||
// getting current goroutine ID
|
||||
n = runtime.Stack(stackBuffer, false)
|
||||
currentGoroutines, errs := gostackparse.Parse(bytes.NewReader(stackBuffer[:n]))
|
||||
if len(errs) > 0 { //nolint:staticcheck
|
||||
// TODO: do something
|
||||
}
|
||||
var currentGoroutineID int
|
||||
switch len(currentGoroutines) {
|
||||
case 0:
|
||||
// TODO: do something
|
||||
case 1:
|
||||
currentGoroutineID = currentGoroutines[0].ID
|
||||
default:
|
||||
// TODO: do something
|
||||
}
|
||||
|
||||
return goroutinesConverted, currentGoroutineID
|
||||
}
|
||||
|
||||
type ErrorMonitorLoggerHook struct {
|
||||
ErrorMonitor errmontypes.ErrorMonitor
|
||||
}
|
||||
|
||||
func (h *ErrorMonitorLoggerHook) ProcessLogEntry(entry *logger.Entry) bool {
|
||||
if entry.Level > logger.LevelWarning {
|
||||
return true
|
||||
}
|
||||
|
||||
goroutines, currentGoroutineID := getGoroutines()
|
||||
h.ErrorMonitor.Emitter().Emit(&errmontypes.Event{
|
||||
Entry: *entry,
|
||||
ID: "",
|
||||
ExternalIDs: []any{},
|
||||
Exception: errmontypes.Exception{
|
||||
IsPanic: entry.Level <= logger.LevelPanic,
|
||||
Error: fmt.Errorf("[%s] %s", entry.Level, entry.Message),
|
||||
StackTrace: xruntime.CallerStackTrace(nil),
|
||||
},
|
||||
CurrentGoroutineID: currentGoroutineID,
|
||||
Goroutines: goroutines,
|
||||
})
|
||||
return true
|
||||
}
|
||||
|
||||
func (h *ErrorMonitorLoggerHook) Flush() {}
|
@@ -13,9 +13,10 @@ import (
|
||||
"github.com/facebookincubator/go-belt/tool/experimental/errmon"
|
||||
errmonsentry "github.com/facebookincubator/go-belt/tool/experimental/errmon/implementation/sentry"
|
||||
"github.com/facebookincubator/go-belt/tool/logger"
|
||||
"github.com/facebookincubator/go-belt/tool/logger/implementation/zap"
|
||||
"github.com/facebookincubator/go-belt/tool/logger/implementation/logrus"
|
||||
"github.com/getsentry/sentry-go"
|
||||
"github.com/spf13/pflag"
|
||||
"github.com/xaionaro-go/streamctl/pkg/observability"
|
||||
"github.com/xaionaro-go/streamctl/pkg/streamd/grpc/go/streamd_grpc"
|
||||
"github.com/xaionaro-go/streamctl/pkg/streamd/server"
|
||||
"github.com/xaionaro-go/streamctl/pkg/streampanel"
|
||||
@@ -35,7 +36,7 @@ func main() {
|
||||
heapProfile := pflag.String("go-profile-heap", "", "file to write memory profile to")
|
||||
sentryDSN := pflag.String("sentry-dsn", "", "DSN of a Sentry instance to send error reports")
|
||||
pflag.Parse()
|
||||
l := zap.Default().WithLevel(loggerLevel)
|
||||
l := logrus.Default().WithLevel(loggerLevel)
|
||||
|
||||
if *cpuProfile != "" {
|
||||
f, err := os.Create(*cpuProfile)
|
||||
@@ -87,17 +88,29 @@ func main() {
|
||||
listener.Close()
|
||||
}()
|
||||
|
||||
var opts []streampanel.Option
|
||||
if *remoteAddr != "" {
|
||||
opts = append(opts, streampanel.OptionRemoteStreamDAddr(*remoteAddr))
|
||||
}
|
||||
if *sentryDSN != "" {
|
||||
opts = append(opts, streampanel.OptionSentryDSN(*sentryDSN))
|
||||
}
|
||||
panel, panelErr := streampanel.New(*configPath, opts...)
|
||||
|
||||
if panel.Config.SentryDSN != "" {
|
||||
l.Infof("setting up Sentry at DSN '%s'", panel.Config.SentryDSN)
|
||||
sentryClient, err := sentry.NewClient(sentry.ClientOptions{
|
||||
Dsn: *sentryDSN,
|
||||
Dsn: panel.Config.SentryDSN,
|
||||
})
|
||||
if err != nil {
|
||||
l.Fatal(err)
|
||||
}
|
||||
sentryErrorMonitor := errmonsentry.New(sentryClient)
|
||||
errmon.CtxWithErrorMonitor(ctx, sentryErrorMonitor)
|
||||
l.WithHooks(&ErrorMonitorLoggerHook{
|
||||
ErrorMonitor: sentryErrorMonitor,
|
||||
})
|
||||
ctx = errmon.CtxWithErrorMonitor(ctx, sentryErrorMonitor)
|
||||
l = l.WithPreHooks(observability.NewErrorMonitorLoggerHook(
|
||||
sentryErrorMonitor,
|
||||
))
|
||||
}
|
||||
|
||||
ctx = logger.CtxWithLogger(ctx, l)
|
||||
logger.Default = func() logger.Logger {
|
||||
@@ -105,13 +118,8 @@ func main() {
|
||||
}
|
||||
defer belt.Flush(ctx)
|
||||
|
||||
var opts []streampanel.Option
|
||||
if *remoteAddr != "" {
|
||||
opts = append(opts, streampanel.OptionRemoteStreamDAddr(*remoteAddr))
|
||||
}
|
||||
panel, err := streampanel.New(*configPath, opts...)
|
||||
if err != nil {
|
||||
l.Fatal(err)
|
||||
if panelErr != nil {
|
||||
l.Fatal(panelErr)
|
||||
}
|
||||
|
||||
if *listenAddr != "" {
|
||||
|
4
go.mod
4
go.mod
@@ -8,7 +8,7 @@ toolchain go1.22.3
|
||||
replace github.com/goccy/go-yaml v1.11.3 => github.com/yoelsusanto/go-yaml v0.0.0-20240324162521-2018c1ab915b
|
||||
|
||||
require (
|
||||
github.com/facebookincubator/go-belt v0.0.0-20230703220935-39cd348f1a38
|
||||
github.com/facebookincubator/go-belt v0.0.0-20240706172357-c5875adb39ef
|
||||
github.com/go-git/go-billy/v5 v5.5.0
|
||||
github.com/goccy/go-yaml v1.11.3
|
||||
github.com/hashicorp/go-multierror v1.1.1
|
||||
@@ -57,6 +57,7 @@ require (
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-ng/slices v0.0.0-20230703171042-6195d35636a2 // indirect
|
||||
github.com/go-ng/sort v0.0.0-20220617173827-2cc7cd04f7c7 // indirect
|
||||
github.com/go-ng/xatomic v0.0.0-20230519181013-85c0ec87e55f // indirect
|
||||
github.com/go-ng/xsort v0.0.0-20220617174223-1d146907bccc // indirect
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
||||
github.com/go-text/render v0.1.0 // indirect
|
||||
@@ -149,6 +150,7 @@ require (
|
||||
github.com/rymdport/portal v0.2.2 // indirect
|
||||
github.com/samber/lo v1.36.0 // indirect
|
||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
|
||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||
github.com/skeema/knownhosts v1.2.2 // indirect
|
||||
github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8 // indirect
|
||||
github.com/spaolacci/murmur3 v1.1.0 // indirect
|
||||
|
8
go.sum
8
go.sum
@@ -149,8 +149,8 @@ github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5y
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/facebookincubator/go-belt v0.0.0-20230703220935-39cd348f1a38 h1:6bNfYFYry8V+BHinBMDEPPnd19unWa76lQhsx88xU6k=
|
||||
github.com/facebookincubator/go-belt v0.0.0-20230703220935-39cd348f1a38/go.mod h1:GX1P3GiO+6E2SxTZaI3z1cqlTGfE1GnKxEsxBVemVnY=
|
||||
github.com/facebookincubator/go-belt v0.0.0-20240706172357-c5875adb39ef h1:ZsOEnpQjCQAF4cPK0gwt/fN65sbOg1e7zbfXIX4iMQ4=
|
||||
github.com/facebookincubator/go-belt v0.0.0-20240706172357-c5875adb39ef/go.mod h1:BLkOQHXT14GLgKvM22LB/fDcq1stjijrTOJQEk3xrUA=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg=
|
||||
github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
|
||||
@@ -211,6 +211,8 @@ github.com/go-ng/slices v0.0.0-20230703171042-6195d35636a2 h1:UkoycH6lT7QfBw3LqH
|
||||
github.com/go-ng/slices v0.0.0-20230703171042-6195d35636a2/go.mod h1:bVEceuoz83G4yjq9Os7lCYe+lf46uY8EFEHkxSCywvM=
|
||||
github.com/go-ng/sort v0.0.0-20220617173827-2cc7cd04f7c7 h1:Ng6QMSlQSB+goG6430/Fp7O4YO2BJZXZJaldtg+7kEc=
|
||||
github.com/go-ng/sort v0.0.0-20220617173827-2cc7cd04f7c7/go.mod h1:QUXmOopthsqLYJ+rAybuCf16J7qQm60TLVdQR0w1Nus=
|
||||
github.com/go-ng/xatomic v0.0.0-20230519181013-85c0ec87e55f h1:lifmuIc/S/hzVoStbEAsdryJ6qBSNeXXb9NseP0vlfs=
|
||||
github.com/go-ng/xatomic v0.0.0-20230519181013-85c0ec87e55f/go.mod h1:+3P6aQ4zDVR6jGPnXq3g/7kHnKLB73EsnGBhM0adWhA=
|
||||
github.com/go-ng/xmath v0.0.0-20230704233441-028f5ea62335 h1:N17hl+3/Zqxg3SM+33Q792qUHDoWwt4M7tVuOVpLspc=
|
||||
github.com/go-ng/xmath v0.0.0-20230704233441-028f5ea62335/go.mod h1:rmcKNA11zmis1auYtl0UY64vE/4+QKeS07w/htllpSE=
|
||||
github.com/go-ng/xsort v0.0.0-20220617174223-1d146907bccc h1:VNz633GRJx2/hL0SpBNoNlLid4xtyi7LSJP1kHpD2Fo=
|
||||
@@ -649,6 +651,8 @@ github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYED
|
||||
github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw=
|
||||
github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw=
|
||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/skeema/knownhosts v1.2.2 h1:Iug2P4fLmDw9f41PB6thxUkNUkJzB5i+1/exaj40L3A=
|
||||
github.com/skeema/knownhosts v1.2.2/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
|
191
pkg/observability/error_monitor_hook.go
Normal file
191
pkg/observability/error_monitor_hook.go
Normal file
@@ -0,0 +1,191 @@
|
||||
package observability
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"runtime"
|
||||
|
||||
"github.com/DataDog/gostackparse"
|
||||
"github.com/facebookincubator/go-belt"
|
||||
"github.com/facebookincubator/go-belt/pkg/field"
|
||||
xruntime "github.com/facebookincubator/go-belt/pkg/runtime"
|
||||
"github.com/facebookincubator/go-belt/tool/experimental/errmon"
|
||||
errmontypes "github.com/facebookincubator/go-belt/tool/experimental/errmon/types"
|
||||
"github.com/facebookincubator/go-belt/tool/logger"
|
||||
"github.com/facebookincubator/go-belt/tool/logger/adapter"
|
||||
loggertypes "github.com/facebookincubator/go-belt/tool/logger/types"
|
||||
)
|
||||
|
||||
func getGoroutines() ([]errmontypes.Goroutine, int) {
|
||||
// TODO: consider pprof.Lookup("goroutine") instead of runtime.Stack
|
||||
|
||||
// getting all goroutines
|
||||
stackBufferSize := 65536 * runtime.NumGoroutine()
|
||||
if stackBufferSize > 10*1024*1024 {
|
||||
stackBufferSize = 10 * 1024 * 1024
|
||||
}
|
||||
stackBuffer := make([]byte, stackBufferSize)
|
||||
n := runtime.Stack(stackBuffer, true)
|
||||
goroutines, errs := gostackparse.Parse(bytes.NewReader(stackBuffer[:n]))
|
||||
if len(errs) > 0 { //nolint:staticcheck
|
||||
// TODO: do something
|
||||
}
|
||||
|
||||
// convert goroutines for the output
|
||||
goroutinesConverted := make([]errmontypes.Goroutine, 0, len(goroutines))
|
||||
for _, goroutine := range goroutines {
|
||||
goroutinesConverted = append(goroutinesConverted, *goroutine)
|
||||
}
|
||||
|
||||
// getting current goroutine ID
|
||||
n = runtime.Stack(stackBuffer, false)
|
||||
currentGoroutines, errs := gostackparse.Parse(bytes.NewReader(stackBuffer[:n]))
|
||||
if len(errs) > 0 { //nolint:staticcheck
|
||||
// TODO: do something
|
||||
}
|
||||
var currentGoroutineID int
|
||||
switch len(currentGoroutines) {
|
||||
case 0:
|
||||
// TODO: do something
|
||||
case 1:
|
||||
currentGoroutineID = currentGoroutines[0].ID
|
||||
default:
|
||||
// TODO: do something
|
||||
}
|
||||
|
||||
return goroutinesConverted, currentGoroutineID
|
||||
}
|
||||
|
||||
type ErrorMonitorLoggerHook struct {
|
||||
ErrorMonitor errmontypes.ErrorMonitor
|
||||
SendChan chan ErrorMonitorMessage
|
||||
}
|
||||
|
||||
func NewErrorMonitorLoggerHook(
|
||||
errorMonitor errmon.ErrorMonitor,
|
||||
) *ErrorMonitorLoggerHook {
|
||||
result := &ErrorMonitorLoggerHook{
|
||||
ErrorMonitor: errorMonitor,
|
||||
SendChan: make(chan ErrorMonitorMessage, 10),
|
||||
}
|
||||
go result.senderLoop()
|
||||
return result
|
||||
}
|
||||
|
||||
var _ loggertypes.PreHook = (*ErrorMonitorLoggerHook)(nil)
|
||||
|
||||
func (h *ErrorMonitorLoggerHook) ProcessInput(traceIDs belt.TraceIDs, level loggertypes.Level, args ...any) loggertypes.PreHookResult {
|
||||
if level > loggertypes.LevelWarning {
|
||||
return loggertypes.PreHookResult{
|
||||
Skip: false,
|
||||
}
|
||||
}
|
||||
|
||||
emitter := &mockEmitter{}
|
||||
l := adapter.LoggerFromEmitter(emitter).WithLevel(logger.LevelWarning)
|
||||
l.Log(level, args...)
|
||||
|
||||
h.sendReport(emitter.LastEntry)
|
||||
|
||||
return loggertypes.PreHookResult{
|
||||
Skip: false,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *ErrorMonitorLoggerHook) ProcessInputf(traceIDs belt.TraceIDs, level loggertypes.Level, format string, args ...any) loggertypes.PreHookResult {
|
||||
if level > loggertypes.LevelWarning {
|
||||
return loggertypes.PreHookResult{
|
||||
Skip: false,
|
||||
}
|
||||
}
|
||||
|
||||
emitter := &mockEmitter{}
|
||||
l := adapter.LoggerFromEmitter(emitter).WithLevel(logger.LevelWarning)
|
||||
l.Logf(level, format, args...)
|
||||
|
||||
h.sendReport(emitter.LastEntry)
|
||||
|
||||
return loggertypes.PreHookResult{
|
||||
Skip: false,
|
||||
}
|
||||
}
|
||||
func (h *ErrorMonitorLoggerHook) ProcessInputFields(traceIDs belt.TraceIDs, level loggertypes.Level, message string, fields field.AbstractFields) loggertypes.PreHookResult {
|
||||
if level > loggertypes.LevelWarning {
|
||||
return loggertypes.PreHookResult{
|
||||
Skip: false,
|
||||
}
|
||||
}
|
||||
|
||||
emitter := &mockEmitter{}
|
||||
l := adapter.LoggerFromEmitter(emitter).WithLevel(logger.LevelWarning)
|
||||
l.LogFields(level, message, fields)
|
||||
h.sendReport(emitter.LastEntry)
|
||||
|
||||
return loggertypes.PreHookResult{
|
||||
Skip: false,
|
||||
}
|
||||
}
|
||||
|
||||
func copyEntry(entry *loggertypes.Entry) *loggertypes.Entry {
|
||||
entryDup := *entry
|
||||
|
||||
if entry.Fields != nil {
|
||||
fields := make(field.Fields, 0, entry.Fields.Len())
|
||||
entry.Fields.ForEachField(func(f *field.Field) bool {
|
||||
fields = append(fields, *f)
|
||||
return true
|
||||
})
|
||||
entryDup.Fields = fields
|
||||
}
|
||||
|
||||
return &entryDup
|
||||
}
|
||||
|
||||
type ErrorMonitorMessage struct {
|
||||
Entry *loggertypes.Entry
|
||||
Goroutines []errmontypes.Goroutine
|
||||
CurrentGoroutineID int
|
||||
StackTrace xruntime.PCs
|
||||
}
|
||||
|
||||
func (h *ErrorMonitorLoggerHook) sendReport(
|
||||
entry *loggertypes.Entry,
|
||||
) {
|
||||
if entry == nil {
|
||||
logger.Default().Errorf("an attempt to send through sentry a nil entry")
|
||||
return
|
||||
}
|
||||
logger.Default().Tracef("sending through sentry entry: %#+v", *entry)
|
||||
entryDup := copyEntry(entry)
|
||||
goroutines, currentGoroutineID := getGoroutines()
|
||||
stackTrace := xruntime.CallerStackTrace(nil)
|
||||
select {
|
||||
case h.SendChan <- ErrorMonitorMessage{
|
||||
Entry: entryDup,
|
||||
Goroutines: goroutines,
|
||||
CurrentGoroutineID: currentGoroutineID,
|
||||
StackTrace: stackTrace,
|
||||
}:
|
||||
default:
|
||||
logger.Default().Errorf("unable to send an error to Sentry, the channel is busy")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (h *ErrorMonitorLoggerHook) senderLoop() {
|
||||
for {
|
||||
message := <-h.SendChan
|
||||
h.ErrorMonitor.Emitter().Emit(&errmontypes.Event{
|
||||
Entry: *message.Entry,
|
||||
ID: "",
|
||||
ExternalIDs: []any{},
|
||||
Exception: errmontypes.Exception{
|
||||
IsPanic: message.Entry.Level <= loggertypes.LevelPanic,
|
||||
Error: fmt.Errorf("[%s] %s", message.Entry.Level, message.Entry.Message),
|
||||
StackTrace: message.StackTrace,
|
||||
},
|
||||
CurrentGoroutineID: message.CurrentGoroutineID,
|
||||
Goroutines: message.Goroutines,
|
||||
})
|
||||
}
|
||||
}
|
14
pkg/observability/mock_emitter.go
Normal file
14
pkg/observability/mock_emitter.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package observability
|
||||
|
||||
import logger "github.com/facebookincubator/go-belt/tool/logger/types"
|
||||
|
||||
type mockEmitter struct {
|
||||
LastEntry *logger.Entry
|
||||
}
|
||||
|
||||
var _ logger.Emitter = (*mockEmitter)(nil)
|
||||
|
||||
func (e *mockEmitter) Emit(entry *logger.Entry) {
|
||||
e.LastEntry = entry
|
||||
}
|
||||
func (e *mockEmitter) Flush() {}
|
@@ -183,6 +183,9 @@ func (c *abstractStreamController) StreamProfileType() reflect.Type {
|
||||
}
|
||||
|
||||
func ToAbstract[T StreamProfile](c StreamController[T]) AbstractStreamController {
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
var zeroProfile T
|
||||
profileType := reflect.TypeOf(zeroProfile)
|
||||
return &abstractStreamController{
|
||||
|
@@ -219,6 +219,15 @@ func getToken(ctx context.Context, cfg Config) (*oauth2.Token, error) {
|
||||
func (yt *YouTube) Ping(ctx context.Context) error {
|
||||
counter := 0
|
||||
for {
|
||||
if yt == nil {
|
||||
return fmt.Errorf("yt is nil")
|
||||
}
|
||||
if yt.YouTubeService == nil {
|
||||
return fmt.Errorf("yt.YouTubeService == nil")
|
||||
}
|
||||
if yt.YouTubeService.I18nLanguages == nil {
|
||||
return fmt.Errorf("yt.YouTubeService.I18nLanguages == nil")
|
||||
}
|
||||
_, err := yt.YouTubeService.I18nLanguages.List(nil).Context(ctx).Do()
|
||||
logger.Debugf(ctx, "YouTube.I18nLanguages result: %v", err)
|
||||
if err != nil {
|
||||
|
@@ -30,6 +30,7 @@ type config struct {
|
||||
GitRepo GitRepoConfig
|
||||
Backends streamcontrol.Config
|
||||
ProfileMetadata map[streamcontrol.ProfileName]ProfileMetadata
|
||||
SentryDSN string `yaml:"sentry_dsn"`
|
||||
}
|
||||
|
||||
type Config config
|
||||
|
@@ -7,6 +7,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/facebookincubator/go-belt/tool/logger"
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"github.com/xaionaro-go/streamctl/pkg/streamcontrol"
|
||||
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/obs"
|
||||
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/twitch"
|
||||
@@ -22,6 +23,7 @@ func (d *StreamD) EXPERIMENTAL_ReinitStreamControllers(ctx context.Context) erro
|
||||
sort.Slice(platNames, func(i, j int) bool {
|
||||
return platNames[i] < platNames[j]
|
||||
})
|
||||
var result *multierror.Error
|
||||
for _, platName := range platNames {
|
||||
var err error
|
||||
switch strings.ToLower(string(platName)) {
|
||||
@@ -32,11 +34,14 @@ func (d *StreamD) EXPERIMENTAL_ReinitStreamControllers(ctx context.Context) erro
|
||||
case strings.ToLower(string(youtube.ID)):
|
||||
err = d.initYouTubeBackend(ctx)
|
||||
}
|
||||
if err != nil && err != ErrSkipBackend {
|
||||
return fmt.Errorf("unable to initialize '%s': %w", platName, err)
|
||||
if err == ErrSkipBackend {
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
result = multierror.Append(result, fmt.Errorf("unable to initialize '%s': %w", platName, err))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return result.ErrorOrNil()
|
||||
}
|
||||
|
||||
var ErrSkipBackend = streamd.ErrSkipBackend
|
||||
|
@@ -50,7 +50,12 @@ type StreamD struct {
|
||||
|
||||
var _ api.StreamD = (*StreamD)(nil)
|
||||
|
||||
func New(config config.Config, ui ui.UI, saveCfgFunc SaveConfigFunc, b *belt.Belt) (*StreamD, error) {
|
||||
func New(
|
||||
config config.Config,
|
||||
ui ui.UI,
|
||||
saveCfgFunc SaveConfigFunc,
|
||||
b *belt.Belt,
|
||||
) (*StreamD, error) {
|
||||
ctx := belt.CtxWithBelt(context.Background(), b)
|
||||
|
||||
d := &StreamD{
|
||||
@@ -417,16 +422,27 @@ func (d *StreamD) Restart(ctx context.Context) error {
|
||||
func (d *StreamD) streamController(
|
||||
platID streamcontrol.PlatformName,
|
||||
) (streamcontrol.AbstractStreamController, error) {
|
||||
var result streamcontrol.AbstractStreamController
|
||||
switch platID {
|
||||
case obs.ID:
|
||||
return streamcontrol.ToAbstract(d.StreamControllers.OBS), nil
|
||||
if d.StreamControllers.OBS != nil {
|
||||
result = streamcontrol.ToAbstract(d.StreamControllers.OBS)
|
||||
}
|
||||
case twitch.ID:
|
||||
return streamcontrol.ToAbstract(d.StreamControllers.Twitch), nil
|
||||
if d.StreamControllers.Twitch != nil {
|
||||
result = streamcontrol.ToAbstract(d.StreamControllers.Twitch)
|
||||
}
|
||||
case youtube.ID:
|
||||
return streamcontrol.ToAbstract(d.StreamControllers.YouTube), nil
|
||||
if d.StreamControllers.YouTube != nil {
|
||||
result = streamcontrol.ToAbstract(d.StreamControllers.YouTube)
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("unexpected platform ID: '%s'", platID)
|
||||
}
|
||||
if result == nil {
|
||||
return nil, fmt.Errorf("controller '%s' is not initialized", platID)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
func (d *StreamD) GetStreamStatus(
|
||||
ctx context.Context,
|
||||
@@ -437,6 +453,10 @@ func (d *StreamD) GetStreamStatus(
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if c == nil {
|
||||
return nil, fmt.Errorf("controller '%s' is not initialized", platID)
|
||||
}
|
||||
|
||||
return c.GetStreamStatus(ctx)
|
||||
}
|
||||
|
||||
|
@@ -10,6 +10,7 @@ import (
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
SentryDSN string `yaml:"sentry_dsn"`
|
||||
RemoteStreamDAddr string `yaml:"streamd_remote"`
|
||||
BuiltinStreamD streamd.Config `yaml:"streamd_builtin"`
|
||||
}
|
||||
|
@@ -18,3 +18,9 @@ type OptionRemoteStreamDAddr string
|
||||
func (o OptionRemoteStreamDAddr) Apply(cfg *Config) {
|
||||
cfg.RemoteStreamDAddr = string(o)
|
||||
}
|
||||
|
||||
type OptionSentryDSN string
|
||||
|
||||
func (o OptionSentryDSN) Apply(cfg *Config) {
|
||||
cfg.SentryDSN = string(o)
|
||||
}
|
||||
|
@@ -6,3 +6,4 @@ type Config = config.Config
|
||||
type Option = config.Option
|
||||
type Options = config.Options
|
||||
type OptionRemoteStreamDAddr = config.OptionRemoteStreamDAddr
|
||||
type OptionSentryDSN = config.OptionSentryDSN
|
||||
|
@@ -51,7 +51,7 @@ type Panel struct {
|
||||
StreamD api.StreamD
|
||||
|
||||
app fyne.App
|
||||
config Config
|
||||
Config Config
|
||||
streamMutex sync.Mutex
|
||||
updateTimerHandler *updateTimerHandler
|
||||
profilesOrder []streamcontrol.ProfileName
|
||||
@@ -100,7 +100,7 @@ func New(
|
||||
|
||||
return &Panel{
|
||||
configPath: configPath,
|
||||
config: Options(opts).ApplyOverrides(cfg),
|
||||
Config: Options(opts).ApplyOverrides(cfg),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -117,11 +117,11 @@ func (p *Panel) Loop(ctx context.Context) error {
|
||||
}
|
||||
|
||||
p.defaultContext = ctx
|
||||
logger.Debug(ctx, "config", p.config)
|
||||
logger.Debug(ctx, "config", p.Config)
|
||||
|
||||
if p.config.RemoteStreamDAddr != "" {
|
||||
if p.Config.RemoteStreamDAddr != "" {
|
||||
if err := p.initRemoteStreamD(ctx); err != nil {
|
||||
return fmt.Errorf("unable to initialize the remote stream controller '%s': %w", p.config.RemoteStreamDAddr, err)
|
||||
return fmt.Errorf("unable to initialize the remote stream controller '%s': %w", p.Config.RemoteStreamDAddr, err)
|
||||
}
|
||||
} else {
|
||||
if err := p.initBuiltinStreamD(ctx); err != nil {
|
||||
@@ -133,7 +133,7 @@ func (p *Panel) Loop(ctx context.Context) error {
|
||||
|
||||
go func() {
|
||||
var loadingWindow fyne.Window
|
||||
if p.config.RemoteStreamDAddr == "" {
|
||||
if p.Config.RemoteStreamDAddr == "" {
|
||||
loadingWindow = p.newLoadingWindow(ctx)
|
||||
resizeWindow(loadingWindow, fyne.NewSize(600, 600))
|
||||
loadingWindowText := widget.NewRichTextFromMarkdown("")
|
||||
@@ -183,7 +183,7 @@ func (p *Panel) Loop(ctx context.Context) error {
|
||||
p.DisplayError(err)
|
||||
}
|
||||
|
||||
if p.config.RemoteStreamDAddr == "" {
|
||||
if p.Config.RemoteStreamDAddr == "" {
|
||||
logger.Tracef(ctx, "hiding the loading window")
|
||||
loadingWindow.Hide()
|
||||
}
|
||||
@@ -212,12 +212,12 @@ func getExpandedConfigPath(configPath string) (string, error) {
|
||||
func (p *Panel) initBuiltinStreamD(ctx context.Context) error {
|
||||
var err error
|
||||
p.StreamD, err = streamd.New(
|
||||
p.config.BuiltinStreamD,
|
||||
p.Config.BuiltinStreamD,
|
||||
p,
|
||||
func(ctx context.Context, cfg streamdconfig.Config) error {
|
||||
p.config.BuiltinStreamD = cfg
|
||||
p.Config.BuiltinStreamD = cfg
|
||||
|
||||
err = config.WriteConfigToPath(ctx, p.configPath, p.config)
|
||||
err = config.WriteConfigToPath(ctx, p.configPath, p.Config)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to save the config: %w", err)
|
||||
}
|
||||
@@ -234,7 +234,7 @@ func (p *Panel) initBuiltinStreamD(ctx context.Context) error {
|
||||
}
|
||||
|
||||
func (p *Panel) initRemoteStreamD(context.Context) error {
|
||||
p.StreamD = client.New(p.config.RemoteStreamDAddr)
|
||||
p.StreamD = client.New(p.Config.RemoteStreamDAddr)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user