Skip to content

Commit ca41f0d

Browse files
committed
Initial support for stdlib logger
1 parent 531f756 commit ca41f0d

File tree

6 files changed

+294
-36
lines changed

6 files changed

+294
-36
lines changed

examples/helloworld/main.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"database/sql"
6+
"log"
7+
"os"
8+
9+
"github.com/vippsas/go-querysql/querysql"
10+
11+
_ "github.com/denisenkom/go-mssqldb"
12+
)
13+
14+
func initdb() (*sql.DB, error) {
15+
dsn := os.Getenv("SQL_DSN")
16+
if dsn == "" {
17+
dsn = "sqlserver://127.0.0.1:1433?database=master&user id=sa&password=VippsPw1"
18+
}
19+
return sql.Open("sqlserver", dsn)
20+
}
21+
22+
func main() {
23+
sqldb, err := initdb()
24+
if err != nil {
25+
panic(err.Error())
26+
}
27+
28+
logger := log.Default()
29+
ctx := querysql.WithLogger(context.Background(), querysql.StdMSSQLLogger(logger))
30+
31+
qry := `select _log='info', message='hello world from a query'`
32+
_, err = querysql.ExecContext(ctx, sqldb, qry, "world")
33+
if err != nil {
34+
panic(err.Error())
35+
}
36+
}

examples/helloworld_logrus/main.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"database/sql"
6+
"os"
7+
8+
"github.com/sirupsen/logrus"
9+
"github.com/vippsas/go-querysql/querysql"
10+
11+
_ "github.com/denisenkom/go-mssqldb"
12+
)
13+
14+
func initdb() (*sql.DB, error) {
15+
dsn := os.Getenv("SQL_DSN")
16+
if dsn == "" {
17+
dsn = "sqlserver://127.0.0.1:1433?database=master&user id=sa&password=VippsPw1"
18+
}
19+
return sql.Open("sqlserver", dsn)
20+
}
21+
22+
func main() {
23+
sqldb, err := initdb()
24+
if err != nil {
25+
panic(err.Error())
26+
}
27+
28+
logger := logrus.StandardLogger()
29+
ctx := querysql.WithLogger(context.Background(), querysql.LogrusMSSQLLogger(logger, logrus.InfoLevel))
30+
31+
qry := `select _log='info', message='hello world from a query'`
32+
_, err = querysql.ExecContext(ctx, sqldb, qry, "world")
33+
if err != nil {
34+
panic(err.Error())
35+
}
36+
}

examples/metrics/main.go

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"database/sql"
6+
"fmt"
7+
"os"
8+
9+
_ "github.com/denisenkom/go-mssqldb"
10+
"github.com/sirupsen/logrus"
11+
"github.com/vippsas/go-querysql/querysql"
12+
)
13+
14+
func initdb() (*sql.DB, error) {
15+
dsn := os.Getenv("SQL_DSN")
16+
if dsn == "" {
17+
dsn = "sqlserver://127.0.0.1:1433?database=master&user id=sa&password=VippsPw1"
18+
}
19+
return sql.Open("sqlserver", dsn)
20+
}
21+
22+
func populatedb(ctx context.Context, sqldb *sql.DB) error {
23+
qry := `if object_id('dbo.MyUsers', 'U') is not null drop table MyUsers
24+
create table MyUsers (
25+
Id int identity(1,1) primary key,
26+
UserName nvarchar(50) not null,
27+
UserAge int
28+
);
29+
insert into MyUsers (UserName, UserAge) values ('Bob Doe', 42);
30+
insert into MyUsers (UserName, UserAge) values ('Johny Doe', 10);
31+
`
32+
_, err := querysql.ExecContext(ctx, sqldb, qry, "world")
33+
return err
34+
}
35+
36+
func processUsers(ctx context.Context, sqldb *sql.DB) error {
37+
qry := `select * from MyUsers;
38+
select _function='UserMetrics', label='size.MyUsers', numProcessed=@@rowcount;
39+
`
40+
type rowType struct {
41+
Id int
42+
UserName string
43+
UserAge int
44+
}
45+
users, err := querysql.Slice[rowType](ctx, sqldb, qry, "world")
46+
fmt.Println(users)
47+
return err
48+
}
49+
50+
// This function is going to be called when the query in processUsers is run
51+
func UserMetrics(label string, count int) {
52+
fmt.Println(label, count)
53+
}
54+
55+
func main() {
56+
sqldb, err := initdb()
57+
if err != nil {
58+
panic(err.Error())
59+
}
60+
61+
logger := logrus.StandardLogger()
62+
ctx := querysql.WithLogger(context.Background(), querysql.LogrusMSSQLLogger(logger, logrus.InfoLevel))
63+
ctx = querysql.WithDispatcher(ctx, querysql.GoMSSQLDispatcher([]interface{}{
64+
UserMetrics,
65+
}))
66+
67+
err = populatedb(ctx, sqldb)
68+
if err != nil {
69+
panic(err.Error())
70+
}
71+
72+
err = processUsers(ctx, sqldb)
73+
if err != nil {
74+
panic(err.Error())
75+
}
76+
77+
}

querysql/logmssql.go

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
package querysql
2+
3+
import (
4+
"database/sql"
5+
"encoding/hex"
6+
"fmt"
7+
"log"
8+
9+
"github.com/sirupsen/logrus"
10+
)
11+
12+
func StdMSSQLLogger(logger *log.Logger) RowsLogger {
13+
defaultLogLevel := logrus.InfoLevel
14+
return func(rows *sql.Rows) error {
15+
var logLevel string
16+
17+
cols, err := rows.Columns()
18+
if err != nil {
19+
return err
20+
}
21+
colTypes, err := rows.ColumnTypes()
22+
if err != nil {
23+
return err
24+
}
25+
26+
// For logging just scan *everything* into a string type straight from SQL driver to make things simple here...
27+
// The first column is the log level by protocol of RowsLogger.
28+
fields := make([]interface{}, len(cols))
29+
scanPointers := make([]interface{}, len(cols))
30+
scanPointers[0] = &logLevel
31+
for i := 1; i < len(cols); i++ {
32+
scanPointers[i] = &fields[i]
33+
}
34+
35+
hadRow := false
36+
for rows.Next() {
37+
hadRow = true
38+
if err = rows.Scan(scanPointers...); err != nil {
39+
return err
40+
}
41+
parsedLogLevel, err := logrus.ParseLevel(logLevel)
42+
if err != nil {
43+
emitLogEntry(logger, logrus.Fields{
44+
"event": "invalid.log.level",
45+
"invalid.level": logLevel,
46+
}, logrus.ErrorLevel)
47+
parsedLogLevel = defaultLogLevel
48+
}
49+
50+
logrusFields := logrus.Fields{}
51+
for i, value := range fields {
52+
if i == 0 {
53+
continue
54+
}
55+
// we post-process the types of the values a bit to make some types more readable in logs
56+
switch typedValue := value.(type) {
57+
case []uint8:
58+
switch colTypes[i].DatabaseTypeName() {
59+
case "MONEY":
60+
value = string(typedValue)
61+
case "UNIQUEIDENTIFIER":
62+
value, err = ParseSQLUUIDBytes(typedValue)
63+
if err != nil {
64+
return fmt.Errorf("could not decode UUID from SQL: %w", err)
65+
}
66+
default:
67+
value = "0x" + hex.EncodeToString(typedValue)
68+
}
69+
}
70+
logrusFields[cols[i]] = value
71+
}
72+
emitLogEntry(logger, logrusFields, parsedLogLevel)
73+
}
74+
if err = rows.Err(); err != nil {
75+
return err
76+
}
77+
if !hadRow {
78+
// it can be quite annoying to have logging of empty tables turn into nothing, so log
79+
// an indication that the log statement was there, with an empty table
80+
// in this case loglevel is unreachable, and we really can only log the keys,
81+
// but let's hope INFO isn't overboard
82+
logrusFields := logrus.Fields{}
83+
logrusFields["_norows"] = true
84+
for _, col := range cols[1:] {
85+
logrusFields[col] = ""
86+
}
87+
emitLogEntry(logger, logrusFields, defaultLogLevel)
88+
}
89+
return nil
90+
}
91+
}
92+
93+
func emitLogEntry(logger *log.Logger, fields logrus.Fields, level logrus.Level) {
94+
switch level {
95+
case logrus.PanicLevel:
96+
logger.Panic()
97+
case logrus.FatalLevel:
98+
logger.Fatal()
99+
case logrus.ErrorLevel, logrus.WarnLevel, logrus.DebugLevel, logrus.TraceLevel, logrus.InfoLevel:
100+
logger.Print() // TODO(dsf): Add level into the message
101+
default:
102+
panic(fmt.Sprintf("Log level %d not handled in emitLogEntry", level))
103+
}
104+
}

querysql/logrusmssql.go

Lines changed: 0 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,8 @@ package querysql
33
import (
44
"database/sql"
55
"encoding/hex"
6-
"errors"
76
"fmt"
87

9-
"github.com/google/uuid"
108
"github.com/sirupsen/logrus"
119
)
1210

@@ -109,37 +107,3 @@ func logrusEmitLogEntry(logger logrus.FieldLogger, level logrus.Level) {
109107
panic(fmt.Sprintf("Log level %d not handled in logrusEmitLogEntry", level))
110108
}
111109
}
112-
113-
func ParseSQLUUIDBytes(v []uint8) (uuid.UUID, error) {
114-
if len(v) != 16 {
115-
return uuid.UUID{}, errors.New("ParseSQLUUIDBytes: did not get 16 bytes")
116-
}
117-
var shuffled [16]uint8
118-
// This: select convert(uniqueidentifier, '00010203-0405-0607-0809-0a0b0c0d0e0f')
119-
// Returns this when passed to uuid.FromBytes:
120-
// 03020100-0504-0706-0809-0a0b0c0d0e0f
121-
// So, shuffling first
122-
shuffled[0x0] = v[0x3]
123-
shuffled[0x1] = v[0x2]
124-
shuffled[0x2] = v[0x1]
125-
shuffled[0x3] = v[0x0]
126-
127-
shuffled[0x4] = v[0x5]
128-
shuffled[0x5] = v[0x4]
129-
130-
shuffled[0x6] = v[0x7]
131-
shuffled[0x7] = v[0x6]
132-
133-
// The rest are not shuffled :shrug:
134-
shuffled[0x8] = v[0x8]
135-
shuffled[0x9] = v[0x9]
136-
137-
shuffled[0xa] = v[0xa]
138-
shuffled[0xb] = v[0xb]
139-
shuffled[0xc] = v[0xc]
140-
shuffled[0xd] = v[0xd]
141-
shuffled[0xe] = v[0xe]
142-
shuffled[0xf] = v[0xf]
143-
144-
return uuid.FromBytes(shuffled[:])
145-
}

querysql/parse.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package querysql
2+
3+
import (
4+
"errors"
5+
6+
"github.com/google/uuid"
7+
)
8+
9+
func ParseSQLUUIDBytes(v []uint8) (uuid.UUID, error) {
10+
if len(v) != 16 {
11+
return uuid.UUID{}, errors.New("ParseSQLUUIDBytes: did not get 16 bytes")
12+
}
13+
var shuffled [16]uint8
14+
// This: select convert(uniqueidentifier, '00010203-0405-0607-0809-0a0b0c0d0e0f')
15+
// Returns this when passed to uuid.FromBytes:
16+
// 03020100-0504-0706-0809-0a0b0c0d0e0f
17+
// So, shuffling first
18+
shuffled[0x0] = v[0x3]
19+
shuffled[0x1] = v[0x2]
20+
shuffled[0x2] = v[0x1]
21+
shuffled[0x3] = v[0x0]
22+
23+
shuffled[0x4] = v[0x5]
24+
shuffled[0x5] = v[0x4]
25+
26+
shuffled[0x6] = v[0x7]
27+
shuffled[0x7] = v[0x6]
28+
29+
// The rest are not shuffled :shrug:
30+
shuffled[0x8] = v[0x8]
31+
shuffled[0x9] = v[0x9]
32+
33+
shuffled[0xa] = v[0xa]
34+
shuffled[0xb] = v[0xb]
35+
shuffled[0xc] = v[0xc]
36+
shuffled[0xd] = v[0xd]
37+
shuffled[0xe] = v[0xe]
38+
shuffled[0xf] = v[0xf]
39+
40+
return uuid.FromBytes(shuffled[:])
41+
}

0 commit comments

Comments
 (0)