Skip to content

Commit dd65f55

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 dd65f55

File tree

7 files changed

+109
-45
lines changed

7 files changed

+109
-45
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/clients/status_backend.py

+3
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,9 @@ def container_exec(self, command):
335335
def find_public_key(self):
336336
self.public_key = self.node_login_event.get("event", {}).get("settings", {}).get("public-key")
337337

338+
def find_key_uid(self):
339+
return self.node_login_event.get("event", {}).get("account", {}).get("key-uid")
340+
338341
@retry(stop=stop_after_delay(10), wait=wait_fixed(0.1), reraise=True)
339342
def change_container_ip(self, new_ipv4=None, new_ipv6=None):
340343
if not self.container:
+29-36
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,50 +21,42 @@ 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))
25-
key_uid = self.ensure_logged_in(backend_client)
24+
backend_client.init_status_backend()
25+
backend_client.create_account_and_login()
2626

2727
# 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-
)
28+
backend_client.api_valid_request("SetLogLevel", {"logLevel": "ERROR"})
29+
backend_client.api_valid_request("SetLogNamespaces", {"logNamespaces": "test1.test2:debug,test1.test2.test3:info"})
30+
31+
log_pattern = [
32+
r"DEBUG\s+test1\.test2\s+",
33+
r"INFO\s+test1\.test2\s+",
34+
r"INFO\s+test1\.test2\.test3\s+",
35+
r"WARN\s+test1\.test2\s+",
36+
r"WARN\s+test1\.test2\.test3\s+",
37+
r"ERROR\s+test1\s+",
38+
r"ERROR\s+test1\.test2\s+",
39+
r"ERROR\s+test1\.test2\.test3\s+",
40+
]
3341

34-
# Re-login (logging settings take effect after re-login)
35-
backend_client.logout()
36-
backend_client.login(str(key_uid))
37-
self.ensure_logged_in(backend_client)
42+
# Ensure changes take effect at runtime
43+
backend_client.rpc_valid_request("wakuext_logTest")
44+
geth_log = backend_client.extract_data(os.path.join(USER_DIR, "geth.log"))
45+
self.expect_logs(geth_log, "test message", log_pattern, count=1)
3846

39-
# Test logging
47+
# Ensure changes are persisted after re-login
48+
backend_client.logout()
49+
backend_client.login(str(backend_client.find_key_uid()))
50+
backend_client.wait_for_login()
4051
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-
)
52+
geth_log = backend_client.extract_data(os.path.join(USER_DIR, "geth.log"))
53+
self.expect_logs(geth_log, "test message", log_pattern, count=2)
5554

56-
def expect_logs(self, log_file, filter_keyword, expected_logs):
55+
def expect_logs(self, log_file, filter_keyword, expected_logs, count):
5756
with open(log_file, "r") as f:
5857
log_content = f.read()
5958

6059
filtered_logs = [line for line in log_content.splitlines() if filter_keyword in line]
6160
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}"
61+
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}"
6362

64-
def ensure_logged_in(self, backend_client):
65-
login_response = backend_client.wait_for_signal("node.login")
66-
backend_client.verify_json_schema(login_response, "signal_node_login")
67-
key_uid = login_response.get("event", {}).get("account", {}).get("key-uid")
68-
assert key_uid is not None, "key-uid not found in login response"
69-
return key_uid

0 commit comments

Comments
 (0)