From d4d2715ff0078dc1823eb730ffe9640bd75a7c61 Mon Sep 17 00:00:00 2001 From: kkumar-gcc Date: Wed, 11 Sep 2024 23:12:12 +0530 Subject: [PATCH 1/3] add methods to entry --- contracts/log/log.go | 16 +++++++++++++++ log/entry.go | 48 ++++++++++++++++++++++++++++++++++++++++---- log/logrus_writer.go | 43 +++++++++++++++++++++++++++++++++++++-- 3 files changed, 101 insertions(+), 6 deletions(-) diff --git a/contracts/log/log.go b/contracts/log/log.go index e7f79d7c9..b22e72ed9 100644 --- a/contracts/log/log.go +++ b/contracts/log/log.go @@ -104,4 +104,20 @@ type Entry interface { Time() time.Time // Message returns the message of the entry. Message() string + // Code returns the associated code. + Code() string + // With returns additional context data. + With() map[string]any + // User returns the user information. + User() any + // Tags returns the list of tags. + Tags() []string + // Owner returns the log's owner. + Owner() any + // Request returns the request data. + Request() map[string]any + // Response returns the response data. + Response() map[string]any + // Trace returns the stack trace or trace data. + Trace() map[string]any } diff --git a/log/entry.go b/log/entry.go index 3d6f887a7..f5c927c0e 100644 --- a/log/entry.go +++ b/log/entry.go @@ -8,10 +8,18 @@ import ( ) type Entry struct { - ctx context.Context - level log.Level - time time.Time - message string + ctx context.Context + level log.Level + time time.Time + message string + code string + user any + tags []string + owner any + request map[string]any + response map[string]any + with map[string]any + stacktrace map[string]any } func (r *Entry) Context() context.Context { @@ -29,3 +37,35 @@ func (r *Entry) Time() time.Time { func (r *Entry) Message() string { return r.message } + +func (r *Entry) Code() string { + return r.code +} + +func (r *Entry) With() map[string]any { + return r.with +} + +func (r *Entry) User() any { + return r.user +} + +func (r *Entry) Tags() []string { + return r.tags +} + +func (r *Entry) Owner() any { + return r.owner +} + +func (r *Entry) Request() map[string]any { + return r.request +} + +func (r *Entry) Response() map[string]any { + return r.response +} + +func (r *Entry) Trace() map[string]any { + return r.stacktrace +} diff --git a/log/logrus_writer.go b/log/logrus_writer.go index fb173c0e0..1516ba23c 100644 --- a/log/logrus_writer.go +++ b/log/logrus_writer.go @@ -7,6 +7,7 @@ import ( "github.com/rotisserie/eris" "github.com/sirupsen/logrus" + "github.com/spf13/cast" "github.com/goravel/framework/contracts/config" "github.com/goravel/framework/contracts/foundation" @@ -365,10 +366,48 @@ func (h *Hook) Levels() []logrus.Level { } func (h *Hook) Fire(entry *logrus.Entry) error { - return h.instance.Fire(&Entry{ + e := &Entry{ ctx: entry.Context, level: log.Level(entry.Level), time: entry.Time, message: entry.Message, - }) + } + + data := entry.Data + if len(data) > 0 { + root, err := cast.ToStringMapE(data["root"]) + if err != nil { + return err + } + + if code, ok := cast.ToStringE(root["code"]); ok == nil { + e.code = code + } + + e.user = root["user"] + + if tags, ok := cast.ToStringSliceE(root["tags"]); ok == nil { + e.tags = tags + } + + e.owner = root["owner"] + + if req, err := cast.ToStringMapE(root["tags"]); err == nil { + e.request = req + } + + if res, err := cast.ToStringMapE(root["tags"]); err == nil { + e.response = res + } + + if context, ok := cast.ToStringMapE(root["context"]); ok == nil { + e.with = context + } + + if stacktrace, ok := cast.ToStringMapE(root["stacktrace"]); ok == nil { + e.stacktrace = stacktrace + } + } + + return h.instance.Fire(e) } From 46066d33ba2ac9ad21b8fd8368eda972cb814d80 Mon Sep 17 00:00:00 2001 From: kkumar-gcc Date: Wed, 11 Sep 2024 17:43:44 +0000 Subject: [PATCH 2/3] chore: update mocks --- mocks/log/Entry.go | 374 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 374 insertions(+) diff --git a/mocks/log/Entry.go b/mocks/log/Entry.go index 7d8fe4d72..714f7a866 100644 --- a/mocks/log/Entry.go +++ b/mocks/log/Entry.go @@ -24,6 +24,51 @@ func (_m *Entry) EXPECT() *Entry_Expecter { return &Entry_Expecter{mock: &_m.Mock} } +// Code provides a mock function with given fields: +func (_m *Entry) Code() string { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for Code") + } + + var r0 string + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} + +// Entry_Code_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Code' +type Entry_Code_Call struct { + *mock.Call +} + +// Code is a helper method to define mock.On call +func (_e *Entry_Expecter) Code() *Entry_Code_Call { + return &Entry_Code_Call{Call: _e.mock.On("Code")} +} + +func (_c *Entry_Code_Call) Run(run func()) *Entry_Code_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *Entry_Code_Call) Return(_a0 string) *Entry_Code_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Entry_Code_Call) RunAndReturn(run func() string) *Entry_Code_Call { + _c.Call.Return(run) + return _c +} + // Context provides a mock function with given fields: func (_m *Entry) Context() context.Context { ret := _m.Called() @@ -161,6 +206,194 @@ func (_c *Entry_Message_Call) RunAndReturn(run func() string) *Entry_Message_Cal return _c } +// Owner provides a mock function with given fields: +func (_m *Entry) Owner() interface{} { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for Owner") + } + + var r0 interface{} + if rf, ok := ret.Get(0).(func() interface{}); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(interface{}) + } + } + + return r0 +} + +// Entry_Owner_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Owner' +type Entry_Owner_Call struct { + *mock.Call +} + +// Owner is a helper method to define mock.On call +func (_e *Entry_Expecter) Owner() *Entry_Owner_Call { + return &Entry_Owner_Call{Call: _e.mock.On("Owner")} +} + +func (_c *Entry_Owner_Call) Run(run func()) *Entry_Owner_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *Entry_Owner_Call) Return(_a0 interface{}) *Entry_Owner_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Entry_Owner_Call) RunAndReturn(run func() interface{}) *Entry_Owner_Call { + _c.Call.Return(run) + return _c +} + +// Request provides a mock function with given fields: +func (_m *Entry) Request() map[string]interface{} { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for Request") + } + + var r0 map[string]interface{} + if rf, ok := ret.Get(0).(func() map[string]interface{}); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(map[string]interface{}) + } + } + + return r0 +} + +// Entry_Request_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Request' +type Entry_Request_Call struct { + *mock.Call +} + +// Request is a helper method to define mock.On call +func (_e *Entry_Expecter) Request() *Entry_Request_Call { + return &Entry_Request_Call{Call: _e.mock.On("Request")} +} + +func (_c *Entry_Request_Call) Run(run func()) *Entry_Request_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *Entry_Request_Call) Return(_a0 map[string]interface{}) *Entry_Request_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Entry_Request_Call) RunAndReturn(run func() map[string]interface{}) *Entry_Request_Call { + _c.Call.Return(run) + return _c +} + +// Response provides a mock function with given fields: +func (_m *Entry) Response() map[string]interface{} { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for Response") + } + + var r0 map[string]interface{} + if rf, ok := ret.Get(0).(func() map[string]interface{}); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(map[string]interface{}) + } + } + + return r0 +} + +// Entry_Response_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Response' +type Entry_Response_Call struct { + *mock.Call +} + +// Response is a helper method to define mock.On call +func (_e *Entry_Expecter) Response() *Entry_Response_Call { + return &Entry_Response_Call{Call: _e.mock.On("Response")} +} + +func (_c *Entry_Response_Call) Run(run func()) *Entry_Response_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *Entry_Response_Call) Return(_a0 map[string]interface{}) *Entry_Response_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Entry_Response_Call) RunAndReturn(run func() map[string]interface{}) *Entry_Response_Call { + _c.Call.Return(run) + return _c +} + +// Tags provides a mock function with given fields: +func (_m *Entry) Tags() []string { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for Tags") + } + + var r0 []string + if rf, ok := ret.Get(0).(func() []string); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]string) + } + } + + return r0 +} + +// Entry_Tags_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Tags' +type Entry_Tags_Call struct { + *mock.Call +} + +// Tags is a helper method to define mock.On call +func (_e *Entry_Expecter) Tags() *Entry_Tags_Call { + return &Entry_Tags_Call{Call: _e.mock.On("Tags")} +} + +func (_c *Entry_Tags_Call) Run(run func()) *Entry_Tags_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *Entry_Tags_Call) Return(_a0 []string) *Entry_Tags_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Entry_Tags_Call) RunAndReturn(run func() []string) *Entry_Tags_Call { + _c.Call.Return(run) + return _c +} + // Time provides a mock function with given fields: func (_m *Entry) Time() time.Time { ret := _m.Called() @@ -206,6 +439,147 @@ func (_c *Entry_Time_Call) RunAndReturn(run func() time.Time) *Entry_Time_Call { return _c } +// Trace provides a mock function with given fields: +func (_m *Entry) Trace() map[string]interface{} { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for Trace") + } + + var r0 map[string]interface{} + if rf, ok := ret.Get(0).(func() map[string]interface{}); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(map[string]interface{}) + } + } + + return r0 +} + +// Entry_Trace_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Trace' +type Entry_Trace_Call struct { + *mock.Call +} + +// Trace is a helper method to define mock.On call +func (_e *Entry_Expecter) Trace() *Entry_Trace_Call { + return &Entry_Trace_Call{Call: _e.mock.On("Trace")} +} + +func (_c *Entry_Trace_Call) Run(run func()) *Entry_Trace_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *Entry_Trace_Call) Return(_a0 map[string]interface{}) *Entry_Trace_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Entry_Trace_Call) RunAndReturn(run func() map[string]interface{}) *Entry_Trace_Call { + _c.Call.Return(run) + return _c +} + +// User provides a mock function with given fields: +func (_m *Entry) User() interface{} { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for User") + } + + var r0 interface{} + if rf, ok := ret.Get(0).(func() interface{}); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(interface{}) + } + } + + return r0 +} + +// Entry_User_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'User' +type Entry_User_Call struct { + *mock.Call +} + +// User is a helper method to define mock.On call +func (_e *Entry_Expecter) User() *Entry_User_Call { + return &Entry_User_Call{Call: _e.mock.On("User")} +} + +func (_c *Entry_User_Call) Run(run func()) *Entry_User_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *Entry_User_Call) Return(_a0 interface{}) *Entry_User_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Entry_User_Call) RunAndReturn(run func() interface{}) *Entry_User_Call { + _c.Call.Return(run) + return _c +} + +// With provides a mock function with given fields: +func (_m *Entry) With() map[string]interface{} { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for With") + } + + var r0 map[string]interface{} + if rf, ok := ret.Get(0).(func() map[string]interface{}); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(map[string]interface{}) + } + } + + return r0 +} + +// Entry_With_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'With' +type Entry_With_Call struct { + *mock.Call +} + +// With is a helper method to define mock.On call +func (_e *Entry_Expecter) With() *Entry_With_Call { + return &Entry_With_Call{Call: _e.mock.On("With")} +} + +func (_c *Entry_With_Call) Run(run func()) *Entry_With_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *Entry_With_Call) Return(_a0 map[string]interface{}) *Entry_With_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Entry_With_Call) RunAndReturn(run func() map[string]interface{}) *Entry_With_Call { + _c.Call.Return(run) + return _c +} + // NewEntry creates a new instance of Entry. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. // The first argument is typically a *testing.T value. func NewEntry(t interface { From 76cc71f75ee7b6b91031e004b854998b8c15d0c3 Mon Sep 17 00:00:00 2001 From: kkumar-gcc Date: Sat, 14 Sep 2024 01:37:35 +0530 Subject: [PATCH 3/3] add test cases for log --- log/logrus_writer_test.go | 71 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/log/logrus_writer_test.go b/log/logrus_writer_test.go index 96ee5b5f1..1fdc8d97b 100644 --- a/log/logrus_writer_test.go +++ b/log/logrus_writer_test.go @@ -8,12 +8,15 @@ import ( "os" "os/exec" "reflect" + "strings" "testing" + "github.com/spf13/cast" "github.com/stretchr/testify/assert" "github.com/goravel/framework/contracts/filesystem" contractshttp "github.com/goravel/framework/contracts/http" + logcontracts "github.com/goravel/framework/contracts/log" contractsession "github.com/goravel/framework/contracts/session" "github.com/goravel/framework/contracts/validation" "github.com/goravel/framework/foundation/json" @@ -390,6 +393,30 @@ func TestLogrus(t *testing.T) { _ = file.Remove("storage") } +func TestLogrusWithCustomLogger(t *testing.T) { + mockConfig := &configmock.Config{} + mockConfig.On("GetString", "logging.default").Return("customLogger").Once() + mockConfig.On("GetString", "logging.channels.customLogger.driver").Return("custom").Twice() + mockConfig.On("Get", "logging.channels.customLogger.via").Return(&CustomLogger{}).Twice() + mockConfig.On("GetString", "app.timezone").Return("UTC") + mockConfig.On("GetString", "app.env").Return("test") + + filename := "custom.log" + + logger := NewApplication(mockConfig, json.NewJson()) + logger.Channel("customLogger"). + WithTrace(). + With(map[string]any{"filename": filename}). + User(map[string]any{"name": "kkumar-gcc"}). + Owner("team@goravel.dev"). + Code("code").Info("Goravel") + + expectedContent := "info: Goravel\ncustom_code: code\ncustom_user: map[name:kkumar-gcc]\n" + assert.True(t, file.Contain(filename, expectedContent), "Log file content does not match expected output") + + assert.Nil(t, file.Remove(filename)) +} + func TestLogrus_Fatal(t *testing.T) { mockConfig := initMockConfig() mockDriverConfig(mockConfig) @@ -521,6 +548,50 @@ func mockDriverConfig(mockConfig *configmock.Config) { mockConfig.On("GetString", "app.env").Return("test") } +type CustomLogger struct { +} + +func (logger *CustomLogger) Handle(channel string) (logcontracts.Hook, error) { + return &CustomHook{}, nil +} + +type CustomHook struct { +} + +func (h *CustomHook) Levels() []logcontracts.Level { + return []logcontracts.Level{ + logcontracts.InfoLevel, + } +} + +func (h *CustomHook) Fire(entry logcontracts.Entry) error { + with := entry.With() + filename, ok := with["filename"] + if ok { + var builder strings.Builder + message := entry.Message() + if len(message) > 0 { + builder.WriteString(fmt.Sprintf("%s: %v\n", entry.Level(), message)) + } + + code := entry.Code() + if len(code) > 0 { + builder.WriteString(fmt.Sprintf("custom_code: %v\n", code)) + } + + user := entry.User() + if user != nil { + builder.WriteString(fmt.Sprintf("custom_user: %v\n", user)) + } + + err := file.Create(cast.ToString(filename), builder.String()) + if err != nil { + return err + } + } + return nil +} + type TestRequest struct{} func (r *TestRequest) Header(key string, defaultValue ...string) string {