From 313ad41a57d8949e51aafbb2d902edfbe3515b2b Mon Sep 17 00:00:00 2001 From: Robert Butts Date: Wed, 30 May 2018 17:13:41 -0600 Subject: [PATCH 1/4] Add TO Go Logs --- lib/go-tc/log.go | 29 ++++++++ traffic_ops/traffic_ops_golang/logs/log.go | 77 ++++++++++++++++++++++ 2 files changed, 106 insertions(+) create mode 100644 lib/go-tc/log.go create mode 100644 traffic_ops/traffic_ops_golang/logs/log.go diff --git a/lib/go-tc/log.go b/lib/go-tc/log.go new file mode 100644 index 0000000000..07db516588 --- /dev/null +++ b/lib/go-tc/log.go @@ -0,0 +1,29 @@ +package tc + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +type Log struct { + ID *int `json:"id"` + LastUpdated *Time `json:"lastUpdated"` + Level *string `json:"level"` + Message *string `json:"message"` + TicketNum *int `json:"ticketNum"` + User *string `json:"user"` +} diff --git a/traffic_ops/traffic_ops_golang/logs/log.go b/traffic_ops/traffic_ops_golang/logs/log.go new file mode 100644 index 0000000000..d6482919da --- /dev/null +++ b/traffic_ops/traffic_ops_golang/logs/log.go @@ -0,0 +1,77 @@ +package logs + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import ( + "database/sql" + "errors" + "net/http" + + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api" +) + +const DefaultLogLimit = 1000 +const DefaultLogLimitForDays = 1000000 +const DefaultLogDays = 30 + +func Get(db *sql.DB) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + inf, userErr, sysErr, errCode := api.NewInfo(r, nil, []string{"days", "limit"}) + if userErr != nil || sysErr != nil { + api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr) + return + } + defer inf.Close() + + limit := DefaultLogLimit + days := DefaultLogDays + if pDays, ok := inf.IntParams["days"]; ok { + days = pDays + limit = DefaultLogLimitForDays + } + if pLimit, ok := inf.IntParams["limit"]; ok { + limit = pLimit + } + api.RespWriter(w, r, inf.Tx.Tx)(getLog(inf.Tx.Tx, days, limit)) + } +} + +func getLog(tx *sql.Tx, days int, limit int) ([]tc.Log, error) { + rows, err := tx.Query(` +SELECT l.id, l.level, l.message, u.username as user, l.ticketnum, l.last_updated +FROM "log" as l JOIN tm_user as u ON l.tm_user = u.id +WHERE l.last_updated > now() - ($1 || ' DAY')::INTERVAL +ORDER BY l.last_updated DESC +LIMIT $2 +`, days, limit) + if err != nil { + return nil, errors.New("querying logs: " + err.Error()) + } + ls := []tc.Log{} + for rows.Next() { + l := tc.Log{} + if err = rows.Scan(&l.ID, &l.Level, &l.Message, &l.User, &l.TicketNum, &l.LastUpdated); err != nil { + return nil, errors.New("scanning logs: " + err.Error()) + } + ls = append(ls, l) + } + return ls, nil +} From 40aa34539cc6c8e6ba6ee14b89817a058b76eb25 Mon Sep 17 00:00:00 2001 From: Robert Butts Date: Thu, 31 May 2018 09:00:01 -0600 Subject: [PATCH 2/4] Add TO Go logs/newcount --- lib/go-tc/log.go | 4 + traffic_ops/traffic_ops_golang/logs/log.go | 91 +++++++++++++++---- .../traffic_ops_golang/routing/routes.go | 5 + 3 files changed, 82 insertions(+), 18 deletions(-) diff --git a/lib/go-tc/log.go b/lib/go-tc/log.go index 07db516588..6cf891e181 100644 --- a/lib/go-tc/log.go +++ b/lib/go-tc/log.go @@ -27,3 +27,7 @@ type Log struct { TicketNum *int `json:"ticketNum"` User *string `json:"user"` } + +type NewLogCountResp struct { + NewLogCount uint64 `json:"newLogcount"` +} diff --git a/traffic_ops/traffic_ops_golang/logs/log.go b/traffic_ops/traffic_ops_golang/logs/log.go index d6482919da..cac5af11f3 100644 --- a/traffic_ops/traffic_ops_golang/logs/log.go +++ b/traffic_ops/traffic_ops_golang/logs/log.go @@ -23,6 +23,7 @@ import ( "database/sql" "errors" "net/http" + "time" "github.com/apache/trafficcontrol/lib/go-tc" "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api" @@ -32,26 +33,72 @@ const DefaultLogLimit = 1000 const DefaultLogLimitForDays = 1000000 const DefaultLogDays = 30 -func Get(db *sql.DB) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - inf, userErr, sysErr, errCode := api.NewInfo(r, nil, []string{"days", "limit"}) - if userErr != nil || sysErr != nil { - api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr) - return - } - defer inf.Close() +func Get(w http.ResponseWriter, r *http.Request) { + inf, userErr, sysErr, errCode := api.NewInfo(r, nil, []string{"days", "limit"}) + if userErr != nil || sysErr != nil { + api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr) + return + } + defer inf.Close() - limit := DefaultLogLimit - days := DefaultLogDays - if pDays, ok := inf.IntParams["days"]; ok { - days = pDays - limit = DefaultLogLimitForDays - } - if pLimit, ok := inf.IntParams["limit"]; ok { - limit = pLimit - } - api.RespWriter(w, r, inf.Tx.Tx)(getLog(inf.Tx.Tx, days, limit)) + limit := DefaultLogLimit + days := DefaultLogDays + if pDays, ok := inf.IntParams["days"]; ok { + days = pDays + limit = DefaultLogLimitForDays + } + if pLimit, ok := inf.IntParams["limit"]; ok { + limit = pLimit + } + + setLastSeenCookie(w) + api.RespWriter(w, r, inf.Tx.Tx)(getLog(inf.Tx.Tx, days, limit)) +} + +func GetNewCount(w http.ResponseWriter, r *http.Request) { + inf, userErr, sysErr, errCode := api.NewInfo(r, nil, []string{"days", "limit"}) + if userErr != nil || sysErr != nil { + api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr) + return + } + defer inf.Close() + + lastSeen, ok := getLastSeenCookie(r) + if !ok { + setLastSeenCookie(w) // only set the cookie if it didn't exist; emulates old Perl behavior + api.WriteResp(w, r, tc.NewLogCountResp{NewLogCount: 0}) + return + } + newCount, err := getLogCountSince(inf.Tx.Tx, lastSeen) + if err != nil { + api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, errors.New("getting log new count: "+err.Error())) + return } + api.WriteResp(w, r, tc.NewLogCountResp{NewLogCount: newCount}) +} + +const LastSeenLogCookieName = "last_seen_log" + +func setLastSeenCookie(w http.ResponseWriter) { + http.SetCookie(w, &http.Cookie{ + Name: LastSeenLogCookieName, + Value: time.Now().Format(time.RFC3339Nano), + Expires: time.Now().Add(time.Hour * 24 * 7), + Path: "/", + }) +} + +// getLastSeenCookie returns the time of the last log seen cookie, or whether the cookie didn't exist or there was an error parsing it. Errors are not logged or returned, only that the cookie could not be retrieved. +func getLastSeenCookie(r *http.Request) (time.Time, bool) { + cookie, err := r.Cookie(LastSeenLogCookieName) + if err != nil { + return time.Time{}, false + } + lastSeen, err := time.Parse(time.RFC3339Nano, cookie.Value) + if err != nil { + return time.Time{}, false + } + return lastSeen, true } func getLog(tx *sql.Tx, days int, limit int) ([]tc.Log, error) { @@ -75,3 +122,11 @@ LIMIT $2 } return ls, nil } + +func getLogCountSince(tx *sql.Tx, since time.Time) (uint64, error) { + count := uint64(0) + if err := tx.QueryRow(`SELECT count(*) from log where last_updated > $1`, since).Scan(&count); err != nil { + return 0, errors.New("querying log last seen count: " + err.Error()) + } + return count, nil +} diff --git a/traffic_ops/traffic_ops_golang/routing/routes.go b/traffic_ops/traffic_ops_golang/routing/routes.go index b7e4cc8b23..f5239d6370 100644 --- a/traffic_ops/traffic_ops_golang/routing/routes.go +++ b/traffic_ops/traffic_ops_golang/routing/routes.go @@ -54,6 +54,7 @@ import ( "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/federations" "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/hwinfo" "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/login" + "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/logs" "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/origin" "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/parameter" "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/physlocation" @@ -155,6 +156,10 @@ func Routes(d ServerData) ([]Route, []RawRoute, http.Handler, error) { {1.1, http.MethodDelete, `divisions/{id}$`, api.DeleteHandler(&division.TODivision{}), auth.PrivLevelOperations, Authenticated, nil}, {1.1, http.MethodGet, `divisions/name/{name}/?(\.json)?$`, api.ReadHandler(&division.TODivision{}), auth.PrivLevelReadOnly, Authenticated, nil}, + {1.1, http.MethodGet, `logs/?(\.json)?$`, logs.Get, auth.PrivLevelReadOnly, Authenticated, nil}, + {1.1, http.MethodGet, `logs/{days}/days/?(\.json)?$`, logs.Get, auth.PrivLevelReadOnly, Authenticated, nil}, + {1.1, http.MethodGet, `logs/newcount/?(\.json)?$`, logs.GetNewCount, auth.PrivLevelReadOnly, Authenticated, nil}, + //HWInfo {1.1, http.MethodGet, `hwinfo-wip/?(\.json)?$`, hwinfo.Get, auth.PrivLevelReadOnly, Authenticated, nil}, From 39a32322b01f52eda6fa8bf7ae2f36d4fe267a3c Mon Sep 17 00:00:00 2001 From: Jeremy Mitchell Date: Wed, 31 Jul 2019 16:07:17 -0600 Subject: [PATCH 3/4] adds a couple of tests for the logs endpoint --- lib/go-tc/log.go | 4 ++ traffic_ops/client/log.go | 66 ++++++++++++++++++++++++ traffic_ops/testing/api/v14/logs_test.go | 44 ++++++++++++++++ 3 files changed, 114 insertions(+) create mode 100644 traffic_ops/client/log.go create mode 100644 traffic_ops/testing/api/v14/logs_test.go diff --git a/lib/go-tc/log.go b/lib/go-tc/log.go index 6cf891e181..7ba8a3eb62 100644 --- a/lib/go-tc/log.go +++ b/lib/go-tc/log.go @@ -19,6 +19,10 @@ package tc * under the License. */ +type LogsResponse struct { + Response []Log `json:"response"` +} + type Log struct { ID *int `json:"id"` LastUpdated *Time `json:"lastUpdated"` diff --git a/traffic_ops/client/log.go b/traffic_ops/client/log.go new file mode 100644 index 0000000000..3d4e18c01a --- /dev/null +++ b/traffic_ops/client/log.go @@ -0,0 +1,66 @@ +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package client + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/apache/trafficcontrol/lib/go-tc" +) + +const ( + API_v14_Logs = "/api/1.4/logs" +) + +// GetLogsByQueryParams gets a list of logs filtered by query params +func (to *Session) GetLogsByQueryParams(queryParams string) ([]tc.Log, ReqInf, error) { + URI := API_v14_Logs + queryParams + resp, remoteAddr, err := to.request(http.MethodGet, URI, nil) + reqInf := ReqInf{CacheHitStatus: CacheHitStatusMiss, RemoteAddr: remoteAddr} + if err != nil { + return nil, reqInf, err + } + defer resp.Body.Close() + + var data tc.LogsResponse + if err := json.NewDecoder(resp.Body).Decode(&data); err != nil { + return nil, reqInf, err + } + + return data.Response, reqInf, nil +} + +// GetLogs gets a list of logs +func (to *Session) GetLogs() ([]tc.Log, ReqInf, error) { + return to.GetLogsByQueryParams("") +} + +// GetLogsByLimit gets a list of logs limited to a certain number of logs +func (to *Session) GetLogsByLimit(limit int) ([]tc.Log, ReqInf, error) { + return to.GetLogsByQueryParams(fmt.Sprintf("?limit=%d", limit)) +} + +// GetLogsByDays gets a list of logs limited to a certain number of days +func (to *Session) GetLogsByDays(days int) ([]tc.Log, ReqInf, error) { + return to.GetLogsByQueryParams(fmt.Sprintf("?days=%d", days)) +} + + + + + diff --git a/traffic_ops/testing/api/v14/logs_test.go b/traffic_ops/testing/api/v14/logs_test.go new file mode 100644 index 0000000000..959062d9ec --- /dev/null +++ b/traffic_ops/testing/api/v14/logs_test.go @@ -0,0 +1,44 @@ +package v14 + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "testing" +) + +func TestLogs(t *testing.T) { + WithObjs(t, []TCObj{}, func() { + GetTestLogs(t) + GetTestLogsByLimit(t) + }) +} + +func GetTestLogs(t *testing.T) { + _, _, err := TOSession.GetLogs() + if err != nil { + t.Fatalf("error getting logs: " + err.Error()) + } +} + +func GetTestLogsByLimit(t *testing.T) { + toLogs, _, err := TOSession.GetLogsByLimit(10) + if err != nil { + t.Fatalf("error getting logs: " + err.Error()) + } + if len(toLogs) != 10 { + t.Fatalf("GET logs by limit: incorrect number of logs returned\n") + } +} From bf1f51f3ddb59a17667bde8e60ab256b9214f9ff Mon Sep 17 00:00:00 2001 From: Robert Butts Date: Fri, 2 Aug 2019 14:48:30 -0600 Subject: [PATCH 4/4] Fix GoDoc comments, gofmt --- traffic_ops/client/log.go | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/traffic_ops/client/log.go b/traffic_ops/client/log.go index 3d4e18c01a..f895510065 100644 --- a/traffic_ops/client/log.go +++ b/traffic_ops/client/log.go @@ -27,7 +27,7 @@ const ( API_v14_Logs = "/api/1.4/logs" ) -// GetLogsByQueryParams gets a list of logs filtered by query params +// GetLogsByQueryParams gets a list of logs filtered by query params. func (to *Session) GetLogsByQueryParams(queryParams string) ([]tc.Log, ReqInf, error) { URI := API_v14_Logs + queryParams resp, remoteAddr, err := to.request(http.MethodGet, URI, nil) @@ -45,22 +45,17 @@ func (to *Session) GetLogsByQueryParams(queryParams string) ([]tc.Log, ReqInf, e return data.Response, reqInf, nil } -// GetLogs gets a list of logs +// GetLogs gets a list of logs. func (to *Session) GetLogs() ([]tc.Log, ReqInf, error) { return to.GetLogsByQueryParams("") } -// GetLogsByLimit gets a list of logs limited to a certain number of logs +// GetLogsByLimit gets a list of logs limited to a certain number of logs. func (to *Session) GetLogsByLimit(limit int) ([]tc.Log, ReqInf, error) { return to.GetLogsByQueryParams(fmt.Sprintf("?limit=%d", limit)) } -// GetLogsByDays gets a list of logs limited to a certain number of days +// GetLogsByDays gets a list of logs limited to a certain number of days. func (to *Session) GetLogsByDays(days int) ([]tc.Log, ReqInf, error) { return to.GetLogsByQueryParams(fmt.Sprintf("?days=%d", days)) } - - - - -