Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/master' into pedro/add_metrics
Browse files Browse the repository at this point in the history
  • Loading branch information
otherview committed May 21, 2024
2 parents 0b16fb5 + e7f2663 commit 605738d
Show file tree
Hide file tree
Showing 7 changed files with 179 additions and 8 deletions.
5 changes: 4 additions & 1 deletion .github/workflows/on-master-commit.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,14 @@ jobs:
if: always() && (needs.publish-docker-image.result != 'success' || needs.run-unit-tests.result != 'success' || needs.lint.result != 'success' || needs.run-e2e-tests.result != 'success' || needs.license-check.result != 'success')
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Get the commit message
id: commit_message
# This is a workaround to get the first line of the commit message. Passing the entire message can cause the payload (JSON) to be invalid.
run: |
echo "commit_message=$(echo ${{ github.event.head_commit.message }} | head -n 1)" >> "$GITHUB_ENV"
echo "commit_message=$(git show-branch --no-name HEAD)" >> "$GITHUB_ENV"
- name: Notify Slack
uses: slackapi/[email protected]
Expand Down
12 changes: 10 additions & 2 deletions api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (

"github.com/gorilla/handlers"
"github.com/gorilla/mux"
"github.com/inconshreveable/log15"
"github.com/vechain/thor/v2/api/accounts"
"github.com/vechain/thor/v2/api/blocks"
"github.com/vechain/thor/v2/api/debug"
Expand All @@ -29,6 +30,8 @@ import (
"github.com/vechain/thor/v2/txpool"
)

var log = log15.New("pkg", "api")

// New return api router
func New(
repo *chain.Repository,
Expand All @@ -43,6 +46,7 @@ func New(
pprofOn bool,
skipLogs bool,
allowCustomTracer bool,
isReqLoggerEnabled bool,
forkConfig thor.ForkConfig,
) (http.HandlerFunc, func()) {
origins := strings.Split(strings.TrimSpace(allowedOrigins), ",")
Expand Down Expand Up @@ -92,11 +96,15 @@ func New(
}

handler := handlers.CompressHandler(router)
if isReqLoggerEnabled {
handler = RequestLoggerHandler(handler, log)
}

handler = handlers.CORS(
handlers.AllowedOrigins(origins),
handlers.AllowedHeaders([]string{"content-type", "x-genesis-id"}),
handlers.ExposedHeaders([]string{"x-genesis-id", "x-thorest-ver"}),
)(handler)
return handler.ServeHTTP,
subs.Close // subscriptions handles hijacked conns, which need to be closed

return handler.ServeHTTP, subs.Close // subscriptions handles hijacked conns, which need to be closed
}
47 changes: 47 additions & 0 deletions api/request_logger.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Copyright (c) 2024 The VeChainThor developers

// Distributed under the GNU Lesser General Public License v3.0 software license, see the accompanying
// file LICENSE or <https://www.gnu.org/licenses/lgpl-3.0.html>

package api

import (
"bytes"
"io"
"net/http"
"time"

"github.com/inconshreveable/log15"
)

// RequestLoggerHandler returns a http handler to ensure requests are syphoned into the writer
func RequestLoggerHandler(handler http.Handler, logger log15.Logger) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
// Read and log the body (note: this can only be done once)
// Ensure you don't disrupt the request body for handlers that need to read it
var bodyBytes []byte
var err error
if r.Body != nil {
bodyBytes, err = io.ReadAll(r.Body)
if err != nil {
logger.Warn("unexpected body read error", "err", err)
return // don't pass bad request to the next handler
}
r.Body = io.NopCloser(io.Reader(bytes.NewReader(bodyBytes)))
}

logger.Info("API Request",
"timestamp", time.Now().Unix(),
"URI", r.URL.String(),
"Method", r.Method,
"Body", string(bodyBytes),
)

// call the original http.Handler we're wrapping
handler.ServeHTTP(w, r)
}

// http.HandlerFunc wraps a function so that it
// implements http.Handler interface
return http.HandlerFunc(fn)
}
95 changes: 95 additions & 0 deletions api/request_logger_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// Copyright (c) 2024 The VeChainThor developers

// Distributed under the GNU Lesser General Public License v3.0 software license, see the accompanying
// file LICENSE or <https://www.gnu.org/licenses/lgpl-3.0.html>
package api

import (
"net/http"
"net/http/httptest"
"strings"
"testing"

"github.com/inconshreveable/log15"
"github.com/stretchr/testify/assert"
)

// mockLogger is a simple logger implementation for testing purposes
type mockLogger struct {
loggedData []interface{}
}

func (m *mockLogger) New(ctx ...interface{}) log15.Logger { return m }

func (m *mockLogger) GetHandler() log15.Handler { return nil }

func (m *mockLogger) SetHandler(h log15.Handler) {}

func (m *mockLogger) Debug(msg string, ctx ...interface{}) {}

func (m *mockLogger) Error(msg string, ctx ...interface{}) {}

func (m *mockLogger) Crit(msg string, ctx ...interface{}) {}

func (m *mockLogger) Info(msg string, ctx ...interface{}) {
m.loggedData = append(m.loggedData, ctx...)
}

func (m *mockLogger) Warn(msg string, ctx ...interface{}) {
m.loggedData = append(m.loggedData, ctx...)
}

func (m *mockLogger) GetLoggedData() []interface{} {
return m.loggedData
}

func TestRequestLoggerHandler(t *testing.T) {
mockLog := &mockLogger{}

// Define a test handler to wrap
testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("OK"))
})

// Create the RequestLoggerHandler
loggerHandler := RequestLoggerHandler(testHandler, mockLog)

// Create a test HTTP request
reqBody := "test body"
req := httptest.NewRequest("POST", "http://example.com/foo", strings.NewReader(reqBody))
req.Header.Set("Content-Type", "application/json")

// Create a ResponseRecorder to record the response
rr := httptest.NewRecorder()

// Serve the HTTP request
loggerHandler.ServeHTTP(rr, req)

// Check the response status code
assert.Equal(t, http.StatusOK, rr.Code)

// Check the response body
assert.Equal(t, "OK", rr.Body.String())

// Verify that the logger recorded the correct information
loggedData := mockLog.GetLoggedData()
assert.Contains(t, loggedData, "URI")
assert.Contains(t, loggedData, "http://example.com/foo")
assert.Contains(t, loggedData, "Method")
assert.Contains(t, loggedData, "POST")
assert.Contains(t, loggedData, "Body")
assert.Contains(t, loggedData, reqBody)

// Check if timestamp is present
foundTimestamp := false
for i := 0; i < len(loggedData); i += 2 {
if loggedData[i] == "timestamp" {
_, ok := loggedData[i+1].(int64)
assert.True(t, ok, "timestamp should be an int64")
foundTimestamp = true
break
}
}
assert.True(t, foundTimestamp, "timestamp should be logged")
}
8 changes: 8 additions & 0 deletions builtin/authority/authority_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,14 @@ func TestAuthority(t *testing.T) {
{M(aut.Candidates(&big.Int{}, thor.InitialMaxBlockProposers)), M(
[]*Candidate{{p2, p2, thor.Bytes32{}, true}, {p3, p3, thor.Bytes32{}, true}}, nil,
)},
{M(aut.AllCandidates()), M([]*Candidate{
{p2, p2, thor.Bytes32{}, true},
{p3, p3, thor.Bytes32{}, true}}, nil),
},
{M(aut.First()), M(&p2, nil)},
{M(aut.Next(p2)), M(&p3, nil)},
{M(aut.Revoke(p1)), M(false, nil)},
{M(aut.Revoke(p3)), M(true, nil)},
}

for i, tt := range tests {
Expand Down
4 changes: 4 additions & 0 deletions cmd/thor/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ var (
Name: "api-allow-custom-tracer",
Usage: "allow custom JS tracer to be used tracer API",
}
apiLogsEnabledFlag = cli.BoolFlag{
Name: "enable-api-logs",
Usage: "enables API requests logging",
}
verbosityFlag = cli.IntFlag{
Name: "verbosity",
Value: int(log15.LvlInfo),
Expand Down
16 changes: 11 additions & 5 deletions cmd/thor/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ func main() {
apiCallGasLimitFlag,
apiBacktraceLimitFlag,
apiAllowCustomTracerFlag,
apiLogsEnabledFlag,
verbosityFlag,
maxPeersFlag,
p2pPortFlag,
Expand Down Expand Up @@ -107,6 +108,7 @@ func main() {
apiCallGasLimitFlag,
apiBacktraceLimitFlag,
apiAllowCustomTracerFlag,
apiLogsEnabledFlag,
onDemandFlag,
persistFlag,
gasLimitFlag,
Expand Down Expand Up @@ -228,8 +230,8 @@ func defaultAction(ctx *cli.Context) error {
ctx.Bool(pprofFlag.Name),
skipLogs,
ctx.Bool(apiAllowCustomTracerFlag.Name),
forkConfig,
)
ctx.Bool(apiLogsEnabledFlag.Name),
forkConfig)
defer func() { log.Info("closing API..."); apiCloser() }()

apiURL, srvCloser, err := startAPIServer(ctx, apiHandler, repo.GenesisBlock().Header().ID())
Expand Down Expand Up @@ -310,6 +312,7 @@ func soloAction(ctx *cli.Context) error {
return err
}
defer func() { log.Info("closing main database..."); mainDB.Close() }()

if logDB, err = openLogDB(ctx, instanceDir); err != nil {
return err
}
Expand Down Expand Up @@ -354,15 +357,18 @@ func soloAction(ctx *cli.Context) error {
ctx.Bool(pprofFlag.Name),
skipLogs,
ctx.Bool(apiAllowCustomTracerFlag.Name),
forkConfig,
)
ctx.Bool(apiLogsEnabledFlag.Name),
forkConfig)
defer func() { log.Info("closing API..."); apiCloser() }()

apiURL, srvCloser, err := startAPIServer(ctx, apiHandler, repo.GenesisBlock().Header().ID())
if err != nil {
return err
}
defer func() { log.Info("stopping API server..."); srvCloser() }()
defer func() {
log.Info("stopping API server...")
srvCloser()
}()

printStartupMessage1(gene, repo, nil, instanceDir, forkConfig)
printStartupMessage2(gene, apiURL, "", metricsURL)
Expand Down

0 comments on commit 605738d

Please sign in to comment.