Add stack of current goroutine to errors

This commit is contained in:
Alexander A. Klimov 2021-04-29 13:59:54 +02:00 committed by Eric Lippmann
parent c25c47ad72
commit 8cbf24932e
10 changed files with 81 additions and 39 deletions

View file

@ -5,6 +5,7 @@ import (
"github.com/icinga/icingadb/pkg/icingadb"
"github.com/icinga/icingadb/pkg/icingaredis"
"github.com/icinga/icingadb/pkg/utils"
"github.com/pkg/errors"
"go.uber.org/zap"
)
@ -27,7 +28,7 @@ func New() *Command {
logger, err := zap.NewDevelopment()
if err != nil {
utils.Fatal(err)
utils.Fatal(errors.Wrap(err, "can't create logger"))
}
sugar := logger.Sugar()

View file

@ -1,8 +1,8 @@
package config
import (
"fmt"
"github.com/jessevdk/go-flags"
"github.com/pkg/errors"
"gopkg.in/yaml.v3"
"os"
)
@ -25,7 +25,7 @@ type Flags struct {
func FromYAMLFile(name string) (*Config, error) {
f, err := os.Open(name)
if err != nil {
return nil, err
return nil, errors.Wrap(err, "can't open YAML file")
}
defer f.Close()
@ -33,7 +33,7 @@ func FromYAMLFile(name string) (*Config, error) {
d := yaml.NewDecoder(f)
if err := d.Decode(&c); err != nil {
return nil, err
return nil, errors.Wrap(err, "can't parse YAML file "+name)
}
return c, nil
@ -43,11 +43,11 @@ func FromYAMLFile(name string) (*Config, error) {
func ValidateFile(name string) error {
f, err := os.Stat(name)
if err != nil {
return err
return errors.Wrap(err, "not a readable file")
}
if f.IsDir() {
return fmt.Errorf("'%s' is a directory", name)
return errors.Errorf("'%s' is a directory", name)
}
return nil
@ -60,7 +60,7 @@ func ParseFlags() (*Flags, error) {
parser := flags.NewParser(f, flags.Default)
if _, err := parser.Parse(); err != nil {
return nil, err
return nil, errors.Wrap(err, "can't parse CLI flags")
}
if err := ValidateFile(f.Config); err != nil {

View file

@ -9,6 +9,7 @@ import (
"github.com/icinga/icingadb/pkg/utils"
"github.com/jmoiron/sqlx"
"github.com/jmoiron/sqlx/reflectx"
"github.com/pkg/errors"
"go.uber.org/zap"
"sync"
)
@ -38,7 +39,7 @@ func (d *Database) Open(logger *zap.SugaredLogger) (*icingadb.DB, error) {
db, err := sqlx.Open("icingadb-mysql", dsn)
if err != nil {
return nil, err
return nil, errors.Wrap(err, "can't open database")
}
db.SetMaxIdleConns(d.MaxConnections / 3)
@ -54,12 +55,12 @@ func (d *Database) Open(logger *zap.SugaredLogger) (*icingadb.DB, error) {
// UnmarshalYAML implements the yaml.Unmarshaler interface.
func (d *Database) UnmarshalYAML(unmarshal func(interface{}) error) error {
if err := defaults.Set(d); err != nil {
return err
return errors.Wrap(err, "can't set default database config")
}
// Prevent recursion.
type self Database
if err := unmarshal((*self)(d)); err != nil {
return err
return errors.Wrap(err, "can't parse database config")
}
if d.MaxConnectionsPerTable < 1 {

View file

@ -2,12 +2,12 @@ package config
import (
"context"
"errors"
"github.com/creasty/defaults"
"github.com/go-redis/redis/v8"
"github.com/icinga/icingadb/pkg/backoff"
"github.com/icinga/icingadb/pkg/icingaredis"
"github.com/icinga/icingadb/pkg/retry"
"github.com/pkg/errors"
"go.uber.org/zap"
"net"
"os"
@ -69,6 +69,11 @@ func dialWithLogging(logger *zap.SugaredLogger) func(context.Context, string, st
backoff.NewExponentialWithJitter(1*time.Millisecond, 1*time.Second),
5*time.Minute,
)
if err != nil {
err = errors.Wrap(err, "can't connect to Redis")
}
return
}
}

View file

@ -44,6 +44,9 @@ func (d Driver) Open(dsn string) (c driver.Conn, err error) {
backoff.NewExponentialWithJitter(time.Millisecond*128, time.Minute*1),
timeout,
)
if err != nil {
err = errors.Wrap(err, "can't connect to database")
}
return
}

View file

@ -151,15 +151,15 @@ func (db *DB) BulkExec(ctx context.Context, query string, count int, sem *semaph
return retry.WithBackoff(
ctx,
func(context.Context) error {
query, args, err := sqlx.In(query, b)
stmt, args, err := sqlx.In(query, b)
if err != nil {
return err
return errors.Wrap(err, "can't build SQL placeholders for "+query)
}
query = db.Rebind(query)
_, err = db.ExecContext(ctx, query, args...)
stmt = db.Rebind(stmt)
_, err = db.ExecContext(ctx, stmt, args...)
if err != nil {
return err
return errors.Wrap(err, "can't perform "+query)
}
cnt.Add(uint64(len(b)))
@ -220,6 +220,7 @@ func (db *DB) NamedBulkExec(
db.logger.Debugf("Executing %s with %d rows..", query, len(b))
_, err := db.NamedExecContext(ctx, query, b)
if err != nil {
err = errors.Wrap(err, "can't perform "+query)
fmt.Println(err)
return err
}
@ -339,7 +340,7 @@ func (db *DB) YieldAll(ctx context.Context, factoryFunc contracts.EntityFactoryF
rows, err := db.Queryx(query, args...)
if err != nil {
return err
return errors.Wrap(err, "can't perform "+query)
}
defer rows.Close()
@ -347,7 +348,7 @@ func (db *DB) YieldAll(ctx context.Context, factoryFunc contracts.EntityFactoryF
e := factoryFunc()
if err := rows.StructScan(e); err != nil {
return err
return errors.Wrap(err, fmt.Sprintf("can't store query result into a %T: %s", e, query))
}
select {
@ -381,8 +382,8 @@ func (db *DB) UpsertStreamed(ctx context.Context, entities <-chan contracts.Enti
}
// TODO(ak): wait for https://github.com/jmoiron/sqlx/issues/694
//stmt, placeholders := db.BuildUpsertStmt(first)
//return db.NamedBulkExec(ctx, stmt, 1<<15/placeholders, 1<<3, forward, succeeded)
// stmt, placeholders := db.BuildUpsertStmt(first)
// return db.NamedBulkExec(ctx, stmt, 1<<15/placeholders, 1<<3, forward, succeeded)
stmt, _ := db.BuildUpsertStmt(first)
sem := db.getSemaphoreForTable(utils.TableName(first))
return db.NamedBulkExec(ctx, stmt, 1, sem, forward, succeeded)

View file

@ -11,6 +11,7 @@ import (
icingaredisv1 "github.com/icinga/icingadb/pkg/icingaredis/v1"
"github.com/icinga/icingadb/pkg/types"
"github.com/icinga/icingadb/pkg/utils"
"github.com/pkg/errors"
"go.uber.org/zap"
"sync"
"time"
@ -171,12 +172,13 @@ func (h *HA) realize(s *icingaredisv1.IcingaStatus, t *types.UnixMilli, shouldLo
})
if err != nil {
cancel()
return err
return errors.Wrap(err, "can't start transaction")
}
rows, err := tx.QueryxContext(ctx, `SELECT id, heartbeat FROM icingadb_instance WHERE environment_id = ? AND responsible = ? AND id != ? AND heartbeat > ?`, s.EnvironmentID(), "y", h.instanceId, utils.UnixMilli(time.Now().Add(-1*timeout)))
query := `SELECT id, heartbeat FROM icingadb_instance WHERE environment_id = ? AND responsible = ? AND id != ? AND heartbeat > ?`
rows, err := tx.QueryxContext(ctx, query, s.EnvironmentID(), "y", h.instanceId, utils.UnixMilli(time.Now().Add(-1*timeout)))
if err != nil {
cancel()
return err
return errors.Wrap(err, "can't perform "+query)
}
takeover := true
if rows.Next() {
@ -218,6 +220,7 @@ func (h *HA) realize(s *icingaredisv1.IcingaStatus, t *types.UnixMilli, shouldLo
_, err = tx.NamedExecContext(ctx, stmt, i)
if err != nil {
err = errors.Wrap(err, "can't perform "+stmt)
cancel()
if !utils.IsDeadlock(err) {
h.logger.Errorw("Can't update or insert instance", zap.Error(err))
@ -235,7 +238,7 @@ func (h *HA) realize(s *icingaredisv1.IcingaStatus, t *types.UnixMilli, shouldLo
if err := tx.Commit(); err != nil {
cancel()
return err
return errors.Wrap(err, "can't commit transaction")
}
if takeover {
h.signalTakeover()
@ -251,9 +254,10 @@ func (h *HA) realize(s *icingaredisv1.IcingaStatus, t *types.UnixMilli, shouldLo
func (h *HA) removeInstance() {
h.logger.Debugw("Removing our row from icingadb_instance", zap.String("instance_id", hex.EncodeToString(h.instanceId)))
// Intentionally not using a context here as this is a cleanup task and h.ctx is already cancelled.
_, err := h.db.Exec("DELETE FROM icingadb_instance WHERE id = ?", h.instanceId)
query := "DELETE FROM icingadb_instance WHERE id = ?"
_, err := h.db.Exec(query, h.instanceId)
if err != nil {
h.logger.Warnw("Could not remove instance from database", zap.Error(err))
h.logger.Warnw("Could not remove instance from database", zap.Error(errors.Wrap(err, "can't perform "+query)))
}
}
@ -262,16 +266,20 @@ func (h *HA) removeOldInstances(s *icingaredisv1.IcingaStatus) {
case <-h.ctx.Done():
return
case <-time.After(timeout):
result, err := h.db.ExecContext(h.ctx, "DELETE FROM icingadb_instance "+
"WHERE id != ? AND environment_id = ? AND endpoint_id = ? AND heartbeat < ?",
h.instanceId, s.EnvironmentID(), s.EndpointId, types.UnixMilli(time.Now().Add(-timeout)))
query := "DELETE FROM icingadb_instance " +
"WHERE id != ? AND environment_id = ? AND endpoint_id = ? AND heartbeat < ?"
result, err := h.db.ExecContext(h.ctx, query, h.instanceId, s.EnvironmentID(),
s.EndpointId, types.UnixMilli(time.Now().Add(-timeout)))
if err != nil {
h.logger.Errorw("Can't remove rows of old instances", zap.Error(err))
h.logger.Errorw("Can't remove rows of old instances", zap.Error(errors.Wrap(err, "can't perform "+query)))
return
}
affected, err := result.RowsAffected()
if err != nil {
h.logger.Errorw("Can't get number of removed old instances", zap.Error(err))
h.logger.Errorw(
"Can't get number of removed old instances",
zap.Error(errors.Wrap(err, "can't get affected rows")),
)
return
}
h.logger.Debugf("Removed %d old instances", affected)

View file

@ -32,7 +32,7 @@ func CreateEntities(ctx context.Context, factoryFunc contracts.EntityFactoryFunc
e := factoryFunc()
if err := json.Unmarshal([]byte(pair.Value), e); err != nil {
return err
return errors.Wrap(err, "can't unJSON entity")
}
e.SetID(id)

View file

@ -2,8 +2,8 @@ package v1
import (
"encoding/json"
"errors"
"github.com/icinga/icingadb/pkg/types"
"github.com/pkg/errors"
)
// StatsMessage represents a message from the Redis stream icinga:stats.
@ -24,7 +24,7 @@ func (m StatsMessage) IcingaStatus() (*IcingaStatus, error) {
}
if err := json.Unmarshal([]byte(s), &envelope); err != nil {
return nil, err
return nil, errors.Wrap(err, "can't parse Icinga 2 status")
}
return &envelope.Status.IcingaApplication.IcingaStatus, nil
@ -38,7 +38,7 @@ func (m StatsMessage) Time() (*types.UnixMilli, error) {
var t types.UnixMilli
if err := json.Unmarshal([]byte(s), &t); err != nil {
return nil, err
return nil, errors.Wrap(err, "can't parse timestamp")
}
return &t, nil

View file

@ -4,8 +4,10 @@ import (
"encoding"
"fmt"
"github.com/icinga/icingadb/pkg/contracts"
"github.com/pkg/errors"
"reflect"
"strconv"
"strings"
"unsafe"
)
@ -35,7 +37,8 @@ func MakeMapStructifier(t reflect.Type, tag string) MapStructifier {
initer.Init()
}
return ptr, structifyMapByTree(kv, tree, vPtr.Elem())
vPtrElem := vPtr.Elem()
return ptr, structifyMapByTree(kv, tree, vPtrElem, vPtrElem, new([]int))
}
}
@ -65,17 +68,37 @@ func buildStructTree(t reflect.Type, tag string) []structBranch {
}
// structifyMapByTree parses src's string values into the struct dest according to tree's specification.
func structifyMapByTree(src map[string]interface{}, tree []structBranch, dest reflect.Value) error {
func structifyMapByTree(src map[string]interface{}, tree []structBranch, dest, root reflect.Value, stack *[]int) error {
*stack = append(*stack, 0)
defer func() {
*stack = (*stack)[:len(*stack)-1]
}()
for _, branch := range tree {
(*stack)[len(*stack)-1] = branch.field
if branch.subTree == nil {
if v, ok := src[branch.leaf]; ok {
if vs, ok := v.(string); ok {
if err := parseString(vs, dest.Field(branch.field).Addr().Interface()); err != nil {
return err
rt := root.Type()
typ := rt
var path []string
for _, i := range *stack {
f := typ.Field(i)
path = append(path, f.Name)
typ = f.Type
}
return errors.Wrap(err, fmt.Sprintf(
"can't parse %s into the %s %s#%s: %s", branch.leaf,
typ.Name(), rt.Name(), strings.Join(path, "."), vs,
))
}
}
}
} else if err := structifyMapByTree(src, branch.subTree, dest.Field(branch.field)); err != nil {
} else if err := structifyMapByTree(src, branch.subTree, dest.Field(branch.field), root, stack); err != nil {
return err
}
}