Skip to content

Commit aa1ed78

Browse files
Logging via the HTTP API (#4074)
This PR adds the ability to read the Lighthouse logs from the HTTP API for both the BN and the VC. This is done in such a way to as minimize any kind of performance hit by adding this feature. The current design creates a tokio broadcast channel and mixes is into a form of slog drain that combines with our main global logger drain, only if the http api is enabled. The drain gets the logs, checks the log level and drops them if they are below INFO. If they are INFO or higher, it sends them via a broadcast channel only if there are users subscribed to the HTTP API channel. If not, it drops the logs. If there are more than one subscriber, the channel clones the log records and converts them to json in their independent HTTP API tasks. Co-authored-by: Michael Sproul <[email protected]>
1 parent c27f2bf commit aa1ed78

File tree

23 files changed

+1038
-409
lines changed

23 files changed

+1038
-409
lines changed

Cargo.lock

Lines changed: 431 additions & 365 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

beacon_node/client/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ edition = "2021"
66

77
[dev-dependencies]
88
serde_yaml = "0.8.13"
9-
logging = { path = "../../common/logging" }
109
state_processing = { path = "../../consensus/state_processing" }
1110
operation_pool = { path = "../operation_pool" }
1211
tokio = "1.14.0"
@@ -17,6 +16,7 @@ store = { path = "../store" }
1716
network = { path = "../network" }
1817
timer = { path = "../timer" }
1918
lighthouse_network = { path = "../lighthouse_network" }
19+
logging = { path = "../../common/logging" }
2020
parking_lot = "0.12.0"
2121
types = { path = "../../consensus/types" }
2222
eth2_config = { path = "../../common/eth2_config" }

beacon_node/client/src/builder.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -478,6 +478,7 @@ where
478478
network_globals: None,
479479
eth1_service: Some(genesis_service.eth1_service.clone()),
480480
log: context.log().clone(),
481+
sse_logging_components: runtime_context.sse_logging_components.clone(),
481482
});
482483

483484
// Discard the error from the oneshot.
@@ -698,6 +699,7 @@ where
698699
network_senders: self.network_senders.clone(),
699700
network_globals: self.network_globals.clone(),
700701
eth1_service: self.eth1_service.clone(),
702+
sse_logging_components: runtime_context.sse_logging_components.clone(),
701703
log: log.clone(),
702704
});
703705

beacon_node/http_api/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,11 @@ tree_hash = "0.5.0"
3636
sysinfo = "0.26.5"
3737
system_health = { path = "../../common/system_health" }
3838
directory = { path = "../../common/directory" }
39+
logging = { path = "../../common/logging" }
3940
ethereum_serde_utils = "0.5.0"
4041
operation_pool = { path = "../operation_pool" }
4142
sensitive_url = { path = "../../common/sensitive_url" }
4243
unused_port = {path = "../../common/unused_port"}
43-
logging = { path = "../../common/logging" }
4444
store = { path = "../store" }
4545

4646
[dev-dependencies]
@@ -51,4 +51,4 @@ genesis = { path = "../genesis" }
5151

5252
[[test]]
5353
name = "bn_http_api_tests"
54-
path = "tests/main.rs"
54+
path = "tests/main.rs"

beacon_node/http_api/src/lib.rs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ use eth2::types::{
3636
};
3737
use lighthouse_network::{types::SyncState, EnrExt, NetworkGlobals, PeerId, PubsubMessage};
3838
use lighthouse_version::version_with_platform;
39+
use logging::SSELoggingComponents;
3940
use network::{NetworkMessage, NetworkSenders, ValidatorSubscriptionMessage};
4041
use operation_pool::ReceivedPreCapella;
4142
use parking_lot::RwLock;
@@ -108,6 +109,7 @@ pub struct Context<T: BeaconChainTypes> {
108109
pub network_senders: Option<NetworkSenders<T::EthSpec>>,
109110
pub network_globals: Option<Arc<NetworkGlobals<T::EthSpec>>>,
110111
pub eth1_service: Option<eth1::Service>,
112+
pub sse_logging_components: Option<SSELoggingComponents>,
111113
pub log: Logger,
112114
}
113115

@@ -448,6 +450,9 @@ pub fn serve<T: BeaconChainTypes>(
448450
let inner_ctx = ctx.clone();
449451
let log_filter = warp::any().map(move || inner_ctx.log.clone());
450452

453+
let inner_components = ctx.sse_logging_components.clone();
454+
let sse_component_filter = warp::any().map(move || inner_components.clone());
455+
451456
// Create a `warp` filter that provides access to local system information.
452457
let system_info = Arc::new(RwLock::new(sysinfo::System::new()));
453458
{
@@ -3729,6 +3734,44 @@ pub fn serve<T: BeaconChainTypes>(
37293734
},
37303735
);
37313736

3737+
// Subscribe to logs via Server Side Events
3738+
// /lighthouse/logs
3739+
let lighthouse_log_events = warp::path("lighthouse")
3740+
.and(warp::path("logs"))
3741+
.and(warp::path::end())
3742+
.and(sse_component_filter)
3743+
.and_then(|sse_component: Option<SSELoggingComponents>| {
3744+
blocking_response_task(move || {
3745+
if let Some(logging_components) = sse_component {
3746+
// Build a JSON stream
3747+
let s =
3748+
BroadcastStream::new(logging_components.sender.subscribe()).map(|msg| {
3749+
match msg {
3750+
Ok(data) => {
3751+
// Serialize to json
3752+
match data.to_json_string() {
3753+
// Send the json as a Server Side Event
3754+
Ok(json) => Ok(Event::default().data(json)),
3755+
Err(e) => Err(warp_utils::reject::server_sent_event_error(
3756+
format!("Unable to serialize to JSON {}", e),
3757+
)),
3758+
}
3759+
}
3760+
Err(e) => Err(warp_utils::reject::server_sent_event_error(
3761+
format!("Unable to receive event {}", e),
3762+
)),
3763+
}
3764+
});
3765+
3766+
Ok::<_, warp::Rejection>(warp::sse::reply(warp::sse::keep_alive().stream(s)))
3767+
} else {
3768+
Err(warp_utils::reject::custom_server_error(
3769+
"SSE Logging is not enabled".to_string(),
3770+
))
3771+
}
3772+
})
3773+
});
3774+
37323775
// Define the ultimate set of routes that will be provided to the server.
37333776
// Use `uor` rather than `or` in order to simplify types (see `UnifyingOrFilter`).
37343777
let routes = warp::get()
@@ -3796,6 +3839,7 @@ pub fn serve<T: BeaconChainTypes>(
37963839
.uor(get_lighthouse_block_packing_efficiency)
37973840
.uor(get_lighthouse_merge_readiness)
37983841
.uor(get_events)
3842+
.uor(lighthouse_log_events.boxed())
37993843
.recover(warp_utils::reject::handle_rejection),
38003844
)
38013845
.boxed()

beacon_node/http_api/src/test_utils.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,7 @@ pub async fn create_api_server_on_port<T: BeaconChainTypes>(
198198
network_senders: Some(network_senders),
199199
network_globals: Some(network_globals),
200200
eth1_service: Some(eth1_service),
201+
sse_logging_components: None,
201202
log,
202203
});
203204

beacon_node/src/cli.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1081,7 +1081,7 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
10811081
.long("gui")
10821082
.hidden(true)
10831083
.help("Enable the graphical user interface and all its requirements. \
1084-
This is equivalent to --http and --validator-monitor-auto.")
1084+
This enables --http and --validator-monitor-auto and enables SSE logging.")
10851085
.takes_value(false)
10861086
)
10871087
.arg(

book/src/api-lighthouse.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -679,3 +679,31 @@ Caveats:
679679
This is because the state _prior_ to the `start_epoch` needs to be loaded from the database, and
680680
loading a state on a boundary is most efficient.
681681

682+
683+
### `/lighthouse/logs`
684+
685+
This is a Server Side Event subscription endpoint. This allows a user to read
686+
the Lighthouse logs directly from the HTTP API endpoint. This currently
687+
exposes INFO and higher level logs. It is only enabled when the `--gui` flag is set in the CLI.
688+
689+
Example:
690+
691+
```bash
692+
curl -N "http://localhost:5052/lighthouse/logs"
693+
```
694+
695+
Should provide an output that emits log events as they occur:
696+
```json
697+
{
698+
"data": {
699+
"time": "Mar 13 15:28:41",
700+
"level": "INFO",
701+
"msg": "Syncing",
702+
"service": "slot_notifier",
703+
"est_time": "1 hr 27 mins",
704+
"speed": "5.33 slots/sec",
705+
"distance": "28141 slots (3 days 21 hrs)",
706+
"peers": "8"
707+
}
708+
}
709+
```

book/src/api-vc-endpoints.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -578,3 +578,33 @@ The following fields may be omitted or nullified to obtain default values:
578578
### Example Response Body
579579

580580
*No data is included in the response body.*
581+
582+
## `GET /lighthouse/logs`
583+
584+
Provides a subscription to receive logs as Server Side Events. Currently the
585+
logs emitted are INFO level or higher.
586+
587+
### HTTP Specification
588+
589+
| Property | Specification |
590+
|-------------------|--------------------------------------------|
591+
| Path | `/lighthouse/logs` |
592+
| Method | GET |
593+
| Required Headers | None |
594+
| Typical Responses | 200 |
595+
596+
### Example Response Body
597+
598+
```json
599+
{
600+
"data": {
601+
"time": "Mar 13 15:26:53",
602+
"level": "INFO",
603+
"msg": "Connected to beacon node(s)",
604+
"service": "notifier",
605+
"synced": 1,
606+
"available": 1,
607+
"total": 1
608+
}
609+
}
610+
```

common/logging/Cargo.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,13 @@ test_logger = [] # Print log output to stderr when running tests instead of drop
1010
[dependencies]
1111
slog = "2.5.2"
1212
slog-term = "2.6.0"
13+
tokio = { version = "1.26.0", features = ["sync"] }
1314
lighthouse_metrics = { path = "../lighthouse_metrics" }
1415
lazy_static = "1.4.0"
1516
sloggers = { version = "2.1.1", features = ["json"] }
17+
slog-async = "2.7.0"
18+
take_mut = "0.2.2"
19+
parking_lot = "0.12.1"
20+
serde = "1.0.153"
21+
serde_json = "1.0.94"
22+
chrono = "0.4.23"

0 commit comments

Comments
 (0)