Skip to content

Commit 1c6394a

Browse files
committed
feat(logging)_: enable runtime logs configuration
Add endpoints for log level and namespaces configuration to `status.go` and deprecate equivalents from service. Endpoints are defined in `status.go`, as it has access to `statusBackend`, the only entity capable of manipulating node configuration without requiring a restart.
1 parent f500dcf commit 1c6394a

File tree

6 files changed

+104
-36
lines changed

6 files changed

+104
-36
lines changed

api/geth_backend.go

+26-6
Original file line numberDiff line numberDiff line change
@@ -2198,12 +2198,6 @@ func (b *GethStatusBackend) loadNodeConfig(inputNodeCfg *params.NodeConfig) erro
21982198
}
21992199
}
22002200

2201-
if len(conf.LogDir) == 0 {
2202-
conf.LogFile = filepath.Join(b.rootDataDir, conf.LogFile)
2203-
} else {
2204-
conf.LogFile = filepath.Join(conf.LogDir, conf.LogFile)
2205-
}
2206-
22072201
b.config = conf
22082202

22092203
if inputNodeCfg != nil && inputNodeCfg.RuntimeLogLevel != "" {
@@ -2984,3 +2978,29 @@ func (b *GethStatusBackend) TogglePanicReporting(enabled bool) error {
29842978
}
29852979
return b.DisablePanicReporting()
29862980
}
2981+
2982+
func (b *GethStatusBackend) SetLogLevel(level string) error {
2983+
b.mu.Lock()
2984+
defer b.mu.Unlock()
2985+
2986+
err := nodecfg.SetLogLevel(b.appDB, level)
2987+
if err != nil {
2988+
return err
2989+
}
2990+
b.config.LogLevel = level
2991+
2992+
return logutils.OverrideRootLoggerWithConfig(b.config.DefaultLogSettings())
2993+
}
2994+
2995+
func (b *GethStatusBackend) SetLogNamespaces(namespaces string) error {
2996+
b.mu.Lock()
2997+
defer b.mu.Unlock()
2998+
2999+
err := nodecfg.SetLogNamespaces(b.appDB, namespaces)
3000+
if err != nil {
3001+
return err
3002+
}
3003+
b.config.LogNamespaces = namespaces
3004+
3005+
return logutils.OverrideRootLoggerWithConfig(b.config.DefaultLogSettings())
3006+
}

logutils/core.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,8 @@ func (core *Core) Sync() error {
107107
}
108108

109109
func (core *Core) UpdateSyncer(newSyncer zapcore.WriteSyncer) {
110-
core.syncer.Store(writeSyncerWrapper{WriteSyncer: newSyncer})
110+
oldSyncer := core.syncer.Swap(writeSyncerWrapper{WriteSyncer: newSyncer})
111+
_ = oldSyncer.(zapcore.WriteSyncer).Sync() // may fail but doesn't impact syncer update
111112
}
112113

113114
func (core *Core) UpdateEncoder(newEncoder zapcore.Encoder) {

mobile/status.go

+39-1
Original file line numberDiff line numberDiff line change
@@ -1248,7 +1248,7 @@ func exportNodeLogs() string {
12481248
if config == nil {
12491249
return makeJSONResponse(errors.New("config and log file are not available"))
12501250
}
1251-
data, err := json.Marshal(exportlogs.ExportFromBaseFile(config.LogFile))
1251+
data, err := json.Marshal(exportlogs.ExportFromBaseFile(config.LogFilePath()))
12521252
if err != nil {
12531253
return makeJSONResponse(fmt.Errorf("error marshalling to json: %v", err))
12541254
}
@@ -2323,6 +2323,44 @@ func addCentralizedMetric(requestJSON string) string {
23232323
return metric.ID
23242324
}
23252325

2326+
func SetLogLevel(requestJSON string) string {
2327+
return callWithResponse(setLogLevel, requestJSON)
2328+
}
2329+
2330+
func setLogLevel(requestJSON string) string {
2331+
var request requests.SetLogLevel
2332+
err := json.Unmarshal([]byte(requestJSON), &request)
2333+
if err != nil {
2334+
return makeJSONResponse(err)
2335+
}
2336+
2337+
err = request.Validate()
2338+
if err != nil {
2339+
return makeJSONResponse(err)
2340+
}
2341+
2342+
return makeJSONResponse(statusBackend.SetLogLevel(request.LogLevel))
2343+
}
2344+
2345+
func SetLogNamespaces(requestJSON string) string {
2346+
return callWithResponse(setLogNamespaces, requestJSON)
2347+
}
2348+
2349+
func setLogNamespaces(requestJSON string) string {
2350+
var request requests.SetLogNamespaces
2351+
err := json.Unmarshal([]byte(requestJSON), &request)
2352+
if err != nil {
2353+
return makeJSONResponse(err)
2354+
}
2355+
2356+
err = request.Validate()
2357+
if err != nil {
2358+
return makeJSONResponse(err)
2359+
}
2360+
2361+
return makeJSONResponse(statusBackend.SetLogNamespaces(request.LogNamespaces))
2362+
}
2363+
23262364
func IntendedPanic(message string) string {
23272365
type intendedPanic struct {
23282366
error

params/config.go

+8-1
Original file line numberDiff line numberDiff line change
@@ -1048,12 +1048,19 @@ func LesTopic(netid int) string {
10481048
}
10491049
}
10501050

1051+
func (c *NodeConfig) LogFilePath() string {
1052+
if c.LogDir == "" {
1053+
return filepath.Join(c.RootDataDir, c.LogFile)
1054+
}
1055+
return filepath.Join(c.LogDir, c.LogFile)
1056+
}
1057+
10511058
func (c *NodeConfig) DefaultLogSettings() logutils.LogSettings {
10521059
return logutils.LogSettings{
10531060
Enabled: c.LogEnabled,
10541061
Level: c.LogLevel,
10551062
Namespaces: c.LogNamespaces,
1056-
File: c.LogFile,
1063+
File: c.LogFilePath(),
10571064
MaxSize: c.LogMaxSize,
10581065
MaxBackups: c.LogMaxBackups,
10591066
CompressRotated: c.LogCompressRotated,

protocol/messenger_settings.go

+2
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ func (m *Messenger) SetSyncingOnMobileNetwork(request *requests.SetSyncingOnMobi
3030
return nil
3131
}
3232

33+
// Deprecated: Use SetLogLevel from status.go instead.
3334
func (m *Messenger) SetLogLevel(request *requests.SetLogLevel) error {
3435
if err := request.Validate(); err != nil {
3536
return err
@@ -38,6 +39,7 @@ func (m *Messenger) SetLogLevel(request *requests.SetLogLevel) error {
3839
return nodecfg.SetLogLevel(m.database, request.LogLevel)
3940
}
4041

42+
// Deprecated: Use SetLogNamespaces from status.go instead.
4143
func (m *Messenger) SetLogNamespaces(request *requests.SetLogNamespaces) error {
4244
if err := request.Validate(); err != nil {
4345
return err

tests-functional/tests/test_logging.py

+27-27
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1+
from resources.constants import USER_DIR
12
import re
23
from clients.status_backend import StatusBackend
34
import pytest
5+
import os
46

57

68
@pytest.mark.rpc
7-
@pytest.mark.skip("waiting for status-backend to be executed on the same host/container")
89
class TestLogging:
910

1011
@pytest.mark.init
@@ -20,46 +21,45 @@ def test_logging(self, tmp_path):
2021
assert backend_client is not None
2122

2223
# Init and login
23-
backend_client.init_status_backend(data_dir=str(tmp_path))
24-
backend_client.create_account_and_login(data_dir=str(tmp_path))
24+
backend_client.init_status_backend()
25+
backend_client.create_account_and_login()
2526
key_uid = self.ensure_logged_in(backend_client)
2627

2728
# Configure logging
28-
backend_client.rpc_valid_request("wakuext_setLogLevel", [{"logLevel": "ERROR"}])
29-
backend_client.rpc_valid_request(
30-
"wakuext_setLogNamespaces",
31-
[{"logNamespaces": "test1.test2:debug,test1.test2.test3:info"}],
32-
)
29+
backend_client.api_valid_request("SetLogLevel", {"logLevel": "ERROR"})
30+
backend_client.api_valid_request("SetLogNamespaces", {"logNamespaces": "test1.test2:debug,test1.test2.test3:info"})
3331

34-
# Re-login (logging settings take effect after re-login)
32+
log_pattern = [
33+
r"DEBUG\s+test1\.test2\s+",
34+
r"INFO\s+test1\.test2\s+",
35+
r"INFO\s+test1\.test2\.test3\s+",
36+
r"WARN\s+test1\.test2\s+",
37+
r"WARN\s+test1\.test2\.test3\s+",
38+
r"ERROR\s+test1\s+",
39+
r"ERROR\s+test1\.test2\s+",
40+
r"ERROR\s+test1\.test2\.test3\s+",
41+
]
42+
43+
# Ensure changes take effect at runtime
44+
backend_client.rpc_valid_request("wakuext_logTest")
45+
geth_log = backend_client.extract_data(os.path.join(USER_DIR, "geth.log"))
46+
self.expect_logs(geth_log, "test message", log_pattern, count=1)
47+
48+
# Ensure changes are persisted after re-login
3549
backend_client.logout()
3650
backend_client.login(str(key_uid))
3751
self.ensure_logged_in(backend_client)
38-
39-
# Test logging
4052
backend_client.rpc_valid_request("wakuext_logTest")
41-
self.expect_logs(
42-
tmp_path / "geth.log",
43-
"test message",
44-
[
45-
r"DEBUG\s+test1\.test2",
46-
r"INFO\s+test1\.test2",
47-
r"INFO\s+test1\.test2\.test3",
48-
r"WARN\s+test1\.test2",
49-
r"WARN\s+test1\.test2\.test3",
50-
r"ERROR\s+test1",
51-
r"ERROR\s+test1\.test2",
52-
r"ERROR\s+test1\.test2\.test3",
53-
],
54-
)
53+
geth_log = backend_client.extract_data(os.path.join(USER_DIR, "geth.log"))
54+
self.expect_logs(geth_log, "test message", log_pattern, count=2)
5555

56-
def expect_logs(self, log_file, filter_keyword, expected_logs):
56+
def expect_logs(self, log_file, filter_keyword, expected_logs, count):
5757
with open(log_file, "r") as f:
5858
log_content = f.read()
5959

6060
filtered_logs = [line for line in log_content.splitlines() if filter_keyword in line]
6161
for expected_log in expected_logs:
62-
assert any(re.search(expected_log, log) for log in filtered_logs), f"Log entry not found: {expected_log}"
62+
assert sum(1 for log in filtered_logs if re.search(expected_log, log)) == count, f"Log entry not found or count mismatch: {expected_log}"
6363

6464
def ensure_logged_in(self, backend_client):
6565
login_response = backend_client.wait_for_signal("node.login")

0 commit comments

Comments
 (0)