Repository: influxdata/influxdb-client-go (go)

Issue:
Make logger configurable
Usage of `log.Logger` is private and hard-wired. Would be helpful to set a custom logger.

Pull request description:
feat: Adding public logging API
Closes #104, closes #144 

## Proposed Changes

- Added Logger interface to enable overriding of the global logger.
- It is also possible to disable logging completely

## Checklist

- [X] CHANGELOG.md updated
- [X] Rebased/mergeable
- [X] A test has been added if appropriate
- [X] Tests pass
- [X] Commit messages are in [semantic format](https://seesparkbox.com/foundry/semantic_commit_messages)



Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8c84874..1917ced 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,7 +6,8 @@
 
 ### Features
 1. [#165](https://github.com/influxdata/influxdb-client-go/pull/165) Allow overriding the http.Client for the http service.
-1. [#179](https://github.com/influxdata/influxdb-client-go/pull/179) Unifying retry strategy among InfluxDB 2 clients: added exponential backoff. 
+1. [#179](https://github.com/influxdata/influxdb-client-go/pull/179) Unifying retry strategy among InfluxDB 2 clients: added exponential backoff.
+1. [#180](https://github.com/influxdata/influxdb-client-go/pull/180) Provided public logger API to enable overriding logging. It is also possible to disable logging. 
 
 ### Bug fixes 
 1. [#175](https://github.com/influxdata/influxdb-client-go/pull/175) Fixed WriteAPIs management. Keeping single instance for each org and bucket pair.
diff --git a/api/query.go b/api/query.go
index cdceae6..bb88435 100644
--- a/api/query.go
+++ b/api/query.go
@@ -26,6 +26,7 @@ import (
 	"github.com/influxdata/influxdb-client-go/api/query"
 	"github.com/influxdata/influxdb-client-go/domain"
 	ihttp "github.com/influxdata/influxdb-client-go/internal/http"
+	"github.com/influxdata/influxdb-client-go/internal/log"
 )
 
 const (
@@ -74,6 +75,7 @@ func (q *queryAPI) QueryRaw(ctx context.Context, query string, dialect *domain.D
 	if err != nil {
 		return "", err
 	}
+	log.Debugf("Query: %s", string(qrJSON))
 	var body string
 	perror := q.httpService.PostRequest(ctx, queryURL, bytes.NewReader(qrJSON), func(req *http.Request) {
 		req.Header.Set("Content-Type", "application/json")
@@ -123,6 +125,7 @@ func (q *queryAPI) Query(ctx context.Context, query string) (*QueryTableResult,
 	if err != nil {
 		return nil, err
 	}
+	log.Debugf("Query: %s", string(qrJSON))
 	perror := q.httpService.PostRequest(ctx, queryURL, bytes.NewReader(qrJSON), func(req *http.Request) {
 		req.Header.Set("Content-Type", "application/json")
 		req.Header.Set("Accept-Encoding", "gzip")
diff --git a/api/write.go b/api/write.go
index ff91115..3165c57 100644
--- a/api/write.go
+++ b/api/write.go
@@ -94,7 +94,7 @@ func (w *WriteAPIImpl) waitForFlushing() {
 		if writeBuffInfo.writeBuffLen == 0 {
 			break
 		}
-		log.Log.Info("Waiting buffer is flushed")
+		log.Info("Waiting buffer is flushed")
 		time.Sleep(time.Millisecond)
 	}
 	for {
@@ -103,13 +103,13 @@ func (w *WriteAPIImpl) waitForFlushing() {
 		if writeBuffInfo.writeBuffLen == 0 {
 			break
 		}
-		log.Log.Info("Waiting buffer is flushed")
+		log.Info("Waiting buffer is flushed")
 		time.Sleep(time.Millisecond)
 	}
 }
 
 func (w *WriteAPIImpl) bufferProc() {
-	log.Log.Info("Buffer proc started")
+	log.Info("Buffer proc started")
 	ticker := time.NewTicker(time.Duration(w.writeOptions.FlushInterval()) * time.Millisecond)
 x:
 	for {
@@ -132,13 +132,13 @@ x:
 			w.bufferInfoCh <- buffInfo
 		}
 	}
-	log.Log.Info("Buffer proc finished")
+	log.Info("Buffer proc finished")
 	w.doneCh <- struct{}{}
 }
 
 func (w *WriteAPIImpl) flushBuffer() {
 	if len(w.writeBuffer) > 0 {
-		log.Log.Info("sending batch")
+		log.Info("sending batch")
 		batch := iwrite.NewBatch(buffer(w.writeBuffer), w.writeOptions.RetryInterval())
 		w.writeCh <- batch
 		w.writeBuffer = w.writeBuffer[:0]
@@ -146,7 +146,7 @@ func (w *WriteAPIImpl) flushBuffer() {
 }
 
 func (w *WriteAPIImpl) writeProc() {
-	log.Log.Info("Write proc started")
+	log.Info("Write proc started")
 x:
 	for {
 		select {
@@ -156,14 +156,14 @@ x:
 				w.errCh <- err
 			}
 		case <-w.writeStop:
-			log.Log.Info("Write proc: received stop")
+			log.Info("Write proc: received stop")
 			break x
 		case buffInfo := <-w.writeInfoCh:
 			buffInfo.writeBuffLen = len(w.writeCh)
 			w.writeInfoCh <- buffInfo
 		}
 	}
-	log.Log.Info("Write proc finished")
+	log.Info("Write proc finished")
 	w.doneCh <- struct{}{}
 }
 
@@ -205,7 +205,7 @@ func (w *WriteAPIImpl) WriteRecord(line string) {
 func (w *WriteAPIImpl) WritePoint(point *write.Point) {
 	line, err := w.service.EncodePoints(point)
 	if err != nil {
-		log.Log.Errorf("point encoding error: %s\n", err.Error())
+		log.Errorf("point encoding error: %s\n", err.Error())
 	} else {
 		w.bufferCh <- line
 	}
diff --git a/client.go b/client.go
index 83253db..6e229e5 100644
--- a/client.go
+++ b/client.go
@@ -15,7 +15,8 @@ import (
 	"github.com/influxdata/influxdb-client-go/api"
 	"github.com/influxdata/influxdb-client-go/domain"
 	ihttp "github.com/influxdata/influxdb-client-go/internal/http"
-	"github.com/influxdata/influxdb-client-go/internal/log"
+	ilog "github.com/influxdata/influxdb-client-go/internal/log"
+	"github.com/influxdata/influxdb-client-go/log"
 )
 
 // Client provides API to communicate with InfluxDBServer.
@@ -101,8 +102,10 @@ func NewClientWithOptions(serverURL string, authToken string, options *Options)
 		httpService:   service,
 		apiClient:     domain.NewClientWithResponses(service),
 	}
-	log.Log.SetDebugLevel(client.Options().LogLevel())
-	log.Log.Infof("Using URL '%s', token '%s'", serverURL, authToken)
+	if log.Log != nil {
+		log.Log.SetLogLevel(options.LogLevel())
+	}
+	ilog.Infof("Using URL '%s', token '%s'", serverURL, authToken)
 	return client
 }
 func (c *clientImpl) Options() *Options {
diff --git a/internal/http/service.go b/internal/http/service.go
index f5bac8a..7d8c3f9 100644
--- a/internal/http/service.go
+++ b/internal/http/service.go
@@ -16,6 +16,7 @@ import (
 	"strconv"
 
 	http2 "github.com/influxdata/influxdb-client-go/api/http"
+	"github.com/influxdata/influxdb-client-go/internal/log"
 )
 
 // RequestCallback defines function called after a request is created before any call
@@ -59,7 +60,7 @@ func NewService(serverURL, authorization string, httpOptions *http2.Options) Ser
 		serverAPIURL:  serverAPIURL,
 		serverURL:     serverURL,
 		authorization: authorization,
-		client: httpOptions.HTTPClient(),
+		client:        httpOptions.HTTPClient(),
 	}
 }
 
@@ -114,6 +115,7 @@ func (s *service) DoHTTPRequest(req *http.Request, requestCallback RequestCallba
 }
 
 func (s *service) DoHTTPRequestWithResponse(req *http.Request, requestCallback RequestCallback) (*http.Response, error) {
+	log.Infof("HTTP %s req to %s", req.Method, req.URL.String())
 	req.Header.Set("Authorization", s.authorization)
 	req.Header.Set("User-Agent", UserAgent)
 	if requestCallback != nil {
diff --git a/internal/log/logger.go b/internal/log/logger.go
index cfc16c4..aa5a212 100644
--- a/internal/log/logger.go
+++ b/internal/log/logger.go
@@ -6,61 +6,50 @@
 package log
 
 import (
-	"fmt"
-	"log"
+	ilog "github.com/influxdata/influxdb-client-go/log"
 )
 
-var Log Logger
-
-// Logger provides filtered and categorized logging API.
-// It logs to standard logger, only errors by default
-type Logger struct {
-	debugLevel uint
-}
-
-// SetDebugLevel to filter log messages. Each level mean to log all categories bellow
-// 0 errors , 1 - warning, 2 - info, 3 - debug
-func (l *Logger) SetDebugLevel(debugLevel uint) {
-	l.debugLevel = debugLevel
-}
-
-func (l *Logger) Debugf(format string, v ...interface{}) {
-	if l.debugLevel > 2 {
-		log.Print("[D]! ", fmt.Sprintf(format, v...))
+func Debugf(format string, v ...interface{}) {
+	if ilog.Log != nil {
+		ilog.Log.Debugf(format, v...)
 	}
 }
-func (l *Logger) Debug(msg string) {
-	if l.debugLevel > 2 {
-		log.Print("[D]! ", msg)
+func Debug(msg string) {
+	if ilog.Log != nil {
+		ilog.Log.Debug(msg)
 	}
 }
 
-func (l *Logger) Infof(format string, v ...interface{}) {
-	if l.debugLevel > 1 {
-		log.Print("[I]! ", fmt.Sprintf(format, v...))
+func Infof(format string, v ...interface{}) {
+	if ilog.Log != nil {
+		ilog.Log.Infof(format, v...)
 	}
 }
-func (l *Logger) Info(msg string) {
-	if l.debugLevel > 1 {
-		log.Print("[I]! ", msg)
+func Info(msg string) {
+	if ilog.Log != nil {
+		ilog.Log.Info(msg)
 	}
 }
 
-func (l *Logger) Warnf(format string, v ...interface{}) {
-	if l.debugLevel > 0 {
-		log.Print("[W]! ", fmt.Sprintf(format, v...))
+func Warnf(format string, v ...interface{}) {
+	if ilog.Log != nil {
+		ilog.Log.Warnf(format, v...)
 	}
 }
-func (l *Logger) Warn(msg string) {
-	if l.debugLevel > 0 {
-		log.Print("[W]! ", msg)
+func Warn(msg string) {
+	if ilog.Log != nil {
+		ilog.Log.Warn(msg)
 	}
 }
 
-func (l *Logger) Errorf(format string, v ...interface{}) {
-	log.Print("[E]! ", fmt.Sprintf(format, v...))
+func Errorf(format string, v ...interface{}) {
+	if ilog.Log != nil {
+		ilog.Log.Errorf(format, v...)
+	}
 }
 
-func (l *Logger) Error(msg string) {
-	log.Print("[E]! ", msg)
+func Error(msg string) {
+	if ilog.Log != nil {
+		ilog.Log.Error(msg)
+	}
 }
diff --git a/internal/write/service.go b/internal/write/service.go
index 238a3b1..8512eba 100644
--- a/internal/write/service.go
+++ b/internal/write/service.go
@@ -59,27 +59,27 @@ func NewService(org string, bucket string, httpService ihttp.Service, options *w
 }
 
 func (w *Service) HandleWrite(ctx context.Context, batch *Batch) error {
-	log.Log.Debug("Write proc: received write request")
+	log.Debug("Write proc: received write request")
 	batchToWrite := batch
 	retrying := false
 	for {
 		select {
 		case <-ctx.Done():
-			log.Log.Debug("Write proc: ctx cancelled req")
+			log.Debug("Write proc: ctx cancelled req")
 			return ctx.Err()
 		default:
 		}
 		if !w.retryQueue.isEmpty() {
-			log.Log.Debug("Write proc: taking batch from retry queue")
+			log.Debug("Write proc: taking batch from retry queue")
 			if !retrying {
 				b := w.retryQueue.first()
 				// Can we write? In case of retryable error we must wait a bit
 				if w.lastWriteAttempt.IsZero() || time.Now().After(w.lastWriteAttempt.Add(time.Millisecond*time.Duration(b.retryDelay))) {
 					retrying = true
 				} else {
-					log.Log.Warn("Write proc: cannot write yet, storing batch to queue")
+					log.Warn("Write proc: cannot write yet, storing batch to queue")
 					if w.retryQueue.push(batch) {
-						log.Log.Warn("Write proc: Retry buffer full, discarding oldest batch")
+						log.Warn("Write proc: Retry buffer full, discarding oldest batch")
 					}
 					batchToWrite = nil
 				}
@@ -89,7 +89,7 @@ func (w *Service) HandleWrite(ctx context.Context, batch *Batch) error {
 				batchToWrite.retryAttempts++
 				if batch != nil { //store actual batch to retry queue
 					if w.retryQueue.push(batch) {
-						log.Log.Warn("Write proc: Retry buffer full, discarding oldest batch")
+						log.Warn("Write proc: Retry buffer full, discarding oldest batch")
 					}
 					batch = nil
 				}
@@ -100,7 +100,7 @@ func (w *Service) HandleWrite(ctx context.Context, batch *Batch) error {
 			perror := w.WriteBatch(ctx, batchToWrite)
 			if perror != nil {
 				if perror.StatusCode >= http.StatusTooManyRequests {
-					log.Log.Errorf("Write error: %s\nBatch kept for retrying\n", perror.Error())
+					log.Errorf("Write error: %s\nBatch kept for retrying\n", perror.Error())
 					if perror.RetryAfter > 0 {
 						batchToWrite.retryDelay = perror.RetryAfter * 1000
 					} else {
@@ -112,16 +112,16 @@ func (w *Service) HandleWrite(ctx context.Context, batch *Batch) error {
 					}
 					if batchToWrite.retryAttempts == 0 {
 						if w.retryQueue.push(batch) {
-							log.Log.Warn("Retry buffer full, discarding oldest batch")
+							log.Warn("Retry buffer full, discarding oldest batch")
 						}
 					} else if batchToWrite.retryAttempts == w.writeOptions.MaxRetries() {
-						log.Log.Warn("Reached maximum number of retries, discarding batch")
+						log.Warn("Reached maximum number of retries, discarding batch")
 						if !batchToWrite.evicted {
 							w.retryQueue.pop()
 						}
 					}
 				} else {
-					log.Log.Errorf("Write error: %s\n", perror.Error())
+					log.Errorf("Write error: %s\n", perror.Error())
 				}
 				return perror
 			} else {
@@ -140,12 +140,12 @@ func (w *Service) HandleWrite(ctx context.Context, batch *Batch) error {
 func (w *Service) WriteBatch(ctx context.Context, batch *Batch) *ihttp.Error {
 	wURL, err := w.WriteURL()
 	if err != nil {
-		log.Log.Errorf("%s\n", err.Error())
+		log.Errorf("%s\n", err.Error())
 		return ihttp.NewError(err)
 	}
 	var body io.Reader
 	body = strings.NewReader(batch.batch)
-	log.Log.Debugf("Writing batch: %s", batch.batch)
+	log.Debugf("Writing batch: %s", batch.batch)
 	if w.writeOptions.UseGZip() {
 		body, err = gzip.CompressWithGzip(body)
 		if err != nil {
@@ -163,7 +163,6 @@ func (w *Service) WriteBatch(ctx context.Context, batch *Batch) *ihttp.Error {
 		// _ = r.Body.Close()
 		return nil
 	})
-
 	return perror
 }
 
diff --git a/log/logger.go b/log/logger.go
new file mode 100644
index 0000000..ceb5882
--- /dev/null
+++ b/log/logger.go
@@ -0,0 +1,102 @@
+// Copyright 2020 InfluxData, Inc. All rights reserved.
+// Use of this source code is governed by MIT
+// license that can be found in the LICENSE file.
+
+// Package log defines Logging API.
+// The global Log variable contains the actual logger. Set it to own implementation to override logging. Set it to nil to disable logging
+package log
+
+import (
+	"fmt"
+	"log"
+)
+
+// Log is the library wide logger. Setting to nil disables logging.
+var Log Logger = &logger{logLevel: ErrorLevel, prefix: "influxdb2client"}
+
+// Log levels
+const (
+	ErrorLevel uint = iota
+	WarningLevel
+	InfoLevel
+	DebugLevel
+)
+
+// Logger defines interface for logging
+type Logger interface {
+	// SetLogLevel sets allowed logging level.
+	SetLogLevel(logLevel uint)
+	// SetPrefix sets logging prefix.
+	SetPrefix(prefix string)
+	// Writes formatted debug message if debug logLevel is enabled.
+	Debugf(format string, v ...interface{})
+	// Writes debug message if debug is enabled.
+	Debug(msg string)
+	// Writes formatted info message if info logLevel is enabled.
+	Infof(format string, v ...interface{})
+	// Writes info message if info logLevel is enabled
+	Info(msg string)
+	// Writes formatted warning message if warning logLevel is enabled.
+	Warnf(format string, v ...interface{})
+	// Writes warning message if warning logLevel is enabled.
+	Warn(msg string)
+	// Writes formatted error message
+	Errorf(format string, v ...interface{})
+	// Writes error message
+	Error(msg string)
+}
+
+// logger provides default implementation for Logger. It logs using Go log API
+type logger struct {
+	prefix   string
+	logLevel uint
+}
+
+func (l *logger) SetLogLevel(logLevel uint) {
+	l.logLevel = logLevel
+}
+
+func (l *logger) SetPrefix(prefix string) {
+	l.prefix = prefix
+}
+
+func (l *logger) Debugf(format string, v ...interface{}) {
+	if l.logLevel >= DebugLevel {
+		log.Print(l.prefix, " D! ", fmt.Sprintf(format, v...))
+	}
+}
+func (l *logger) Debug(msg string) {
+	if l.logLevel >= DebugLevel {
+		log.Print(l.prefix, " D! ", msg)
+	}
+}
+
+func (l *logger) Infof(format string, v ...interface{}) {
+	if l.logLevel >= InfoLevel {
+		log.Print(l.prefix, " I! ", fmt.Sprintf(format, v...))
+	}
+}
+func (l *logger) Info(msg string) {
+	if l.logLevel >= DebugLevel {
+		log.Print(l.prefix, " I! ", msg)
+	}
+}
+
+func (l *logger) Warnf(format string, v ...interface{}) {
+	if l.logLevel >= WarningLevel {
+		log.Print(l.prefix, " W! ", fmt.Sprintf(format, v...))
+	}
+}
+func (l *logger) Warn(msg string) {
+	if l.logLevel >= WarningLevel {
+		log.Print(l.prefix, " W! ", msg)
+	}
+}
+
+func (l *logger) Errorf(format string, v ...interface{}) {
+	log.Print(l.prefix, " E! ", fmt.Sprintf(format, v...))
+}
+
+func (l *logger) Error(msg string) {
+	log.Print(l.prefix, " [E]! ", msg)
+}
diff --git a/options.go b/options.go
index 9e4326a..3318d90 100644
--- a/options.go
+++ b/options.go
@@ -94,8 +94,15 @@ func (o *Options) LogLevel() uint {
 	return o.logLevel
 }
 
-// SetLogLevel set level to filter log messages. Each level mean to log all categories bellow. 0 error, 1 - warning, 2 - info, 3 - debug
-// Debug level will print also content of writen batches
+// SetLogLevel set level to filter log messages. Each level mean to log all categories bellow. Default is ErrorLevel.
+// There are four level constant int the log package in this library:
+//   - ErrorLevel
+//   - WarningLevel
+//   - InfoLevel
+//   - DebugLevel
+// The DebugLevel will print also content of writen batches, queries.
+// The InfoLevel prints HTTP requests info, among others.
+// Set log.Log to nil in order to completely disable logging.
 func (o *Options) SetLogLevel(logLevel uint) *Options {
 	o.logLevel = logLevel
 	return o


Test patch:
diff --git a/api/write_test.go b/api/write_test.go
index 21f581a..013c08c 100644
--- a/api/write_test.go
+++ b/api/write_test.go
@@ -14,8 +14,8 @@ import (
 
 	"github.com/influxdata/influxdb-client-go/api/write"
 	ihttp "github.com/influxdata/influxdb-client-go/internal/http"
-	"github.com/influxdata/influxdb-client-go/internal/log"
 	"github.com/influxdata/influxdb-client-go/internal/test"
+	"github.com/influxdata/influxdb-client-go/log"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 )
@@ -101,7 +101,7 @@ func TestWriteAPIImpl_Write(t *testing.T) {
 
 func TestGzipWithFlushing(t *testing.T) {
 	service := test.NewTestService(t, "http://localhost:8888")
-	log.Log.SetDebugLevel(4)
+	log.Log.SetLogLevel(log.DebugLevel)
 	writeAPI := NewWriteAPI("my-org", "my-bucket", service, write.DefaultOptions().SetBatchSize(5).SetUseGZip(true))
 	points := genPoints(5)
 	for _, p := range points {
@@ -151,7 +151,7 @@ func TestFlushInterval(t *testing.T) {
 
 func TestRetry(t *testing.T) {
 	service := test.NewTestService(t, "http://localhost:8888")
-	log.Log.SetDebugLevel(5)
+	log.Log.SetLogLevel(log.DebugLevel)
 	writeAPI := NewWriteAPI("my-org", "my-bucket", service, write.DefaultOptions().SetBatchSize(5).SetRetryInterval(10000))
 	points := genPoints(15)
 	for i := 0; i < 5; i++ {
@@ -188,7 +188,7 @@ func TestRetry(t *testing.T) {
 
 func TestWriteError(t *testing.T) {
 	service := test.NewTestService(t, "http://localhost:8888")
-	log.Log.SetDebugLevel(3)
+	log.Log.SetLogLevel(log.DebugLevel)
 	service.SetReplyError(&ihttp.Error{
 		StatusCode: 400,
 		Code:       "write",
diff --git a/internal/log/logger_test.go b/internal/log/logger_test.go
new file mode 100644
index 0000000..4be462e
--- /dev/null
+++ b/internal/log/logger_test.go
@@ -0,0 +1,51 @@
+// Copyright 2020 InfluxData, Inc. All rights reserved.
+// Use of this source code is governed by MIT
+// license that can be found in the LICENSE file.
+
+package log_test
+
+import (
+	"log"
+	"strings"
+	"testing"
+
+	ilog "github.com/influxdata/influxdb-client-go/internal/log"
+	dlog "github.com/influxdata/influxdb-client-go/log"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestLogging(t *testing.T) {
+	var sb strings.Builder
+	log.SetOutput(&sb)
+	dlog.Log.SetLogLevel(dlog.DebugLevel)
+	//test default settings
+	ilog.Debug("Debug")
+	ilog.Debugf("Debugf %s %d", "message", 1)
+	ilog.Info("Info")
+	ilog.Infof("Infof %s %d", "message", 2)
+	ilog.Warn("Warn")
+	ilog.Warnf("Warnf %s %d", "message", 3)
+	ilog.Error("Error")
+	ilog.Errorf("Errorf %s %d", "message", 4)
+	assert.True(t, strings.Contains(sb.String(), "Debug"))
+	assert.True(t, strings.Contains(sb.String(), "Debugf message 1"))
+	assert.True(t, strings.Contains(sb.String(), "Info"))
+	assert.True(t, strings.Contains(sb.String(), "Infof message 2"))
+	assert.True(t, strings.Contains(sb.String(), "Warn"))
+	assert.True(t, strings.Contains(sb.String(), "Warnf message 3"))
+	assert.True(t, strings.Contains(sb.String(), "Error"))
+	assert.True(t, strings.Contains(sb.String(), "Errorf message 4"))
+
+	sb.Reset()
+
+	dlog.Log = nil
+	ilog.Debug("Debug")
+	ilog.Debugf("Debugf %s %d", "message", 1)
+	ilog.Info("Info")
+	ilog.Infof("Infof %s %d", "message", 2)
+	ilog.Warn("Warn")
+	ilog.Warnf("Warnf %s %d", "message", 3)
+	ilog.Error("Error")
+	ilog.Errorf("Errorf %s %d", "message", 4)
+	assert.True(t, len(sb.String()) == 0)
+}
diff --git a/internal/write/service_test.go b/internal/write/service_test.go
index 6653785..bb84646 100644
--- a/internal/write/service_test.go
+++ b/internal/write/service_test.go
@@ -11,8 +11,8 @@ import (
 
 	"github.com/influxdata/influxdb-client-go/api/write"
 	ihttp "github.com/influxdata/influxdb-client-go/internal/http"
-	"github.com/influxdata/influxdb-client-go/internal/log"
 	"github.com/influxdata/influxdb-client-go/internal/test"
+	"github.com/influxdata/influxdb-client-go/log"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 )
@@ -58,7 +58,7 @@ func TestAddDefaultTags(t *testing.T) {
 }
 
 func TestDefaultRetryDelay(t *testing.T) {
-	log.Log.SetDebugLevel(4)
+	log.Log.SetLogLevel(log.DebugLevel)
 	hs := test.NewTestService(t, "http://localhost:9999")
 	opts := write.DefaultOptions()
 	ctx := context.Background()
@@ -88,7 +88,7 @@ func TestDefaultRetryDelay(t *testing.T) {
 }
 
 func TestCustomRetryDelayWithFLush(t *testing.T) {
-	log.Log.SetDebugLevel(4)
+	log.Log.SetLogLevel(log.DebugLevel)
 	hs := test.NewTestService(t, "http://localhost:9999")
 	opts := write.DefaultOptions().SetRetryInterval(1)
 	ctx := context.Background()
@@ -130,7 +130,7 @@ func TestCustomRetryDelayWithFLush(t *testing.T) {
 }
 
 func TestBufferOverwrite(t *testing.T) {
-	log.Log.SetDebugLevel(4)
+	log.Log.SetLogLevel(log.DebugLevel)
 	hs := test.NewTestService(t, "http://localhost:9999")
 	//
 	opts := write.DefaultOptions().SetRetryInterval(1).SetRetryBufferLimit(15000)
@@ -181,7 +181,7 @@ func TestBufferOverwrite(t *testing.T) {
 }
 
 func TestMaxRetryInterval(t *testing.T) {
-	log.Log.SetDebugLevel(4)
+	log.Log.SetLogLevel(log.DebugLevel)
 	hs := test.NewTestService(t, "http://localhost:9999")
 	//
 	opts := write.DefaultOptions().SetRetryInterval(1).SetMaxRetryInterval(10)
diff --git a/log/logger_test.go b/log/logger_test.go
new file mode 100644
index 0000000..67f12d0
--- /dev/null
+++ b/log/logger_test.go
@@ -0,0 +1,132 @@
+// Copyright 2020 InfluxData, Inc. All rights reserved.
+// Use of this source code is governed by MIT
+// license that can be found in the LICENSE file.
+
+package log_test
+
+import (
+	"fmt"
+	"log"
+	"strings"
+	"testing"
+
+	dlog "github.com/influxdata/influxdb-client-go/log"
+	"github.com/stretchr/testify/assert"
+)
+
+func logMessages() {
+	dlog.Log.Debug("Debug")
+	dlog.Log.Debugf("Debugf %s %d", "message", 1)
+	dlog.Log.Info("Info")
+	dlog.Log.Infof("Infof %s %d", "message", 2)
+	dlog.Log.Warn("Warn")
+	dlog.Log.Warnf("Warnf %s %d", "message", 3)
+	dlog.Log.Error("Error")
+	dlog.Log.Errorf("Errorf %s %d", "message", 4)
+}
+
+func verifyLogs(t *testing.T, sb *strings.Builder, logLevel uint, prefix string) {
+	if logLevel >= dlog.DebugLevel {
+		assert.True(t, strings.Contains(sb.String(), prefix+" D! Debug"))
+		assert.True(t, strings.Contains(sb.String(), prefix+" D! Debugf message 1"))
+	} else {
+		assert.False(t, strings.Contains(sb.String(), prefix+" D! Debug"))
+		assert.False(t, strings.Contains(sb.String(), prefix+" D! Debugf message 1"))
+	}
+	if logLevel >= dlog.InfoLevel {
+		assert.True(t, strings.Contains(sb.String(), prefix+" I! Info"))
+		assert.True(t, strings.Contains(sb.String(), prefix+" I! Infof message 2"))
+	} else {
+		assert.False(t, strings.Contains(sb.String(), prefix+" I! Info"))
+		assert.False(t, strings.Contains(sb.String(), prefix+" I! Infof message 2"))
+
+	}
+	if logLevel >= dlog.WarningLevel {
+		assert.True(t, strings.Contains(sb.String(), prefix+" W! Warn"))
+		assert.True(t, strings.Contains(sb.String(), prefix+" W! Warnf message 3"))
+	} else {
+		assert.False(t, strings.Contains(sb.String(), prefix+" W! Warn"))
+		assert.False(t, strings.Contains(sb.String(), prefix+" W! Warnf message 3"))
+	}
+	if logLevel >= dlog.ErrorLevel {
+		assert.True(t, strings.Contains(sb.String(), prefix+" E! Error"))
+		assert.True(t, strings.Contains(sb.String(), prefix+" E! Errorf message 4"))
+	}
+}
+
+func TestLogging(t *testing.T) {
+	var sb strings.Builder
+	log.SetOutput(&sb)
+	log.SetFlags(0)
+	//test default settings
+	logMessages()
+	verifyLogs(t, &sb, dlog.ErrorLevel, "influxdb2client")
+
+	sb.Reset()
+	dlog.Log.SetLogLevel(dlog.WarningLevel)
+	logMessages()
+	verifyLogs(t, &sb, dlog.WarningLevel, "influxdb2client")
+
+	sb.Reset()
+	dlog.Log.SetLogLevel(dlog.InfoLevel)
+	logMessages()
+	verifyLogs(t, &sb, dlog.InfoLevel, "influxdb2client")
+
+	sb.Reset()
+	dlog.Log.SetLogLevel(dlog.DebugLevel)
+	logMessages()
+	verifyLogs(t, &sb, dlog.DebugLevel, "influxdb2client")
+
+	sb.Reset()
+	dlog.Log.SetPrefix("client")
+	logMessages()
+	verifyLogs(t, &sb, dlog.DebugLevel, "client")
+}
+
+func TestCustomLogger(t *testing.T) {
+	var sb strings.Builder
+	log.SetOutput(&sb)
+	log.SetFlags(0)
+	dlog.Log = &testLogger{}
+	//test default settings
+	logMessages()
+	verifyLogs(t, &sb, dlog.DebugLevel, "testlogger")
+}
+
+type testLogger struct {
+}
+
+func (l *testLogger) SetLogLevel(_ uint) {
+}
+
+func (l *testLogger) SetPrefix(_ string) {
+}
+
+func (l *testLogger) Debugf(format string, v ...interface{}) {
+	log.Print("testlogger", " D! ", fmt.Sprintf(format, v...))
+}
+func (l *testLogger) Debug(msg string) {
+	log.Print("testlogger", " D! ", msg)
+}
+
+func (l *testLogger) Infof(format string, v ...interface{}) {
+	log.Print("testlogger", " I! ", fmt.Sprintf(format, v...))
+}
+func (l *testLogger) Info(msg string) {
+	log.Print("testlogger", " I! ", msg)
+}
+
+func (l *testLogger) Warnf(format string, v ...interface{}) {
+	log.Print("testlogger", " W! ", fmt.Sprintf(format, v...))
+}
+func (l *testLogger) Warn(msg string) {
+	log.Print("testlogger", " W! ", msg)
+}
+
+func (l *testLogger) Errorf(format string, v ...interface{}) {
+	log.Print("testlogger", " E! ", fmt.Sprintf(format, v...))
+}
+
+func (l *testLogger) Error(msg string) {
+	log.Print("testlogger", " [E]! ", msg)
+}