Skip to content

Commit

Permalink
Add runtime log level control via HTTP (Fix #1181)
Browse files Browse the repository at this point in the history
Introduce HTTP server to dynamically adjust log levels at runtime.
Refactor log level handling to use a new LogLevel struct.
Update tests and documentation to reflect these changes.

Enhances logging flexibility and improves runtime configurability.
  • Loading branch information
derrix060 committed Jan 6, 2025
1 parent 4edb34b commit 06930ca
Show file tree
Hide file tree
Showing 11 changed files with 405 additions and 124 deletions.
18 changes: 15 additions & 3 deletions cmd/juno/juno.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ const (
corsEnableF = "rpc-cors-enable"
versionedConstantsFileF = "versioned-constants-file"
pluginPathF = "plugin-path"
logHostF = "log-host"
logPortF = "log-port"

defaultConfig = ""
defaulHost = "localhost"
Expand Down Expand Up @@ -124,6 +126,7 @@ const (
defaultCorsEnable = false
defaultVersionedConstantsFile = ""
defaultPluginPath = ""
defaultLogPort = 0

configFlagUsage = "The YAML configuration file."
logLevelFlagUsage = "Options: trace, debug, info, warn, error."
Expand Down Expand Up @@ -177,6 +180,8 @@ const (
corsEnableUsage = "Enable CORS on RPC endpoints"
versionedConstantsFileUsage = "Use custom versioned constants from provided file"
pluginPathUsage = "Path to the plugin .so file"
logHostUsage = "The interface on which the log level HTTP server will listen for requests."
logPortUsage = "The port on which the log level HTTP server will listen for requests."
)

var Version string
Expand All @@ -203,7 +208,13 @@ func main() {
return err
}

n, err := node.New(config, Version)
logLevel := utils.NewLogLevel(utils.INFO)
err = logLevel.Set(config.LogLevel)
if err != nil {
return err
}

n, err := node.New(config, Version, logLevel)
if err != nil {
return err
}
Expand Down Expand Up @@ -308,13 +319,12 @@ func NewCmd(config *node.Config, run func(*cobra.Command, []string) error) *cobr

// For testing purposes, these variables cannot be declared outside the function because Cobra
// may mutate their values.
defaultLogLevel := utils.INFO
defaultNetwork := utils.Mainnet
defaultMaxVMs := 3 * runtime.GOMAXPROCS(0)
defaultCNUnverifiableRange := []int{} // Uint64Slice is not supported in Flags()

junoCmd.Flags().StringVar(&cfgFile, configF, defaultConfig, configFlagUsage)
junoCmd.Flags().Var(&defaultLogLevel, logLevelF, logLevelFlagUsage)
junoCmd.Flags().String(logLevelF, utils.INFO.String(), logLevelFlagUsage)
junoCmd.Flags().Bool(httpF, defaultHTTP, httpUsage)
junoCmd.Flags().String(httpHostF, defaulHost, httpHostUsage)
junoCmd.Flags().Uint16(httpPortF, defaultHTTPPort, httpPortUsage)
Expand Down Expand Up @@ -365,6 +375,8 @@ func NewCmd(config *node.Config, run func(*cobra.Command, []string) error) *cobr
junoCmd.Flags().String(versionedConstantsFileF, defaultVersionedConstantsFile, versionedConstantsFileUsage)
junoCmd.MarkFlagsMutuallyExclusive(p2pFeederNodeF, p2pPeersF)
junoCmd.Flags().String(pluginPathF, defaultPluginPath, pluginPathUsage)
junoCmd.Flags().String(logHostF, defaulHost, logHostUsage)
junoCmd.Flags().Uint16(logPortF, defaultLogPort, logPortUsage)

junoCmd.AddCommand(GenP2PKeyPair(), DBCmd(defaultDBPath))

Expand Down
48 changes: 39 additions & 9 deletions cmd/juno/juno_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ func TestConfigPrecedence(t *testing.T) {
// tested for sanity. These tests are not intended to perform semantics
// checks on the config, those will be checked by the node implementation.
defaultHost := "localhost"
defaultLogLevel := utils.INFO
defaultLogLevel := "info"
defaultHTTP := false
defaultHTTPPort := uint16(6060)
defaultWS := false
Expand Down Expand Up @@ -83,7 +83,7 @@ func TestConfigPrecedence(t *testing.T) {
"--cn-core-contract-address", "0xc662c410C0ECf747543f5bA90660f6ABeBD9C8c4",
},
expectedConfig: &node.Config{
LogLevel: utils.DEBUG,
LogLevel: "debug",
HTTP: defaultHTTP,
HTTPHost: "0.0.0.0",
HTTPPort: 4576,
Expand All @@ -110,6 +110,8 @@ func TestConfigPrecedence(t *testing.T) {
DBMaxHandles: defaultMaxHandles,
RPCCallMaxSteps: defaultCallMaxSteps,
GatewayTimeout: defaultGwTimeout,
LogHost: defaultHost,
LogPort: 0,
},
},
"custom network config file": {
Expand All @@ -128,7 +130,7 @@ cn-core-contract-address: 0xc662c410C0ECf747543f5bA90660f6ABeBD9C8c4
cn-unverifiable-range: [0,10]
`,
expectedConfig: &node.Config{
LogLevel: utils.DEBUG,
LogLevel: "debug",
HTTP: defaultHTTP,
HTTPHost: "0.0.0.0",
HTTPPort: 4576,
Expand All @@ -155,6 +157,8 @@ cn-unverifiable-range: [0,10]
DBMaxHandles: defaultMaxHandles,
RPCCallMaxSteps: defaultCallMaxSteps,
GatewayTimeout: defaultGwTimeout,
LogHost: defaultHost,
LogPort: 0,
},
},
"default config with no flags": {
Expand Down Expand Up @@ -187,6 +191,8 @@ cn-unverifiable-range: [0,10]
DBMaxHandles: defaultMaxHandles,
RPCCallMaxSteps: defaultCallMaxSteps,
GatewayTimeout: defaultGwTimeout,
LogHost: defaultHost,
LogPort: 0,
},
},
"config file path is empty string": {
Expand Down Expand Up @@ -219,6 +225,8 @@ cn-unverifiable-range: [0,10]
DBMaxHandles: defaultMaxHandles,
RPCCallMaxSteps: defaultCallMaxSteps,
GatewayTimeout: defaultGwTimeout,
LogHost: defaultHost,
LogPort: 0,
},
},
"config file doesn't exist": {
Expand Down Expand Up @@ -256,6 +264,8 @@ cn-unverifiable-range: [0,10]
DBMaxHandles: defaultMaxHandles,
RPCCallMaxSteps: defaultCallMaxSteps,
GatewayTimeout: defaultGwTimeout,
LogHost: defaultHost,
LogPort: 0,
},
},
"config file with all settings but without any other flags": {
Expand All @@ -268,7 +278,7 @@ network: sepolia
pprof: true
`,
expectedConfig: &node.Config{
LogLevel: utils.DEBUG,
LogLevel: "debug",
HTTP: defaultHTTP,
HTTPHost: "0.0.0.0",
HTTPPort: 4576,
Expand All @@ -295,6 +305,8 @@ pprof: true
DBMaxHandles: defaultMaxHandles,
RPCCallMaxSteps: defaultCallMaxSteps,
GatewayTimeout: defaultGwTimeout,
LogHost: defaultHost,
LogPort: 0,
},
},
"config file with some settings but without any other flags": {
Expand All @@ -304,7 +316,7 @@ http-host: 0.0.0.0
http-port: 4576
`,
expectedConfig: &node.Config{
LogLevel: utils.DEBUG,
LogLevel: "debug",
HTTP: defaultHTTP,
HTTPHost: "0.0.0.0",
HTTPPort: 4576,
Expand All @@ -331,6 +343,8 @@ http-port: 4576
DBMaxHandles: defaultMaxHandles,
RPCCallMaxSteps: defaultCallMaxSteps,
GatewayTimeout: defaultGwTimeout,
LogHost: defaultHost,
LogPort: 0,
},
},
"all flags without config file": {
Expand All @@ -339,7 +353,7 @@ http-port: 4576
"--db-path", "/home/.juno", "--network", "sepolia-integration", "--pprof", "--db-cache-size", "1024",
},
expectedConfig: &node.Config{
LogLevel: utils.DEBUG,
LogLevel: "debug",
HTTP: defaultHTTP,
HTTPHost: "0.0.0.0",
HTTPPort: 4576,
Expand All @@ -366,6 +380,8 @@ http-port: 4576
RPCCallMaxSteps: defaultCallMaxSteps,
GatewayTimeout: defaultGwTimeout,
PendingPollInterval: defaultPendingPollInterval,
LogHost: defaultHost,
LogPort: 0,
},
},
"some flags without config file": {
Expand All @@ -374,7 +390,7 @@ http-port: 4576
"--network", "sepolia",
},
expectedConfig: &node.Config{
LogLevel: utils.DEBUG,
LogLevel: "debug",
HTTP: defaultHTTP,
HTTPHost: "0.0.0.0",
HTTPPort: 4576,
Expand All @@ -401,6 +417,8 @@ http-port: 4576
DBMaxHandles: defaultMaxHandles,
RPCCallMaxSteps: defaultCallMaxSteps,
GatewayTimeout: defaultGwTimeout,
LogHost: defaultHost,
LogPort: 0,
},
},
"all setting set in both config file and flags": {
Expand Down Expand Up @@ -433,7 +451,7 @@ db-cache-size: 1024
"--db-cache-size", "9",
},
expectedConfig: &node.Config{
LogLevel: utils.ERROR,
LogLevel: "error",
HTTP: true,
HTTPHost: "127.0.0.1",
HTTPPort: 4577,
Expand All @@ -460,6 +478,8 @@ db-cache-size: 1024
DBMaxHandles: defaultMaxHandles,
RPCCallMaxSteps: defaultCallMaxSteps,
GatewayTimeout: defaultGwTimeout,
LogHost: defaultHost,
LogPort: 0,
},
},
"some setting set in both config file and flags": {
Expand All @@ -471,7 +491,7 @@ network: sepolia
`,
inputArgs: []string{"--db-path", "/home/flag/.juno"},
expectedConfig: &node.Config{
LogLevel: utils.WARN,
LogLevel: "warn",
HTTP: defaultHTTP,
HTTPHost: "0.0.0.0",
HTTPPort: 4576,
Expand All @@ -498,6 +518,8 @@ network: sepolia
DBMaxHandles: defaultMaxHandles,
RPCCallMaxSteps: defaultCallMaxSteps,
GatewayTimeout: defaultGwTimeout,
LogHost: defaultHost,
LogPort: 0,
},
},
"some setting set in default, config file and flags": {
Expand Down Expand Up @@ -532,6 +554,8 @@ network: sepolia
DBMaxHandles: defaultMaxHandles,
RPCCallMaxSteps: defaultCallMaxSteps,
GatewayTimeout: defaultGwTimeout,
LogHost: defaultHost,
LogPort: 0,
},
},
"only set env variables": {
Expand Down Expand Up @@ -564,6 +588,8 @@ network: sepolia
DBMaxHandles: defaultMaxHandles,
RPCCallMaxSteps: defaultCallMaxSteps,
GatewayTimeout: defaultGwTimeout,
LogHost: defaultHost,
LogPort: 0,
},
},
"some setting set in both env variables and flags": {
Expand Down Expand Up @@ -597,6 +623,8 @@ network: sepolia
DBMaxHandles: defaultMaxHandles,
RPCCallMaxSteps: defaultCallMaxSteps,
GatewayTimeout: defaultGwTimeout,
LogHost: defaultHost,
LogPort: 0,
},
},
"some setting set in both env variables and config file": {
Expand Down Expand Up @@ -631,6 +659,8 @@ network: sepolia
DBMaxHandles: defaultMaxHandles,
RPCCallMaxSteps: defaultCallMaxSteps,
GatewayTimeout: defaultGwTimeout,
LogHost: defaultHost,
LogPort: 0,
},
},
}
Expand Down
4 changes: 2 additions & 2 deletions db/pebble/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ func New(path string) (db.DB, error) {
func NewWithOptions(path string, cacheSizeMB uint, maxOpenFiles int, colouredLogger bool) (db.DB, error) {
// Ensure that the specified cache size meets a minimum threshold.
cacheSizeMB = max(cacheSizeMB, minCacheSizeMB)

dbLog, err := utils.NewZapLogger(utils.ERROR, colouredLogger)
log := utils.NewLogLevel(utils.ERROR)
dbLog, err := utils.NewZapLogger(log, colouredLogger)
if err != nil {
return nil, fmt.Errorf("create DB logger: %w", err)
}
Expand Down
28 changes: 28 additions & 0 deletions docs/docs/monitoring.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,31 @@ To have Juno write logs to a file, use the command:
![Grafana dashboard](/img/grafana-1.png)

![Grafana dashboard](/img/grafana-2.png)

## Change log level in runtime

In case you want to change the log level in runtime without the need to restart the juno process, you can do it via HTTP calls.

To enable this feature, use the following configuration options:

- `log-host`: The interface to listen for requests. Defaults to `localhost`.
- `log-port`: The port to listen for requests. REQUIRED

Examples:

```console
# Start juno specifying the log port
juno --log-port=6789 --log-level=error ...

# Get current level
curl -X GET 'localhost:6789/log/level'
error

# Set level
curl -X PUT 'localhost:6789/log/level?level=trace'
Replaced log level with 'trace' successfully

# Get new level
curl -X GET 'localhost:6789/log/level'
trace
```
10 changes: 10 additions & 0 deletions node/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,16 @@ func makeMetrics(host string, port uint16) *httpService {
promhttp.HandlerFor(prometheus.DefaultGatherer, promhttp.HandlerOpts{Registry: prometheus.DefaultRegisterer}))
}

// Create a new service that updates the log level setting.
func makeLogService(host string, port uint16, logLevel *utils.LogLevel) *httpService {
mux := http.NewServeMux()
mux.HandleFunc("/log/level", func(w http.ResponseWriter, r *http.Request) {
utils.HTTPLogSettings(w, r, logLevel)
})
var handler http.Handler = mux
return makeHTTPService(host, port, handler)
}

type grpcService struct {
srv *grpc.Server
host string
Expand Down
Loading

0 comments on commit 06930ca

Please sign in to comment.