Skip to content

Commit 1c57ce9

Browse files
committed
Add runtime log level control via HTTP (Fix #1181)
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.
1 parent 53d6812 commit 1c57ce9

File tree

11 files changed

+405
-124
lines changed

11 files changed

+405
-124
lines changed

cmd/juno/juno.go

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ const (
8585
corsEnableF = "rpc-cors-enable"
8686
versionedConstantsFileF = "versioned-constants-file"
8787
pluginPathF = "plugin-path"
88+
logHostF = "log-host"
89+
logPortF = "log-port"
8890

8991
defaultConfig = ""
9092
defaulHost = "localhost"
@@ -124,6 +126,7 @@ const (
124126
defaultCorsEnable = false
125127
defaultVersionedConstantsFile = ""
126128
defaultPluginPath = ""
129+
defaultLogPort = 0
127130

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

182187
var Version string
@@ -203,7 +208,13 @@ func main() {
203208
return err
204209
}
205210

206-
n, err := node.New(config, Version)
211+
logLevel := utils.NewLogLevel(utils.INFO)
212+
err = logLevel.Set(config.LogLevel)
213+
if err != nil {
214+
return err
215+
}
216+
217+
n, err := node.New(config, Version, logLevel)
207218
if err != nil {
208219
return err
209220
}
@@ -308,13 +319,12 @@ func NewCmd(config *node.Config, run func(*cobra.Command, []string) error) *cobr
308319

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

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

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

cmd/juno/juno_test.go

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ func TestConfigPrecedence(t *testing.T) {
3030
// tested for sanity. These tests are not intended to perform semantics
3131
// checks on the config, those will be checked by the node implementation.
3232
defaultHost := "localhost"
33-
defaultLogLevel := utils.INFO
33+
defaultLogLevel := "info"
3434
defaultHTTP := false
3535
defaultHTTPPort := uint16(6060)
3636
defaultWS := false
@@ -83,7 +83,7 @@ func TestConfigPrecedence(t *testing.T) {
8383
"--cn-core-contract-address", "0xc662c410C0ECf747543f5bA90660f6ABeBD9C8c4",
8484
},
8585
expectedConfig: &node.Config{
86-
LogLevel: utils.DEBUG,
86+
LogLevel: "debug",
8787
HTTP: defaultHTTP,
8888
HTTPHost: "0.0.0.0",
8989
HTTPPort: 4576,
@@ -110,6 +110,8 @@ func TestConfigPrecedence(t *testing.T) {
110110
DBMaxHandles: defaultMaxHandles,
111111
RPCCallMaxSteps: defaultCallMaxSteps,
112112
GatewayTimeout: defaultGwTimeout,
113+
LogHost: defaultHost,
114+
LogPort: 0,
113115
},
114116
},
115117
"custom network config file": {
@@ -128,7 +130,7 @@ cn-core-contract-address: 0xc662c410C0ECf747543f5bA90660f6ABeBD9C8c4
128130
cn-unverifiable-range: [0,10]
129131
`,
130132
expectedConfig: &node.Config{
131-
LogLevel: utils.DEBUG,
133+
LogLevel: "debug",
132134
HTTP: defaultHTTP,
133135
HTTPHost: "0.0.0.0",
134136
HTTPPort: 4576,
@@ -155,6 +157,8 @@ cn-unverifiable-range: [0,10]
155157
DBMaxHandles: defaultMaxHandles,
156158
RPCCallMaxSteps: defaultCallMaxSteps,
157159
GatewayTimeout: defaultGwTimeout,
160+
LogHost: defaultHost,
161+
LogPort: 0,
158162
},
159163
},
160164
"default config with no flags": {
@@ -187,6 +191,8 @@ cn-unverifiable-range: [0,10]
187191
DBMaxHandles: defaultMaxHandles,
188192
RPCCallMaxSteps: defaultCallMaxSteps,
189193
GatewayTimeout: defaultGwTimeout,
194+
LogHost: defaultHost,
195+
LogPort: 0,
190196
},
191197
},
192198
"config file path is empty string": {
@@ -219,6 +225,8 @@ cn-unverifiable-range: [0,10]
219225
DBMaxHandles: defaultMaxHandles,
220226
RPCCallMaxSteps: defaultCallMaxSteps,
221227
GatewayTimeout: defaultGwTimeout,
228+
LogHost: defaultHost,
229+
LogPort: 0,
222230
},
223231
},
224232
"config file doesn't exist": {
@@ -256,6 +264,8 @@ cn-unverifiable-range: [0,10]
256264
DBMaxHandles: defaultMaxHandles,
257265
RPCCallMaxSteps: defaultCallMaxSteps,
258266
GatewayTimeout: defaultGwTimeout,
267+
LogHost: defaultHost,
268+
LogPort: 0,
259269
},
260270
},
261271
"config file with all settings but without any other flags": {
@@ -268,7 +278,7 @@ network: sepolia
268278
pprof: true
269279
`,
270280
expectedConfig: &node.Config{
271-
LogLevel: utils.DEBUG,
281+
LogLevel: "debug",
272282
HTTP: defaultHTTP,
273283
HTTPHost: "0.0.0.0",
274284
HTTPPort: 4576,
@@ -295,6 +305,8 @@ pprof: true
295305
DBMaxHandles: defaultMaxHandles,
296306
RPCCallMaxSteps: defaultCallMaxSteps,
297307
GatewayTimeout: defaultGwTimeout,
308+
LogHost: defaultHost,
309+
LogPort: 0,
298310
},
299311
},
300312
"config file with some settings but without any other flags": {
@@ -304,7 +316,7 @@ http-host: 0.0.0.0
304316
http-port: 4576
305317
`,
306318
expectedConfig: &node.Config{
307-
LogLevel: utils.DEBUG,
319+
LogLevel: "debug",
308320
HTTP: defaultHTTP,
309321
HTTPHost: "0.0.0.0",
310322
HTTPPort: 4576,
@@ -331,6 +343,8 @@ http-port: 4576
331343
DBMaxHandles: defaultMaxHandles,
332344
RPCCallMaxSteps: defaultCallMaxSteps,
333345
GatewayTimeout: defaultGwTimeout,
346+
LogHost: defaultHost,
347+
LogPort: 0,
334348
},
335349
},
336350
"all flags without config file": {
@@ -339,7 +353,7 @@ http-port: 4576
339353
"--db-path", "/home/.juno", "--network", "sepolia-integration", "--pprof", "--db-cache-size", "1024",
340354
},
341355
expectedConfig: &node.Config{
342-
LogLevel: utils.DEBUG,
356+
LogLevel: "debug",
343357
HTTP: defaultHTTP,
344358
HTTPHost: "0.0.0.0",
345359
HTTPPort: 4576,
@@ -366,6 +380,8 @@ http-port: 4576
366380
RPCCallMaxSteps: defaultCallMaxSteps,
367381
GatewayTimeout: defaultGwTimeout,
368382
PendingPollInterval: defaultPendingPollInterval,
383+
LogHost: defaultHost,
384+
LogPort: 0,
369385
},
370386
},
371387
"some flags without config file": {
@@ -374,7 +390,7 @@ http-port: 4576
374390
"--network", "sepolia",
375391
},
376392
expectedConfig: &node.Config{
377-
LogLevel: utils.DEBUG,
393+
LogLevel: "debug",
378394
HTTP: defaultHTTP,
379395
HTTPHost: "0.0.0.0",
380396
HTTPPort: 4576,
@@ -401,6 +417,8 @@ http-port: 4576
401417
DBMaxHandles: defaultMaxHandles,
402418
RPCCallMaxSteps: defaultCallMaxSteps,
403419
GatewayTimeout: defaultGwTimeout,
420+
LogHost: defaultHost,
421+
LogPort: 0,
404422
},
405423
},
406424
"all setting set in both config file and flags": {
@@ -433,7 +451,7 @@ db-cache-size: 1024
433451
"--db-cache-size", "9",
434452
},
435453
expectedConfig: &node.Config{
436-
LogLevel: utils.ERROR,
454+
LogLevel: "error",
437455
HTTP: true,
438456
HTTPHost: "127.0.0.1",
439457
HTTPPort: 4577,
@@ -460,6 +478,8 @@ db-cache-size: 1024
460478
DBMaxHandles: defaultMaxHandles,
461479
RPCCallMaxSteps: defaultCallMaxSteps,
462480
GatewayTimeout: defaultGwTimeout,
481+
LogHost: defaultHost,
482+
LogPort: 0,
463483
},
464484
},
465485
"some setting set in both config file and flags": {
@@ -471,7 +491,7 @@ network: sepolia
471491
`,
472492
inputArgs: []string{"--db-path", "/home/flag/.juno"},
473493
expectedConfig: &node.Config{
474-
LogLevel: utils.WARN,
494+
LogLevel: "warn",
475495
HTTP: defaultHTTP,
476496
HTTPHost: "0.0.0.0",
477497
HTTPPort: 4576,
@@ -498,6 +518,8 @@ network: sepolia
498518
DBMaxHandles: defaultMaxHandles,
499519
RPCCallMaxSteps: defaultCallMaxSteps,
500520
GatewayTimeout: defaultGwTimeout,
521+
LogHost: defaultHost,
522+
LogPort: 0,
501523
},
502524
},
503525
"some setting set in default, config file and flags": {
@@ -532,6 +554,8 @@ network: sepolia
532554
DBMaxHandles: defaultMaxHandles,
533555
RPCCallMaxSteps: defaultCallMaxSteps,
534556
GatewayTimeout: defaultGwTimeout,
557+
LogHost: defaultHost,
558+
LogPort: 0,
535559
},
536560
},
537561
"only set env variables": {
@@ -564,6 +588,8 @@ network: sepolia
564588
DBMaxHandles: defaultMaxHandles,
565589
RPCCallMaxSteps: defaultCallMaxSteps,
566590
GatewayTimeout: defaultGwTimeout,
591+
LogHost: defaultHost,
592+
LogPort: 0,
567593
},
568594
},
569595
"some setting set in both env variables and flags": {
@@ -597,6 +623,8 @@ network: sepolia
597623
DBMaxHandles: defaultMaxHandles,
598624
RPCCallMaxSteps: defaultCallMaxSteps,
599625
GatewayTimeout: defaultGwTimeout,
626+
LogHost: defaultHost,
627+
LogPort: 0,
600628
},
601629
},
602630
"some setting set in both env variables and config file": {
@@ -631,6 +659,8 @@ network: sepolia
631659
DBMaxHandles: defaultMaxHandles,
632660
RPCCallMaxSteps: defaultCallMaxSteps,
633661
GatewayTimeout: defaultGwTimeout,
662+
LogHost: defaultHost,
663+
LogPort: 0,
634664
},
635665
},
636666
}

db/pebble/db.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@ func New(path string) (db.DB, error) {
4040
func NewWithOptions(path string, cacheSizeMB uint, maxOpenFiles int, colouredLogger bool) (db.DB, error) {
4141
// Ensure that the specified cache size meets a minimum threshold.
4242
cacheSizeMB = max(cacheSizeMB, minCacheSizeMB)
43-
44-
dbLog, err := utils.NewZapLogger(utils.ERROR, colouredLogger)
43+
log := utils.NewLogLevel(utils.ERROR)
44+
dbLog, err := utils.NewZapLogger(log, colouredLogger)
4545
if err != nil {
4646
return nil, fmt.Errorf("create DB logger: %w", err)
4747
}

docs/docs/monitoring.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,3 +85,31 @@ To have Juno write logs to a file, use the command:
8585
![Grafana dashboard](/img/grafana-1.png)
8686

8787
![Grafana dashboard](/img/grafana-2.png)
88+
89+
## Change log level in runtime
90+
91+
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.
92+
93+
To enable this feature, use the following configuration options:
94+
95+
- `log-host`: The interface to listen for requests. Defaults to `localhost`.
96+
- `log-port`: The port to listen for requests. REQUIRED
97+
98+
Examples:
99+
100+
```console
101+
# Start juno specifying the log port
102+
juno --log-port=6789 --log-level=error ...
103+
104+
# Get current level
105+
curl -X GET 'localhost:6789/log/level'
106+
error
107+
108+
# Set level
109+
curl -X PUT 'localhost:6789/log/level?level=trace'
110+
Replaced log level with 'trace' successfully
111+
112+
# Get new level
113+
curl -X GET 'localhost:6789/log/level'
114+
trace
115+
```

node/http.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,16 @@ func makeMetrics(host string, port uint16) *httpService {
143143
promhttp.HandlerFor(prometheus.DefaultGatherer, promhttp.HandlerOpts{Registry: prometheus.DefaultRegisterer}))
144144
}
145145

146+
// Create a new service that updates the log level setting.
147+
func makeLogService(host string, port uint16, logLevel *utils.LogLevel) *httpService {
148+
mux := http.NewServeMux()
149+
mux.HandleFunc("/log/level", func(w http.ResponseWriter, r *http.Request) {
150+
utils.HTTPLogSettings(w, r, logLevel)
151+
})
152+
var handler http.Handler = mux
153+
return makeHTTPService(host, port, handler)
154+
}
155+
146156
type grpcService struct {
147157
srv *grpc.Server
148158
host string

0 commit comments

Comments
 (0)