From c3ce4eaea8ce6be721684acf77f1109e141f6abd Mon Sep 17 00:00:00 2001 From: Mohsen Samiei Date: Tue, 5 May 2020 01:06:12 +0430 Subject: [PATCH 1/3] refactor: rename setlogger to setformatter --- entry.go | 51 ++++++++++++++++++++++++++++----------------------- log.go | 7 ++++--- 2 files changed, 32 insertions(+), 26 deletions(-) diff --git a/entry.go b/entry.go index 37a2033..2e3f827 100644 --- a/entry.go +++ b/entry.go @@ -3,7 +3,6 @@ package log import ( "fmt" "log" - "os" "reflect" "runtime" "time" @@ -16,11 +15,20 @@ const ( // Entry implements log data type Entry struct { - Raised time.Time - Level Level - Source string + // Raised keeps log raised time + Raised time.Time + + // Level keeps log level + Level Level + + // Source keeps log source code with line + Source string + + // Message keeps log message text Message string - Data map[string]interface{} + + // Data keeps all data + Data map[string]interface{} } // NewEntry returns new entry with defaults @@ -41,34 +49,29 @@ func NewEntry() *Entry { } // Debug logs entry with message in debug level -func (entry *Entry) Debug(message string) { - entry.Level = LevelDebug - entry.log(message) +func (entry Entry) Debug(message string) { + entry.log(LevelDebug, message) } // Info logs entry with message in info level -func (entry *Entry) Info(message string) { - entry.Level = LevelInfo - entry.log(message) +func (entry Entry) Info(message string) { + entry.log(LevelInfo, message) } // Warning logs entry with message in warning level -func (entry *Entry) Warning(message string) { - entry.Level = LevelWarning - entry.log(message) +func (entry Entry) Warning(message string) { + entry.log(LevelWarning, message) } // Error logs entry with message in error level -func (entry *Entry) Error(message string) { - entry.Level = LevelError - entry.log(message) +func (entry Entry) Error(message string) { + entry.log(LevelError, message) } // Fatal logs entry with message in fatal level so exit with code 1 -func (entry *Entry) Fatal(message string) { - entry.Level = LevelFatal - entry.log(message) - os.Exit(1) +func (entry Entry) Fatal(message string) { + entry.log(LevelFatal, message) + exit(1) } // With appends data to entry and returns that @@ -83,6 +86,7 @@ func (entry *Entry) With(data interface{}) *Entry { value = reflect.ValueOf(value).Elem().Interface() } refType := reflect.TypeOf(value) + switch refType.Kind() { case reflect.Map: dic := reflect.ValueOf(value) @@ -108,8 +112,9 @@ func (entry *Entry) Value(key string, value interface{}) *Entry { return entry } -func (entry *Entry) log(message string) { - entry.Message = message +func (entry *Entry) log(lvl Level, msg string) { + entry.Level = lvl + entry.Message = msg if entry.Level >= level { if _, err := fmt.Fprintln(output, formatter.Format(*entry)); err != nil { log.Printf("can not write on output: %v", err) diff --git a/log.go b/log.go index 1589a65..a7d9d89 100644 --- a/log.go +++ b/log.go @@ -8,6 +8,7 @@ import ( var ( output io.Writer = os.Stdout level = LevelInfo + exit = os.Exit formatter = NewTextFormatter() constants = make(map[string]interface{}) ) @@ -22,9 +23,9 @@ func SetLevel(lvl Level) { level = lvl } -// SetLogger sets logging formatter -func SetLogger(log Formatter) { - formatter = log +// SetFormatter sets logging formatter +func SetFormatter(f Formatter) { + formatter = f } // SetConstant sets logging constants data From 795f44527d9ff113cddb9b5debb29fef0b3608a2 Mon Sep 17 00:00:00 2001 From: Mohsen Samiei Date: Tue, 5 May 2020 01:06:52 +0430 Subject: [PATCH 2/3] test: implements test coverage --- entry_test.go | 159 ++++++++++++++++++++++++++++++++++++ formatter_test.go | 155 +++++++++++++++++++++++++++++++++++ go.mod | 2 +- go.sum | 6 +- level_test.go | 50 ++++++++++++ log_test.go | 201 ++++++++++++++++++++++++++++++++++++++++++++++ main_test.go | 26 ++++++ 7 files changed, 595 insertions(+), 4 deletions(-) create mode 100644 entry_test.go create mode 100644 formatter_test.go create mode 100644 level_test.go create mode 100644 log_test.go create mode 100644 main_test.go diff --git a/entry_test.go b/entry_test.go new file mode 100644 index 0000000..95f05f6 --- /dev/null +++ b/entry_test.go @@ -0,0 +1,159 @@ +package log + +import ( + "fmt" + "github.com/stretchr/testify/assert" + "strings" + "testing" + "time" +) + +func createTestEntry() *Entry { + return &Entry{ + Raised: time.Now(), + Source: "at test in test.go:11", + Data: make(map[string]interface{}), + } +} + +func TestEntry_Debug(t *testing.T) { + resetTest() + t.Run("must returns debug message in output", func(t *testing.T) { + message := "text message" + createTestEntry().Debug(message) + text := strings.ToLower(testOutput.String()) + assert.Contains(t, text, fmt.Sprintf("\"message\":\"%v\"", message)) + assert.Contains(t, text, fmt.Sprintf("\"level\":%d", LevelDebug)) + }) +} + +func TestEntry_Info(t *testing.T) { + resetTest() + t.Run("must returns info message in output", func(t *testing.T) { + message := "text message" + createTestEntry().Info(message) + text := strings.ToLower(testOutput.String()) + assert.Contains(t, text, fmt.Sprintf("\"message\":\"%v\"", message)) + assert.Contains(t, text, fmt.Sprintf("\"level\":%d", LevelInfo)) + }) +} + +func TestEntry_Warning(t *testing.T) { + resetTest() + t.Run("must returns warning message in output", func(t *testing.T) { + message := "text message" + createTestEntry().Warning(message) + text := strings.ToLower(testOutput.String()) + assert.Contains(t, text, fmt.Sprintf("\"message\":\"%v\"", message)) + assert.Contains(t, text, fmt.Sprintf("\"level\":%d", LevelWarning)) + }) +} + +func TestEntry_Error(t *testing.T) { + resetTest() + t.Run("must returns error message in output", func(t *testing.T) { + message := "text message" + createTestEntry().Error(message) + text := strings.ToLower(testOutput.String()) + assert.Contains(t, text, fmt.Sprintf("\"message\":\"%v\"", message)) + assert.Contains(t, text, fmt.Sprintf("\"level\":%d", LevelError)) + }) +} + +func TestEntry_Fatal(t *testing.T) { + resetTest() + t.Run("must returns error message in output", func(t *testing.T) { + message := "text message" + exit = func(code int) { + assert.Equal(t, code, 1) + } + createTestEntry().Fatal(message) + text := strings.ToLower(testOutput.String()) + assert.Contains(t, text, fmt.Sprintf("\"message\":\"%v\"", message)) + assert.Contains(t, text, fmt.Sprintf("\"level\":%d", LevelFatal)) + }) +} + +func TestEntry_Value(t *testing.T) { + resetTest() + t.Run("must returns entry with value", func(t *testing.T) { + entry := createTestEntry().Value("key", "value") + assert.Equal(t, entry.Data["key"], "value") + }) +} + +func TestEntry_With(t *testing.T) { + resetTest() + t.Run("must returns entry with value", func(t *testing.T) { + entry := createTestEntry().With("value1").With("value2").With("value3") + assert.Equal(t, entry.Data[dataValues], []interface{}{"value1", "value2", "value3"}) + }) + t.Run("must returns entry with pointer value", func(t *testing.T) { + now := time.Now() + entry := createTestEntry().With(&now) + assert.Equal(t, entry.Data["Time"], now) + }) + t.Run("must returns entry with inline struct", func(t *testing.T) { + testData := struct { + Name string + }{ + Name: "name", + } + entry := createTestEntry().With(testData) + assert.Equal(t, entry.Data[""], testData) + }) + t.Run("must returns entry with defined struct", func(t *testing.T) { + type testData struct { + Name string + } + td := testData{Name: "name"} + entry := createTestEntry().With(td) + assert.Equal(t, entry.Data["testData"], td) + }) + t.Run("must returns entry with error", func(t *testing.T) { + err := fmt.Errorf("test message") + entry := createTestEntry().With(err) + assert.Equal(t, entry.Data[dataError], err.Error()) + }) + t.Run("must returns entry with map", func(t *testing.T) { + dict := map[string]interface{}{ + "key1": "value1", + "key2": "value2", + "key3": "value3", + } + entry := createTestEntry().With(dict) + for key, value := range dict { + assert.Equal(t, entry.Data[key], value) + } + }) + t.Run("must returns entry without data affect with nil", func(t *testing.T) { + entry := createTestEntry().With(nil) + assert.NotNil(t, entry) + }) +} + +func TestNewEntry(t *testing.T) { + tests := []struct { + name string + want *Entry + }{ + { + name: "must returns new entry with defaults", + want: &Entry{ + Raised: time.Now(), + Source: "at .* in .*[.]go:[\\d]*", + Data: make(map[string]interface{}), + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + entry := NewEntry() + assert.Equal(t, entry.Data, tt.want.Data) + assert.Equal(t, entry.Message, tt.want.Message) + assert.Equal(t, entry.Level, tt.want.Level) + assert.Equal(t, entry.Raised.Format("2006-01-02 15:04:05"), tt.want.Raised.Format("2006-01-02 15:04:05")) + assert.Regexp(t, tt.want.Source, entry.Source) + }) + } +} diff --git a/formatter_test.go b/formatter_test.go new file mode 100644 index 0000000..564cfe5 --- /dev/null +++ b/formatter_test.go @@ -0,0 +1,155 @@ +package log + +import ( + "encoding/json" + "fmt" + "github.com/stretchr/testify/assert" + "gopkg.in/yaml.v2" + "strings" + "testing" + "time" +) + +func TestNewJSONFormatter(t *testing.T) { + tests := []struct { + name string + want Formatter + }{ + { + name: "must returns json formatter", + want: new(jsonFormatter), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, NewJSONFormatter(), tt.want) + }) + } +} + +func TestNewTextFormatter(t *testing.T) { + tests := []struct { + name string + want Formatter + }{ + { + name: "must returns text formatter", + want: new(textFormatter), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, NewTextFormatter(), tt.want) + }) + } +} + +func TestNewYAMLFormatter(t *testing.T) { + tests := []struct { + name string + want Formatter + }{ + { + name: "must returns yaml formatter", + want: new(yamlFormatter), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, NewYAMLFormatter(), tt.want) + }) + } +} + +func Test_textFormatter_Format(t *testing.T) { + formatter := new(textFormatter) + type Test struct { + name string + arg Entry + } + var tests []Test + for _, lvl := range testLevels { + tests = append(tests, Test{ + name: fmt.Sprintf("must returns marshal string with %s entry", lvl), + arg: Entry{ + Raised: time.Now(), + Level: lvl, + Source: "at test in test.go:10", + Message: "text message", + Data: map[string]interface{}{ + "error": "can not do job", + "key": "value", + }, + }, + }) + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + text := strings.ToLower(formatter.Format(tt.arg)) + assert.Contains(t, text, strings.ToLower(tt.arg.Level.String())) + assert.Contains(t, text, strings.ToLower(tt.arg.Source)) + assert.Contains(t, text, strings.ToLower(tt.arg.Message)) + assert.Contains(t, text, strings.ToLower(tt.arg.Raised.Format("2006-01-02 15:04:05"))) + bytes, _ := yaml.Marshal(tt.arg.Data) + for _, line := range strings.Split(string(bytes), "\n") { + assert.Contains(t, text, strings.ToLower(strings.TrimSpace(line))) + } + }) + } +} + +func Test_jsonFormatter_Format(t *testing.T) { + formatter := new(jsonFormatter) + tests := []struct { + name string + arg Entry + }{ + { + name: "must returns json marshal", + arg: Entry{ + Raised: time.Now(), + Level: LevelWarning, + Source: "at test in test.go:10", + Message: "text message", + Data: map[string]interface{}{ + "error": "can not do job", + "key": "value", + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + bytes, _ := json.Marshal(tt.arg) + assert.Equal(t, formatter.Format(tt.arg), string(bytes)) + }) + } +} + +func Test_yamlFormatter_Format(t *testing.T) { + formatter := new(yamlFormatter) + tests := []struct { + name string + arg Entry + }{ + { + name: "must returns yaml marshal", + arg: Entry{ + Raised: time.Now(), + Level: LevelWarning, + Source: "at test in test.go:10", + Message: "text message", + Data: map[string]interface{}{ + "error": "can not do job", + "key": "value", + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + bytes, _ := yaml.Marshal(tt.arg) + assert.Equal(t, formatter.Format(tt.arg), string(bytes)) + }) + } +} diff --git a/go.mod b/go.mod index 00c23f2..64921f0 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,6 @@ module github.com/golage/log go 1.13 require ( - github.com/golage/strings v0.1.0 // indirect + github.com/stretchr/testify v1.5.1 gopkg.in/yaml.v2 v2.2.8 ) diff --git a/go.sum b/go.sum index a9b3e09..3379993 100644 --- a/go.sum +++ b/go.sum @@ -1,10 +1,10 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/golage/strings v0.1.0 h1:6qi5w5EJ/MrIyjbsuG84l5ftUKwRTLM4IkP3FofFBXY= -github.com/golage/strings v0.1.0/go.mod h1:nOYweqntoqcsGGJ1sBAaWI6A70JbvParlTwmuoS2JB8= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= diff --git a/level_test.go b/level_test.go new file mode 100644 index 0000000..64be4c6 --- /dev/null +++ b/level_test.go @@ -0,0 +1,50 @@ +package log + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestLevel_String(t *testing.T) { + tests := []struct { + name string + lvl Level + want string + }{ + { + name: "must returns debug string", + lvl: LevelDebug, + want: "Debug", + }, + { + name: "must returns info string", + lvl: LevelInfo, + want: "Info", + }, + { + name: "must returns warning string", + lvl: LevelWarning, + want: "Warning", + }, + { + name: "must returns error string", + lvl: LevelError, + want: "Error", + }, + { + name: "must returns fatal string", + lvl: LevelFatal, + want: "Fatal", + }, + { + name: "must returns unknown string", + lvl: 10, + want: "Unknown", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.lvl.String(), tt.want) + }) + } +} diff --git a/log_test.go b/log_test.go new file mode 100644 index 0000000..7bfb052 --- /dev/null +++ b/log_test.go @@ -0,0 +1,201 @@ +package log + +import ( + "bytes" + "fmt" + "github.com/stretchr/testify/assert" + "strings" + "testing" +) + +func TestDebug(t *testing.T) { + resetTest() + t.Run("must returns debug message in output", func(t *testing.T) { + message := "text message" + Debug(message) + text := strings.ToLower(testOutput.String()) + assert.Contains(t, text, fmt.Sprintf("\"message\":\"%v\"", message)) + assert.Contains(t, text, fmt.Sprintf("\"level\":%d", LevelDebug)) + }) +} + +func TestInfo(t *testing.T) { + resetTest() + t.Run("must returns info message in output", func(t *testing.T) { + message := "text message" + Info(message) + text := strings.ToLower(testOutput.String()) + assert.Contains(t, text, fmt.Sprintf("\"message\":\"%v\"", message)) + assert.Contains(t, text, fmt.Sprintf("\"level\":%d", LevelInfo)) + }) +} + +func TestWarning(t *testing.T) { + resetTest() + t.Run("must returns warning message in output", func(t *testing.T) { + message := "text message" + Warning(message) + text := strings.ToLower(testOutput.String()) + assert.Contains(t, text, fmt.Sprintf("\"message\":\"%v\"", message)) + assert.Contains(t, text, fmt.Sprintf("\"level\":%d", LevelWarning)) + }) +} + +func TestError(t *testing.T) { + resetTest() + t.Run("must returns error message in output", func(t *testing.T) { + message := "text message" + Error(message) + text := strings.ToLower(testOutput.String()) + assert.Contains(t, text, fmt.Sprintf("\"message\":\"%v\"", message)) + assert.Contains(t, text, fmt.Sprintf("\"level\":%d", LevelError)) + }) +} + +func TestFatal(t *testing.T) { + resetTest() + t.Run("must returns fatal message in output", func(t *testing.T) { + message := "fatal text message" + exit = func(code int) { + assert.Equal(t, code, 1) + } + Fatal(message) + text := strings.ToLower(testOutput.String()) + assert.Contains(t, text, fmt.Sprintf("\"message\":\"%v\"", message)) + assert.Contains(t, text, fmt.Sprintf("\"level\":%d", LevelFatal)) + }) +} + +func TestSetConstant(t *testing.T) { + resetTest() + data := "\"Data\":{\"const_key\":\"const_value\"}" + SetConstant("const_key", "const_value") + for _, lvl := range testLevels { + t.Run(fmt.Sprintf("must returns constant values in %v", strings.ToLower(lvl.String())), func(t *testing.T) { + switch lvl { + case LevelDebug: + Debug("") + case LevelInfo: + Info("") + case LevelWarning: + Warning("") + case LevelError: + Error("") + case LevelFatal: + Fatal("") + } + assert.Contains(t, testOutput.String(), data) + testOutput.Reset() + }) + } +} + +func TestSetLevel(t *testing.T) { + resetTest() + for _, lvl := range testLevels { + SetLevel(lvl) + t.Run(fmt.Sprintf("must allowed to logs equal or greater than %v", strings.ToLower(lvl.String())), func(t *testing.T) { + for _, l := range testLevels { + switch l { + case LevelDebug: + Debug("") + case LevelInfo: + Info("") + case LevelWarning: + Warning("") + case LevelError: + Error("") + case LevelFatal: + Fatal("") + } + if lvl <= l { + assert.NotEmpty(t, testOutput.String()) + } else { + assert.Empty(t, testOutput.String()) + } + testOutput.Reset() + } + }) + } +} + +func TestSetFormatter(t *testing.T) { + resetTest() + t.Run("must sets json-formatter in formatter var", func(t *testing.T) { + f := new(jsonFormatter) + SetFormatter(f) + assert.Equal(t, formatter, f) + }) + t.Run("must sets yaml-formatter in formatter var", func(t *testing.T) { + f := new(yamlFormatter) + SetFormatter(f) + assert.Equal(t, formatter, f) + }) + t.Run("must sets text-formatter in formatter var", func(t *testing.T) { + f := new(textFormatter) + SetFormatter(f) + assert.Equal(t, formatter, f) + }) +} + +func TestSetOutput(t *testing.T) { + resetTest() + t.Run("must sets buffer in output var", func(t *testing.T) { + buf := new(bytes.Buffer) + SetOutput(buf) + assert.Equal(t, output, buf) + }) +} + +func TestValue(t *testing.T) { + resetTest() + t.Run("must returns entry with value", func(t *testing.T) { + entry := Value("key", "value") + assert.Equal(t, entry.Data["key"], "value") + }) +} + +func TestWith(t *testing.T) { + resetTest() + t.Run("must returns entry with value", func(t *testing.T) { + entry := With("value1").With("value2").With("value3") + assert.Equal(t, entry.Data[dataValues], []interface{}{"value1", "value2", "value3"}) + }) + t.Run("must returns entry with inline struct", func(t *testing.T) { + testData := struct { + Name string + }{ + Name: "name", + } + entry := With(testData) + assert.Equal(t, entry.Data[""], testData) + }) + t.Run("must returns entry with defined struct", func(t *testing.T) { + type testData struct { + Name string + } + td := testData{Name: "name"} + entry := With(td) + assert.Equal(t, entry.Data["testData"], td) + }) + t.Run("must returns entry with error", func(t *testing.T) { + err := fmt.Errorf("test message") + entry := With(err) + assert.Equal(t, entry.Data[dataError], err.Error()) + }) + t.Run("must returns entry with map", func(t *testing.T) { + dict := map[string]interface{}{ + "key1": "value1", + "key2": "value2", + "key3": "value3", + } + entry := With(dict) + for key, value := range dict { + assert.Equal(t, entry.Data[key], value) + } + }) + t.Run("must returns entry without data affect with nil", func(t *testing.T) { + entry := With(nil) + assert.NotNil(t, entry) + }) +} diff --git a/main_test.go b/main_test.go new file mode 100644 index 0000000..98bc1ed --- /dev/null +++ b/main_test.go @@ -0,0 +1,26 @@ +package log + +import ( + "bytes" + "os" + "testing" +) + +var ( + testOutput = new(bytes.Buffer) + testLevels = []Level{LevelDebug, LevelInfo, LevelWarning, LevelError, LevelFatal} +) + +func TestMain(m *testing.M) { + resetTest() + os.Exit(m.Run()) +} + +func resetTest() { + testOutput.Reset() + level = LevelDebug + output = testOutput + exit = func(code int) {} + formatter = new(jsonFormatter) + constants = make(map[string]interface{}) +} From ee95118e4dcad0cf77edd4fa42b786d00589cb8e Mon Sep 17 00:00:00 2001 From: Mohsen Samiei Date: Tue, 5 May 2020 01:23:54 +0430 Subject: [PATCH 3/3] doc: write some douments --- README.md | 34 +++++++++++++++++++++++++++++++++- examples/main.go | 2 ++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 6426671..fd3a4b3 100644 --- a/README.md +++ b/README.md @@ -11,4 +11,36 @@ go get github.com/golage/log Import into your code: ```go import "github.com/golage/log" -``` \ No newline at end of file +``` +Set output writer: +```go +log.SetOutput(w) +``` +Set minimum log level (default: LevelInfo): +```go +log.SetLevel(log.LevelDebug) +``` +Set log formatter (default: TextFormatter): +```go +log.SetFormatter(log.NewTextFormatter) +log.SetFormatter(log.NewJSONFormatter) +log.SetFormatter(log.NewYAMLFormatter) +``` +Set constants data in all logs: +```go +log.SetConstant("key", "value") +``` +Add data to log: +```go +log.With(err) +log.Value("key", "value") +``` +Write log: +```go +log.Debug("message") +log.Info("message") +log.Warning("message") +log.Error("message") +log.Fatal("message") +``` +For more see [example](examples/main.go) \ No newline at end of file diff --git a/examples/main.go b/examples/main.go index 5ea6e47..ab5b541 100644 --- a/examples/main.go +++ b/examples/main.go @@ -2,6 +2,7 @@ package main import ( "errors" + "github.com/golage/log" ) @@ -11,6 +12,7 @@ type data struct { func main() { log.SetLevel(log.LevelDebug) + log.SetConstant("code_name", "example") log.Value("name", "john").Debug("debug message")