futu_opend/startup/phase4/
summary.rs1use futu_gateway_core::bridge::GatewayBridge;
2
3use crate::config::RuntimeConfig;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6pub(super) enum RestTransportKind {
7 Disabled,
8 Plaintext,
9 Tls,
10}
11
12pub(super) fn merge_push_health_snapshots_for_rest(
13 push: serde_json::Value,
14 qot_login: serde_json::Value,
15 backend_connected: bool,
16 stock_list_static_query_ready: bool,
17) -> serde_json::Value {
18 let mut combined = match push {
19 serde_json::Value::Object(map) => map,
20 other => {
21 let mut map = serde_json::Map::new();
22 map.insert(
23 "error".to_string(),
24 serde_json::Value::String(format!(
25 "push_health snapshot serialized to non-object {}",
26 json_value_kind(&other)
27 )),
28 );
29 map
30 }
31 };
32 combined.insert("qot_login_health".to_string(), qot_login);
33 combined.insert(
34 "backend_connected".to_string(),
35 serde_json::Value::Bool(backend_connected),
36 );
37 combined.insert(
38 "stock_list_static_query_ready".to_string(),
39 serde_json::Value::Bool(stock_list_static_query_ready),
40 );
41 serde_json::Value::Object(combined)
42}
43
44fn json_value_kind(value: &serde_json::Value) -> &'static str {
45 match value {
46 serde_json::Value::Null => "null",
47 serde_json::Value::Bool(_) => "bool",
48 serde_json::Value::Number(_) => "number",
49 serde_json::Value::String(_) => "string",
50 serde_json::Value::Array(_) => "array",
51 serde_json::Value::Object(_) => "object",
52 }
53}
54
55fn optional_surface_addr(bind_ip: &str, port: Option<u16>) -> String {
56 port.map(|port| format!("{bind_ip}:{port}"))
57 .unwrap_or_else(|| "disabled".to_string())
58}
59
60pub(super) fn http_host_for_bind(ip: &str) -> String {
61 match ip {
62 "0.0.0.0" | "::" => "127.0.0.1".to_string(),
63 value if value.contains(':') && !value.starts_with('[') => format!("[{value}]"),
64 value => value.to_string(),
65 }
66}
67
68pub(super) fn readyz_url(config: &RuntimeConfig, transport: RestTransportKind) -> String {
69 match (transport, config.rest_port) {
70 (RestTransportKind::Disabled, _) | (_, None) => "disabled".to_string(),
71 (RestTransportKind::Plaintext, Some(port)) => {
72 format!("http://{}:{port}/readyz", http_host_for_bind(&config.ip))
73 }
74 (RestTransportKind::Tls, Some(port)) => {
75 format!("https://{}:{port}/readyz", http_host_for_bind(&config.ip))
76 }
77 }
78}
79
80pub(super) fn log_startup_summary(
81 config: &RuntimeConfig,
82 listen_addr: &str,
83 bridge: &GatewayBridge,
84 rest_transport: RestTransportKind,
85) {
86 let status = bridge.snapshot_status();
87 let brokers_total = status.brokers.len();
88 let brokers_connected = status
89 .brokers
90 .iter()
91 .filter(|broker| broker.connected)
92 .count();
93 let attribution = status.login.attribution.as_deref().unwrap_or("unknown");
94 let readyz = readyz_url(config, rest_transport);
95 let websocket_addr = optional_surface_addr(&config.ip, config.websocket_port);
96 let rest_addr = optional_surface_addr(&config.ip, config.rest_port);
97 let grpc_addr = optional_surface_addr(&config.ip, config.grpc_port);
98 let telnet_addr = optional_surface_addr(&config.telnet_ip, config.telnet_port);
99 let uid_fp = status
100 .login
101 .user_id
102 .map(futu_core::log_redact::uid_log_fingerprint)
103 .unwrap_or_else(|| "uid-none".to_string());
104
105 tracing::info!(
106 marker = "startup_summary",
107 version = status.version,
108 uid_fp = %uid_fp,
109 attribution,
110 platform_connected = status.platform.connected,
111 brokers_connected,
112 brokers_total,
113 ftapi_addr = %listen_addr,
114 rest_addr = %rest_addr,
115 grpc_addr = %grpc_addr,
116 websocket_addr = %websocket_addr,
117 telnet_addr = %telnet_addr,
118 readyz = %readyz,
119 "startup summary: version={} uid_fp={} attribution={} brokers={}/{} ftapi={} rest={} grpc={} ws={} telnet={} readyz={}",
120 status.version,
121 uid_fp,
122 attribution,
123 brokers_connected,
124 brokers_total,
125 listen_addr,
126 rest_addr,
127 grpc_addr,
128 websocket_addr,
129 telnet_addr,
130 readyz
131 );
132}