Skip to content

Commit

Permalink
Initial support for stdlib logger
Browse files Browse the repository at this point in the history
  • Loading branch information
dfava committed Oct 31, 2024
1 parent 531f756 commit ca41f0d
Show file tree
Hide file tree
Showing 6 changed files with 294 additions and 36 deletions.
36 changes: 36 additions & 0 deletions examples/helloworld/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package main

import (
"context"
"database/sql"
"log"
"os"

"github.com/vippsas/go-querysql/querysql"

_ "github.com/denisenkom/go-mssqldb"
)

func initdb() (*sql.DB, error) {
dsn := os.Getenv("SQL_DSN")
if dsn == "" {
dsn = "sqlserver://127.0.0.1:1433?database=master&user id=sa&password=VippsPw1"
}
return sql.Open("sqlserver", dsn)
}

func main() {
sqldb, err := initdb()
if err != nil {
panic(err.Error())
}

logger := log.Default()
ctx := querysql.WithLogger(context.Background(), querysql.StdMSSQLLogger(logger))

qry := `select _log='info', message='hello world from a query'`
_, err = querysql.ExecContext(ctx, sqldb, qry, "world")
if err != nil {
panic(err.Error())
}
}
36 changes: 36 additions & 0 deletions examples/helloworld_logrus/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package main

import (
"context"
"database/sql"
"os"

"github.com/sirupsen/logrus"
"github.com/vippsas/go-querysql/querysql"

_ "github.com/denisenkom/go-mssqldb"
)

func initdb() (*sql.DB, error) {
dsn := os.Getenv("SQL_DSN")
if dsn == "" {
dsn = "sqlserver://127.0.0.1:1433?database=master&user id=sa&password=VippsPw1"
}
return sql.Open("sqlserver", dsn)
}

func main() {
sqldb, err := initdb()
if err != nil {
panic(err.Error())
}

logger := logrus.StandardLogger()
ctx := querysql.WithLogger(context.Background(), querysql.LogrusMSSQLLogger(logger, logrus.InfoLevel))

qry := `select _log='info', message='hello world from a query'`
_, err = querysql.ExecContext(ctx, sqldb, qry, "world")
if err != nil {
panic(err.Error())
}
}
77 changes: 77 additions & 0 deletions examples/metrics/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package main

import (
"context"
"database/sql"
"fmt"
"os"

_ "github.com/denisenkom/go-mssqldb"
"github.com/sirupsen/logrus"
"github.com/vippsas/go-querysql/querysql"
)

func initdb() (*sql.DB, error) {
dsn := os.Getenv("SQL_DSN")
if dsn == "" {
dsn = "sqlserver://127.0.0.1:1433?database=master&user id=sa&password=VippsPw1"
}
return sql.Open("sqlserver", dsn)
}

func populatedb(ctx context.Context, sqldb *sql.DB) error {
qry := `if object_id('dbo.MyUsers', 'U') is not null drop table MyUsers
create table MyUsers (
Id int identity(1,1) primary key,
UserName nvarchar(50) not null,
UserAge int
);
insert into MyUsers (UserName, UserAge) values ('Bob Doe', 42);
insert into MyUsers (UserName, UserAge) values ('Johny Doe', 10);
`
_, err := querysql.ExecContext(ctx, sqldb, qry, "world")
return err
}

func processUsers(ctx context.Context, sqldb *sql.DB) error {
qry := `select * from MyUsers;
select _function='UserMetrics', label='size.MyUsers', numProcessed=@@rowcount;
`
type rowType struct {
Id int
UserName string
UserAge int
}
users, err := querysql.Slice[rowType](ctx, sqldb, qry, "world")
fmt.Println(users)
return err
}

// This function is going to be called when the query in processUsers is run
func UserMetrics(label string, count int) {
fmt.Println(label, count)
}

func main() {
sqldb, err := initdb()
if err != nil {
panic(err.Error())
}

logger := logrus.StandardLogger()
ctx := querysql.WithLogger(context.Background(), querysql.LogrusMSSQLLogger(logger, logrus.InfoLevel))
ctx = querysql.WithDispatcher(ctx, querysql.GoMSSQLDispatcher([]interface{}{
UserMetrics,
}))

err = populatedb(ctx, sqldb)
if err != nil {
panic(err.Error())
}

err = processUsers(ctx, sqldb)
if err != nil {
panic(err.Error())
}

}
104 changes: 104 additions & 0 deletions querysql/logmssql.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package querysql

import (
"database/sql"
"encoding/hex"
"fmt"
"log"

"github.com/sirupsen/logrus"
)

func StdMSSQLLogger(logger *log.Logger) RowsLogger {
defaultLogLevel := logrus.InfoLevel
return func(rows *sql.Rows) error {
var logLevel string

cols, err := rows.Columns()
if err != nil {
return err
}
colTypes, err := rows.ColumnTypes()
if err != nil {
return err
}

// For logging just scan *everything* into a string type straight from SQL driver to make things simple here...
// The first column is the log level by protocol of RowsLogger.
fields := make([]interface{}, len(cols))
scanPointers := make([]interface{}, len(cols))
scanPointers[0] = &logLevel
for i := 1; i < len(cols); i++ {
scanPointers[i] = &fields[i]
}

hadRow := false
for rows.Next() {
hadRow = true
if err = rows.Scan(scanPointers...); err != nil {
return err
}
parsedLogLevel, err := logrus.ParseLevel(logLevel)
if err != nil {
emitLogEntry(logger, logrus.Fields{
"event": "invalid.log.level",
"invalid.level": logLevel,
}, logrus.ErrorLevel)
parsedLogLevel = defaultLogLevel
}

logrusFields := logrus.Fields{}
for i, value := range fields {
if i == 0 {
continue
}
// we post-process the types of the values a bit to make some types more readable in logs
switch typedValue := value.(type) {
case []uint8:
switch colTypes[i].DatabaseTypeName() {
case "MONEY":
value = string(typedValue)
case "UNIQUEIDENTIFIER":
value, err = ParseSQLUUIDBytes(typedValue)
if err != nil {
return fmt.Errorf("could not decode UUID from SQL: %w", err)
}
default:
value = "0x" + hex.EncodeToString(typedValue)
}
}
logrusFields[cols[i]] = value
}
emitLogEntry(logger, logrusFields, parsedLogLevel)
}
if err = rows.Err(); err != nil {
return err
}
if !hadRow {
// it can be quite annoying to have logging of empty tables turn into nothing, so log
// an indication that the log statement was there, with an empty table
// in this case loglevel is unreachable, and we really can only log the keys,
// but let's hope INFO isn't overboard
logrusFields := logrus.Fields{}
logrusFields["_norows"] = true
for _, col := range cols[1:] {
logrusFields[col] = ""
}
emitLogEntry(logger, logrusFields, defaultLogLevel)
}
return nil
}
}

func emitLogEntry(logger *log.Logger, fields logrus.Fields, level logrus.Level) {
switch level {
case logrus.PanicLevel:
logger.Panic()
case logrus.FatalLevel:
logger.Fatal()
case logrus.ErrorLevel, logrus.WarnLevel, logrus.DebugLevel, logrus.TraceLevel, logrus.InfoLevel:
logger.Print() // TODO(dsf): Add level into the message
default:
panic(fmt.Sprintf("Log level %d not handled in emitLogEntry", level))
}
}
36 changes: 0 additions & 36 deletions querysql/logrusmssql.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,8 @@ package querysql
import (
"database/sql"
"encoding/hex"
"errors"
"fmt"

"github.com/google/uuid"
"github.com/sirupsen/logrus"
)

Expand Down Expand Up @@ -109,37 +107,3 @@ func logrusEmitLogEntry(logger logrus.FieldLogger, level logrus.Level) {
panic(fmt.Sprintf("Log level %d not handled in logrusEmitLogEntry", level))
}
}

func ParseSQLUUIDBytes(v []uint8) (uuid.UUID, error) {
if len(v) != 16 {
return uuid.UUID{}, errors.New("ParseSQLUUIDBytes: did not get 16 bytes")
}
var shuffled [16]uint8
// This: select convert(uniqueidentifier, '00010203-0405-0607-0809-0a0b0c0d0e0f')
// Returns this when passed to uuid.FromBytes:
// 03020100-0504-0706-0809-0a0b0c0d0e0f
// So, shuffling first
shuffled[0x0] = v[0x3]
shuffled[0x1] = v[0x2]
shuffled[0x2] = v[0x1]
shuffled[0x3] = v[0x0]

shuffled[0x4] = v[0x5]
shuffled[0x5] = v[0x4]

shuffled[0x6] = v[0x7]
shuffled[0x7] = v[0x6]

// The rest are not shuffled :shrug:
shuffled[0x8] = v[0x8]
shuffled[0x9] = v[0x9]

shuffled[0xa] = v[0xa]
shuffled[0xb] = v[0xb]
shuffled[0xc] = v[0xc]
shuffled[0xd] = v[0xd]
shuffled[0xe] = v[0xe]
shuffled[0xf] = v[0xf]

return uuid.FromBytes(shuffled[:])
}
41 changes: 41 additions & 0 deletions querysql/parse.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package querysql

import (
"errors"

"github.com/google/uuid"
)

func ParseSQLUUIDBytes(v []uint8) (uuid.UUID, error) {
if len(v) != 16 {
return uuid.UUID{}, errors.New("ParseSQLUUIDBytes: did not get 16 bytes")
}
var shuffled [16]uint8
// This: select convert(uniqueidentifier, '00010203-0405-0607-0809-0a0b0c0d0e0f')
// Returns this when passed to uuid.FromBytes:
// 03020100-0504-0706-0809-0a0b0c0d0e0f
// So, shuffling first
shuffled[0x0] = v[0x3]
shuffled[0x1] = v[0x2]
shuffled[0x2] = v[0x1]
shuffled[0x3] = v[0x0]

shuffled[0x4] = v[0x5]
shuffled[0x5] = v[0x4]

shuffled[0x6] = v[0x7]
shuffled[0x7] = v[0x6]

// The rest are not shuffled :shrug:
shuffled[0x8] = v[0x8]
shuffled[0x9] = v[0x9]

shuffled[0xa] = v[0xa]
shuffled[0xb] = v[0xb]
shuffled[0xc] = v[0xc]
shuffled[0xd] = v[0xd]
shuffled[0xe] = v[0xe]
shuffled[0xf] = v[0xf]

return uuid.FromBytes(shuffled[:])
}

0 comments on commit ca41f0d

Please sign in to comment.