Skip to content

Instantly share code, notes, and snippets.

@Semior001
Last active March 19, 2023 21:29
Show Gist options
  • Save Semior001/6a381171af263df1fc5d9e287f11d2f4 to your computer and use it in GitHub Desktop.
Save Semior001/6a381171af263df1fc5d9e287f11d2f4 to your computer and use it in GitHub Desktop.
package bot
// Run starts service until context is dead.
func (b *Bot) Run(ctx context.Context) error {
if err := b.notifyAdmins(ctx, "bot started"); err != nil {
return fmt.Errorf("send start message to admins: %w", err)
}
wg := &sync.WaitGroup{}
wg.Add(b.Workers)
for i := 0; i < b.Workers; i++ {
go func(idx int) {
b.logger.InfoCtx(ctx, "started worker", slog.Int("idx", idx))
defer func() {
b.logger.InfoCtx(ctx, "stopped worker", slog.Int("idx", idx))
wg.Done()
}()
for {
select {
case <-ctx.Done():
return
case req := <-b.ctrl.Updates():
b.handleUpdate(ctx, req)
}
}
}(i)
}
wg.Wait()
if err := b.notifyAdmins(context.Background(), "bot stopped"); err != nil {
return fmt.Errorf("send stop message to admins: %w", err)
}
return nil
}
func (b *Bot) handleUpdate(ctx context.Context, req route.Request) {
if b.Timeout > 0 {
var cancel func()
ctx, cancel = context.WithTimeout(ctx, b.Timeout)
defer cancel()
}
resps, err := b.h(ctx, req)
if err != nil {
b.logger.ErrorCtx(ctx, "failed to handle request", slog.Any("err", err))
}
for _, resp := range resps {
if err := b.ctrl.SendMessage(ctx, resp); err != nil {
b.logger.WarnCtx(ctx, "failed to send message", slog.Any("err", err))
}
}
}
// Package logging contains logging middleware for request id.
package logging
import (
"context"
"golang.org/x/exp/slog"
)
// HandleFunc is a function that handles a record.
type HandleFunc func(context.Context, slog.Record) error
// Middleware is a middleware for logging handler.
type Middleware func(HandleFunc) HandleFunc
// Chain is a chain of middleware.
type Chain struct {
Middleware []Middleware
slog.Handler
}
// Handle runs the chain of middleware and the handler.
func (c *Chain) Handle(ctx context.Context, rec slog.Record) error {
h := c.Handler.Handle
for i := len(c.Middleware) - 1; i >= 0; i-- {
h = c.Middleware[i](h)
}
return h(ctx, rec)
}
// WithGroup returns a new Chain with the given group.
func (c *Chain) WithGroup(group string) slog.Handler {
return &Chain{
Middleware: c.Middleware,
Handler: c.Handler.WithGroup(group),
}
}
// WithAttrs returns a new Chain with the given attributes.
func (c *Chain) WithAttrs(attrs []slog.Attr) slog.Handler {
return &Chain{
Middleware: c.Middleware,
Handler: c.Handler.WithAttrs(attrs),
}
}
func setupLog() {
handler := slog.HandlerOptions{
AddSource: false,
Level: slog.LevelInfo,
ReplaceAttr: nil,
}
if opts.Debug {
handler.Level = slog.LevelDebug
handler.AddSource = true
}
h := slog.Handler(handler.NewTextHandler(os.Stderr))
if opts.JSONLogs {
h = handler.NewJSONHandler(os.Stderr)
}
lg := slog.New(&logging.Chain{
Handler: h,
Middleware: []logging.Middleware{
logging.RequestID(),
logging.StacktraceOnError(),
},
})
slog.SetDefault(lg)
}
package logging
import (
"context"
"golang.org/x/exp/slog"
)
type requestIDKey struct{}
// ContextWithRequestID returns a new context with the given request ID.
func ContextWithRequestID(parent context.Context, reqID string) context.Context {
return context.WithValue(parent, requestIDKey{}, reqID)
}
// RequestIDFromContext returns request id from context.
func RequestIDFromContext(ctx context.Context) (string, bool) {
v, ok := ctx.Value(requestIDKey{}).(string)
return v, ok
}
// RequestID returns a middleware that adds request id to record.
func RequestID() Middleware {
return func(next HandleFunc) HandleFunc {
return func(ctx context.Context, rec slog.Record) error {
if reqID, ok := RequestIDFromContext(ctx); ok {
rec.AddAttrs(slog.String("request_id", reqID))
}
return next(ctx, rec)
}
}
}
package logging
import (
"context"
"regexp"
"runtime"
"golang.org/x/exp/slog"
)
var reTrace = regexp.MustCompile(`.*/slog/logger\.go.*\n`)
// StacktraceOnError returns a middleware that adds stacktrace to record if level is error.
func StacktraceOnError() Middleware {
return func(next HandleFunc) HandleFunc {
return func(ctx context.Context, rec slog.Record) error {
if rec.Level != slog.LevelError {
return next(ctx, rec)
}
stackInfo := make([]byte, 1024*1024)
if stackSize := runtime.Stack(stackInfo, false); stackSize > 0 {
traceLines := reTrace.Split(string(stackInfo[:stackSize]), -1)
if len(traceLines) == 0 {
return next(ctx, rec)
}
rec.AddAttrs(slog.String("stacktrace", traceLines[len(traceLines)-1]))
}
return next(ctx, rec)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment