diff --git a/logger/context.go b/logger/context.go new file mode 100644 index 0000000000000000000000000000000000000000..9f709dd5dec4f850d6834e3fb3d8a8a6376155e3 --- /dev/null +++ b/logger/context.go @@ -0,0 +1,92 @@ +package logger + +import ( + "fmt" +) + +func GetContextLogger() Logger { + return logger +} + +// func ClearInfo() { +// InitLogs(nil, nil) +// } + +// func InitLogs(requestID *string, request *events.APIGatewayProxyRequest) { +// logger = Logger{ +// currentRequestID: requestID, +// apiRequest: request, +// level: LevelInfo, +// writer: os.Stderr, +// data: map[string]interface{}{ +// "environment": getEnvironment(), +// }, +// } +// if requestID != nil { +// logger.data["request_id"] = *requestID +// } + +// if val, exists := os.LookupEnv("DEBUGGING"); exists && strings.ToLower(val) == "true" { +// logger.level = LevelDebug +// } +// } + +// func getEnvironment() string { +// environment := os.Getenv("ENVIRONMENT") +// if environment == "" { +// environment = "prod" +// } +// return environment +// } + +func LogMessageWithFields(fields map[string]interface{}, message interface{}) { + logger.WithFields(fields).log(LevelInfo, 1, fmt.Sprintf("%v", message)) +} + +func LogMessage(format string, a ...interface{}) { + logger.log(LevelInfo, 1, fmt.Sprintf(format, a...)) +} + +func LogError(fields map[string]interface{}, err error) { + // sendRaygunError(fields, err) + logger.WithFields(fields).log(LevelError, 1, fmt.Sprintf("%+v", err)) +} + +func LogErrorMessage(message interface{}, err error) { + if err != nil || message != nil { + logger.WithFields(map[string]interface{}{ + "error": fmt.Sprintf("%+v", err), + }).log(LevelError, 1, fmt.Sprintf("%v", message)) + } +} + +func LogWarningMessage(format string, a ...interface{}) { + logger.log(LevelWarn, 1, fmt.Sprintf(format, a...)) +} + +func LogWarning(fields map[string]interface{}, err error) { + logger.WithFields(fields).log(LevelWarn, 1, fmt.Sprintf("%+v", err)) +} + +func SQLDebugInfo(sql string) { + logger.WithFields(map[string]interface{}{ + "sql": sql, + }).log(LevelInfo, 1, "SQL") +} + +// func LogRequestInfo(req events.APIGatewayProxyRequest) { +// fields := map[string]interface{}{ +// "http_method": req.HTTPMethod, +// "path": req.Path, +// "api_gateway_request_id": req.RequestContext.RequestID, +// "user_cognito_auth_provider": req.RequestContext.Identity.CognitoAuthenticationProvider, +// "user_arn": req.RequestContext.Identity.UserArn, +// } +// logger.WithFields(fields).log(LevelInfo, 1, "Request Info start") +// } + +// func LogSQSEvent(event events.SQSEvent) { +// logger.WithFields(map[string]interface{}{ +// "records": event.Records, +// }).log(LevelInfo, 1, "SQS event start") +// } diff --git a/logger/db.go b/logger/db.go new file mode 100644 index 0000000000000000000000000000000000000000..c684855c087543acb668af1fa4e399a1de4b33f4 --- /dev/null +++ b/logger/db.go @@ -0,0 +1,24 @@ +package logger + +import ( + "context" + "os" + + "github.com/go-pg/pg/v10" +) + +type DbLogger struct{} + +func (d DbLogger) BeforeQuery(c context.Context, q *pg.QueryEvent) (context.Context, error) { + return c, nil +} + +func (d DbLogger) AfterQuery(c context.Context, q *pg.QueryEvent) error { + sql, _ := q.FormattedQuery() + if os.Getenv("DEBUGGING") == "true" { + Debugf(string(sql)) + } else { + SQLDebugInfo(string(sql)) + } + return nil +} diff --git a/logger/fields.go b/logger/fields.go new file mode 100644 index 0000000000000000000000000000000000000000..4133d9ac1ea372e296f9e7b47ff8e185e055cc40 --- /dev/null +++ b/logger/fields.go @@ -0,0 +1,3 @@ +package logger + +type Fields map[string]interface{} diff --git a/logger/global.go b/logger/global.go new file mode 100644 index 0000000000000000000000000000000000000000..ec746ab32c4ce6d58a0ce8e8f3a8e4f781c70fcf --- /dev/null +++ b/logger/global.go @@ -0,0 +1,68 @@ +package logger + +import ( + "fmt" + "os" + + "github.com/uafrica/go-utils/errors" +) + +var logger Logger + +func init() { + logger = Logger{ + level: LevelDebug, + writer: os.Stderr, + data: map[string]interface{}{}, + } + // InitLogs(nil, nil) +} + +func New() Logger { + return logger.WithFields(nil) +} + +//shortcut functions to use current logger +//this should only be used outside of a request context +//or anywhere if you have a single threaded process +func Fatalf(format string, args ...interface{}) { + logger.WithFields(map[string]interface{}{"call_stack": errors.Stack(3)}).log(LevelFatal, 1, fmt.Sprintf(format, args...)) + os.Exit(1) +} + +func Fatal(args ...interface{}) { + logger.WithFields(map[string]interface{}{"call_stack": errors.Stack(3)}).log(LevelFatal, 1, fmt.Sprint(args...)) + os.Exit(1) +} + +func Errorf(format string, args ...interface{}) { + logger.WithFields(map[string]interface{}{"call_stack": errors.Stack(3)}).log(LevelError, 1, fmt.Sprintf(format, args...)) +} + +func Error(args ...interface{}) { + logger.WithFields(map[string]interface{}{"call_stack": errors.Stack(3)}).log(LevelError, 1, fmt.Sprint(args...)) +} + +func Warnf(format string, args ...interface{}) { + logger.log(LevelError, 1, fmt.Sprintf(format, args...)) +} + +func Warn(args ...interface{}) { + logger.log(LevelError, 1, fmt.Sprint(args...)) +} + +func Infof(format string, args ...interface{}) { + logger.log(LevelError, 1, fmt.Sprintf(format, args...)) +} + +func Info(args ...interface{}) { + logger.log(LevelError, 1, fmt.Sprint(args...)) +} + +func Debugf(format string, args ...interface{}) { + logger.log(LevelError, 1, fmt.Sprintf(format, args...)) +} + +func Debug(args ...interface{}) { + logger.log(LevelError, 1, fmt.Sprint(args...)) +} diff --git a/logger/level.go b/logger/level.go new file mode 100644 index 0000000000000000000000000000000000000000..3014994e7f9fa39006368bf8ad965ffb44e3ced9 --- /dev/null +++ b/logger/level.go @@ -0,0 +1,33 @@ +package logger + +import "fmt" + +type Level int + +func (level Level) String() string { + switch level { + case LevelFatal: + return "fatal" + case LevelError: + return "error" + case LevelWarn: + return "warn" + case LevelInfo: + return "info" + case LevelDebug: + return "debug" + } + return fmt.Sprintf("Level(%d)", level) +} + +func (level Level) MarshalJSON() ([]byte, error) { + return []byte("\"" + level.String() + "\""), nil +} + +const ( + LevelFatal Level = iota + LevelError + LevelWarn + LevelInfo + LevelDebug +) diff --git a/logger/logger.go b/logger/logger.go new file mode 100644 index 0000000000000000000000000000000000000000..0a6419137ef080b30145af491f314cdc8213c6af --- /dev/null +++ b/logger/logger.go @@ -0,0 +1,210 @@ +package logger + +import ( + "encoding/json" + "fmt" + "io" + "strings" + "time" + + "github.com/uafrica/go-utils/errors" +) + +type ILogger interface { + Fatalf(format string, args ...interface{}) + Fatal(args ...interface{}) + Errorf(format string, args ...interface{}) + Error(args ...interface{}) + Warnf(format string, args ...interface{}) + Warn(args ...interface{}) + Infof(format string, args ...interface{}) + Info(args ...interface{}) + Debugf(format string, args ...interface{}) + Debug(args ...interface{}) +} + +type Logger struct { + //apiRequest *events.APIGatewayProxyRequest + //currentRequestID *string + level Level + writer io.Writer + data map[string]interface{} +} + +func (l Logger) WithFields(data map[string]interface{}) Logger { + newLogger := Logger{ + level: l.level, + writer: l.writer, + data: map[string]interface{}{}, + } + for n, v := range l.data { + newLogger.data[n] = v + } + for n, v := range data { + newLogger.data[n] = v + } + return newLogger +} + +func (l Logger) Fatalf(format string, args ...interface{}) { + l.WithFields(map[string]interface{}{"call_stack": errors.Stack(3)}).log(LevelFatal, 1, fmt.Sprintf(format, args...)) +} + +func (l Logger) Fatal(args ...interface{}) { + l.WithFields(map[string]interface{}{"call_stack": errors.Stack(3)}).log(LevelFatal, 1, fmt.Sprint(args...)) +} + +func (l Logger) Errorf(format string, args ...interface{}) { + l.WithFields(map[string]interface{}{"call_stack": errors.Stack(3)}).log(LevelError, 1, fmt.Sprintf(format, args...)) +} + +func (l Logger) Error(args ...interface{}) { + l.WithFields(map[string]interface{}{"call_stack": errors.Stack(3)}).log(LevelError, 1, fmt.Sprint(args...)) +} + +func (l Logger) Warnf(format string, args ...interface{}) { + l.log(LevelError, 1, fmt.Sprintf(format, args...)) +} + +func (l Logger) Warn(args ...interface{}) { + l.log(LevelError, 1, fmt.Sprint(args...)) +} + +func (l Logger) Infof(format string, args ...interface{}) { + l.log(LevelInfo, 1, fmt.Sprintf(format, args...)) +} + +func (l Logger) Info(args ...interface{}) { + l.log(LevelInfo, 1, fmt.Sprint(args...)) +} + +func (l Logger) Debugf(format string, args ...interface{}) { + l.log(LevelDebug, 1, fmt.Sprintf(format, args...)) +} + +func (l Logger) Debug(args ...interface{}) { + l.log(LevelDebug, 1, fmt.Sprint(args...)) +} + +func (l Logger) log(level Level, skip int, msg string) { + if level <= l.level && l.writer != nil { + entry := Entry{ + Timestamp: time.Now(), + Level: level, + Caller: errors.GetCaller(skip + 2).Info(), + Data: l.data, + Message: strings.ReplaceAll(msg, "\n", ";"), + } + + // jsonEntry, err := json.Marshal(entry) + // if err != nil { + // l.writer.Write([]byte(fmt.Sprintf("failed to marshal entry: %v: %+v\n", err, entry))) + // } + // l.writer.Write(append(jsonEntry, []byte("\n")...)) + + source := fmt.Sprintf("%s(%d)", entry.Caller.File, entry.Caller.Line) + if len(source) > 25 { + source = source[len(source)-25:] + } + textEntry := fmt.Sprintf("%s %5.5s %-25.25s %s", + entry.Timestamp.Format("2006-01-02 15:04:05"), + entry.Level, + source, + entry.Message) + if len(entry.Data) > 0 { + jsonData, _ := json.Marshal(entry.Data) + textEntry += " " + string(jsonData) + } + l.writer.Write(append([]byte(textEntry), []byte("\n")...)) + } +} + +type Entry struct { + Timestamp time.Time `json:"time"` + Level Level `json:"level"` + Caller errors.CallerInfo `json:"caller"` + Data map[string]interface{} `json:"data"` + Message string `json:"message"` +} + +// func sendRaygunError(fields map[string]interface{}, errToSend error) { +// if os.Getenv("DEBUGGING") == "true" { +// // Don't log raygun errors on debug +// return +// } + +// raygun, err := RaygunReporter() +// if err != nil || raygun == nil { +// logger.Errorf("Unable to create Raygun client: " + err.Error()) +// return +// } + +// env := getEnvironment() +// tags := []string{env} +// //todo: raygun.Version(globals.BuildVersion) +// //todo: tags = append(tags, globals.BuildVersion) + +// if logger.apiRequest != nil { +// methodAndPath := logger.apiRequest.HTTPMethod + ": " + logger.apiRequest.Path +// tags = append(tags, methodAndPath) +// fields["body"] = logger.apiRequest.Body +// fields["query"] = logger.apiRequest.QueryStringParameters +// fields["identity"] = logger.apiRequest.RequestContext.Identity +// } + +// raygun.Tags(tags) +// if logger.currentRequestID != nil { +// fields["request_id"] = logger.currentRequestID +// } + +// fields["env"] = env +// raygun.CustomData(fields) +// raygun.Request(fakeHttpRequest()) + +// if errToSend == nil { +// errToSend = errors.New("") +// } +// err = raygun.SendError(errToSend) +// if err != nil { +// logger.Errorf("Failed to send raygun error: " + err.Error()) +// } +// } + +// func fakeHttpRequest() *http.Request { +// if logger.apiRequest == nil { +// return nil +// } + +// requestURL := url.URL{ +// Path: logger.apiRequest.Path, +// Host: logger.apiRequest.Headers["Host"], +// } +// request := http.Request{ +// Method: logger.apiRequest.HTTPMethod, +// URL: &requestURL, +// Header: logger.apiRequest.MultiValueHeaders, +// } +// return &request +// } + +// func RaygunReporter() (*raygun4go.Client, error) { +// key := "AwdpHhwOF1lTT6AppyEHA" + +// // TODO Raygun per environment +// //env := getEnvironment() +// //if env == "dev" { +// // key = "q98iTyE5CcF7raZsIiLA" +// //} else if env == "stage" { +// // key = "TA54mzcv9cBWBLmfwIQMDg" +// //} else if env == "prod" { +// // key = "BXdraqiPKBXImqP4siK1w" +// //} + +// raygun, err := raygun4go.New("uAfrica V3", key) +// if err != nil || raygun == nil { +// logger.Errorf("Unable to create Raygun client:" + err.Error()) +// return nil, err +// } + +// return raygun, nil +// } diff --git a/logger/logs_test.go b/logger/logs_test.go new file mode 100644 index 0000000000000000000000000000000000000000..f27ff0af7d74078320cfc6a5d13845e19aa5dc74 --- /dev/null +++ b/logger/logs_test.go @@ -0,0 +1,36 @@ +package logger_test + +import ( + "os" + "testing" + + "github.com/uafrica/go-utils/errors" + "github.com/uafrica/go-utils/logger" +) + +func TestLogs(t *testing.T) { + //requestID := t.Name() + //event := events.APIGatewayProxyRequest{} + os.Setenv("DEBUGGING", "true") + //logger.InitLogs(&requestID, &event) + + // formatter := log.TextFormatter{} + // log.SetFormatter(&formatter) + + logger.LogMessageWithFields(map[string]interface{}{"a": 1, "b": 2}, "MyLogMessage1") + logger.LogMessage("MyLogMessage2=%d,%d,%d", 1, 2, 3) + logger.LogError(map[string]interface{}{"a": 4, "b": 5}, errors.Errorf("simple mistake")) + logger.LogErrorMessage("Error Message", errors.Errorf("another simple mistake")) + logger.LogWarningMessage("Warning about a=%s,%s,%s", "a", "b", "c") + logger.LogWarning(map[string]interface{}{"a": 4, "b": 5}, errors.Errorf("Cant believe it failed")) + logger.SQLDebugInfo("SELECT * from user") + //logger.LogRequestInfo(event) + //logs.LogSQSEvent(sqsEvent) + + ctx := logger.GetContextLogger() + ctx.Debugf("Debugging %d!", 456) + ctx.Infof("Info %d", 789) + + //logs.Errorf("Debugging %d!", 456) + //logs.Error("Info") +}