Skip to content

Commit

Permalink
feat: add iconlogger
Browse files Browse the repository at this point in the history
Signed-off-by: DRAGON <anantvijay3@gmail.com>
  • Loading branch information
XDRAGON2002 committed Aug 11, 2023
1 parent 4b73283 commit adf71a5
Show file tree
Hide file tree
Showing 10 changed files with 395 additions and 111 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ We also added OpenTelemetry (otel) spans and logs using helpers and wrappers.
* Pretty printer
* [Zap](go.uber.org/zap) with otel support
* Mock (empty logger)
* Icon printer

## TODO
* log
Expand Down Expand Up @@ -53,6 +54,11 @@ func main() {
logger.L().Info("This is the pretty logger")
// output: [info] This is the pretty logger

// initialize icon logger
logger.InitLogger("icon")
logger.L().Info("This is the icon logger")
// output: ℹ️ This is the icon logger

// initialize zap (json) logger
logger.InitLogger("zap")
logger.L().Info("This is the zap logger")
Expand Down
44 changes: 44 additions & 0 deletions iconlogger/colors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package iconlogger

import (
"io"

"github.com/kubescape/go-logger/helpers"

"github.com/fatih/color"
)

var prefixError = color.New(color.Bold, color.FgHiRed).FprintfFunc()
var prefixWarning = color.New(color.Bold, color.FgHiYellow).FprintfFunc()
var prefixInfo = color.New(color.Bold, color.FgCyan).FprintfFunc()
var prefixSuccess = color.New(color.Bold, color.FgHiGreen).FprintfFunc()
var prefixDebug = color.New(color.Bold, color.FgWhite).FprintfFunc()
var message = color.New().FprintfFunc()

func prefix(l helpers.Level) func(w io.Writer, format string, a ...interface{}) {
switch l {
case helpers.DebugLevel:
return prefixDebug
case helpers.InfoLevel:
return prefixInfo
case helpers.SuccessLevel:
return prefixSuccess
case helpers.WarningLevel:
return prefixWarning
case helpers.ErrorLevel, helpers.FatalLevel:
return prefixError
}
return message
}

func DisableColor(flag bool) {
if flag {
color.NoColor = true
}
}

func EnableColor(flag bool) {
if flag {
color.NoColor = false
}
}
118 changes: 118 additions & 0 deletions iconlogger/logger.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package iconlogger

import (
"context"
"fmt"
"os"
"sync"

spinnerpkg "github.com/briandowns/spinner"
"github.com/kubescape/go-logger/helpers"
)

const LoggerName string = "icon"

type IconLogger struct {
writer *os.File
level helpers.Level
spinner *spinnerpkg.Spinner
mutex sync.Mutex
}

var _ helpers.ILogger = (*IconLogger)(nil) // ensure all interface methods are here

func NewIconLogger() *IconLogger {

return &IconLogger{
writer: os.Stderr, // default to stderr
level: helpers.InfoLevel,
spinner: nil,
mutex: sync.Mutex{},
}
}

func (il *IconLogger) GetLevel() string { return il.level.String() }
func (il *IconLogger) SetWriter(w *os.File) { il.writer = w }
func (il *IconLogger) GetWriter() *os.File { return il.writer }
func (il *IconLogger) Ctx(_ context.Context) helpers.ILogger { return il }
func (il *IconLogger) LoggerName() string { return LoggerName }

func (il *IconLogger) SetLevel(level string) error {
il.level = helpers.ToLevel(level)
if il.level == helpers.UnknownLevel {
return fmt.Errorf("level '%s' unknown", level)
}
return nil
}
func (il *IconLogger) Fatal(msg string, details ...helpers.IDetails) {
il.print(helpers.FatalLevel, msg, details...)
os.Exit(1)
}
func (il *IconLogger) Error(msg string, details ...helpers.IDetails) {
il.print(helpers.ErrorLevel, msg, details...)
}
func (il *IconLogger) Warning(msg string, details ...helpers.IDetails) {
il.print(helpers.WarningLevel, msg, details...)
}
func (il *IconLogger) Info(msg string, details ...helpers.IDetails) {
il.print(helpers.InfoLevel, msg, details...)
}
func (il *IconLogger) Debug(msg string, details ...helpers.IDetails) {
il.print(helpers.DebugLevel, msg, details...)
}
func (il *IconLogger) Success(msg string, details ...helpers.IDetails) {
il.print(helpers.SuccessLevel, msg, details...)
}
func (il *IconLogger) Start(msg string, details ...helpers.IDetails) {
il.StartSpinner(il.writer, generateMessage(msg, details))
}
func (il *IconLogger) StopSuccess(msg string, details ...helpers.IDetails) {
il.StopSpinner(getSymbol("success") + generateMessage(msg, details) + "\n")
}
func (il *IconLogger) StopError(msg string, details ...helpers.IDetails) {
il.StopSpinner(getSymbol("error") + generateMessage(msg, details) + "\n")
}

func (il *IconLogger) print(level helpers.Level, msg string, details ...helpers.IDetails) {
il.PauseSpinner()
if !level.Skip(il.level) {
il.mutex.Lock()
prefix(level)(il.writer, "%s", getSymbol(level.String()))
message(il.writer, fmt.Sprintf("%s\n", generateMessage(msg, details)))
il.mutex.Unlock()
}
il.ResumeSpinner()
}

func detailsToString(details []helpers.IDetails) string {
s := ""
for i := range details {
s += fmt.Sprintf("%s: %v", details[i].Key(), details[i].Value())
if i < len(details)-1 {
s += "; "
}
}
return s
}

func getSymbol(level string) string {
switch level {
case "warning":
return " ❗ "
case "success":
return "✅ "
case "fatal", "error":
return "❌ "
case "debug":
return "🐞 "
default:
return "ℹ️ "
}
}

func generateMessage(msg string, details []helpers.IDetails) string {
if d := detailsToString(details); d != "" {
msg = fmt.Sprintf("%s. %s", msg, d)
}
return msg
}
154 changes: 154 additions & 0 deletions iconlogger/logger_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
package iconlogger

import (
"context"
"os"
"sync"
"testing"

"github.com/kubescape/go-logger/helpers"
"github.com/stretchr/testify/assert"
)

func TestIconLoggerPrint(t *testing.T) {
tests := []struct {
name string
loggerLevel helpers.Level
printLevel helpers.Level
msg string
details []helpers.IDetails
expected string
shouldPrint bool
}{
{
"Print Info",
helpers.InfoLevel,
helpers.InfoLevel,
"Info Message",
[]helpers.IDetails{
helpers.String("key1", "value1"),
helpers.Int("key2", 123),
},
"〜 Info Message\n",
true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
logger := &IconLogger{
level: tt.loggerLevel,
}

// Use a WaitGroup to wait for all goroutines to finish
var wg sync.WaitGroup

// Start multiple goroutines to call the print method concurrently
for i := 0; i < 1000; i++ {
wg.Add(1)
go func() {
defer wg.Done()
logger.print(tt.printLevel, tt.msg, tt.details...)
}()
}

// Wait for all goroutines to finish
wg.Wait()

})
}
}

type mockDetail struct {
key string
value interface{}
}

func (m *mockDetail) Key() string {
return m.key
}

func (m *mockDetail) Value() interface{} {
return m.value
}

func TestDetailsToString(t *testing.T) {
tests := []struct {
name string
details []helpers.IDetails
expected string
}{
{
"Single Detail",
[]helpers.IDetails{&mockDetail{"key1", "value1"}},
"key1: value1",
},
{
"Multiple Details",
[]helpers.IDetails{
&mockDetail{"key1", "value1"},
&mockDetail{"key2", 123},
},
"key1: value1; key2: 123",
},
{
"No Details",
[]helpers.IDetails{},
"",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := detailsToString(tt.details)
assert.Equal(t, tt.expected, result)
})
}
}

func TestGetSymbol(t *testing.T) {
tests := []struct {
name string
level string
expect string
}{
{"Warning", "warning", " ❗ "},
{"Success", "success", "✅ "},
{"Fatal", "fatal", "❌ "},
{"Error", "error", "❌ "},
{"Debug", "debug", "🐞 "},
{"Default", "info", "ℹ️ "},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := getSymbol(tt.level)
assert.Equal(t, tt.expect, got)
})
}
}

func TestIconLoggerGetLevel(t *testing.T) {
logger := &IconLogger{
level: helpers.InfoLevel,
}
assert.Equal(t, "info", logger.GetLevel())
}

func TestIconLoggerSetAndGetWriter(t *testing.T) {
logger := &IconLogger{}
writer := os.Stdout
logger.SetWriter(writer)
assert.Equal(t, writer, logger.GetWriter())
}

func TestIconLoggerCtx(t *testing.T) {
logger := &IconLogger{}
ctx := context.Background()
assert.Equal(t, logger, logger.Ctx(ctx))
}

func TestIconLoggerLoggerName(t *testing.T) {
logger := &IconLogger{}
assert.Equal(t, LoggerName, logger.LoggerName())
}
57 changes: 57 additions & 0 deletions iconlogger/spinner.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package iconlogger

import (
"os"
"time"

spinnerpkg "github.com/briandowns/spinner"
"github.com/mattn/go-isatty"
)

func (il *IconLogger) StartSpinner(w *os.File, message string) {
il.mutex.Lock()
defer il.mutex.Unlock()

if il.spinner != nil && il.spinner.Active() {
return
}
if isSupported() {
il.spinner = spinnerpkg.New(spinnerpkg.CharSets[14], 100*time.Millisecond, spinnerpkg.WithWriterFile(w)) // Build our new spinner
il.spinner.Suffix = " " + message
il.spinner.Start()
}
}

func (il *IconLogger) StopSpinner(message string) {
il.mutex.Lock()
defer il.mutex.Unlock()

if il.spinner == nil || !il.spinner.Active() {
return
}
il.spinner.FinalMSG = message
il.spinner.Stop()
il.spinner = nil
}

func (il *IconLogger) PauseSpinner() {
if il.spinner == nil || !il.spinner.Active() {
return
}

il.spinner.Stop()
}

func (il *IconLogger) ResumeSpinner() {
if il.spinner == nil || il.spinner.Active() {
return
}
if !isSupported() {
return
}
il.spinner.Start()
}

func isSupported() bool {
return isatty.IsTerminal(os.Stdout.Fd())
}

0 comments on commit adf71a5

Please sign in to comment.