mirror of https://github.com/usememos/memos.git
Add a new LOG_STACKTRACES option
Adds a new command line configuration parameter for logging stacktraces on panics I found this useful for development, where I kept hitting nil panics when using the API and had trouble figuring out where they were happening with the default logging
This commit is contained in:
parent
8319516d1a
commit
e86d3d59ad
|
|
@ -36,15 +36,16 @@ var (
|
|||
Short: `An open source, lightweight note-taking service. Easily capture and share your great thoughts.`,
|
||||
Run: func(_ *cobra.Command, _ []string) {
|
||||
instanceProfile := &profile.Profile{
|
||||
Mode: viper.GetString("mode"),
|
||||
Addr: viper.GetString("addr"),
|
||||
Port: viper.GetInt("port"),
|
||||
UNIXSock: viper.GetString("unix-sock"),
|
||||
Data: viper.GetString("data"),
|
||||
Driver: viper.GetString("driver"),
|
||||
DSN: viper.GetString("dsn"),
|
||||
InstanceURL: viper.GetString("instance-url"),
|
||||
Version: version.GetCurrentVersion(viper.GetString("mode")),
|
||||
Mode: viper.GetString("mode"),
|
||||
Addr: viper.GetString("addr"),
|
||||
Port: viper.GetInt("port"),
|
||||
UNIXSock: viper.GetString("unix-sock"),
|
||||
Data: viper.GetString("data"),
|
||||
Driver: viper.GetString("driver"),
|
||||
DSN: viper.GetString("dsn"),
|
||||
InstanceURL: viper.GetString("instance-url"),
|
||||
LogStacktraces: viper.GetBool("log-stacktraces"),
|
||||
Version: version.GetCurrentVersion(viper.GetString("mode")),
|
||||
}
|
||||
if err := instanceProfile.Validate(); err != nil {
|
||||
panic(err)
|
||||
|
|
@ -112,6 +113,7 @@ func init() {
|
|||
rootCmd.PersistentFlags().String("driver", "sqlite", "database driver")
|
||||
rootCmd.PersistentFlags().String("dsn", "", "database source name(aka. DSN)")
|
||||
rootCmd.PersistentFlags().String("instance-url", "", "the url of your memos instance")
|
||||
rootCmd.PersistentFlags().Bool("log-stacktraces", false, "if true, log stacktraces when requests panic")
|
||||
|
||||
if err := viper.BindPFlag("mode", rootCmd.PersistentFlags().Lookup("mode")); err != nil {
|
||||
panic(err)
|
||||
|
|
@ -137,12 +139,18 @@ func init() {
|
|||
if err := viper.BindPFlag("instance-url", rootCmd.PersistentFlags().Lookup("instance-url")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := viper.BindPFlag("log-stacktraces", rootCmd.PersistentFlags().Lookup("log-stacktraces")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
viper.SetEnvPrefix("memos")
|
||||
viper.AutomaticEnv()
|
||||
if err := viper.BindEnv("instance-url", "MEMOS_INSTANCE_URL"); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := viper.BindEnv("log-stacktraces", "MEMOS_LOG_STACKTRACES"); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func printGreetings(profile *profile.Profile) {
|
||||
|
|
@ -159,8 +167,9 @@ port: %d
|
|||
unix-sock: %s
|
||||
mode: %s
|
||||
driver: %s
|
||||
log-stacktraces: %t
|
||||
---
|
||||
`, profile.Version, profile.Data, profile.Addr, profile.Port, profile.UNIXSock, profile.Mode, profile.Driver)
|
||||
`, profile.Version, profile.Data, profile.Addr, profile.Port, profile.UNIXSock, profile.Mode, profile.Driver, profile.LogStacktraces)
|
||||
|
||||
print(greetingBanner)
|
||||
if len(profile.UNIXSock) == 0 {
|
||||
|
|
|
|||
|
|
@ -32,6 +32,8 @@ type Profile struct {
|
|||
Version string
|
||||
// InstanceURL is the url of your memos instance.
|
||||
InstanceURL string
|
||||
// LogStacktraces determines if panic logs should include stacktraces
|
||||
LogStacktraces bool
|
||||
}
|
||||
|
||||
func (p *Profile) IsDev() bool {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package v1
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
|
|
@ -10,10 +11,11 @@ import (
|
|||
)
|
||||
|
||||
type LoggerInterceptor struct {
|
||||
logStacktrace bool
|
||||
}
|
||||
|
||||
func NewLoggerInterceptor() *LoggerInterceptor {
|
||||
return &LoggerInterceptor{}
|
||||
func NewLoggerInterceptor(logStacktrace bool) *LoggerInterceptor {
|
||||
return &LoggerInterceptor{logStacktrace: logStacktrace}
|
||||
}
|
||||
|
||||
func (in *LoggerInterceptor) LoggerInterceptor(ctx context.Context, request any, serverInfo *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {
|
||||
|
|
@ -22,7 +24,7 @@ func (in *LoggerInterceptor) LoggerInterceptor(ctx context.Context, request any,
|
|||
return resp, err
|
||||
}
|
||||
|
||||
func (*LoggerInterceptor) loggerInterceptorDo(ctx context.Context, fullMethod string, err error) {
|
||||
func (li *LoggerInterceptor) loggerInterceptorDo(ctx context.Context, fullMethod string, err error) {
|
||||
st := status.Convert(err)
|
||||
var logLevel slog.Level
|
||||
var logMsg string
|
||||
|
|
@ -43,6 +45,9 @@ func (*LoggerInterceptor) loggerInterceptorDo(ctx context.Context, fullMethod st
|
|||
logAttrs := []slog.Attr{slog.String("method", fullMethod)}
|
||||
if err != nil {
|
||||
logAttrs = append(logAttrs, slog.String("error", err.Error()))
|
||||
if li.logStacktrace {
|
||||
logAttrs = append(logAttrs, slog.String("stacktrace", fmt.Sprintf("%v", err)))
|
||||
}
|
||||
}
|
||||
slog.LogAttrs(ctx, logLevel, logMsg, logAttrs...)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import (
|
|||
"net"
|
||||
"net/http"
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
|
|
@ -87,8 +88,8 @@ func NewServer(ctx context.Context, profile *profile.Profile, store *store.Store
|
|||
// Override the maximum receiving message size to math.MaxInt32 for uploading large attachments.
|
||||
grpc.MaxRecvMsgSize(math.MaxInt32),
|
||||
grpc.ChainUnaryInterceptor(
|
||||
apiv1.NewLoggerInterceptor().LoggerInterceptor,
|
||||
grpcrecovery.UnaryServerInterceptor(),
|
||||
apiv1.NewLoggerInterceptor(profile.LogStacktraces).LoggerInterceptor,
|
||||
newRecoveryInterceptor(profile.LogStacktraces),
|
||||
apiv1.NewGRPCAuthInterceptor(store, secret).AuthenticationInterceptor,
|
||||
))
|
||||
s.grpcServer = grpcServer
|
||||
|
|
@ -102,6 +103,26 @@ func NewServer(ctx context.Context, profile *profile.Profile, store *store.Store
|
|||
return s, nil
|
||||
}
|
||||
|
||||
func newRecoveryInterceptor(logStacktraces bool) grpc.UnaryServerInterceptor {
|
||||
var recoveryOptions []grpcrecovery.Option
|
||||
if logStacktraces {
|
||||
recoveryOptions = append(recoveryOptions, grpcrecovery.WithRecoveryHandler(func(p any) error {
|
||||
if p == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch val := p.(type) {
|
||||
case runtime.Error:
|
||||
return &stacktraceError{err: val, stacktrace: debug.Stack()}
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
return grpcrecovery.UnaryServerInterceptor(recoveryOptions...)
|
||||
}
|
||||
|
||||
func (s *Server) Start(ctx context.Context) error {
|
||||
var address, network string
|
||||
if len(s.Profile.UNIXSock) == 0 {
|
||||
|
|
@ -227,3 +248,23 @@ func (s *Server) getOrUpsertWorkspaceBasicSetting(ctx context.Context) (*storepb
|
|||
}
|
||||
return workspaceBasicSetting, nil
|
||||
}
|
||||
|
||||
// stacktraceError wraps an underlying error and captures the stacktrace. It
|
||||
// implements fmt.Formatter, so it'll be rendered when invoked by something like
|
||||
// `fmt.Sprint("%v", err)`.
|
||||
type stacktraceError struct {
|
||||
err error
|
||||
stacktrace []byte
|
||||
}
|
||||
|
||||
func (e *stacktraceError) Error() string {
|
||||
return e.err.Error()
|
||||
}
|
||||
|
||||
func (e *stacktraceError) Unwrap() error {
|
||||
return e.err
|
||||
}
|
||||
|
||||
func (e *stacktraceError) Format(f fmt.State, _ rune) {
|
||||
f.Write(e.stacktrace)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue