Skip to content

Commit d50ebc5

Browse files
committed
Implement Logging
1 parent 3443106 commit d50ebc5

24 files changed

Lines changed: 1174 additions & 178 deletions

cmd/mangosql/mangosql.go

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"os"
1010
"path"
1111
"regexp"
12+
"slices"
1213
"strings"
1314

1415
"github.com/kefniark/mango-sql/internal"
@@ -17,40 +18,64 @@ import (
1718

1819
func main() {
1920
app := &cli.App{
21+
Version: "v0.0.1",
2022
Name: "mangosql",
2123
HelpName: "MangoSQL",
2224
Usage: "Generate a SQL Client from a SQL file or folder of SQL migrations",
2325
UsageText: `Syntax: mangosql [options] <source folder>
2426
Example: mangosql --output db/file.go db/schema.sql`,
25-
Suggest: true,
27+
Suggest: true,
28+
EnableBashCompletion: true,
2629
Flags: []cli.Flag{
2730
&cli.StringFlag{
28-
Name: "output",
29-
Value: "database/client.go",
30-
Usage: "Output file",
31+
Name: "output",
32+
Aliases: []string{"o"},
33+
Value: "database/client.go",
34+
Usage: "Output file",
3135
},
3236
&cli.StringFlag{
33-
Name: "package",
34-
Value: "database",
35-
Usage: "Go Package",
37+
Name: "package",
38+
Aliases: []string{"p"},
39+
Value: "database",
40+
Usage: "Go Package",
3641
},
3742
&cli.StringFlag{
38-
Name: "driver",
39-
Value: "pgx",
40-
Usage: "SQL Driver",
43+
Name: "driver",
44+
Aliases: []string{"d"},
45+
Value: "pgx",
46+
Usage: "SQL Driver",
47+
},
48+
&cli.StringFlag{
49+
Name: "logger",
50+
Aliases: []string{"l"},
51+
Value: "none",
52+
Usage: "Logging library",
4153
},
4254
},
4355
Action: func(ctx *cli.Context) error {
4456
if ctx.NArg() <= 0 {
4557
return fmt.Errorf("missing source folder")
4658
}
4759

60+
allowed_drivers := []string{"pq", "pgx", "sqlite"}
61+
driver := ctx.String("driver")
62+
if !slices.Contains(allowed_drivers, driver) {
63+
return fmt.Errorf("unknown driver, should be one of %v", allowed_drivers)
64+
}
65+
66+
allowed_logger := []string{"none", "zap", "logrus", "zerolog", "console"}
67+
logger := ctx.String("logger")
68+
if !slices.Contains(allowed_logger, logger) {
69+
return fmt.Errorf("unknown logger, should be one of %v", allowed_logger)
70+
}
71+
4872
name := ctx.Args().Get(0)
4973
return generate(GenerateOptions{
5074
Src: name,
5175
Output: ctx.String("output"),
5276
Package: ctx.String("package"),
53-
Driver: ctx.String("driver"),
77+
Driver: driver,
78+
Logger: logger,
5479
})
5580
},
5681
}
@@ -65,6 +90,7 @@ type GenerateOptions struct {
6590
Output string
6691
Package string
6792
Driver string
93+
Logger string
6894
}
6995

7096
func generate(opts GenerateOptions) error {
@@ -110,7 +136,7 @@ func generate(opts GenerateOptions) error {
110136
var b bytes.Buffer
111137
contents := bufio.NewWriter(&b)
112138

113-
if err = internal.Generate(schema, contents, opts.Package, opts.Driver); err != nil {
139+
if err = internal.Generate(schema, contents, opts.Package, opts.Driver, opts.Logger); err != nil {
114140
return err
115141
}
116142

docs/.vitepress/config.mts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export default defineConfig({
3434
items: [
3535
// { text: 'UUID Generation', link: '/api/mutations' },
3636
{ text: 'Soft Delete', link: '/features/soft-delete' },
37-
// { text: 'Logging', link: '/api/mutations' },
37+
{ text: 'Logging', link: '/features/logging' },
3838
// { text: 'Migrations', link: '/api/mutations' },
3939
]
4040
},

docs/features/logging.md

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
# Logging
2+
3+
MangoSQL has integration for common Go loggers, this make working with SQL queries easier.
4+
5+
This logging feature is opt-in and comes with:
6+
* Every Query will emit a **Debug** log
7+
* With the function name
8+
* With duration measurements
9+
* With SQL Query and arguments
10+
* **warning** for slow queries, when a query takes more than >500ms
11+
* Every SQL error will be automatically logged as **error**
12+
13+
::: tips
14+
15+
During development, we recommend to set the log level to `DebugLevel` to see the SQL queries generated and how long they take.
16+
17+
:::
18+
19+
## Logrus (https://github.com/sirupsen/logrus) { #logrus }
20+
21+
Add `--logger logrus` to the cli command
22+
23+
```sh
24+
mangosql --logger logrus ./schema.sql
25+
```
26+
27+
And provide the logger to MangoSQL Client at initialization.
28+
29+
```go
30+
package database
31+
32+
import (
33+
// ...
34+
"github.com/sirupsen/logrus"
35+
)
36+
37+
// create your own logger instance
38+
logger := logrus.New()
39+
40+
// instantiate DBClient
41+
return New(db, logger)
42+
```
43+
44+
## Zap (https://github.com/uber-go/zap) { #zap }
45+
46+
Add `--logger zap` to the cli command
47+
48+
```sh
49+
mangosql --logger zap ./schema.sql
50+
```
51+
52+
And provide the logger to MangoSQL Client at initialization.
53+
54+
```go
55+
package database
56+
57+
import (
58+
// ...
59+
"go.uber.org/zap"
60+
)
61+
62+
// create your own logger instance
63+
logger, _ := zap.NewProduction()
64+
65+
// instantiate DBClient
66+
return New(db, logger)
67+
```
68+
69+
## Zerolog (https://github.com/rs/zerolog) { #zerolog }
70+
71+
Add `--logger zerolog` to the cli command
72+
73+
```sh
74+
mangosql --logger zerolog ./schema.sql
75+
```
76+
77+
And provide the logger to MangoSQL Client at initialization.
78+
79+
```go
80+
package database
81+
82+
import (
83+
// ...
84+
"github.com/rs/zerolog"
85+
)
86+
87+
// create your own logger instance
88+
logger := zerolog.New(os.Stderr).With().Timestamp().Logger()
89+
90+
// instantiate DBClient
91+
return New(db, logger)
92+
```
93+
94+
## Testing / Console
95+
96+
This logger option is a bit special, this is a console writer intended for development and testing only.
97+
It's literally just calling `log.Println()`, has colors and is zero configuration.
98+
99+
Add `--logger console` to the cli command and you have nothing else to modify in your code.
100+
101+
```sh
102+
mangosql --logger console ./schema.sql
103+
```
104+
105+
The output will looks like this
106+
```logs
107+
2024/08/20 04:32:46 [DEBUG] DB.User.FindMany 308.236µs
108+
| Args: [[1]]
109+
| SQL: SELECT id, name, created_at, deleted_at FROM users WHERE id = $1 LIMIT 1 OFFSET 0
110+
```

internal/database/database.go

Lines changed: 71 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ var customTypes = []FieldInitializer{
3131
},
3232
}
3333

34-
func Generate(schema *core.SQLSchema, contents io.Writer, pkg string, driver string) error {
34+
func Generate(schema *core.SQLSchema, contents io.Writer, pkg string, driver string, logger string) error {
3535
templateType := ""
3636
switch driver {
3737
case "pgx":
@@ -50,6 +50,11 @@ func Generate(schema *core.SQLSchema, contents io.Writer, pkg string, driver str
5050
return err
5151
}
5252

53+
loggerTmpl, err := template.ParseFS(templates, fmt.Sprintf("templates/logger_%s.tmpl", logger))
54+
if err != nil {
55+
return err
56+
}
57+
5358
modelTmpl, err := template.ParseFS(templates, "templates/model.tmpl")
5459
if err != nil {
5560
return err
@@ -110,6 +115,38 @@ func Generate(schema *core.SQLSchema, contents io.Writer, pkg string, driver str
110115
placeholder = "squirrel.Question"
111116
}
112117

118+
logConfig := LoggerConfig{}
119+
120+
switch logger {
121+
case "zap":
122+
deps["zap"] = "go.uber.org/zap"
123+
deps["time"] = "time"
124+
logConfig.HasLogger = true
125+
logConfig.HasLoggerParam = true
126+
logConfig.Type = `*zap.Logger`
127+
case "logrus":
128+
deps["logrus"] = "github.com/sirupsen/logrus"
129+
deps["time"] = "time"
130+
logConfig.HasLogger = true
131+
logConfig.HasLoggerParam = true
132+
logConfig.Type = `*logrus.Logger`
133+
case "zerolog":
134+
deps["zero"] = "github.com/rs/zerolog"
135+
deps["time"] = "time"
136+
logConfig.HasLogger = true
137+
logConfig.HasLoggerParam = true
138+
logConfig.Type = `zerolog.Logger`
139+
case "console":
140+
deps["log"] = "log"
141+
deps["time"] = "time"
142+
143+
logConfig.HasLogger = true
144+
logConfig.HasLoggerParam = false
145+
logConfig.Type = ``
146+
}
147+
148+
fmt.Println(logger, logConfig)
149+
113150
if err = headerTmpl.Execute(contents, HeaderData{
114151
Package: pkg,
115152
Url: "https://github.com/kefniark/mangosql",
@@ -125,25 +162,49 @@ func Generate(schema *core.SQLSchema, contents io.Writer, pkg string, driver str
125162
Tables []*PostgresTable
126163
Queries []*PostgresQuery
127164
Filters []FilterMethod
165+
Logger LoggerConfig
128166
}{
129167
Tables: postgresTables,
130168
Queries: postgresQueries,
131169
Filters: GetFilterMethods(postgresTables, driver),
170+
Logger: logConfig,
132171
}); err != nil {
133172
return err
134173
}
135174

175+
if err = loggerTmpl.Execute(contents, nil); err != nil {
176+
return err
177+
}
178+
136179
for _, table := range postgresTables {
137-
if err = modelTmpl.Execute(contents, table); err != nil {
180+
if err = modelTmpl.Execute(contents, struct {
181+
Table *PostgresTable
182+
Logger LoggerConfig
183+
}{
184+
Table: table,
185+
Logger: logConfig,
186+
}); err != nil {
138187
return err
139188
}
140189

141-
if err = queriesTmpl.Execute(contents, table); err != nil {
190+
if err = queriesTmpl.Execute(contents, struct {
191+
Table *PostgresTable
192+
Logger LoggerConfig
193+
}{
194+
Table: table,
195+
Logger: logConfig,
196+
}); err != nil {
142197
return err
143198
}
144199
}
145200

146-
if err = customQueriesTmpl.Execute(contents, postgresQueries); err != nil {
201+
if err = customQueriesTmpl.Execute(contents, struct {
202+
Queries []*PostgresQuery
203+
Logger LoggerConfig
204+
}{
205+
Queries: postgresQueries,
206+
Logger: logConfig,
207+
}); err != nil {
147208
return err
148209
}
149210

@@ -789,3 +850,9 @@ type PrimaryFieldInit struct {
789850
Init string
790851
Setter string
791852
}
853+
854+
type LoggerConfig struct {
855+
HasLoggerParam bool
856+
HasLogger bool
857+
Type string
858+
}

internal/database/templates/custom.tmpl

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
1-
{{ if len . }}
1+
{{ if len .Queries }}
22
type CustomQueries struct {
33
ctx *DBContext
44
}
55

6-
{{ range . }}
6+
{{ range .Queries }}
77
// Find {{ .NameNormalized }} records based on the provided conditions
88
//
99
// Usage:
1010
// entities, err := db.Queries.{{ .NameNormalized }}(
1111
// // ... can use filters here (cf db.{{ .NameNormalized }}.Query.*)
1212
// )
13-
func (q *CustomQueries) {{ .NameNormalized }}(filters ...WhereCondition) ([]{{ .NameNormalized }}Model, error ) {
13+
func (q *CustomQueries) {{ .NameNormalized }}(filters ...WhereCondition) (requestData []{{ .NameNormalized }}Model, requestErr error ) {
1414
query := squirrel.Select("{{ .Select }}")
1515
query = query.From("{{ .From }}").PlaceholderFormat(placeholder)
1616
{{ if .Where }} query = query.Where("{{ .Where }}")
@@ -23,7 +23,11 @@ type CustomQueries struct {
2323
sql, args, err := query.ToSql()
2424
if err != nil {
2525
return nil, err
26-
}
26+
}{{ if $.Logger.HasLogger }}
27+
start := time.Now()
28+
defer func() {
29+
q.ctx.logQuery(Debug, "DB.Queries.{{ .NameNormalized }}", requestErr, time.Since(start), sql, args)
30+
}(){{ end }}
2731

2832
return QueryMany[{{ .NameNormalized }}Model](q.ctx, sql, args...)
2933
}

0 commit comments

Comments
 (0)