Skip to content

Commit

Permalink
adding default logger functionality to internal (Azure#15182)
Browse files Browse the repository at this point in the history
  • Loading branch information
seankane-msft authored and vindicatesociety committed Sep 18, 2021
1 parent 3d4c8c2 commit fdb2b08
Show file tree
Hide file tree
Showing 2 changed files with 168 additions and 0 deletions.
116 changes: 116 additions & 0 deletions sdk/internal/logger/logger.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
// +build go1.13

// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package logger

import (
"fmt"
"os"
"time"
)

// LogClassification is used to group entries. Each group can be toggled on or off
type LogClassification string

const (
// LogRequest entries contain information about HTTP requests.
// This includes information like the URL, query parameters, and headers.
LogRequest LogClassification = "Request"

// LogResponse entries containe information about HTTP responses.
// This includes information like the HTTP status code, headers, and request URL.
LogResponse LogClassification = "Response"

// LogRetryPolicy entries contain information specific to the rety policy in use.
LogRetryPolicy LogClassification = "RetryPolicy"

// LogLongRunningOperation entries contian information specific to long-running operations.
// This includes information like polling location, operation state, and sleep intervals.
LogLongRunningOperation LogClassification = "LongRunningOperation"
)

// Listener is the funciton signature invoked when writing log entries.
// A Listener is required to perform its own synchronization if it's expected to be called
// from multiple Go routines
type Listener func(LogClassification, string)

// Logger controls which classifications to log and writing to the underlying log.
type Logger struct {
cls []LogClassification
lst Listener
}

// SetClassifications is used to control which classifications are written to
// the log. By default all log classifications are writen.
func (l *Logger) SetClassifications(cls ...LogClassification) {
l.cls = cls
}

// SetListener will set the Logger to write to the specified Listener.
func (l *Logger) SetListener(lst Listener) {
l.lst = lst
}

// Should returns true if the specified log classification should be written to the log.
// By default all log classifications will be logged. Call SetClassification() to limit
// the log classifications for logging.
// If no listener has been set this will return false.
// Calling this method is useful when the message to log is computationally expensive
// and you want to avoid the overhead if its log classification is not enabled.
func (l *Logger) Should(cls LogClassification) bool {
if l.lst == nil {
return false
}
if l.cls == nil || len(l.cls) == 0 {
return true
}
for _, c := range l.cls {
if c == cls {
return true
}
}
return false
}

// Write invokes the underlying Listener with the specified classification and message.
// If the classification shouldn't be logged or there is no listener then Write does nothing.
func (l *Logger) Write(cls LogClassification, message string) {
if !l.Should(cls) {
return
}
l.lst(cls, message)
}

// Writef invokes the underlying Listener with the specified classification and formatted message.
// If the classification shouldn't be logged or there is no listener then Writef does nothing.
func (l *Logger) Writef(cls LogClassification, format string, a ...interface{}) {
if !l.Should(cls) {
return
}
l.lst(cls, fmt.Sprintf(format, a...))
}

// for testing purposes
func (l *Logger) resetClassifications() {
l.cls = nil
}

var logger Logger

// Log returns the process-wide logger.
func Log() *Logger {
return &logger
}

func init() {
if cls := os.Getenv("AZURE_SDK_GO_LOGGING"); cls == "all" {
// cls could be enhanced to support a comma-delimited list of log classifications
logger.lst = func(cls LogClassification, msg string) {
// simple console logger, it writes to stderr in the following format:
// [time-stamp] Classification: message
fmt.Fprintf(os.Stderr, "[%s] %s: %s\n", time.Now().Format(time.StampMicro), cls, msg)
}
}
}
52 changes: 52 additions & 0 deletions sdk/internal/logger/logger_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package logger

import (
"fmt"
"net/http"
"testing"
)

func TestLoggingdefault(t *testing.T) {
// ensure logging with nil listener doesn't fail
Log().SetListener(nil)
Log().Write(LogRequest, "this should work just fine")

log := map[LogClassification]string{}
Log().SetListener(func(cls LogClassification, msg string) {
log[cls] = msg
})
const req = "this is a request"
Log().Write(LogRequest, req)
const resp = "this is a response: %d"
Log().Writef(LogResponse, resp, http.StatusOK)
if l := len(log); l != 2 {
t.Fatalf("unexpected log entry count: %d", l)
}
if log[LogRequest] != req {
t.Fatalf("unexpected log request: %s", log[LogRequest])
}
if log[LogResponse] != fmt.Sprintf(resp, http.StatusOK) {
t.Fatalf("unexpected log response: %s", log[LogResponse])
}
}

func TestLoggingClassification(t *testing.T) {
log := map[LogClassification]string{}
Log().SetListener(func(cls LogClassification, msg string) {
log[cls] = msg
})
Log().SetClassifications(LogRequest)
defer Log().resetClassifications()
Log().Write(LogResponse, "this shouldn't be in the log")
if s, ok := log[LogResponse]; ok {
t.Fatalf("unexpected log entry %s", s)
}
const req = "this is a request"
Log().Write(LogRequest, req)
if log[LogRequest] != req {
t.Fatalf("unexpected log entry: %s", log[LogRequest])
}
}

0 comments on commit fdb2b08

Please sign in to comment.