Skip to content

Commit b8b30e6

Browse files
DISTMYSQL-157: Improve error logging of MySQL errors
https://jira.percona.com/browse/DISTMYSQL-157 Problem: Some error logs indicate only the error returned by the server without the information about the server identity itself. On the other hand there are also logs which display the server identity together with the returned error. Cause: There is an utility layer implemented by golib/sqlutils which intercepts database queries. Some queries done by orchestrator go through this layer and some don't. sqlutils layer in case of query error prints out the query to the stdout Solution: golib/sqlutils layer is not aware of the server identity it operates on. It proxies creation of sql.DB object and then uses this object for queries execution. Logger interface has been introduced. golib/sqlutils client can register its own logger which will be used instead of internal logging when the error occurs during usage of the related sql.DB object. Additionally, I've made the local copy of golib utility module.
1 parent a091725 commit b8b30e6

File tree

14 files changed

+1270
-18
lines changed

14 files changed

+1270
-18
lines changed

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,4 +49,5 @@ replace (
4949
github.com/hashicorp/raft => github.com/openark/raft v0.0.0-20170918052300-fba9f909f7fe
5050
golang.org/x/text v0.3.0 => golang.org/x/text v0.3.8
5151
golang.org/x/text v0.3.7 => golang.org/x/text v0.3.8
52+
github.com/openark/golib => ./go/golib
5253
)

go/db/db.go

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"errors"
2222
"fmt"
2323
"regexp"
24+
"strconv"
2425
"strings"
2526
"sync"
2627
"time"
@@ -100,7 +101,8 @@ func openTopology(host string, port int, readTimeout int) (db *sql.DB, err error
100101
return nil, err
101102
}
102103
}
103-
if db, _, err = sqlutils.GetDB(mysql_uri); err != nil {
104+
sqlUtilsLogger := SqlUtilsLogger{client_context: host + ":" + strconv.Itoa(port)}
105+
if db, _, err = sqlutils.GetDB(mysql_uri, sqlUtilsLogger); err != nil {
104106
return nil, err
105107
}
106108
if config.Config.MySQLConnectionLifetimeSeconds > 0 {
@@ -123,7 +125,8 @@ func openOrchestratorMySQLGeneric() (db *sql.DB, fromCache bool, err error) {
123125
if config.Config.MySQLOrchestratorUseMutualTLS {
124126
uri, _ = SetupMySQLOrchestratorTLS(uri)
125127
}
126-
return sqlutils.GetDB(uri)
128+
sqlUtilsLogger := SqlUtilsLogger{client_context: config.Config.MySQLOrchestratorHost + ":" + config.Config.MySQLOrchestratorHost}
129+
return sqlutils.GetDB(uri, sqlUtilsLogger)
127130
}
128131

129132
func IsSQLite() bool {
@@ -174,7 +177,7 @@ func safeMySQLURI(dsn string) string {
174177
func OpenOrchestrator() (db *sql.DB, err error) {
175178
var fromCache bool
176179
if IsSQLite() {
177-
db, fromCache, err = sqlutils.GetSQLiteDB(config.Config.SQLite3DataFile)
180+
db, fromCache, err = sqlutils.GetSQLiteDB(config.Config.SQLite3DataFile, nil)
178181
if err == nil && !fromCache {
179182
log.Debugf("Connected to orchestrator backend: sqlite on %v", config.Config.SQLite3DataFile)
180183
}
@@ -191,7 +194,8 @@ func OpenOrchestrator() (db *sql.DB, err error) {
191194
}
192195
}
193196
dsn := getMySQLURI()
194-
db, fromCache, err = sqlutils.GetDB(dsn)
197+
sqlUtilsLogger := SqlUtilsLogger{client_context: dsn}
198+
db, fromCache, err = sqlutils.GetDB(dsn, sqlUtilsLogger)
195199
if err == nil && !fromCache {
196200
log.Debugf("Connected to orchestrator backend: %v", safeMySQLURI(dsn))
197201

go/db/tls.go

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package db
1919
import (
2020
"crypto/tls"
2121
"fmt"
22+
"strconv"
2223
"strings"
2324
"time"
2425

@@ -55,6 +56,23 @@ func init() {
5556
metrics.Register("instance_tls.write_cache", writeInstanceTLSCacheCounter)
5657
}
5758

59+
type SqlUtilsLogger struct {
60+
client_context string
61+
}
62+
63+
func (logger SqlUtilsLogger) OnError(caller_context string, query string, err error) error {
64+
query = strings.Join(strings.Fields(query), " ") // trim whitespaces
65+
query = strings.Replace(query, "%", "%%", -1) // escape %
66+
67+
msg := fmt.Sprintf("%+v(%+v) %+v: %+v",
68+
caller_context,
69+
logger.client_context,
70+
query,
71+
err)
72+
73+
return log.Errorf(msg)
74+
}
75+
5876
func requiresTLS(host string, port int, mysql_uri string) bool {
5977
cacheKey := fmt.Sprintf("%s:%d", host, port)
6078

@@ -64,7 +82,8 @@ func requiresTLS(host string, port int, mysql_uri string) bool {
6482
}
6583

6684
required := false
67-
db, _, _ := sqlutils.GetDB(mysql_uri)
85+
sqlUtilsLogger := SqlUtilsLogger{client_context: host + ":" + strconv.Itoa(port)}
86+
db, _, _ := sqlutils.GetDB(mysql_uri, sqlUtilsLogger)
6887
if err := db.Ping(); err != nil && (strings.Contains(err.Error(), Error3159) || strings.Contains(err.Error(), Error1045)) {
6988
required = true
7089
}

go/golib/go.mod

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module golib
2+
3+
go 1.18

go/golib/log/log.go

Lines changed: 268 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,268 @@
1+
/*
2+
Copyright 2014 Outbrain Inc.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package log
18+
19+
import (
20+
"errors"
21+
"fmt"
22+
"log/syslog"
23+
"os"
24+
"runtime/debug"
25+
"time"
26+
)
27+
28+
// LogLevel indicates the severity of a log entry
29+
type LogLevel int
30+
31+
func (this LogLevel) String() string {
32+
switch this {
33+
case FATAL:
34+
return "FATAL"
35+
case CRITICAL:
36+
return "CRITICAL"
37+
case ERROR:
38+
return "ERROR"
39+
case WARNING:
40+
return "WARNING"
41+
case NOTICE:
42+
return "NOTICE"
43+
case INFO:
44+
return "INFO"
45+
case DEBUG:
46+
return "DEBUG"
47+
}
48+
return "unknown"
49+
}
50+
51+
func LogLevelFromString(logLevelName string) (LogLevel, error) {
52+
switch logLevelName {
53+
case "FATAL":
54+
return FATAL, nil
55+
case "CRITICAL":
56+
return CRITICAL, nil
57+
case "ERROR":
58+
return ERROR, nil
59+
case "WARNING":
60+
return WARNING, nil
61+
case "NOTICE":
62+
return NOTICE, nil
63+
case "INFO":
64+
return INFO, nil
65+
case "DEBUG":
66+
return DEBUG, nil
67+
}
68+
return 0, fmt.Errorf("Unknown LogLevel name: %+v", logLevelName)
69+
}
70+
71+
const (
72+
FATAL LogLevel = iota
73+
CRITICAL
74+
ERROR
75+
WARNING
76+
NOTICE
77+
INFO
78+
DEBUG
79+
)
80+
81+
const TimeFormat = "2006-01-02 15:04:05"
82+
83+
// globalLogLevel indicates the global level filter for all logs (only entries with level equals or higher
84+
// than this value will be logged)
85+
var globalLogLevel LogLevel = DEBUG
86+
var printStackTrace bool = false
87+
88+
// syslogWriter is optional, and defaults to nil (disabled)
89+
var syslogLevel LogLevel = ERROR
90+
var syslogWriter *syslog.Writer
91+
92+
// SetPrintStackTrace enables/disables dumping the stack upon error logging
93+
func SetPrintStackTrace(shouldPrintStackTrace bool) {
94+
printStackTrace = shouldPrintStackTrace
95+
}
96+
97+
// SetLevel sets the global log level. Only entries with level equals or higher than
98+
// this value will be logged
99+
func SetLevel(logLevel LogLevel) {
100+
globalLogLevel = logLevel
101+
}
102+
103+
// GetLevel returns current global log level
104+
func GetLevel() LogLevel {
105+
return globalLogLevel
106+
}
107+
108+
// EnableSyslogWriter enables, if possible, writes to syslog. These will execute _in addition_ to normal logging
109+
func EnableSyslogWriter(tag string) (err error) {
110+
syslogWriter, err = syslog.New(syslog.LOG_ERR, tag)
111+
if err != nil {
112+
syslogWriter = nil
113+
}
114+
return err
115+
}
116+
117+
// SetSyslogLevel sets the minimal syslog level. Only entries with level equals or higher than
118+
// this value will be logged. However, this is also capped by the global log level. That is,
119+
// messages with lower level than global-log-level will be discarded at any case.
120+
func SetSyslogLevel(logLevel LogLevel) {
121+
syslogLevel = logLevel
122+
}
123+
124+
// logFormattedEntry nicely formats and emits a log entry
125+
func logFormattedEntry(logLevel LogLevel, message string, args ...interface{}) string {
126+
if logLevel > globalLogLevel {
127+
return ""
128+
}
129+
// if TZ env variable is set, update the timestamp timezone
130+
localizedTime := time.Now()
131+
tzLocation := os.Getenv("TZ")
132+
if tzLocation != "" {
133+
location, err := time.LoadLocation(tzLocation)
134+
if err == nil { // if invalid tz location was provided, just leave it as the default
135+
localizedTime = time.Now().In(location)
136+
}
137+
}
138+
139+
msgArgs := fmt.Sprintf(message, args...)
140+
entryString := fmt.Sprintf("%s %s %s", localizedTime.Format(TimeFormat), logLevel, msgArgs)
141+
fmt.Fprintln(os.Stderr, entryString)
142+
143+
if syslogWriter != nil {
144+
go func() error {
145+
if logLevel > syslogLevel {
146+
return nil
147+
}
148+
switch logLevel {
149+
case FATAL:
150+
return syslogWriter.Emerg(msgArgs)
151+
case CRITICAL:
152+
return syslogWriter.Crit(msgArgs)
153+
case ERROR:
154+
return syslogWriter.Err(msgArgs)
155+
case WARNING:
156+
return syslogWriter.Warning(msgArgs)
157+
case NOTICE:
158+
return syslogWriter.Notice(msgArgs)
159+
case INFO:
160+
return syslogWriter.Info(msgArgs)
161+
case DEBUG:
162+
return syslogWriter.Debug(msgArgs)
163+
}
164+
return nil
165+
}()
166+
}
167+
return entryString
168+
}
169+
170+
// logEntry emits a formatted log entry
171+
func logEntry(logLevel LogLevel, message string, args ...interface{}) string {
172+
entryString := message
173+
for _, s := range args {
174+
entryString += fmt.Sprintf(" %s", s)
175+
}
176+
return logFormattedEntry(logLevel, entryString)
177+
}
178+
179+
// logErrorEntry emits a log entry based on given error object
180+
func logErrorEntry(logLevel LogLevel, err error) error {
181+
if err == nil {
182+
// No error
183+
return nil
184+
}
185+
entryString := fmt.Sprintf("%+v", err)
186+
logEntry(logLevel, entryString)
187+
if printStackTrace {
188+
debug.PrintStack()
189+
}
190+
return err
191+
}
192+
193+
func Debug(message string, args ...interface{}) string {
194+
return logEntry(DEBUG, message, args...)
195+
}
196+
197+
func Debugf(message string, args ...interface{}) string {
198+
return logFormattedEntry(DEBUG, message, args...)
199+
}
200+
201+
func Info(message string, args ...interface{}) string {
202+
return logEntry(INFO, message, args...)
203+
}
204+
205+
func Infof(message string, args ...interface{}) string {
206+
return logFormattedEntry(INFO, message, args...)
207+
}
208+
209+
func Notice(message string, args ...interface{}) string {
210+
return logEntry(NOTICE, message, args...)
211+
}
212+
213+
func Noticef(message string, args ...interface{}) string {
214+
return logFormattedEntry(NOTICE, message, args...)
215+
}
216+
217+
func Warning(message string, args ...interface{}) error {
218+
return errors.New(logEntry(WARNING, message, args...))
219+
}
220+
221+
func Warningf(message string, args ...interface{}) error {
222+
return errors.New(logFormattedEntry(WARNING, message, args...))
223+
}
224+
225+
func Error(message string, args ...interface{}) error {
226+
return errors.New(logEntry(ERROR, message, args...))
227+
}
228+
229+
func Errorf(message string, args ...interface{}) error {
230+
return errors.New(logFormattedEntry(ERROR, message, args...))
231+
}
232+
233+
func Errore(err error) error {
234+
return logErrorEntry(ERROR, err)
235+
}
236+
237+
func Critical(message string, args ...interface{}) error {
238+
return errors.New(logEntry(CRITICAL, message, args...))
239+
}
240+
241+
func Criticalf(message string, args ...interface{}) error {
242+
return errors.New(logFormattedEntry(CRITICAL, message, args...))
243+
}
244+
245+
func Criticale(err error) error {
246+
return logErrorEntry(CRITICAL, err)
247+
}
248+
249+
// Fatal emits a FATAL level entry and exists the program
250+
func Fatal(message string, args ...interface{}) error {
251+
logEntry(FATAL, message, args...)
252+
os.Exit(1)
253+
return errors.New(logEntry(CRITICAL, message, args...))
254+
}
255+
256+
// Fatalf emits a FATAL level entry and exists the program
257+
func Fatalf(message string, args ...interface{}) error {
258+
logFormattedEntry(FATAL, message, args...)
259+
os.Exit(1)
260+
return errors.New(logFormattedEntry(CRITICAL, message, args...))
261+
}
262+
263+
// Fatale emits a FATAL level entry and exists the program
264+
func Fatale(err error) error {
265+
logErrorEntry(FATAL, err)
266+
os.Exit(1)
267+
return err
268+
}

0 commit comments

Comments
 (0)