github.com/eden-framework/sqlx@v0.0.2/mysqlconnector/driver.go (about)

     1  package mysqlconnector
     2  
     3  import (
     4  	"context"
     5  	"database/sql/driver"
     6  	"fmt"
     7  	"strings"
     8  	"time"
     9  
    10  	"github.com/go-sql-driver/mysql"
    11  	"github.com/sirupsen/logrus"
    12  )
    13  
    14  func init() {
    15  	mysql.SetLogger(&logger{})
    16  }
    17  
    18  type logger struct{}
    19  
    20  func (l *logger) Print(args ...interface{}) {
    21  }
    22  
    23  var _ driver.Driver = (*MySqlLoggingDriver)(nil)
    24  
    25  type MySqlLoggingDriver struct {
    26  	Logger *logrus.Logger
    27  	Driver *mysql.MySQLDriver
    28  }
    29  
    30  func (d *MySqlLoggingDriver) Open(dsn string) (driver.Conn, error) {
    31  	cfg, err := mysql.ParseDSN(dsn)
    32  	if err != nil {
    33  		panic(err)
    34  	}
    35  	cfg.Passwd = strings.Repeat("*", len(cfg.Passwd))
    36  	conn, err := d.Driver.Open(dsn)
    37  	if err != nil {
    38  		d.Logger.Errorf("failed to open connection: %s %s", cfg.FormatDSN(), err)
    39  		return nil, err
    40  	}
    41  
    42  	d.Logger.Debugf("connected %s", cfg.FormatDSN())
    43  	return &loggerConn{Conn: conn, cfg: cfg, logger: d.Logger.WithField("driver", "mysql")}, nil
    44  }
    45  
    46  var _ interface {
    47  	driver.ConnBeginTx
    48  	driver.ExecerContext
    49  	driver.QueryerContext
    50  } = (*loggerConn)(nil)
    51  
    52  type loggerConn struct {
    53  	logger *logrus.Entry
    54  	cfg    *mysql.Config
    55  	driver.Conn
    56  }
    57  
    58  func (c *loggerConn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, error) {
    59  	logger := c.logger.WithContext(ctx)
    60  
    61  	logger.Debug("=========== Beginning Transaction ===========")
    62  	tx, err := c.Conn.(driver.ConnBeginTx).BeginTx(ctx, opts)
    63  	if err != nil {
    64  		logger.Errorf("failed to begin transaction: %s", err)
    65  		return nil, err
    66  	}
    67  	return &loggingTx{Tx: tx, logger: logger}, nil
    68  }
    69  
    70  func (c *loggerConn) Close() error {
    71  	if err := c.Conn.Close(); err != nil {
    72  		c.logger.Errorf("failed to close connection: %s", err)
    73  		return err
    74  	}
    75  	return nil
    76  }
    77  
    78  func (c *loggerConn) Prepare(query string) (driver.Stmt, error) {
    79  	panic(fmt.Errorf("don't use Prepare"))
    80  }
    81  
    82  func (c *loggerConn) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (rows driver.Rows, err error) {
    83  	cost := startTimer()
    84  	logger := c.logger.WithContext(ctx)
    85  
    86  	defer func() {
    87  		q := c.interpolateParams(query, args)
    88  
    89  		if err != nil {
    90  			if mysqlErr, ok := err.(*mysql.MySQLError); !ok {
    91  				logger.Errorf("failed query %s: %s", err, q)
    92  			} else {
    93  				logger.Warnf("failed query %s: %s", mysqlErr, q)
    94  			}
    95  		} else {
    96  			logger.WithField("cost", cost().String()).Debugf("%s", q)
    97  		}
    98  	}()
    99  
   100  	rows, err = c.Conn.(driver.QueryerContext).QueryContext(ctx, query, args)
   101  	return
   102  }
   103  
   104  func (c *loggerConn) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (result driver.Result, err error) {
   105  	cost := startTimer()
   106  	logger := c.logger.WithContext(ctx)
   107  
   108  	defer func() {
   109  		q := c.interpolateParams(query, args)
   110  
   111  		if err != nil {
   112  			if mysqlErr, ok := err.(*mysql.MySQLError); !ok {
   113  				logger.Errorf("failed exec %s: %s", err, q)
   114  			} else if mysqlErr.Number == DuplicateEntryErrNumber {
   115  				logger.Warnf("failed exec %s: %s", err, q)
   116  			} else {
   117  				logger.Errorf("failed exec %s: %s", mysqlErr, q)
   118  			}
   119  			return
   120  		}
   121  
   122  		logger.WithField("cost", cost().String()).Debugf("%s", q)
   123  	}()
   124  
   125  	result, err = c.Conn.(driver.ExecerContext).ExecContext(ctx, query, args)
   126  	return
   127  }
   128  
   129  func (c *loggerConn) interpolateParams(query string, args []driver.NamedValue) fmt.Stringer {
   130  	return &SqlPrinter{query, args, c.cfg}
   131  }
   132  
   133  type SqlPrinter struct {
   134  	query string
   135  	args  []driver.NamedValue
   136  	cfg   *mysql.Config
   137  }
   138  
   139  func (p *SqlPrinter) String() string {
   140  	if len(p.args) == 0 {
   141  		return p.query
   142  	}
   143  	argValues, err := namedValueToValue(p.args)
   144  	if err != nil {
   145  		return p.query
   146  	}
   147  	sqlForLog, err := interpolateParams(p.query, argValues, p.cfg.Loc, p.cfg.MaxAllowedPacket)
   148  	if err != nil {
   149  		return p.query
   150  	}
   151  
   152  	return sqlForLog
   153  }
   154  
   155  var DuplicateEntryErrNumber uint16 = 1062
   156  
   157  func startTimer() func() time.Duration {
   158  	startTime := time.Now()
   159  	return func() time.Duration {
   160  		return time.Now().Sub(startTime)
   161  	}
   162  }
   163  
   164  type loggingTx struct {
   165  	logger *logrus.Entry
   166  	driver.Tx
   167  }
   168  
   169  func (tx *loggingTx) Commit() error {
   170  	if err := tx.Tx.Commit(); err != nil {
   171  		tx.logger.Debugf("failed to commit transaction: %s", err)
   172  		return err
   173  	}
   174  	tx.logger.Debug("=========== Committed Transaction ===========")
   175  	return nil
   176  }
   177  
   178  func (tx *loggingTx) Rollback() error {
   179  	if err := tx.Tx.Rollback(); err != nil {
   180  		tx.logger.Debugf("failed to rollback transaction: %s", err)
   181  		return err
   182  	}
   183  	tx.logger.Debug("=========== Rollback Transaction ===========")
   184  	return nil
   185  }