How to disable default error logger in Go-Gorm - mysql

I am using GORM with MySQL, I have encountered and handled the error Error 1062: Duplicate entry. The problem is that it's still printed to the console.
Code in gym/models/auth.go:49:
func AddAuth(username, password string) error {
passwordHash, err := auth.HashPassword(password, argon2Conf)
if err != nil {
return err
}
userAuth := Auth{
Username: username,
Password: passwordHash,
}
return db.Create(&userAuth).Error
}
I am handling the error in the handler function:
func SignUpHandler(c *gin.Context) {
var form user
if err := c.ShouldBind(&form); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if err := models.AddAuth(form.Username, form.Password); err == nil {
c.JSON(http.StatusOK, gin.H{"status": "you are signed in"})
} else {
// I think I have handled the sql error here
c.JSON(http.StatusBadRequest, gin.H{"error": "sign in failed"})
}
}
When I send a POST request, the error is correctly handled and I get the correct response with {"error": "sign in failed"}. But the console still prints this error message:
(/...../gym/models/auth.go:49)
[2019-04-28 23:37:06] Error 1062: Duplicate entry '123123' for key 'username'
[GIN] 2019/04/28 - 23:37:06 | 400 | 136.690908ms | ::1 | POST /signup
I am confused since I handled the error but it still gets printed. How to prevent this error from getting printed to the error log? Or am I handle the error correct?

UPDATE: for gorm v2:
Use the Logger in gorm.Config:
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{
Logger: logger.Default.LogMode(logger.Silent),
})
For gorm v1:
Use db.LogMode to silence the default error logger.
LogMode set log mode, true for detailed logs, false for no log, default, will only print error logs.
db.LogMode(false) should do the job!

I don't have enough reputation to comment but just to add to the answer by ifnotak, you can log sql conditionally by controlling it through an environment variable. This can be handy during debugging.
gormConfig := &gorm.Config{}
if !logSql {
// I use an env variable LOG_SQL to set logSql to either true or false.
gormConfig.Logger = logger.Default.LogMode(logger.Silent)
}
db, err := gorm.Open(dialector, gormConfig)

Related

Read committed and transaction Error 1213: Deadlock

I have MySql 5.7 and following golang code that may be run concurrently:
tx := s.db.Begin()
if err := tx.Exec(fmt.Sprintf("DELETE FROM related_table WHERE id = %d " item.ID)).Error; err != nil {
tx.Rollback()
}
// Save is ORM method, it make inserts into 'related_table' from the first query
if err := tx.Save(&item).Error; err != nil {
tx.Rollback()
}
I catch error during tx.Save(&item)
Error 1213: Deadlock found when trying to get lock; try restarting transaction
The question is:
How it possible that mysql transaction is not protected against deadlocks? Don't transactions run sequentially?
i can see something "Error; err != nil { tx.Rollback()" ; so i guess this means rollback on failure, right? ;so what happens if its a success; do you have to mention commit explictly; or its autocommit in your setup?
select ... for update is setting exclusive locks on the rows until the end of transaction
Just run following at the beginning of transaction:
tx.Exec("SELECT * FROM %s WHERE coupon_id = ? FOR UPDATE", item.ID))

How to test mysql insert method

I'm setting up testing in Go. I use go-sqlmock to test mysql connection. Now I try to test mysql insert logic. But the error occurs.
I want to know how to resolve this error.
server side: golang
db: mysql
web framework: gin
dao.go
func PostDao(db *sql.DB, article util.Article, uu string) {
ins, err := db.Prepare("INSERT INTO articles(uuid, title,content) VALUES(?,?,?)")
if err != nil {
log.Fatal(err)
}
ins.Exec(uu, article.TITLE, article.CONTENT)
}
dao_test.go
func TestPostArticleDao(t *testing.T) {
db, mock, err := sqlmock.New()
if err != nil {
t.Fatalf("an error '%s' was not expected when opening a stub database connection", err)
}
defer db.Close()
mock.ExpectExec("^INSERT INTO articles*").
WithArgs("bea1b24d-0627-4ea0-aa2b-8af4c6c2a41c", "test", "test").
WillReturnResult(sqlmock.NewResult(1, 1))
article := util.Article{
ID: 1,
TITLE: "test",
CONTENT: "test",
}
PostDao(db, article, "bea1b24d-0627-4ea0-aa2b-8af4c6c2a41c")
if err := mock.ExpectationsWereMet(); err != nil {
t.Errorf("there were unfulfilled expections: %s", err)
}
}
I expect go test -v runs without error.
But the actual is not.
Here is the error.
=== RUN TestPostArticleDao
2019/08/31 00:08:11 call to Prepare statement with query 'INSERT INTO articles(uuid, title,content) VALUES(?,?,?)', was not expected, next expectation is: ExpectedExec => expecting Exec or ExecContext which:
- matches sql: 'INSERT INTO articles(uuid, title,content) VALUES(?,?,?)'
- is with arguments:
0 - bea1b24d-0627-4ea0-aa2b-8af4c6c2a41c
1 - test
2 - test
- should return Result having:
LastInsertId: 1
RowsAffected: 1
exit status 1
FAIL article/api/dao 0.022s
As #Flimzy suggested, it needs to set ExpectPrepare first.
So I changed dao_test.go in this way:
prep := mock.ExpectPrepare("^INSERT INTO articles*")
prep.ExpectExec().
WithArgs("bea1b24d-0627-4ea0-aa2b-8af4c6c2a41c", "test", "test").
WillReturnResult(sqlmock.NewResult(1, 1))
In my case it worked without asterix:
mock.ExpectExec("INSERT INTO `mytable`").WithArgs(mockdbutils.AnyTime{}, mockdbutils.AnyTime{}, nil, 4455,false).WillReturnResult(sqlmock.NewResult(int64(4455), 1))
mock.ExpectCommit()

How to run tests using gorm with mysql in golang?

I'm puzzled.
I try to run test cases using gorm with mysql in golang and I wanna buile MySQL just for testing, but it does not run safely.
I wanna use this package go-test-mysqld
Error message is below.
panic: sql: Register called twice for driver mysql
My code is
func TestMain(m *testing.M) {
mysqld, err := mysqltest.NewMysqld(nil)
if err != nil {
log.Fatal("runTests: failed", err)
}
defer mysqld.Stop()
dbm, err = gorm.Open("mysqld", mysqld.Datasource("test", "", "", 0 ))
if err != nil {
log.Fatal("db connection error:", err)
}
defer dbm.Close()
code := m.Run()
os.Exit(code)
}
What is the problems in my code?
Or is it impossible to build another mysql in using gorm?
Do you have some ideas?

How to connect to Amazon RDS using go-sql-driver

I can connect to the RDS instance using mysql -h ... command so I know it's not a security group problem.
I've tried to use:
sql.Open("mysql", "id:password#tcp(your-amazonaws-uri.com:3306)/dbname")
in the readme file of go-sql-driver(https://github.com/go-sql-driver/mysql), but it doesn't seem to work.
I'm using my username under the RDS instance instead of id here though.
Edit:
The error returned is: panic runtime error: invalid memory address or nil pointer deference [signal 0xb code=0x1 addr=0x20 pc=0x5b551e]
goroutine 16 [running]
runtime.panic(0x7d4fc0, 0xa6ca73)...database/sql.(*Rows).Next...
It works fine with my local DB.
The connection string for sql.Open() is in DSN format.
import (
"database/sql"
"fmt"
_ "github.com/go-sql-driver/mysql"
)
db, err := sql.Open("mysql", "<username>:<password>#tcp(<AWSConnectionEndpoint >:<port>)/<dbname>")
if err != nil {
fmt.Print(err.Error())
}
defer db.Close()
Make sure the actual error isn't related to an import issue (as in issues 266)
Check (to be sure you are using the latest versions, as in this issue):
your Go-MySQL-Driver version (or git SHA)
your Go version (run go version in your console)
If the error isn't directly in the Open step, but when accessing the Rows, check this comment out:
Use either a for loop (for rows.Next() { ... }) or something like this:
if rows.Next() {
// whatever
} else {
// catch error with rows.Err()
}
rows.Close() // <- don't forget this if you are not iterating over ALL results

sending JSON with go

I'm trying to send a JSON message with Go.
This is the server code:
func (network *Network) Join(
w http.ResponseWriter,
r *http.Request) {
//the request is not interesting
//the response will be a message with just the clientId value set
log.Println("client wants to join")
message := Message{-1, -1, -1, ClientId(len(network.Clients)), -1, -1}
var buffer bytes.Buffer
enc := json.NewEncoder(&buffer)
err := enc.Encode(message)
if err != nil {
fmt.Println("error encoding the response to a join request")
log.Fatal(err)
}
fmt.Printf("the json: %s\n", buffer.Bytes())
fmt.Fprint(w, buffer.Bytes())
}
Network is a custom struct. In the main function, I'm creating a network object and registering it's methods as callbacks to http.HandleFunc(...)
func main() {
runtime.GOMAXPROCS(2)
var network = new(Network)
var clients = make([]Client, 0, 10)
network.Clients = clients
log.Println("starting the server")
http.HandleFunc("/request", network.Request)
http.HandleFunc("/update", network.GetNews)
http.HandleFunc("/join", network.Join)
log.Fatal(http.ListenAndServe("localhost:5000", nil))
}
Message is a struct, too. It has six fields all of a type alias for int.
When a client sends an http GET request to the url "localhost:5000/join", this should happen
The method Join on the network object is called
A new Message object with an Id for the client is created
This Message is encoded as JSON
To check if the encoding is correct, the encoded message is printed on the cmd
The message is written to the ResponseWriter
The client is rather simple. It has the exact same code for the Message struct. In the main function it just sends a GET request to "localhost:5000/join" and tries to decode the response. Here's the code
func main() {
// try to join
var clientId ClientId
start := time.Now()
var message Message
resp, err := http.Get("http://localhost:5000/join")
if err != nil {
log.Fatal(err)
}
fmt.Println(resp.Status)
dec := json.NewDecoder(resp.Body)
err = dec.Decode(&message)
if err != nil {
fmt.Println("error decoding the response to the join request")
log.Fatal(err)
}
fmt.Println(message)
duration := time.Since(start)
fmt.Println("connected after: ", duration)
fmt.Println("with clientId", message.ClientId)
}
I've started the server, waited a few seconds and then ran the client. This is the result
The server prints "client wants to join"
The server prints "the json: {"What":-1,"Tag":-1,"Id":-1,"ClientId":0,"X":-1,"Y":-1}"
The client prints "200 OK"
The client crashes "error decoding the response to the join request"
The error is "invalid character "3" after array element"
This error message really confused me. After all, nowhere in my json, there's the number 3. So I imported io/ioutil on the client and just printed the response with this code
b, _ := ioutil.ReadAll(resp.Body)
fmt.Printf("the json: %s\n", b)
Please note that the print statement is the same as on the server. I expected to see my encoded JSON. Instead I got this
"200 OK"
"the json: [123 34 87 104 97 116 ....]" the list went on for a long time
I'm new to go and don't know if i did this correctly. But it seems as if the above code just printed the slice of bytes. Strange, on the server the output was converted to a string.
My guess is that somehow I'm reading the wrong data or that the message was corrupted on the way between server and client. But honestly these are just wild guesses.
In your server, instead of
fmt.Fprint(w, buffer.Bytes())
you need to use:
w.Write(buffer.Bytes())
The fmt package will format the Bytes() into a human-readable slice with the bytes represented as integers, like so:
[123 34 87 104 97 116 ... etc
You don't want to use fmt.Print to write stuff to the response. Eg
package main
import (
"fmt"
"os"
)
func main() {
bs := []byte("Hello, playground")
fmt.Fprint(os.Stdout, bs)
}
(playground link)
Produces
[72 101 108 108 111 44 32 112 108 97 121 103 114 111 117 110 100]
Use the Write() method of the ResponseWriter instead
You could have found this out by telneting to your server as an experiment - always a good idea when you aren't sure what is going on!