-
Notifications
You must be signed in to change notification settings - Fork 249
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
Ability to change log verbosity while the node is running #799
Changes from 10 commits
003cf6f
f8fc941
2675df3
d845b7a
68f93d2
624a7e2
a188d63
de824ba
8edfdd1
0fda3ef
1aae362
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
// 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 admin | ||
|
||
import ( | ||
"encoding/json" | ||
"log/slog" | ||
"net/http" | ||
|
||
"github.com/gorilla/handlers" | ||
"github.com/gorilla/mux" | ||
"github.com/vechain/thor/v2/log" | ||
) | ||
|
||
type logLevelRequest struct { | ||
Level string `json:"level"` | ||
} | ||
|
||
type logLevelResponse struct { | ||
CurrentLevel string `json:"currentLevel"` | ||
} | ||
|
||
type errorResponse struct { | ||
ErrorCode int `json:"errorCode"` | ||
ErrorMessage string `json:"errorMessage"` | ||
} | ||
|
||
func writeError(w http.ResponseWriter, errCode int, errMsg string) { | ||
w.Header().Set("Content-Type", "application/json") | ||
w.WriteHeader(errCode) | ||
json.NewEncoder(w).Encode(errorResponse{ | ||
ErrorCode: errCode, | ||
ErrorMessage: errMsg, | ||
}) | ||
} | ||
|
||
func getLogLevelHandler(logLevel *slog.LevelVar) http.HandlerFunc { | ||
return func(w http.ResponseWriter, r *http.Request) { | ||
response := logLevelResponse{ | ||
CurrentLevel: logLevel.Level().String(), | ||
} | ||
if err := json.NewEncoder(w).Encode(response); err != nil { | ||
writeError(w, http.StatusInternalServerError, "Failed to encode response") | ||
} | ||
} | ||
} | ||
|
||
func postLogLevelHandler(logLevel *slog.LevelVar) http.HandlerFunc { | ||
return func(w http.ResponseWriter, r *http.Request) { | ||
var req logLevelRequest | ||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil { | ||
writeError(w, http.StatusBadRequest, "Invalid request body") | ||
return | ||
} | ||
|
||
switch req.Level { | ||
case "debug": | ||
logLevel.Set(log.LevelDebug) | ||
case "info": | ||
logLevel.Set(log.LevelInfo) | ||
case "warn": | ||
logLevel.Set(log.LevelWarn) | ||
case "error": | ||
logLevel.Set(log.LevelError) | ||
case "trace": | ||
logLevel.Set(log.LevelTrace) | ||
case "crit": | ||
logLevel.Set(log.LevelCrit) | ||
default: | ||
writeError(w, http.StatusBadRequest, "Invalid verbosity level") | ||
return | ||
} | ||
|
||
w.Header().Set("Content-Type", "application/json") | ||
response := logLevelResponse{ | ||
CurrentLevel: logLevel.Level().String(), | ||
} | ||
w.WriteHeader(http.StatusOK) | ||
json.NewEncoder(w).Encode(response) | ||
} | ||
} | ||
|
||
func logLevelHandler(logLevel *slog.LevelVar) http.HandlerFunc { | ||
return func(w http.ResponseWriter, r *http.Request) { | ||
switch r.Method { | ||
case http.MethodGet: | ||
getLogLevelHandler(logLevel).ServeHTTP(w, r) | ||
case http.MethodPost: | ||
postLogLevelHandler(logLevel).ServeHTTP(w, r) | ||
default: | ||
writeError(w, http.StatusMethodNotAllowed, "method not allowed") | ||
} | ||
} | ||
} | ||
|
||
func HTTPHandler(logLevel *slog.LevelVar) http.Handler { | ||
router := mux.NewRouter() | ||
router.HandleFunc("/admin/loglevel", logLevelHandler(logLevel)) | ||
return handlers.CompressHandler(router) | ||
} | ||
Comment on lines
+99
to
+103
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What if the same pattern of the api package was used ?
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wouldn't it make more sense to use the same pattern as the metrics server since our goal is something similar? In the metric's case the |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -39,6 +39,7 @@ import ( | |
"github.com/mattn/go-isatty" | ||
"github.com/mattn/go-tty" | ||
"github.com/pkg/errors" | ||
"github.com/vechain/thor/v2/admin" | ||
"github.com/vechain/thor/v2/api/doc" | ||
"github.com/vechain/thor/v2/chain" | ||
"github.com/vechain/thor/v2/cmd/thor/node" | ||
|
@@ -60,21 +61,25 @@ import ( | |
|
||
var devNetGenesisID = genesis.NewDevnet().ID() | ||
|
||
func initLogger(lvl int, jsonLogs bool) { | ||
func initLogger(lvl int, jsonLogs bool) *slog.LevelVar { | ||
logLevel := log.FromLegacyLevel(lvl) | ||
output := io.Writer(os.Stdout) | ||
var level slog.LevelVar | ||
level.Set(logLevel) | ||
|
||
var handler slog.Handler | ||
if jsonLogs { | ||
handler = log.JSONHandlerWithLevel(output, logLevel) | ||
handler = log.JSONHandlerWithLevel(output, &level) | ||
} else { | ||
useColor := (isatty.IsTerminal(os.Stderr.Fd()) || isatty.IsCygwinTerminal(os.Stderr.Fd())) && os.Getenv("TERM") != "dumb" | ||
handler = log.NewTerminalHandlerWithLevel(output, logLevel, useColor) | ||
handler = log.NewTerminalHandlerWithLevel(output, &level, useColor) | ||
MakisChristou marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
log.SetDefault(log.NewLogger(handler)) | ||
ethlog.Root().SetHandler(ethlog.LvlFilterHandler(ethlog.LvlWarn, ðLogger{ | ||
logger: log.WithContext("pkg", "geth"), | ||
})) | ||
|
||
return &level | ||
} | ||
|
||
type ethLogger struct { | ||
|
@@ -587,6 +592,27 @@ func startMetricsServer(addr string) (string, func(), error) { | |
}, nil | ||
} | ||
|
||
func startAdminServer(addr string, logLevel *slog.LevelVar) (string, func(), error) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should this live in the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure. For this I just used the same pattern that is used in metrics with |
||
listener, err := net.Listen("tcp", addr) | ||
if err != nil { | ||
return "", nil, errors.Wrapf(err, "listen admin API addr [%v]", addr) | ||
} | ||
|
||
router := mux.NewRouter() | ||
router.PathPrefix("/admin").Handler(admin.HTTPHandler(logLevel)) | ||
handler := handlers.CompressHandler(router) | ||
|
||
srv := &http.Server{Handler: handler, ReadHeaderTimeout: time.Second, ReadTimeout: 5 * time.Second} | ||
var goes co.Goes | ||
goes.Go(func() { | ||
srv.Serve(listener) | ||
}) | ||
return "http://" + listener.Addr().String() + "/admin", func() { | ||
srv.Close() | ||
goes.Wait() | ||
}, nil | ||
} | ||
|
||
func printStartupMessage1( | ||
gene *genesis.Genesis, | ||
repo *chain.Repository, | ||
|
@@ -638,8 +664,9 @@ func printStartupMessage2( | |
apiURL string, | ||
nodeID string, | ||
metricsURL string, | ||
adminURL string, | ||
) { | ||
fmt.Printf(`%v API portal [ %v ]%v%v`, | ||
fmt.Printf(`%v API portal [ %v ]%v%v%v`, | ||
func() string { // node ID | ||
if nodeID == "" { | ||
return "" | ||
|
@@ -659,6 +686,15 @@ func printStartupMessage2( | |
metricsURL) | ||
} | ||
}(), | ||
func() string { // admin URL | ||
if adminURL == "" { | ||
return "" | ||
} else { | ||
return fmt.Sprintf(` | ||
Admin [ %v ]`, | ||
adminURL) | ||
} | ||
}(), | ||
func() string { | ||
// print default dev net's dev accounts info | ||
if gene.ID() == devNetGenesisID { | ||
|
@@ -681,6 +717,7 @@ func printSoloStartupMessage( | |
apiURL string, | ||
forkConfig thor.ForkConfig, | ||
metricsURL string, | ||
adminURL string, | ||
) { | ||
bestBlock := repo.BestBlockSummary() | ||
|
||
|
@@ -691,6 +728,7 @@ func printSoloStartupMessage( | |
Data dir [ %v ] | ||
API portal [ %v ] | ||
Metrics [ %v ] | ||
Admin [ %v ] | ||
`, | ||
common.MakeName("Thor solo", fullVersion()), | ||
gene.ID(), gene.Name(), | ||
|
@@ -704,6 +742,12 @@ func printSoloStartupMessage( | |
} | ||
return metricsURL | ||
}(), | ||
func() string { | ||
if adminURL == "" { | ||
return "Disabled" | ||
} | ||
return adminURL | ||
}(), | ||
) | ||
|
||
if gene.ID() == devNetGenesisID { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you think it would be good to have some unit tests ? Since we're setting the package up, perhaps it would make any refactor easy at this stage.