Skip to content

Add log query endpoint #103

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions cmds/admin_server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
"syscall"

"github.com/linuxboot/contest/cmds/admin_server/server"
mongoAdapter "github.com/linuxboot/contest/cmds/admin_server/storage/mongo"
mongoStorage "github.com/linuxboot/contest/cmds/admin_server/storage/mongo"
"github.com/linuxboot/contest/pkg/logging"
"github.com/linuxboot/contest/pkg/xcontext/bundles/logrusctx"
"github.com/linuxboot/contest/pkg/xcontext/logger"
Expand Down Expand Up @@ -51,7 +51,7 @@ func main() {
exitWithError(err, 1)
}

storage, err := mongoAdapter.NewMongoStorage(*flagDBURI)
storage, err := mongoStorage.NewMongoStorage(*flagDBURI)
if err != nil {
exitWithError(err, 1)
}
Expand Down
78 changes: 75 additions & 3 deletions cmds/admin_server/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,66 @@ import (
"fmt"
"net/http"
"os"
"time"

"github.com/gin-gonic/gin"
"github.com/linuxboot/contest/cmds/admin_server/storage"
"github.com/linuxboot/contest/pkg/xcontext"
)

var (
MaxPageSize uint = 100
DefaultPage uint = 0
)

type Query struct {
Text *string `form:"text"`
LogLevel *string `form:"logLevel"`
StartDate *time.Time `form:"startDate" time_format:"2006-01-02T15:04:05.000Z07:00"`
EndDate *time.Time `form:"endDate" time_format:"2006-01-02T15:04:05.000Z07:00"`
PageSize *uint `form:"pageSize"`
Page *uint `form:"page"`
}

// toStorageQurey returns a storage Query and populates the required fields
func (q *Query) ToStorageQuery() storage.Query {
storageQuery := storage.Query{
Page: DefaultPage,
PageSize: MaxPageSize,
}

storageQuery.Text = q.Text
storageQuery.LogLevel = q.LogLevel
storageQuery.StartDate = q.StartDate
storageQuery.EndDate = q.EndDate

if q.Page != nil {
storageQuery.Page = *q.Page
}

if q.PageSize != nil && *q.PageSize < MaxPageSize {
storageQuery.PageSize = *q.PageSize
}

return storageQuery
}

type Log struct {
JobID uint64 `json:"jobID"`
LogData string `json:"logData"`
Date time.Time `json:"date"`
LogLevel string `json:"logLevel"`
}

func (l *Log) ToStorageLog() storage.Log {
return storage.Log{
JobID: l.JobID,
LogData: l.LogData,
Date: l.Date,
LogLevel: l.LogLevel,
}
}

type RouteHandler struct {
storage storage.Storage
}
Expand All @@ -22,14 +76,14 @@ func (r *RouteHandler) status(c *gin.Context) {

// addLog inserts a new log entry inside the database
func (r *RouteHandler) addLog(c *gin.Context) {
var log storage.Log
var log Log
if err := c.Bind(&log); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"status": "err", "msg": "bad formatted log"})
c.JSON(http.StatusBadRequest, gin.H{"status": "err", "msg": "badly formatted log"})
fmt.Fprintf(os.Stderr, "Err while binding request body %v", err)
return
}

err := r.storage.StoreLog(log)
err := r.storage.StoreLog(log.ToStorageLog())
if err != nil {
switch {
case errors.Is(err, storage.ErrInsert):
Expand All @@ -45,13 +99,31 @@ func (r *RouteHandler) addLog(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"status": "ok"})
}

// geLogs gets logs form the db based on the filters
func (r *RouteHandler) getLogs(c *gin.Context) {
var query Query
if err := c.BindQuery(&query); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"status": "err", "msg": fmt.Sprintf("bad formatted query %v", err)})
return
}

result, err := r.storage.GetLogs(query.ToStorageQuery())
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"status": "err", "msg": "error while getting the logs"})
return
}

c.JSON(http.StatusOK, result)
}

func initRouter(ctx xcontext.Context, rh RouteHandler) *gin.Engine {

r := gin.New()
r.Use(gin.Logger())

r.GET("/status", rh.status)
r.POST("/log", rh.addLog)
r.GET("/log", rh.getLogs)

return r
}
Expand Down
102 changes: 91 additions & 11 deletions cmds/admin_server/storage/mongo/mongo.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@ import (
"context"
"fmt"
"os"
"strings"
"time"

"github.com/linuxboot/contest/cmds/admin_server/storage"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
Expand Down Expand Up @@ -53,15 +56,59 @@ func NewMongoStorage(uri string) (storage.Storage, error) {
}, nil
}

func (s *MongoStorage) StoreLog(log storage.Log) error {
logEntry := Log{
log,
func toMongoQuery(query storage.Query) bson.D {
q := bson.D{}

if query.Text != nil {
q = append(q, bson.E{
Key: "logdata",
Value: bson.M{
"$regex": primitive.Regex{Pattern: *query.Text, Options: "ig"},
},
})
}

if query.StartDate != nil && query.EndDate != nil {
q = append(q, bson.E{
Key: "date",
Value: bson.M{
"$gte": primitive.NewDateTimeFromTime(*query.StartDate),
"$lte": primitive.NewDateTimeFromTime(*query.EndDate),
},
})
} else if query.StartDate != nil {
q = append(q, bson.E{
Key: "date",
Value: bson.M{"$gte": primitive.NewDateTimeFromTime(*query.StartDate)},
})
} else if query.EndDate != nil {
q = append(q, bson.E{
Key: "date",
Value: bson.M{"$lte": primitive.NewDateTimeFromTime(*query.EndDate)},
})
}

if query.LogLevel != nil && *query.LogLevel != "" {
levels := strings.Split(*query.LogLevel, ",")
q = append(q,
bson.E{
Key: "loglevel",
Value: bson.M{
"$in": levels,
},
},
)
}

return q
}

func (s *MongoStorage) StoreLog(log storage.Log) error {

ctx, cancel := context.WithTimeout(context.Background(), DefaultConnectionTimeout)
defer cancel()

_, err := s.collection.InsertOne(ctx, logEntry)
_, err := s.collection.InsertOne(ctx, log)
if err != nil {
// for better debugging
fmt.Fprintf(os.Stderr, "Error while inserting into the db: %v", err)
Expand All @@ -70,15 +117,48 @@ func (s *MongoStorage) StoreLog(log storage.Log) error {
return nil
}

func (s *MongoStorage) GetLogs(query storage.Query) ([]storage.Log, error) {
// TODO
return nil, nil
func (s *MongoStorage) GetLogs(query storage.Query) (*storage.Result, error) {

q := toMongoQuery(query)

//get the count of the logs
ctx, cancel := context.WithTimeout(context.Background(), DefaultConnectionTimeout)
defer cancel()
count, err := s.collection.CountDocuments(ctx, q)
if err != nil {
fmt.Fprintf(os.Stderr, "Error while performing count query: %v", err)
return nil, storage.ErrQuery
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be nice to add original err

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's not the concern of the frontend to know the details of the err. And I am logging it already in the server for debugging.

}

opts := options.Find()
opts.SetSkip(int64(int(query.PageSize) * int(query.Page)))
opts.SetLimit(int64(query.PageSize))

ctx, cancel = context.WithTimeout(context.Background(), DefaultConnectionTimeout)
defer cancel()
cur, err := s.collection.Find(ctx, q, opts)
if err != nil {
fmt.Fprintf(os.Stderr, "Error while querying logs from db: %v", err)
return nil, storage.ErrQuery
}

var logs []storage.Log
ctx, cancel = context.WithTimeout(context.Background(), DefaultConnectionTimeout)
defer cancel()
err = cur.All(ctx, &logs)
if err != nil {
fmt.Fprintf(os.Stderr, "Error while reading query result from db: %v", err)
return nil, storage.ErrQuery
}

return &storage.Result{
Logs: logs,
Count: uint64(count),
Page: query.Page,
PageSize: query.PageSize,
}, nil
}

func (s *MongoStorage) Close() error {
return s.dbClient.Disconnect(context.Background())
}

type Log struct {
storage.Log `json:"log"`
}
27 changes: 21 additions & 6 deletions cmds/admin_server/storage/storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,17 @@ import (
var (
ErrReadOnlyStorage = errors.New("error read only storage")
ErrInsert = errors.New("error inserting into the db")
ErrConstructQuery = errors.New("error forming db query from api query")
ErrQuery = errors.New("error querying from the database")
)

var (
DefaultTimestampFormat = "2006-01-02T15:04:05.000Z07:00"
)

type Storage interface {
StoreLog(Log) error
GetLogs(Query) ([]Log, error)
GetLogs(Query) (*Result, error)

Close() error
}
Expand All @@ -27,9 +33,18 @@ type Log struct {

// Query defines the different options to filter with
type Query struct {
Text string
LogLevel string
StartDate string
EndDate string
Page int
Text *string
LogLevel *string
StartDate *time.Time
EndDate *time.Time
PageSize uint
Page uint
}

//Result defines the expected result returned from the db
type Result struct {
Logs []Log `json:"logs"`
Count uint64 `json:"count"`
Page uint `json:"page"`
PageSize uint `json:"pageSize"`
}
15 changes: 15 additions & 0 deletions tests/integ/admin_server/flags_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//go:build integration
// +build integration

package test

import (
"flag"
"time"
)

var (
flagAdminEndpoint = flag.String("adminServer", "http://adminserver:8000/log", "admin server log push endpoint")
flagMongoEndpoint = flag.String("mongoDBURI", "mongodb://mongostorage:27017", "mongodb URI")
flagOperationTimeout = flag.Duration("operationTimeout", time.Duration(10*time.Second), "operation timeout duration")
)
Loading