1use std::{
2 collections::{BTreeMap, HashMap, HashSet},
3 fmt::{self, Display, Formatter},
4 net::SocketAddr,
5};
6
7use prettytable::{Row, Table, cell, row};
8use time::format_description;
9use x509_parser::time::ASN1Time;
10
11use super::command::FilteredHistogram;
12use crate::{
13 AsString,
14 proto::{
15 DisplayError,
16 command::{
17 AggregatedMetrics, AvailableMetrics, CertificateAndKey, CertificateSummary,
18 CertificatesWithFingerprints, ClusterMetrics, CustomHttpAnswers, Event, EventKind,
19 FilteredMetrics, HealthChecksList, HttpEndpoint, HttpListenerConfig,
20 HttpsListenerConfig, ListOfCertificatesByAddress, ListedFrontends, ListenersList,
21 MetricDetailStatus, ProtobufEndpoint, QueryCertificatesFilters, RequestCounts,
22 Response, ResponseContent, ResponseStatus, RunState, SocketAddress, TlsVersion,
23 WorkerInfos, WorkerMetrics, WorkerResponses, filtered_metrics, protobuf_endpoint,
24 request::RequestType, response_content::ContentType,
25 },
26 },
27};
28
29impl Display for CertificateAndKey {
30 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
31 let versions = self.versions.iter().fold(String::new(), |acc, tls_v| {
32 acc + " "
33 + match TlsVersion::try_from(*tls_v) {
34 Ok(v) => v.as_str_name(),
35 Err(_) => "",
36 }
37 });
38 write!(
39 f,
40 "\tcertificate: {}\n\tcertificate_chain: {:?}\n\tkey: {}\n\tTLS versions: {}\n\tnames: {:?}",
41 self.certificate,
42 self.certificate_chain,
43 self.key,
44 versions,
45 concatenate_vector(&self.names)
46 )
47 }
48}
49
50impl Display for CertificateSummary {
51 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
52 write!(f, "{}:\t{}", self.fingerprint, self.domain)
53 }
54}
55
56impl Display for QueryCertificatesFilters {
57 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
58 if let Some(d) = self.domain.clone() {
59 write!(f, "domain:{d}")
60 } else if let Some(fp) = self.fingerprint.clone() {
61 write!(f, "fingerprint:{fp}")
62 } else {
63 write!(f, "all certificates")
64 }
65 }
66}
67
68pub fn concatenate_vector(vec: &[String]) -> String {
69 vec.join(", ")
70}
71
72pub fn format_request_type(request_type: &RequestType) -> &str {
73 match request_type {
74 RequestType::SaveState(_) => "SaveState",
75 RequestType::LoadState(_) => "LoadState",
76 RequestType::CountRequests(_) => "CountRequests",
77 RequestType::ListWorkers(_) => "ListWorkers",
78 RequestType::ListFrontends(_) => "ListFrontends",
79 RequestType::ListListeners(_) => "ListListeners",
80 RequestType::LaunchWorker(_) => "LaunchWorker",
81 RequestType::UpgradeMain(_) => "UpgradeMain",
82 RequestType::UpgradeWorker(_) => "UpgradeWorker",
83 RequestType::SubscribeEvents(_) => "SubscribeEvents",
84 RequestType::ReloadConfiguration(_) => "ReloadConfiguration",
85 RequestType::Status(_) => "Status",
86 RequestType::AddCluster(_) => "AddCluster",
87 RequestType::RemoveCluster(_) => "RemoveCluster",
88 RequestType::AddHttpFrontend(_) => "AddHttpFrontend",
89 RequestType::RemoveHttpFrontend(_) => "RemoveHttpFrontend",
90 RequestType::AddHttpsFrontend(_) => "AddHttpsFrontend",
91 RequestType::RemoveHttpsFrontend(_) => "RemoveHttpsFrontend",
92 RequestType::AddCertificate(_) => "AddCertificate",
93 RequestType::ReplaceCertificate(_) => "ReplaceCertificate",
94 RequestType::RemoveCertificate(_) => "RemoveCertificate",
95 RequestType::AddTcpFrontend(_) => "AddTcpFrontend",
96 RequestType::RemoveTcpFrontend(_) => "RemoveTcpFrontend",
97 RequestType::AddBackend(_) => "AddBackend",
98 RequestType::RemoveBackend(_) => "RemoveBackend",
99 RequestType::AddHttpListener(_) => "AddHttpListener",
100 RequestType::AddHttpsListener(_) => "AddHttpsListener",
101 RequestType::AddTcpListener(_) => "AddTcpListener",
102 RequestType::AddUdpListener(_) => "AddUdpListener",
103 RequestType::UpdateUdpListener(_) => "UpdateUdpListener",
104 RequestType::AddUdpFrontend(_) => "AddUdpFrontend",
105 RequestType::RemoveUdpFrontend(_) => "RemoveUdpFrontend",
106 RequestType::RemoveListener(_) => "RemoveListener",
107 RequestType::ActivateListener(_) => "ActivateListener",
108 RequestType::DeactivateListener(_) => "DeactivateListener",
109 RequestType::QueryClusterById(_) => "QueryClusterById",
110 RequestType::QueryClustersByDomain(_) => "QueryClustersByDomain",
111 RequestType::QueryClustersHashes(_) => "QueryClustersHashes",
112 RequestType::QueryMetrics(_) => "QueryMetrics",
113 RequestType::SoftStop(_) => "SoftStop",
114 RequestType::HardStop(_) => "HardStop",
115 RequestType::ConfigureMetrics(_) => "ConfigureMetrics",
116 RequestType::Logging(_) => "Logging",
117 RequestType::ReturnListenSockets(_) => "ReturnListenSockets",
118 RequestType::QueryCertificatesFromTheState(_) => "QueryCertificatesFromTheState",
119 RequestType::QueryCertificatesFromWorkers(_) => "QueryCertificatesFromWorkers",
120 RequestType::UpdateHttpListener(_) => "UpdateHttpListener",
121 RequestType::UpdateHttpsListener(_) => "UpdateHttpsListener",
122 RequestType::UpdateTcpListener(_) => "UpdateTcpListener",
123 RequestType::SetMaxConnectionsPerIp(_) => "SetMaxConnectionsPerIp",
124 RequestType::QueryMaxConnectionsPerIp(_) => "QueryMaxConnectionsPerIp",
125 RequestType::SetHealthCheck(_) => "SetHealthCheck",
126 RequestType::RemoveHealthCheck(_) => "RemoveHealthCheck",
127 RequestType::QueryHealthChecks(_) => "QueryHealthChecks",
128 RequestType::SetMetricDetail(_) => "SetMetricDetail",
129 }
130}
131
132pub fn print_json_response<T: ::serde::Serialize>(input: &T) -> Result<(), DisplayError> {
133 let pretty_json = serde_json::to_string_pretty(&input).map_err(DisplayError::Json)?;
134 println!("{pretty_json}");
135 Ok(())
136}
137
138impl Response {
139 pub fn display(&self, json: bool) -> Result<(), DisplayError> {
140 match self.status() {
141 ResponseStatus::Ok => {
142 if !json {
144 println!("Success: {}", self.message)
145 }
146 }
147 ResponseStatus::Failure => println!("Failure: {}", self.message),
148 ResponseStatus::Processing => {
149 return Err(DisplayError::WrongResponseType(
150 "ResponseStatus::Processing".to_string(),
151 ));
152 }
153 }
154
155 match &self.content {
156 Some(content) => content.display(json),
157 None => {
158 if json {
159 println!("{{}}");
160 } else {
161 println!("No content");
162 }
163 Ok(())
164 }
165 }
166 }
167}
168
169impl ResponseContent {
170 fn display(&self, json: bool) -> Result<(), DisplayError> {
171 let content_type = match &self.content_type {
172 Some(content_type) => content_type,
173 None => {
174 println!("No content");
175 return Ok(());
176 }
177 };
178
179 if json {
180 return print_json_response(&content_type);
181 }
182
183 match content_type {
184 ContentType::Workers(worker_infos) => print_status(worker_infos),
185 ContentType::Metrics(aggr_metrics) => print_metrics(aggr_metrics),
186 ContentType::FrontendList(frontends) => print_frontends(frontends),
187 ContentType::ListenersList(listeners) => print_listeners(listeners),
188 ContentType::WorkerMetrics(worker_metrics) => print_worker_metrics(worker_metrics),
189 ContentType::AvailableMetrics(list) => print_available_metrics(list),
190 ContentType::RequestCounts(request_counts) => print_request_counts(request_counts),
191 ContentType::CertificatesWithFingerprints(certs) => {
192 print_certificates_with_validity(certs)
193 }
194 ContentType::WorkerResponses(worker_responses) => {
195 if worker_responses.contain_cluster_infos() {
197 print_cluster_infos(worker_responses)
198 } else if worker_responses.contain_cluster_hashes() {
199 print_cluster_hashes(worker_responses)
200 } else {
201 print_responses_by_worker(worker_responses, json)
202 }
203 }
204 ContentType::Clusters(_) | ContentType::ClusterHashes(_) => Ok(()), ContentType::CertificatesByAddress(certs) => print_certificates_by_address(certs),
206 ContentType::MetricDetailStatus(status) => print_metric_detail_status(status),
207 ContentType::MaxConnectionsPerIpLimit(limit_info) => {
208 if limit_info.limit == 0 {
209 println!("Max connections per (cluster, source-IP): unlimited (0)");
210 } else {
211 println!(
212 "Max connections per (cluster, source-IP): {}",
213 limit_info.limit
214 );
215 }
216 Ok(())
217 }
218 ContentType::HealthChecksList(list) => print_health_checks(list),
219 ContentType::Event(_event) => Ok(()), ContentType::WorkerMetricDetailStatus(_) => Ok(()),
228 }
229 }
230}
231
232impl WorkerResponses {
233 fn contain_cluster_infos(&self) -> bool {
234 for (_worker_id, response) in self.map.iter() {
235 if let Some(content_type) = &response.content_type {
236 if matches!(content_type, ContentType::Clusters(_)) {
237 return true;
238 }
239 }
240 }
241 false
242 }
243
244 fn contain_cluster_hashes(&self) -> bool {
245 for (_worker_id, response) in self.map.iter() {
246 if let Some(content_type) = &response.content_type {
247 if matches!(content_type, ContentType::ClusterHashes(_)) {
248 return true;
249 }
250 }
251 }
252 false
253 }
254}
255
256pub fn print_status(worker_infos: &WorkerInfos) -> Result<(), DisplayError> {
257 let mut table = Table::new();
258 table.set_format(*prettytable::format::consts::FORMAT_BOX_CHARS);
259 table.add_row(row!["worker id", "pid", "run state"]);
260
261 let mut sorted_infos = worker_infos.vec.clone();
262 sorted_infos.sort_by_key(|worker| worker.id);
263
264 for worker_info in &sorted_infos {
265 let row = row!(
266 worker_info.id,
267 worker_info.pid,
268 RunState::try_from(worker_info.run_state)
269 .map_err(DisplayError::DecodeError)?
270 .as_str_name()
271 );
272 table.add_row(row);
273 }
274
275 table.printstd();
276 Ok(())
277}
278
279pub fn print_metrics(aggregated_metrics: &AggregatedMetrics) -> Result<(), DisplayError> {
280 println!("\nMAIN PROCESS\n============");
282 print_proxy_metrics(&aggregated_metrics.main);
283
284 if !aggregated_metrics.proxying.is_empty() {
285 println!("\nPROXYING\n============");
286 print_proxy_metrics(&aggregated_metrics.proxying);
287 }
288
289 for (worker_id, worker) in aggregated_metrics.workers.iter() {
291 if !worker.clusters.is_empty() && !worker.proxy.is_empty() {
292 println!("\nWorker {worker_id}\n=========");
293 print_worker_metrics(worker)?;
294 }
295 }
296
297 if !aggregated_metrics.clusters.is_empty() {
299 println!("\nClusters\n=======");
300 print_cluster_metrics(&aggregated_metrics.clusters);
301 }
302
303 Ok(())
304}
305
306fn print_proxy_metrics(proxy_metrics: &BTreeMap<String, FilteredMetrics>) {
307 let filtered = filter_metrics(proxy_metrics);
308 print_gauges_and_counts(&filtered);
309 print_percentiles(&filtered);
310 print_histograms(&filtered);
311}
312
313fn print_worker_metrics(worker_metrics: &WorkerMetrics) -> Result<(), DisplayError> {
314 print_proxy_metrics(&worker_metrics.proxy);
315 print_cluster_metrics(&worker_metrics.clusters);
316
317 Ok(())
318}
319
320fn print_cluster_metrics(cluster_metrics: &BTreeMap<String, ClusterMetrics>) {
321 for (cluster_id, cluster_metrics_data) in cluster_metrics.iter() {
322 println!("\nCluster {cluster_id}\n--------");
323
324 let filtered = filter_metrics(&cluster_metrics_data.cluster);
325 print_gauges_and_counts(&filtered);
326 print_percentiles(&filtered);
327 print_histograms(&filtered);
328
329 let mut backend_metric_acc: HashMap<String, BTreeMap<String, FilteredMetrics>> =
331 HashMap::new();
332
333 for backend in &cluster_metrics_data.backends {
334 for (metric_name, value) in &backend.metrics {
335 backend_metric_acc
336 .entry(backend.backend_id.clone())
337 .and_modify(|map| {
338 map.insert(metric_name.clone(), value.clone());
339 })
340 .or_insert(BTreeMap::from([(metric_name.clone(), value.clone())]));
341 }
342 }
343
344 for (backend_id, metrics) in backend_metric_acc {
345 println!("\n{cluster_id}/{backend_id}\n--------");
346 let filtered = filter_metrics(&metrics);
347 print_gauges_and_counts(&filtered);
348 print_percentiles(&filtered);
349 print_histograms(&filtered);
350 }
351 }
352}
353
354fn filter_metrics(
355 metrics: &BTreeMap<String, FilteredMetrics>,
356) -> BTreeMap<String, FilteredMetrics> {
357 let mut filtered_metrics = BTreeMap::new();
358
359 for (metric_key, filtered_value) in metrics.iter() {
360 filtered_metrics.insert(
361 metric_key.replace('\t', ".").to_string(),
362 filtered_value.clone(),
363 );
364 }
365 filtered_metrics
366}
367
368fn print_gauges_and_counts(filtered_metrics: &BTreeMap<String, FilteredMetrics>) {
369 let mut titles: Vec<String> = filtered_metrics
370 .iter()
371 .filter_map(|(title, filtered_data)| match filtered_data.inner {
372 Some(filtered_metrics::Inner::Count(_)) | Some(filtered_metrics::Inner::Gauge(_)) => {
373 Some(title.to_owned())
374 }
375 _ => None,
376 })
377 .collect();
378
379 titles.sort();
381
382 if titles.is_empty() {
383 return;
384 }
385
386 let mut table = Table::new();
387 table.set_format(*prettytable::format::consts::FORMAT_BOX_CHARS);
388
389 table.set_titles(Row::new(vec![cell!(""), cell!("gauge"), cell!("count")]));
390
391 for title in titles {
392 let mut row = vec![cell!(title)];
393 match filtered_metrics.get(&title) {
394 Some(filtered_metrics) => match filtered_metrics.inner {
395 Some(filtered_metrics::Inner::Count(c)) => {
396 row.push(cell!(""));
397 row.push(cell!(c))
398 }
399 Some(filtered_metrics::Inner::Gauge(c)) => {
400 row.push(cell!(c));
401 row.push(cell!(""))
402 }
403 _ => {}
404 },
405 _ => row.push(cell!("")),
406 }
407 table.add_row(Row::new(row));
408 }
409
410 table.printstd();
411}
412
413fn print_percentiles(filtered_metrics: &BTreeMap<String, FilteredMetrics>) {
414 let mut percentile_titles: Vec<String> = filtered_metrics
415 .iter()
416 .filter_map(|(title, filtered_data)| match filtered_data.inner.clone() {
417 Some(filtered_metrics::Inner::Percentiles(_)) => Some(title.to_owned()),
418 _ => None,
419 })
420 .collect();
421
422 percentile_titles.sort();
424
425 if percentile_titles.is_empty() {
426 return;
427 }
428
429 let mut percentile_table = Table::new();
430 percentile_table.set_format(*prettytable::format::consts::FORMAT_BOX_CHARS);
431
432 percentile_table.set_titles(Row::new(vec![
433 cell!("Percentiles"),
434 cell!("samples"),
435 cell!("p50"),
436 cell!("p90"),
437 cell!("p99"),
438 cell!("p99.9"),
439 cell!("p99.99"),
440 cell!("p99.999"),
441 cell!("p100"),
442 ]));
443
444 for title in percentile_titles {
445 if let Some(FilteredMetrics {
446 inner: Some(filtered_metrics::Inner::Percentiles(percentiles)),
447 }) = filtered_metrics.get(&title)
448 {
449 percentile_table.add_row(Row::new(vec![
450 cell!(title),
451 cell!(percentiles.samples),
452 cell!(percentiles.p_50),
453 cell!(percentiles.p_90),
454 cell!(percentiles.p_99),
455 cell!(percentiles.p_99_9),
456 cell!(percentiles.p_99_99),
457 cell!(percentiles.p_99_999),
458 cell!(percentiles.p_100),
459 ]));
460 } else {
461 println!("Something went VERY wrong here");
462 }
463 }
464
465 percentile_table.printstd();
466}
467
468fn print_histograms(filtered_metrics: &BTreeMap<String, FilteredMetrics>) {
469 let histograms: BTreeMap<String, FilteredHistogram> = filtered_metrics
470 .iter()
471 .filter_map(|(name, metric)| match metric.inner.clone() {
472 Some(filtered_metrics::Inner::Histogram(hist)) => Some((name.to_owned(), hist)),
473 _ => None,
474 })
475 .collect();
476
477 let mut histogram_titles: Vec<String> = histograms.keys().map(ToOwned::to_owned).collect();
478 histogram_titles.sort();
479 if histogram_titles.is_empty() {
480 return;
481 }
482
483 let mut histogram_table = Table::new();
484 histogram_table.set_format(*prettytable::format::consts::FORMAT_BOX_CHARS);
485
486 let mut first_row = Row::new(vec![cell!("Histograms (ms)"), cell!("sum"), cell!("count")]);
487
488 let biggest_hist_length = histograms
489 .values()
490 .map(|hist| hist.buckets.len())
491 .max()
492 .unwrap_or(0);
493
494 for exponent in 0..biggest_hist_length {
496 first_row.add_cell(cell!(format!("{}", (1 << exponent) - 1)));
497 }
498 histogram_table.set_titles(first_row);
499
500 for title in histogram_titles {
501 if let Some(hist) = histograms.get(&title) {
502 let trimmed_name = title.strip_suffix("_histogram").unwrap_or_default();
503 let mut row = Row::new(vec![
504 cell!(trimmed_name),
505 cell!(hist.sum),
506 cell!(hist.count),
507 ]);
508 let mut last_bucket_count = 0;
510 for bucket in &hist.buckets {
511 row.add_cell(cell!(bucket.count - last_bucket_count));
512 last_bucket_count = bucket.count;
513 }
514 histogram_table.add_row(row);
515 }
516 }
517
518 histogram_table.printstd();
519}
520
521fn print_available_metrics(available_metrics: &AvailableMetrics) -> Result<(), DisplayError> {
522 println!("Available metrics on the proxy level:");
523 for metric_name in &available_metrics.proxy_metrics {
524 println!("\t{metric_name}");
525 }
526 println!("Available metrics on the cluster level:");
527 for metric_name in &available_metrics.cluster_metrics {
528 println!("\t{metric_name}");
529 }
530 Ok(())
531}
532
533fn print_frontends(frontends: &ListedFrontends) -> Result<(), DisplayError> {
534 trace!(" We received this frontends to display {:#?}", frontends);
535 if !frontends.http_frontends.is_empty() {
537 let mut table = Table::new();
538 table.set_format(*prettytable::format::consts::FORMAT_BOX_CHARS);
539 table.add_row(row!["HTTP frontends "]);
540 table.add_row(row![
541 "cluster_id",
542 "address",
543 "hostname",
544 "path",
545 "method",
546 "position",
547 "tags"
548 ]);
549 for http_frontend in frontends.http_frontends.iter() {
550 table.add_row(row!(
551 http_frontend
552 .cluster_id
553 .clone()
554 .unwrap_or("Deny".to_owned()),
555 http_frontend.address.to_string(),
556 http_frontend.hostname.to_string(),
557 format!("{:?}", http_frontend.path),
558 format!("{:?}", http_frontend.method),
559 format!("{:?}", http_frontend.position),
560 format_tags_to_string(&http_frontend.tags)
561 ));
562 }
563 table.printstd();
564 }
565
566 if !frontends.https_frontends.is_empty() {
568 let mut table = Table::new();
569 table.set_format(*prettytable::format::consts::FORMAT_BOX_CHARS);
570 table.add_row(row!["HTTPS frontends"]);
571 table.add_row(row![
572 "cluster_id",
573 "address",
574 "hostname",
575 "path",
576 "method",
577 "position",
578 "tags"
579 ]);
580 for https_frontend in frontends.https_frontends.iter() {
581 table.add_row(row!(
582 https_frontend
583 .cluster_id
584 .clone()
585 .unwrap_or("Deny".to_owned()),
586 https_frontend.address.to_string(),
587 https_frontend.hostname.to_string(),
588 format!("{:?}", https_frontend.path),
589 format!("{:?}", https_frontend.method),
590 format!("{:?}", https_frontend.position),
591 format_tags_to_string(&https_frontend.tags)
592 ));
593 }
594 table.printstd();
595 }
596
597 if !frontends.tcp_frontends.is_empty() {
599 let mut table = Table::new();
600 table.set_format(*prettytable::format::consts::FORMAT_BOX_CHARS);
601 table.add_row(row!["TCP frontends "]);
602 table.add_row(row!["Cluster ID", "address", "tags"]);
603 for tcp_frontend in frontends.tcp_frontends.iter() {
604 table.add_row(row!(
605 tcp_frontend.cluster_id,
606 tcp_frontend.address,
607 format_tags_to_string(&tcp_frontend.tags)
608 ));
609 }
610 table.printstd();
611 }
612
613 if !frontends.udp_frontends.is_empty() {
615 let mut table = Table::new();
616 table.set_format(*prettytable::format::consts::FORMAT_BOX_CHARS);
617 table.add_row(row!["UDP frontends "]);
618 table.add_row(row!["Cluster ID", "address", "tags"]);
619 for udp_frontend in frontends.udp_frontends.iter() {
620 table.add_row(row!(
621 udp_frontend.cluster_id,
622 udp_frontend.address,
623 format_tags_to_string(&udp_frontend.tags)
624 ));
625 }
626 table.printstd();
627 }
628 Ok(())
629}
630
631pub fn print_listeners(listeners_list: &ListenersList) -> Result<(), DisplayError> {
632 println!("\nHTTP LISTENERS\n================");
633
634 for (_, http_listener) in listeners_list.http_listeners.iter() {
635 println!("{http_listener}");
636 }
637
638 println!("\nHTTPS LISTENERS\n================");
639
640 for (_, https_listener) in listeners_list.https_listeners.iter() {
641 println!("{https_listener}");
642 }
643
644 println!("\nTCP LISTENERS\n================");
645
646 if !listeners_list.tcp_listeners.is_empty() {
647 let mut table = Table::new();
648 table.set_format(*prettytable::format::consts::FORMAT_BOX_CHARS);
649 table.add_row(row!["TCP frontends"]);
650 table.add_row(row![
651 "socket address",
652 "public address",
653 "expect proxy",
654 "front timeout",
655 "back timeout",
656 "connect timeout",
657 "activated"
658 ]);
659 for (_, tcp_listener) in listeners_list.tcp_listeners.iter() {
660 table.add_row(row![
661 format!("{:?}", tcp_listener.address),
662 format!("{:?}", tcp_listener.public_address),
663 tcp_listener.expect_proxy,
664 tcp_listener.front_timeout,
665 tcp_listener.back_timeout,
666 tcp_listener.connect_timeout,
667 tcp_listener.active,
668 ]);
669 }
670 table.printstd();
671 }
672
673 println!("\nUDP LISTENERS\n================");
674
675 if !listeners_list.udp_listeners.is_empty() {
676 let mut table = Table::new();
677 table.set_format(*prettytable::format::consts::FORMAT_BOX_CHARS);
678 table.add_row(row!["UDP listeners"]);
679 table.add_row(row![
682 "socket address",
683 "public address",
684 "front timeout",
685 "back timeout",
686 "max rx datagram size",
687 "max flows",
688 "activated"
689 ]);
690 for (_, udp_listener) in listeners_list.udp_listeners.iter() {
691 table.add_row(row![
692 format!("{:?}", udp_listener.address),
693 format!("{:?}", udp_listener.public_address),
694 udp_listener.front_timeout,
695 udp_listener.back_timeout,
696 udp_listener.max_rx_datagram_size,
697 udp_listener.max_flows,
698 udp_listener.active,
699 ]);
700 }
701 table.printstd();
702 }
703 Ok(())
704}
705
706fn print_cluster_infos(worker_responses: &WorkerResponses) -> Result<(), DisplayError> {
707 let mut cluster_table = create_cluster_table(
708 vec!["id", "sticky_session", "https_redirect"],
709 &worker_responses.map,
710 );
711
712 let mut frontend_table =
713 create_cluster_table(vec!["id", "hostname", "path"], &worker_responses.map);
714
715 let mut https_frontend_table =
716 create_cluster_table(vec!["id", "hostname", "path"], &worker_responses.map);
717
718 let mut tcp_frontend_table = create_cluster_table(vec!["id", "address"], &worker_responses.map);
719
720 let mut udp_frontend_table = create_cluster_table(vec!["id", "address"], &worker_responses.map);
721
722 let mut backend_table = create_cluster_table(
723 vec!["backend id", "IP address", "Backup"],
724 &worker_responses.map,
725 );
726
727 let worker_ids: HashSet<&String> = worker_responses.map.keys().collect();
728
729 let mut cluster_infos = BTreeMap::new();
730 let mut http_frontends = BTreeMap::new();
731 let mut https_frontends = BTreeMap::new();
732 let mut tcp_frontends = BTreeMap::new();
733 let mut udp_frontends = BTreeMap::new();
734 let mut backends = BTreeMap::new();
735
736 for (worker_id, response_content) in worker_responses.map.iter() {
737 if let Some(ContentType::Clusters(clusters)) = &response_content.content_type {
738 for cluster in clusters.vec.iter() {
739 if cluster.configuration.is_some() {
740 let entry = cluster_infos.entry(cluster).or_insert(Vec::new());
741 entry.push(worker_id.to_owned());
742 }
743
744 for frontend in cluster.http_frontends.iter() {
745 let entry = http_frontends.entry(frontend).or_insert(Vec::new());
746 entry.push(worker_id.to_owned());
747 }
748
749 for frontend in cluster.https_frontends.iter() {
750 let entry = https_frontends.entry(frontend).or_insert(Vec::new());
751 entry.push(worker_id.to_owned());
752 }
753
754 for frontend in cluster.tcp_frontends.iter() {
755 let entry = tcp_frontends.entry(frontend).or_insert(Vec::new());
756 entry.push(worker_id.to_owned());
757 }
758
759 for frontend in cluster.udp_frontends.iter() {
760 let entry = udp_frontends.entry(frontend).or_insert(Vec::new());
761 entry.push(worker_id.to_owned());
762 }
763
764 for backend in cluster.backends.iter() {
765 let entry = backends.entry(backend).or_insert(Vec::new());
766 entry.push(worker_id.to_owned());
767 }
768 }
769 }
770 }
771
772 if cluster_infos.is_empty() {
773 println!("no cluster found");
774 return Ok(());
775 }
776
777 println!("Cluster level configuration:\n");
778
779 for (cluster_info, workers_the_cluster_is_present_on) in cluster_infos.iter() {
780 let mut row = Vec::new();
781 row.push(cell!(
782 cluster_info
783 .configuration
784 .as_ref()
785 .map(|conf| conf.cluster_id.to_owned())
786 .unwrap_or_else(|| String::from("None"))
787 ));
788 row.push(cell!(
789 cluster_info
790 .configuration
791 .as_ref()
792 .map(|conf| conf.sticky_session)
793 .unwrap_or_else(|| false)
794 ));
795 row.push(cell!(
796 cluster_info
797 .configuration
798 .as_ref()
799 .map(|conf| conf.https_redirect)
800 .unwrap_or_else(|| false)
801 ));
802
803 for worker in workers_the_cluster_is_present_on {
804 if worker_ids.contains(worker) {
805 row.push(cell!("X"));
806 } else {
807 row.push(cell!(""));
808 }
809 }
810
811 cluster_table.add_row(Row::new(row));
812 }
813
814 cluster_table.printstd();
815
816 println!("\nHTTP frontends configuration for:\n");
817
818 for (key, values) in http_frontends.iter() {
819 let mut row = Vec::new();
820 match &key.cluster_id {
821 Some(cluster_id) => row.push(cell!(cluster_id)),
822 None => row.push(cell!("-")),
823 }
824 row.push(cell!(key.hostname));
825 row.push(cell!(key.path));
826
827 for val in values.iter() {
828 if worker_ids.contains(val) {
829 row.push(cell!("X"));
830 } else {
831 row.push(cell!(""));
832 }
833 }
834
835 frontend_table.add_row(Row::new(row));
836 }
837
838 frontend_table.printstd();
839
840 println!("\nHTTPS frontends configuration for:\n");
841
842 for (key, values) in https_frontends.iter() {
843 let mut row = Vec::new();
844 match &key.cluster_id {
845 Some(cluster_id) => row.push(cell!(cluster_id)),
846 None => row.push(cell!("-")),
847 }
848 row.push(cell!(key.hostname));
849 row.push(cell!(key.path));
850
851 for val in values.iter() {
852 if worker_ids.contains(val) {
853 row.push(cell!("X"));
854 } else {
855 row.push(cell!(""));
856 }
857 }
858
859 https_frontend_table.add_row(Row::new(row));
860 }
861
862 https_frontend_table.printstd();
863
864 println!("\nTCP frontends configuration:\n");
865
866 for (key, values) in tcp_frontends.iter() {
867 let mut row = vec![cell!(key.cluster_id), cell!(format!("{}", key.address))];
868
869 for val in values.iter() {
870 if worker_ids.contains(val) {
871 row.push(cell!(String::from("X")));
872 } else {
873 row.push(cell!(String::from("")));
874 }
875 }
876
877 tcp_frontend_table.add_row(Row::new(row));
878 }
879
880 tcp_frontend_table.printstd();
881
882 println!("\nUDP frontends configuration:\n");
883
884 for (key, values) in udp_frontends.iter() {
885 let mut row = vec![cell!(key.cluster_id), cell!(format!("{}", key.address))];
886
887 for val in values.iter() {
888 if worker_ids.contains(val) {
889 row.push(cell!(String::from("X")));
890 } else {
891 row.push(cell!(String::from("")));
892 }
893 }
894
895 udp_frontend_table.add_row(Row::new(row));
896 }
897
898 udp_frontend_table.printstd();
899
900 println!("\nbackends configuration:\n");
901
902 for (key, values) in backends.iter() {
903 let mut row = vec![
904 cell!(key.backend_id),
905 cell!(format!("{}", key.address)),
906 cell!(
907 key.backup
908 .map(|b| if b { "X" } else { "" })
909 .unwrap_or_else(|| "")
910 ),
911 ];
912
913 for val in values {
914 if worker_ids.contains(&val) {
915 row.push(cell!("X"));
916 } else {
917 row.push(cell!(""));
918 }
919 }
920
921 backend_table.add_row(Row::new(row));
922 }
923
924 backend_table.printstd();
925
926 Ok(())
927}
928
929fn print_cluster_hashes(worker_responses: &WorkerResponses) -> Result<(), DisplayError> {
931 let mut clusters_table = Table::new();
932 clusters_table.set_format(*prettytable::format::consts::FORMAT_BOX_CHARS);
933 let mut header = vec![cell!("cluster id")];
934 for worker_id in worker_responses.map.keys() {
935 header.push(cell!(format!("worker {}", worker_id)));
936 }
937 header.push(cell!("desynchronized"));
938 clusters_table.add_row(Row::new(header));
939
940 let mut cluster_hashes = HashMap::new();
941
942 for response_content in worker_responses.map.values() {
943 if let Some(ContentType::ClusterHashes(hashes)) = &response_content.content_type {
944 for (cluster_id, hash) in hashes.map.iter() {
945 cluster_hashes
946 .entry(cluster_id)
947 .or_insert(Vec::new())
948 .push(hash);
949 }
950 }
951 }
952
953 for (cluster_id, hashes) in cluster_hashes.iter() {
954 let mut row = vec![cell!(cluster_id)];
955 for val in hashes.iter() {
956 row.push(cell!(format!("{val}")));
957 }
958
959 let hs: HashSet<&u64> = hashes.iter().cloned().collect();
960 if hs.len() > 1 {
961 row.push(cell!("X"));
962 } else {
963 row.push(cell!(""));
964 }
965
966 clusters_table.add_row(Row::new(row));
967 }
968
969 clusters_table.printstd();
970 Ok(())
971}
972
973fn print_responses_by_worker(
974 worker_responses: &WorkerResponses,
975 json: bool,
976) -> Result<(), DisplayError> {
977 for (worker_id, content) in worker_responses.map.iter() {
978 println!("Worker {worker_id}");
979 content.display(json)?;
980 }
981
982 Ok(())
983}
984
985pub fn print_certificates_with_validity(
986 certs: &CertificatesWithFingerprints,
987) -> Result<(), DisplayError> {
988 if certs.certs.is_empty() {
989 println!("No certificates match your request.");
990 return Ok(());
991 }
992
993 let mut table = Table::new();
994 table.set_format(*prettytable::format::consts::FORMAT_CLEAN);
995 table.add_row(row![
996 "fingeprint",
997 "valid not before",
998 "valide not after",
999 "domain names",
1000 ]);
1001
1002 for (fingerprint, cert) in &certs.certs {
1003 let (_unparsed, pem_certificate) =
1004 x509_parser::pem::parse_x509_pem(cert.certificate.as_bytes())
1005 .expect("Could not parse pem certificate");
1006
1007 let x509_certificate = pem_certificate
1008 .parse_x509()
1009 .expect("Could not parse x509 certificate");
1010
1011 let validity = x509_certificate.validity();
1012
1013 table.add_row(row!(
1014 fingerprint,
1015 format_datetime(validity.not_before)?,
1016 format_datetime(validity.not_after)?,
1017 concatenate_vector(&cert.names),
1018 ));
1019 }
1020 table.printstd();
1021
1022 Ok(())
1023}
1024
1025fn print_certificates_by_address(list: &ListOfCertificatesByAddress) -> Result<(), DisplayError> {
1026 for certs in list.certificates.iter() {
1027 println!("\t{}:", certs.address);
1028
1029 for summary in certs.certificate_summaries.iter() {
1030 println!("\t\t{summary}");
1031 }
1032 }
1033 Ok(())
1034}
1035
1036fn print_metric_detail_status(status: &MetricDetailStatus) -> Result<(), DisplayError> {
1037 let mut table = Table::new();
1038 table.set_format(*prettytable::format::consts::FORMAT_BOX_CHARS);
1039 table.add_row(row![
1040 "scope",
1041 "configured",
1042 "previous effective",
1043 "effective",
1044 "active leases"
1045 ]);
1046 table.add_row(row!(
1047 "main",
1048 status.configured().as_str_name(),
1049 status.previous_effective().as_str_name(),
1050 status.effective().as_str_name(),
1051 "—",
1052 ));
1053 for (worker_id, w) in &status.workers {
1054 table.add_row(row!(
1055 format!("worker:{worker_id}"),
1056 w.configured().as_str_name(),
1057 w.previous_effective().as_str_name(),
1058 w.effective().as_str_name(),
1059 w.active_lease_count,
1060 ));
1061 }
1062 table.printstd();
1063 Ok(())
1064}
1065
1066fn print_request_counts(request_counts: &RequestCounts) -> Result<(), DisplayError> {
1067 let mut table = Table::new();
1068 table.set_format(*prettytable::format::consts::FORMAT_BOX_CHARS);
1069 table.add_row(row!["request type", "count"]);
1070
1071 for (request_type, count) in &request_counts.map {
1072 table.add_row(row!(request_type, count));
1073 }
1074 table.printstd();
1075 Ok(())
1076}
1077
1078fn print_health_checks(list: &HealthChecksList) -> Result<(), DisplayError> {
1079 if list.map.is_empty() {
1080 println!("No health checks configured.");
1081 return Ok(());
1082 }
1083
1084 let mut table = Table::new();
1085 table.set_format(*prettytable::format::consts::FORMAT_BOX_CHARS);
1086 table.add_row(row![
1087 "cluster",
1088 "uri",
1089 "interval",
1090 "timeout",
1091 "healthy threshold",
1092 "unhealthy threshold",
1093 "expected status"
1094 ]);
1095
1096 let mut entries: Vec<_> = list.map.iter().collect();
1097 entries.sort_by_key(|(id, _)| id.as_str());
1098
1099 for (cluster_id, config) in entries {
1100 let expected = if config.expected_status == 0 {
1101 "any 2xx".to_owned()
1102 } else {
1103 config.expected_status.to_string()
1104 };
1105
1106 table.add_row(row![
1107 cluster_id,
1108 config.uri,
1109 format!("{}s", config.interval),
1110 format!("{}s", config.timeout),
1111 config.healthy_threshold,
1112 config.unhealthy_threshold,
1113 expected
1114 ]);
1115 }
1116 table.printstd();
1117 Ok(())
1118}
1119
1120fn format_tags_to_string(tags: &BTreeMap<String, String>) -> String {
1121 tags.iter()
1122 .map(|(k, v)| format!("{k}={v}"))
1123 .collect::<Vec<_>>()
1124 .join(", ")
1125}
1126
1127fn list_string_vec(vec: &[String]) -> String {
1128 let mut output = String::new();
1129 for item in vec.iter() {
1130 output.push_str(item);
1131 output.push('\n');
1132 }
1133 output
1134}
1135
1136fn format_datetime(asn1_time: ASN1Time) -> Result<String, DisplayError> {
1138 let datetime = asn1_time.to_datetime();
1139
1140 let formatted = datetime
1141 .format(&format_description::well_known::Iso8601::DEFAULT)
1142 .map_err(|_| DisplayError::DateTime)?;
1143 Ok(formatted)
1144}
1145
1146fn create_cluster_table(headers: Vec<&str>, data: &BTreeMap<String, ResponseContent>) -> Table {
1159 let mut table = Table::new();
1160 table.set_format(*prettytable::format::consts::FORMAT_BOX_CHARS);
1161 let mut row_header: Vec<_> = headers.iter().map(|h| cell!(h)).collect();
1162 for ref key in data.keys() {
1163 row_header.push(cell!(&key));
1164 }
1165 table.add_row(Row::new(row_header));
1166 table
1167}
1168
1169impl Display for SocketAddress {
1170 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1171 write!(f, "{}", SocketAddr::from(*self))
1172 }
1173}
1174
1175impl Display for ProtobufEndpoint {
1176 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
1177 match &self.inner {
1178 Some(protobuf_endpoint::Inner::Http(HttpEndpoint {
1179 method,
1180 authority,
1181 path,
1182 status,
1183 ..
1184 })) => write!(
1185 f,
1186 "{} {} {} -> {}",
1187 authority.as_string_or("-"),
1188 method.as_string_or("-"),
1189 path.as_string_or("-"),
1190 status.as_string_or("-"),
1191 ),
1192 Some(protobuf_endpoint::Inner::Tcp(_)) => {
1193 write!(f, "-")
1194 }
1195 None => Ok(()),
1196 }
1197 }
1198}
1199
1200impl Display for HttpListenerConfig {
1201 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1202 let mut table = Table::new();
1203 table.set_format(*prettytable::format::consts::FORMAT_BOX_CHARS);
1204 table.add_row(row!["socket address", format!("{:?}", self.address)]);
1205 table.add_row(row!["public address", format!("{:?}", self.public_address),]);
1206 for http_answer_row in CustomHttpAnswers::to_rows(&self.http_answers) {
1207 table.add_row(http_answer_row);
1208 }
1209 table.add_row(row!["expect proxy", self.expect_proxy]);
1210 table.add_row(row!["sticky name", self.sticky_name]);
1211 table.add_row(row!["front timeout", self.front_timeout]);
1212 table.add_row(row!["back timeout", self.back_timeout]);
1213 table.add_row(row!["connect timeout", self.connect_timeout]);
1214 table.add_row(row!["request timeout", self.request_timeout]);
1215 table.add_row(row!["activated", self.active]);
1216 add_h2_flood_rows(
1217 &mut table,
1218 &self.h2_max_rst_stream_per_window,
1219 &self.h2_max_ping_per_window,
1220 &self.h2_max_settings_per_window,
1221 &self.h2_max_empty_data_per_window,
1222 &self.h2_max_window_update_stream0_per_window,
1223 &self.h2_max_continuation_frames,
1224 &self.h2_max_glitch_count,
1225 &self.h2_max_rst_stream_lifetime,
1226 &self.h2_max_rst_stream_abusive_lifetime,
1227 &self.h2_max_rst_stream_emitted_lifetime,
1228 &self.h2_max_header_list_size,
1229 &self.h2_max_header_table_size,
1230 &self.h2_max_header_fields,
1231 );
1232 add_h2_connection_rows(
1233 &mut table,
1234 &self.h2_initial_connection_window,
1235 &self.h2_max_concurrent_streams,
1236 &self.h2_stream_shrink_ratio,
1237 );
1238 if let Some(v) = &self.h2_stream_idle_timeout_seconds {
1239 table.add_row(row!["h2 stream idle timeout (seconds)", v]);
1240 }
1241 if let Some(v) = &self.h2_graceful_shutdown_deadline_seconds {
1242 table.add_row(row!["h2 graceful shutdown deadline (seconds)", v]);
1243 }
1244 if let Some(v) = &self.sozu_id_header {
1245 table.add_row(row!["Sozu-Id correlation header name", v]);
1246 }
1247 if let Some(v) = self.elide_x_real_ip {
1248 table.add_row(row!["elide X-Real-IP (anti-spoof)", v]);
1249 }
1250 if let Some(v) = self.send_x_real_ip {
1251 table.add_row(row!["send X-Real-IP (peer IP)", v]);
1252 }
1253 write!(f, "{table}")
1254 }
1255}
1256
1257impl Display for HttpsListenerConfig {
1258 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1259 let mut table = Table::new();
1260 table.set_format(*prettytable::format::consts::FORMAT_BOX_CHARS);
1261 let mut tls_versions = String::new();
1262 for tls_version in self.versions.iter() {
1263 tls_versions.push_str(&format!("{tls_version:?}\n"));
1264 }
1265
1266 table.add_row(row!["socket address", format!("{:?}", self.address)]);
1267 table.add_row(row!["public address", format!("{:?}", self.public_address)]);
1268 for http_answer_row in CustomHttpAnswers::to_rows(&self.http_answers) {
1269 table.add_row(http_answer_row);
1270 }
1271 table.add_row(row!["versions", tls_versions]);
1272 table.add_row(row!["cipher list", list_string_vec(&self.cipher_list),]);
1273 table.add_row(row!["cipher suites", list_string_vec(&self.cipher_suites),]);
1274 table.add_row(row![
1275 "signature algorithms",
1276 list_string_vec(&self.signature_algorithms),
1277 ]);
1278 table.add_row(row!["groups list", list_string_vec(&self.groups_list),]);
1279 table.add_row(row![
1280 "alpn protocols",
1281 list_string_vec(&self.alpn_protocols),
1282 ]);
1283 table.add_row(row!["key", format!("{:?}", self.key),]);
1284 table.add_row(row!["expect proxy", self.expect_proxy]);
1285 table.add_row(row!["sticky name", self.sticky_name]);
1286 table.add_row(row!["front timeout", self.front_timeout]);
1287 table.add_row(row!["back timeout", self.back_timeout]);
1288 table.add_row(row!["connect timeout", self.connect_timeout]);
1289 table.add_row(row!["request timeout", self.request_timeout]);
1290 table.add_row(row!["activated", self.active]);
1291 add_h2_flood_rows(
1292 &mut table,
1293 &self.h2_max_rst_stream_per_window,
1294 &self.h2_max_ping_per_window,
1295 &self.h2_max_settings_per_window,
1296 &self.h2_max_empty_data_per_window,
1297 &self.h2_max_window_update_stream0_per_window,
1298 &self.h2_max_continuation_frames,
1299 &self.h2_max_glitch_count,
1300 &self.h2_max_rst_stream_lifetime,
1301 &self.h2_max_rst_stream_abusive_lifetime,
1302 &self.h2_max_rst_stream_emitted_lifetime,
1303 &self.h2_max_header_list_size,
1304 &self.h2_max_header_table_size,
1305 &self.h2_max_header_fields,
1306 );
1307 add_h2_connection_rows(
1308 &mut table,
1309 &self.h2_initial_connection_window,
1310 &self.h2_max_concurrent_streams,
1311 &self.h2_stream_shrink_ratio,
1312 );
1313 if let Some(v) = self.strict_sni_binding {
1314 table.add_row(row!["strict sni binding", v]);
1315 }
1316 if let Some(v) = self.disable_http11 {
1317 table.add_row(row!["disable http/1.1", v]);
1318 }
1319 if let Some(v) = &self.h2_stream_idle_timeout_seconds {
1320 table.add_row(row!["h2 stream idle timeout (seconds)", v]);
1321 }
1322 if let Some(v) = &self.h2_graceful_shutdown_deadline_seconds {
1323 table.add_row(row!["h2 graceful shutdown deadline (seconds)", v]);
1324 }
1325 if let Some(v) = &self.sozu_id_header {
1326 table.add_row(row!["Sozu-Id correlation header name", v]);
1327 }
1328 if let Some(v) = self.elide_x_real_ip {
1329 table.add_row(row!["elide X-Real-IP (anti-spoof)", v]);
1330 }
1331 if let Some(v) = self.send_x_real_ip {
1332 table.add_row(row!["send X-Real-IP (peer IP)", v]);
1333 }
1334 write!(f, "{table}")
1335 }
1336}
1337
1338#[allow(clippy::too_many_arguments)]
1341fn add_h2_flood_rows(
1342 table: &mut Table,
1343 max_rst_stream: &Option<u32>,
1344 max_ping: &Option<u32>,
1345 max_settings: &Option<u32>,
1346 max_empty_data: &Option<u32>,
1347 max_window_update_stream0: &Option<u32>,
1348 max_continuation: &Option<u32>,
1349 max_glitch: &Option<u32>,
1350 max_rst_stream_lifetime: &Option<u64>,
1351 max_rst_stream_abusive_lifetime: &Option<u64>,
1352 max_rst_stream_emitted_lifetime: &Option<u64>,
1353 max_header_list_size: &Option<u32>,
1354 max_header_table_size: &Option<u32>,
1355 max_header_fields: &Option<u32>,
1356) {
1357 if let Some(v) = max_rst_stream {
1358 table.add_row(row!["h2 max rst_stream/window", v]);
1359 }
1360 if let Some(v) = max_ping {
1361 table.add_row(row!["h2 max ping/window", v]);
1362 }
1363 if let Some(v) = max_settings {
1364 table.add_row(row!["h2 max settings/window", v]);
1365 }
1366 if let Some(v) = max_empty_data {
1367 table.add_row(row!["h2 max empty_data/window", v]);
1368 }
1369 if let Some(v) = max_window_update_stream0 {
1370 table.add_row(row!["h2 max window_update stream0/window", v]);
1371 }
1372 if let Some(v) = max_continuation {
1373 table.add_row(row!["h2 max continuation frames", v]);
1374 }
1375 if let Some(v) = max_glitch {
1376 table.add_row(row!["h2 max glitch count", v]);
1377 }
1378 if let Some(v) = max_rst_stream_lifetime {
1379 table.add_row(row!["h2 max rst_stream lifetime", v]);
1380 }
1381 if let Some(v) = max_rst_stream_abusive_lifetime {
1382 table.add_row(row!["h2 max rst_stream abusive lifetime", v]);
1383 }
1384 if let Some(v) = max_rst_stream_emitted_lifetime {
1385 table.add_row(row!["h2 max rst_stream emitted lifetime", v]);
1386 }
1387 if let Some(v) = max_header_list_size {
1388 table.add_row(row!["h2 max header list size", v]);
1389 }
1390 if let Some(v) = max_header_table_size {
1391 table.add_row(row!["h2 max header table size", v]);
1392 }
1393 if let Some(v) = max_header_fields {
1394 table.add_row(row!["h2 max header fields", v]);
1395 }
1396}
1397
1398fn add_h2_connection_rows(
1401 table: &mut Table,
1402 window: &Option<u32>,
1403 max_streams: &Option<u32>,
1404 shrink_ratio: &Option<u32>,
1405) {
1406 if let Some(v) = window {
1407 table.add_row(row!["h2 initial connection window", v]);
1408 }
1409 if let Some(v) = max_streams {
1410 table.add_row(row!["h2 max concurrent streams", v]);
1411 }
1412 if let Some(v) = shrink_ratio {
1413 table.add_row(row!["h2 stream shrink ratio", v]);
1414 }
1415}
1416
1417impl CustomHttpAnswers {
1418 fn to_rows(option: &Option<Self>) -> Vec<Row> {
1419 let mut rows = Vec::new();
1420 if let Some(answers) = option {
1421 if let Some(a) = &answers.answer_301 {
1422 rows.push(row!("301", a));
1423 }
1424 if let Some(a) = &answers.answer_400 {
1425 rows.push(row!("400", a));
1426 }
1427 if let Some(a) = &answers.answer_404 {
1428 rows.push(row!("404", a));
1429 }
1430 if let Some(a) = &answers.answer_408 {
1431 rows.push(row!("408", a));
1432 }
1433 if let Some(a) = &answers.answer_413 {
1434 rows.push(row!("413", a));
1435 }
1436 if let Some(a) = &answers.answer_421 {
1437 rows.push(row!("421", a));
1438 }
1439 if let Some(a) = &answers.answer_502 {
1440 rows.push(row!("502", a));
1441 }
1442 if let Some(a) = &answers.answer_503 {
1443 rows.push(row!("503", a));
1444 }
1445 if let Some(a) = &answers.answer_504 {
1446 rows.push(row!("504", a));
1447 }
1448 if let Some(a) = &answers.answer_507 {
1449 rows.push(row!("507", a));
1450 }
1451 }
1452 rows
1453 }
1454}
1455
1456impl Display for Event {
1457 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1458 let kind = match self.kind() {
1459 EventKind::BackendDown => "backend down",
1460 EventKind::BackendUp => "backend up",
1461 EventKind::NoAvailableBackends => "no available backends",
1462 EventKind::RemovedBackendHasNoConnections => "removed backend has no connections",
1463 EventKind::ClusterAdded => "cluster added",
1464 EventKind::ClusterRemoved => "cluster removed",
1465 EventKind::FrontendAdded => "frontend added",
1466 EventKind::FrontendRemoved => "frontend removed",
1467 EventKind::CertificateAdded => "certificate added",
1468 EventKind::CertificateRemoved => "certificate removed",
1469 EventKind::CertificateReplaced => "certificate replaced",
1470 EventKind::ListenerActivated => "listener activated",
1471 EventKind::ListenerDeactivated => "listener deactivated",
1472 EventKind::ConfigurationReloaded => "configuration reloaded",
1473 EventKind::WorkerKilled => "worker killed",
1474 EventKind::WorkerRelaunched => "worker relaunched",
1475 EventKind::LoggingLevelChanged => "logging level changed",
1476 EventKind::MetricsConfigured => "metrics configured",
1477 EventKind::ListenerUpdated => "listener updated",
1478 EventKind::StateLoaded => "state loaded",
1479 EventKind::StateSaved => "state saved",
1480 EventKind::ListenerAdded => "listener added",
1481 EventKind::ListenerRemoved => "listener removed",
1482 EventKind::SozuStopRequested => "stop requested",
1483 EventKind::MainUpgraded => "main upgraded",
1484 EventKind::WorkerUpgraded => "worker upgraded",
1485 EventKind::EventsSubscribed => "events subscribed",
1486 EventKind::HealthCheckHealthy => "health check: backend healthy",
1487 EventKind::HealthCheckUnhealthy => "health check: backend unhealthy",
1488 EventKind::ClusterRecovered => "cluster recovered",
1489 EventKind::MetricDetailChanged => "metric detail changed",
1490 };
1491 let address = match &self.address {
1492 Some(a) => a.to_string(),
1493 None => String::new(),
1494 };
1495 write!(
1496 f,
1497 "{}, backend={}, cluster={}, address={}",
1498 kind,
1499 self.backend_id(),
1500 self.cluster_id(),
1501 address,
1502 )
1503 }
1504}