diff --git a/app/app.go b/app/app.go index a19e1f6..1f692ae 100644 --- a/app/app.go +++ b/app/app.go @@ -9,6 +9,7 @@ import ( "github.com/gorilla/mux" "github.com/rs/zerolog" + "github.com/rs/zerolog/log" gremcos "github.com/supplyon/gremcos" "github.com/imeplusplus/dont-panic-api/app/handler" @@ -31,7 +32,7 @@ func (app *App) Initialize() { fmt.Println(username) fmt.Println(password) - app.Logger = zerolog.New(os.Stdout).Output(zerolog.ConsoleWriter{Out: os.Stdout, TimeFormat: zerolog.TimeFieldFormat}).With().Timestamp().Logger() + app.Logger = log.Logger app.Cosmos, err = gremcos.New(host, gremcos.WithAuth(username, password), gremcos.WithLogger(app.Logger), @@ -41,7 +42,7 @@ func (app *App) Initialize() { ) if err != nil { - fmt.Println("Could not connect database") + log.Error().Err(err).Msg("Could not connect to server") } app.Router = mux.NewRouter() @@ -87,5 +88,5 @@ func (app *App) handleRequest(handler RequestHandlerFunction) http.HandlerFunc { // Run the app on it's router func (app *App) Run(host string) { - fmt.Println(http.ListenAndServe(host, app.Router)) + log.Info().Msg(http.ListenAndServe(host, app.Router).Error()) } diff --git a/app/dbOperations/subjects.go b/app/dbOperations/subjects.go index 0b84b1e..b07a24e 100644 --- a/app/dbOperations/subjects.go +++ b/app/dbOperations/subjects.go @@ -1,13 +1,11 @@ package dbOperations import ( - "errors" - "fmt" - gremcos "github.com/supplyon/gremcos" "github.com/supplyon/gremcos/api" "github.com/supplyon/gremcos/interfaces" + "github.com/imeplusplus/dont-panic-api/app/logger" storageModel "github.com/imeplusplus/dont-panic-api/app/model/storage" ) @@ -18,8 +16,6 @@ func GetSubjects(cosmos gremcos.Cosmos) ([]storageModel.Subject, error) { res, err := cosmos.ExecuteQuery(query) if err != nil { - fmt.Println("Failed to execute a gremlin command " + query.String()) - //logger.Error().Err(err).Msg("Failed to execute a gremlin command") return nil, err } @@ -41,8 +37,6 @@ func GetSubjectByName(cosmos gremcos.Cosmos, name string) (storageModel.Subject, res, err := cosmos.ExecuteQuery(query) if err != nil { - fmt.Println("Failed to execute a gremlin command " + query.String()) - //logger.Error().Err(err).Msg("Failed to execute a gremlin command") return subject, err } @@ -53,19 +47,17 @@ func CreateSubject(cosmos gremcos.Cosmos, subject storageModel.Subject) (storage _, err := GetSubjectByName(cosmos, subject.Name) if err == nil { - return storageModel.Subject{}, errors.New("There is already a subject with name " + subject.Name) + err := logger.ErrorResourceAlreadyExists{ResourceName: subject.Name} + return storageModel.Subject{}, err } g := api.NewGraph("g") query := g.AddV("subject").Property("partitionKey", "subject") - query = addVertexProperties(query, subject) res, err := cosmos.ExecuteQuery(query) if err != nil { - fmt.Println("Failed to execute a gremlin command " + query.String()) - // logger.Error().Err(err).Msg("Failed to execute gremlin command") return storageModel.Subject{}, err } @@ -74,9 +66,8 @@ func CreateSubject(cosmos gremcos.Cosmos, subject storageModel.Subject) (storage func UpdateSubject(cosmos gremcos.Cosmos, subject storageModel.Subject, name string) (storageModel.Subject, error) { oldSubject, err := GetSubjectByName(cosmos, name) - if err != nil { - return storageModel.Subject{}, errors.New("There is no subject with name " + oldSubject.Name) + return storageModel.Subject{}, err } g := api.NewGraph("g") @@ -84,8 +75,6 @@ func UpdateSubject(cosmos gremcos.Cosmos, subject storageModel.Subject, name str res, err := cosmos.ExecuteQuery(query) if err != nil { - fmt.Println("Failed to execute a gremlin command " + query.String()) - //logger.Error().Err(err).Msg("Failed to execute a gremlin command") return storageModel.Subject{}, err } @@ -124,7 +113,8 @@ func getSubjectFromResponse(res []interfaces.Response) (storageModel.Subject, er vertices, _ := response.ToVertices() if len(vertices) == 0 { - return subject, errors.New("there is no vertex in the response") + err := logger.ErrorNoVerticesInQuery{} + return subject, err } subject = vertexToSubject(vertices[0]) diff --git a/app/handler/subjects.go b/app/handler/subjects.go index b6fa512..01d7706 100644 --- a/app/handler/subjects.go +++ b/app/handler/subjects.go @@ -2,70 +2,90 @@ package handler import ( "encoding/json" - "fmt" "net/http" "github.com/gorilla/mux" + "github.com/rs/zerolog/log" gremcos "github.com/supplyon/gremcos" "github.com/imeplusplus/dont-panic-api/app/dbOperations" + "github.com/imeplusplus/dont-panic-api/app/logger" apiModel "github.com/imeplusplus/dont-panic-api/app/model/api" storageModel "github.com/imeplusplus/dont-panic-api/app/model/storage" ) func GetSubjects(cosmos gremcos.Cosmos, w http.ResponseWriter, _ *http.Request) { storageSubjects, err := dbOperations.GetSubjects(cosmos) - if err != nil { - fmt.Println(err) + msg := logger.FailedToExecuteGremlinQuery{} + log.Error().Stack().Err(err).Msg(msg.Info()) w.WriteHeader(http.StatusInternalServerError) return } w.Header().Add("Content-Type", "application/json") err = json.NewEncoder(w).Encode(storageSubjects) - if err != nil { - fmt.Println(err) + msg := logger.FailedToEncodeJSON{ + Resource: "storageModel.Subject", + } + log.Error().Stack().Err(err).Msg(msg.Info()) w.WriteHeader(http.StatusInternalServerError) } + + msg := logger.ResourceRead{ + ResourceName: "subjects", + ResourceContent: storageModel.PrettyPrint(storageSubjects), + } + log.Info().Msg(msg.Info()) } func GetSubject(cosmos gremcos.Cosmos, w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) storageSubject, err := dbOperations.GetSubjectByName(cosmos, vars["name"]) - if err != nil { - fmt.Println(err) + msg := logger.FailedToExecuteGremlinQuery{} + log.Error().Stack().Err(err).Msg(msg.Info()) w.WriteHeader(http.StatusInternalServerError) return } w.Header().Add("Content-Type", "application/json") err = json.NewEncoder(w).Encode(storageSubject) - if err != nil { - fmt.Println(err) + msg := logger.FailedToEncodeJSON{ + Resource: "storageModel.Subject", + } + log.Error().Stack().Err(err).Msg(msg.Info()) w.WriteHeader(http.StatusInternalServerError) } + + msg := logger.ResourceRead{ + ResourceName: "subjects/" + storageSubject.Name, + ResourceContent: storageModel.PrettyPrint(storageSubject), + } + log.Info().Msg(msg.Info()) } func UpdateSubject(cosmos gremcos.Cosmos, w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) + name := vars["name"] apiSubject := apiModel.Subject{} - err := json.NewDecoder(r.Body).Decode(&apiSubject) - - if err != nil { + if err := json.NewDecoder(r.Body).Decode(&apiSubject); err != nil { + msg := logger.FailedToDecodeJSON{ + Resource: "apiModel.Subject", + } + log.Error().Stack().Err(err).Msg(msg.Info()) w.WriteHeader(http.StatusBadRequest) return } - storageSubject, err := dbOperations.UpdateSubject(cosmos, storageModel.Subject(apiSubject), vars["name"]) - + storageSubject, err := dbOperations.UpdateSubject(cosmos, storageModel.Subject(apiSubject), name) if err != nil { - fmt.Println(err) + msg := logger.FailedToExecuteGremlinQuery{} + log.Error().Stack().Err(err).Msg(msg.Info()) w.WriteHeader(http.StatusInternalServerError) return } @@ -75,48 +95,75 @@ func UpdateSubject(cosmos gremcos.Cosmos, w http.ResponseWriter, r *http.Request err = json.NewEncoder(w).Encode(storageSubject) if err != nil { - fmt.Println(err) + msg := logger.FailedToEncodeJSON{ + Resource: "apiModel.Subject", + } + log.Error().Stack().Err(err).Msg(msg.Info()) w.WriteHeader(http.StatusInternalServerError) } + + msg := logger.ResourceUpdated{ + ResourceName: "subjects/" + storageSubject.Name, + ResourceContent: storageModel.PrettyPrint(storageSubject), + } + log.Info().Msg(msg.Info()) } func DeleteSubject(cosmos gremcos.Cosmos, w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) + name := vars["name"] - err := dbOperations.DeleteSubject(cosmos, vars["name"]) - + err := dbOperations.DeleteSubject(cosmos, name) if err != nil { - fmt.Println(err) + msg := logger.FailedToExecuteGremlinQuery{} + log.Error().Stack().Err(err).Msg(msg.Info()) w.WriteHeader(http.StatusInternalServerError) return } w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusAccepted) + + msg := logger.ResourceDeleted{ + ResourceName: "subjects/" + name, + } + log.Info().Msg(msg.Info()) } func CreateSubject(cosmos gremcos.Cosmos, w http.ResponseWriter, r *http.Request) { apiSubject := apiModel.Subject{} - var err error - if err = json.NewDecoder(r.Body).Decode(&apiSubject); err != nil { + if err := json.NewDecoder(r.Body).Decode(&apiSubject); err != nil { + msg := logger.FailedToDecodeJSON{ + Resource: "apiModel.Subject", + } + log.Error().Stack().Err(err).Msg(msg.Info()) w.WriteHeader(http.StatusBadRequest) return } storageSubject, err := dbOperations.CreateSubject(cosmos, storageModel.Subject(apiSubject)) - if err != nil { - fmt.Println(err) + msg := logger.FailedToExecuteGremlinQuery{} + log.Error().Stack().Err(err).Msg(msg.Info()) w.WriteHeader(http.StatusInternalServerError) return } w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) - err = json.NewEncoder(w).Encode(storageSubject) + err = json.NewEncoder(w).Encode(storageSubject) if err != nil { - fmt.Println(err) + msg := logger.FailedToEncodeJSON{ + Resource: "storageModel.Subject", + } + log.Error().Stack().Err(err).Msg(msg.Info()) w.WriteHeader(http.StatusInternalServerError) } + + msg := logger.ResourceCreated{ + ResourceName: "subjects/" + storageSubject.Name, + ResourceContent: storageModel.PrettyPrint(storageSubject), + } + log.Info().Msg(msg.Info()) } diff --git a/app/logger/errors.go b/app/logger/errors.go new file mode 100644 index 0000000..f1ee5c7 --- /dev/null +++ b/app/logger/errors.go @@ -0,0 +1,27 @@ +package logger + +import ( + "fmt" +) + +var ( + errorResourceAlreadyExistsMessage = LogEvent{1, "resource %s already exists"} + errorResourceNotFoundMessage = LogEvent{2, "resource %s not found"} + errorNoVerticesInQueryMessage = LogEvent{3, "the Gremlin Query did not return any vertices"} +) + +type ErrorResourceAlreadyExists struct{ ResourceName string } +type ErrorResourceNotFound struct{ ResourceName string } +type ErrorNoVerticesInQuery struct{} + +func (e ErrorResourceAlreadyExists) Error() string { + return fmt.Sprintf(errorResourceAlreadyExistsMessage.message, e.ResourceName) +} + +func (e ErrorResourceNotFound) Error() string { + return fmt.Sprintf(errorResourceNotFoundMessage.message, e.ResourceName) +} + +func (e ErrorNoVerticesInQuery) Error() string { + return fmt.Sprintf(errorNoVerticesInQueryMessage.message) +} diff --git a/app/logger/logger.go b/app/logger/logger.go new file mode 100644 index 0000000..1d48fd9 --- /dev/null +++ b/app/logger/logger.go @@ -0,0 +1,19 @@ +package logger + +import ( + "os" + + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" + "github.com/rs/zerolog/pkgerrors" +) + +type LogEvent struct { + id int + message string +} + +func init() { + log.Logger = zerolog.New(os.Stdout).Output(zerolog.ConsoleWriter{Out: os.Stdout, TimeFormat: zerolog.TimeFieldFormat}).With().Timestamp().Logger() + zerolog.ErrorStackMarshaler = pkgerrors.MarshalStack +} diff --git a/app/logger/messages.go b/app/logger/messages.go new file mode 100644 index 0000000..07bfcd9 --- /dev/null +++ b/app/logger/messages.go @@ -0,0 +1,60 @@ +package logger + +import ( + "fmt" +) + +var ( + resourceCreatedMessage = LogEvent{1001, "Resource %s created.\nContent:\n%s"} + resourceUpdatedMessage = LogEvent{1002, "Resource %s updated.\nNew Content:\n%s"} + resourceReadMessage = LogEvent{1003, "Resource %s read.\nContent:\n%s"} + resourceDeletedMessage = LogEvent{1004, "Resource %s deleted.\n"} + failedToDecodeJSONMessage = LogEvent{1005, "Failed to parse JSON into %s.\n"} + failedToEncodeJSONMessage = LogEvent{1006, "Failed to encode %s into JSON format.\n"} + failedToExecuteGremlinQueryMessage = LogEvent{1007, "Failed to execute Gremlin query.\n"} +) + +type ResourceCreated struct { + ResourceName string + ResourceContent string +} +type ResourceUpdated struct { + ResourceName string + ResourceContent string +} +type ResourceRead struct { + ResourceName string + ResourceContent string +} +type ResourceDeleted struct{ ResourceName string } +type FailedToDecodeJSON struct{ Resource string } +type FailedToEncodeJSON struct{ Resource string } +type FailedToExecuteGremlinQuery struct{} + +func (e ResourceCreated) Info() string { + return fmt.Sprintf(resourceCreatedMessage.message, e.ResourceName, e.ResourceContent) +} + +func (e ResourceUpdated) Info() string { + return fmt.Sprintf(resourceUpdatedMessage.message, e.ResourceName, e.ResourceContent) +} + +func (e ResourceRead) Info() string { + return fmt.Sprintf(resourceReadMessage.message, e.ResourceName, e.ResourceContent) +} + +func (e ResourceDeleted) Info() string { + return fmt.Sprintf(resourceDeletedMessage.message, e.ResourceName) +} + +func (e FailedToDecodeJSON) Info() string { + return fmt.Sprintf(failedToDecodeJSONMessage.message, e.Resource) +} + +func (e FailedToEncodeJSON) Info() string { + return fmt.Sprintf(failedToEncodeJSONMessage.message, e.Resource) +} + +func (e FailedToExecuteGremlinQuery) Info() string { + return fmt.Sprintf(failedToExecuteGremlinQueryMessage.message) +} diff --git a/app/model/api/subject.go b/app/model/api/subject.go index a635432..6d5c21e 100644 --- a/app/model/api/subject.go +++ b/app/model/api/subject.go @@ -1,5 +1,12 @@ package model +import ( + "encoding/json" + "fmt" + + "github.com/rs/zerolog/log" +) + type Subject struct { Id string `json:"id"` Name string `json:"name"` @@ -8,3 +15,12 @@ type Subject struct { Category string `json:"category"` PartitionKey string `json:"partitionKey"` } + +func PrettyPrint(subjects ...interface{}) string { + subjectJSON, err := json.MarshalIndent(subjects, "", " ") + if err != nil { + log.Error().Stack().Err(err).Msg("Couldn't make resource pretty to print") + return fmt.Sprintf("%v", subjects) + } + return string(subjectJSON) +} diff --git a/app/model/storage/subject.go b/app/model/storage/subject.go index a635432..6d5c21e 100644 --- a/app/model/storage/subject.go +++ b/app/model/storage/subject.go @@ -1,5 +1,12 @@ package model +import ( + "encoding/json" + "fmt" + + "github.com/rs/zerolog/log" +) + type Subject struct { Id string `json:"id"` Name string `json:"name"` @@ -8,3 +15,12 @@ type Subject struct { Category string `json:"category"` PartitionKey string `json:"partitionKey"` } + +func PrettyPrint(subjects ...interface{}) string { + subjectJSON, err := json.MarshalIndent(subjects, "", " ") + if err != nil { + log.Error().Stack().Err(err).Msg("Couldn't make resource pretty to print") + return fmt.Sprintf("%v", subjects) + } + return string(subjectJSON) +} diff --git a/go.mod b/go.mod index 5b8eb84..c0eaea9 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.16 require ( github.com/gorilla/mux v1.8.0 + github.com/pkg/errors v0.9.1 // indirect github.com/rs/zerolog v1.22.0 github.com/supplyon/gremcos v0.1.7 )