1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
use gethostname::gethostname;
use opentelemetry::KeyValue;
use opentelemetry_otlp::{Protocol, WithExportConfig};
use opentelemetry_sdk::trace::{self, Sampler};
use opentelemetry_sdk::Resource;
use std::time::Duration;
use tracing::Subscriber;
use tracing_subscriber::Registry;
use tracing_subscriber::{prelude::*, EnvFilter};

pub const MAX_EVENTS_PER_SPAN: u32 = 64 * 1024;
pub const MAX_ATTRIBUTES_PER_SPAN: u32 = 128;

// TODO: this is coming back later
// #[allow(dead_code)]
// pub fn init_metrics() -> metrics::Result<MeterProvider> {
//     let export_config = opentelemetry_otlp::ExportConfig {
//         endpoint: "http://localhost:4318/v1/metrics".to_string(),
//         ..opentelemetry_otlp::ExportConfig::default()
//     };
//     opentelemetry_otlp::new_pipeline()
//         .metrics(opentelemetry_sdk::runtime::Tokio)
//         .with_exporter(
//             opentelemetry_otlp::new_exporter()
//                 .http()
//                 .with_export_config(export_config),
//         )
//         .build()
// }

/// This does all the startup things for the logging pipeline
pub fn start_logging_pipeline(
    otlp_endpoint: &Option<String>,
    log_filter: crate::LogLevel,
    service_name: &'static str,
) -> Result<Box<dyn Subscriber + Send + Sync>, String> {
    let forest_filter: EnvFilter = EnvFilter::builder()
        .with_default_directive(log_filter.into())
        .from_env_lossy();

    // TODO: work out how to do metrics things
    match otlp_endpoint {
        Some(endpoint) => {
            // adding these filters because when you close out the process the OTLP comms layer is NOISY
            let forest_filter = forest_filter
                .add_directive(
                    "tonic=info"
                        .parse()
                        .expect("Failed to set tonic logging to info"),
                )
                .add_directive("h2=info".parse().expect("Failed to set h2 logging to info"))
                .add_directive(
                    "hyper=info"
                        .parse()
                        .expect("Failed to set hyper logging to info"),
                );
            let forest_layer = tracing_forest::ForestLayer::default().with_filter(forest_filter);
            let t_filter: EnvFilter = EnvFilter::builder()
                .with_default_directive(log_filter.into())
                .from_env_lossy();

            let tracer = opentelemetry_otlp::new_pipeline().tracing().with_exporter(
                opentelemetry_otlp::new_exporter()
                    .tonic()
                    .with_endpoint(endpoint)
                    .with_timeout(Duration::from_secs(5))
                    .with_protocol(Protocol::HttpBinary),
            );

            // this env var gets set at build time, if we can pull it, add it to the metadata
            let git_rev = match option_env!("KANIDM_PKG_COMMIT_REV") {
                Some(rev) => format!("-{}", rev),
                None => "".to_string(),
            };

            let version = format!("{}{}", env!("CARGO_PKG_VERSION"), git_rev);
            let hostname = gethostname();
            let hostname = hostname.to_string_lossy();
            let hostname = hostname.to_lowercase();

            let tracer = tracer
                .with_trace_config(
                    trace::config()
                        // we want *everything!*
                        .with_sampler(Sampler::AlwaysOn)
                        .with_max_events_per_span(MAX_EVENTS_PER_SPAN)
                        .with_max_attributes_per_span(MAX_ATTRIBUTES_PER_SPAN)
                        .with_resource(Resource::new(vec![
                            KeyValue::new("service.name", service_name),
                            KeyValue::new("service.version", version),
                            KeyValue::new("host.name", hostname),
                            // TODO: it'd be really nice to be able to set the instance ID here, from the server UUID so we know *which* instance on this host is logging
                        ])),
                )
                .install_batch(opentelemetry::runtime::Tokio)
                .map_err(|err| {
                    let err = format!("Failed to start OTLP pipeline: {:?}", err);
                    eprintln!("{}", err);
                    err
                })?;
            // Create a tracing layer with the configured tracer;
            let telemetry = tracing_opentelemetry::layer()
                .with_tracer(tracer)
                .with_threads(true)
                .with_filter(t_filter);

            Ok(Box::new(
                Registry::default().with(forest_layer).with(telemetry),
            ))
        }
        None => {
            let forest_layer = tracing_forest::ForestLayer::default().with_filter(forest_filter);
            Ok(Box::new(Registry::default().with(forest_layer)))
        }
    }
}

/// This helps with cleanly shutting down the tracing/logging providers when done,
/// so we don't lose traces.
pub struct TracingPipelineGuard {}

impl Drop for TracingPipelineGuard {
    fn drop(&mut self) {
        opentelemetry::global::shutdown_tracer_provider();
        opentelemetry::global::shutdown_logger_provider();
        eprintln!("Logging pipeline completed shutdown");
    }
}