Categorygithub.com/simukti/sqldb-logger
modulepackage
0.0.0-20230108155151-646c1a075551
Repository: https://github.com/simukti/sqldb-logger.git
Documentation: pkg.go.dev

# README

SQLDB-Logger

Coverage Status Go Report Card Sonar Violations (long format) Sonar Tech Debt Sonar Quality Gate Documentation License

A logger for Go SQL database driver without modify existing *sql.DB stdlib usage.

shameless console output sample Colored console writer output above only for sample/development

FEATURES

  • Leveled, detailed and configurable logging.
  • Keep using (or re-use existing) *sql.DB as is.
  • Bring your own logger backend via simple log interface.
  • Trackable log output:
    • Every call has its own unique ID.
    • Prepared statement and execution will have same ID.
    • On execution/result error, it will include the query, arguments, params, and related IDs.

INSTALL

go get -u -v github.com/simukti/sqldb-logger

Version pinning using dependency manager such as Mod or Dep is highly recommended.

USAGE

As a start, Logger is just a simple interface:

type Logger interface {
	Log(ctx context.Context, level Level, msg string, data map[string]interface{})
}

There are 4 included basic implementation that uses well-known JSON structured logger for quickstart:

Note: those adapters does not use given context, you need to modify it and adjust with your needs. (example: add http request id/whatever value from context to query log when you call QueryerContext andExecerContext methods)

Then for that logger to works, you need to integrate with a compatible driver which will be used by *sql.DB.

INTEGRATE WITH EXISTING SQL DB DRIVER

Re-use from existing *sql.DB driver, this is the simplest way:

For example, from:

dsn := "username:passwd@tcp(mysqlserver:3306)/dbname?parseTime=true"
db, err := sql.Open("mysql", dsn) // db is *sql.DB
db.Ping() // to check connectivity and DSN correctness

To:

// import sqldblogger "github.com/simukti/sqldb-logger"
// import "github.com/simukti/sqldb-logger/logadapter/zerologadapter"
dsn := "username:passwd@tcp(mysqlserver:3306)/dbname?parseTime=true"
db, err := sql.Open("mysql", dsn) // db is *sql.DB
// handle err
loggerAdapter := zerologadapter.New(zerolog.New(os.Stdout))
db = sqldblogger.OpenDriver(dsn, db.Driver(), loggerAdapter/*, using_default_options*/) // db is STILL *sql.DB
db.Ping() // to check connectivity and DSN correctness

That's it, all *sql.DB interaction now logged.

INTEGRATE WITH SQL DRIVER STRUCT

It is also possible to integrate with following public empty struct driver directly:

MySQL (go-sql-driver/mysql)

db := sqldblogger.OpenDriver(dsn, &mysql.MySQLDriver{}, loggerAdapter /*, ...options */)

PostgreSQL (lib/pq)

db := sqldblogger.OpenDriver(dsn, &pq.Driver{}, loggerAdapter /*, ...options */) 

SQLite3 (mattn/go-sqlite3)

db := sqldblogger.OpenDriver(dsn, &sqlite3.SQLiteDriver{}, loggerAdapter /*, ...options */)

Following struct drivers maybe compatible:

SQL Server (denisenkom/go-mssqldb)

db := sqldblogger.OpenDriver(dsn, &mssql.Driver{}, loggerAdapter /*, ...options */)

Oracle (mattn/go-oci8)

db := sqldblogger.OpenDriver(dsn, oci8.OCI8Driver, loggerAdapter /*, ...options */)

LOGGER OPTIONS

When using sqldblogger.OpenDriver(dsn, driver, logger, opt...) without 4th variadic argument, it will use default options.

Here is sample of OpenDriver() using all available options and use non-default value:

db = sqldblogger.OpenDriver(
    dsn, 
    db.Driver(), 
    loggerAdapter,
    // AVAILABLE OPTIONS
    sqldblogger.WithErrorFieldname("sql_error"),                    // default: error
    sqldblogger.WithDurationFieldname("query_duration"),            // default: duration
    sqldblogger.WithTimeFieldname("log_time"),                      // default: time
    sqldblogger.WithSQLQueryFieldname("sql_query"),                 // default: query
    sqldblogger.WithSQLArgsFieldname("sql_args"),                   // default: args
    sqldblogger.WithMinimumLevel(sqldblogger.LevelTrace),           // default: LevelDebug
    sqldblogger.WithLogArguments(false),                            // default: true
    sqldblogger.WithDurationUnit(sqldblogger.DurationNanosecond),   // default: DurationMillisecond
    sqldblogger.WithTimeFormat(sqldblogger.TimeFormatRFC3339),      // default: TimeFormatUnix
    sqldblogger.WithLogDriverErrorSkip(true),                       // default: false
    sqldblogger.WithSQLQueryAsMessage(true),                        // default: false
    sqldblogger.WithUIDGenerator(sqldblogger.UIDGenerator),         // default: *defaultUID
    sqldblogger.WithConnectionIDFieldname("con_id"),                // default: conn_id
    sqldblogger.WithStatementIDFieldname("stm_id"),                 // default: stmt_id
    sqldblogger.WithTransactionIDFieldname("trx_id"),               // default: tx_id
    sqldblogger.WithWrapResult(false),                              // default: true
    sqldblogger.WithIncludeStartTime(true),                         // default: false
    sqldblogger.WithStartTimeFieldname("start_time"),               // default: start
    sqldblogger.WithPreparerLevel(sqldblogger.LevelDebug),          // default: LevelInfo
    sqldblogger.WithQueryerLevel(sqldblogger.LevelDebug),           // default: LevelInfo
    sqldblogger.WithExecerLevel(sqldblogger.LevelDebug),            // default: LevelInfo
)

Click here for options documentation.

MOTIVATION

I want to:

  • Keep using *sql.DB.
  • Have configurable output field.
  • Leverage structured logging.
  • Fetch and log context.Context value if needed.
  • Re-use pgx log interface.

I haven't found Go *sql.DB logger with that features, so why not created myself?

REFERENCES

CONTRIBUTE

If you found a bug, typo, wrong test, idea, help with existing issue, or anything constructive.

Don't hesitate to create an issue or pull request.

CREDITS

  • pgx for awesome PostgreSQL driver.

LICENSE

MIT

# Functions

OpenDriver wrap given driver with logger and return *sql.DB.
WithConnectionIDFieldname to customize connection ID fieldname on log output.
WithDurationFieldname to customize duration fieldname on log output.
WithDurationUnit to customize log duration unit.
WithErrorFieldname to customize error fieldname on log output.
WithExecerLevel set default level of Exec(Context) method calls.
WithIncludeStartTime flag to include actual start time before actual driver execution.
WithLogArguments set flag to log SQL query argument or not.
WithLogDriverErrorSkip set flag for driver.ErrSkip.
WithMinimumLevel set minimum level to be logged.
WithPreparerLevel set default level of Prepare(Context) method calls.
WithQueryerLevel set default level of Query(Context) method calls.
WithSQLArgsFieldname to customize SQL query arguments fieldname on log output.
WithSQLQueryAsMessage set SQL query as message in log output (only for function call with SQL query).
WithSQLQueryFieldname to customize SQL query fieldname on log output.
WithStartTimeFieldname to customize start time fieldname on log output.
WithStatementIDFieldname to customize prepared statement ID fieldname on log output.
WithTimeFieldname to customize log timestamp fieldname on log output.
WithTimeFormat to customize log time format.
WithTransactionIDFieldname to customize database transaction ID fieldname on log output.
WithUIDGenerator set custom unique id generator for context call (connection, statement, transaction).
WithWrapResult set flag to wrap Queryer(Context) and Execer(Context) driver.Rows/driver.Result response.

# Constants

DurationMicrosecond will format time.Since() result to microsecond unit (1/1_000_000 second).
DurationMillisecond will format time.Since() result to millisecond unit (1/1_000 second).
DurationNanosecond will format time.Since() result to nanosecond unit (1/1_000_000_000 second).
LevelDebug is used by non Queryer(Context) and Execer(Context) call like Ping() and Connect().
LevelError is used on actual driver error or when driver not implement some optional sql/driver interface.
LevelInfo is used by Queryer, Execer, Preparer, and Stmt.
LevelTrace is the lowest level and the most detailed.
TimeFormatRFC3339 will format log time to time.RFC3339 format.
TimeFormatRFC3339Nano will format log time to time.RFC3339Nano format.
TimeFormatUnix will format log time to unix timestamp.
TimeFormatUnixNano will format log time to unix timestamp with nano seconds.

# Structs

NullUID is used to disable unique id when set to WithUIDGenerator().

# Interfaces

Logger interface copied from: https://github.com/jackc/pgx/blob/f3a3ee1a0e5c8fc8991928bcd06fdbcd1ee9d05c/logger.go#L46-L49.
UIDGenerator is an interface to generate unique ID for context call (connection, statement, transaction).

# Type aliases

DurationUnit is total time spent on an actual driver function call calculated by time.Since(start).
Level is a log level which filterable by minimum level option.
Option is optional variadic type in OpenDriver().
TimeFormat is time.Now() format when Log() deliver the log message.