itsi-server 0.1.1 → 0.1.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of itsi-server might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +7 -0
- data/Cargo.lock +4417 -0
- data/Cargo.toml +7 -0
- data/README.md +4 -0
- data/Rakefile +8 -1
- data/_index.md +6 -0
- data/exe/itsi +94 -45
- data/ext/itsi_error/Cargo.toml +2 -0
- data/ext/itsi_error/src/from.rs +68 -0
- data/ext/itsi_error/src/lib.rs +18 -34
- data/ext/itsi_error/target/debug/build/clang-sys-da71b0344e568175/out/common.rs +355 -0
- data/ext/itsi_error/target/debug/build/clang-sys-da71b0344e568175/out/dynamic.rs +276 -0
- data/ext/itsi_error/target/debug/build/clang-sys-da71b0344e568175/out/macros.rs +49 -0
- data/ext/itsi_error/target/debug/build/rb-sys-49f554618693db24/out/bindings-0.9.110-mri-arm64-darwin23-3.4.2.rs +8865 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-1mmt5sux7jb0i/s-h510z7m8v9-0bxu7yd.lock +0 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-2vn3jey74oiw0/s-h5113n0e7e-1v5qzs6.lock +0 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-37uv9dicz7awp/s-h510ykifhe-0tbnep2.lock +0 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-37uv9dicz7awp/s-h510yyocpj-0tz7ug7.lock +0 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-37uv9dicz7awp/s-h510z0xc8g-14ol18k.lock +0 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-3g5qf4y7d54uj/s-h5113n0e7d-1trk8on.lock +0 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-3lpfftm45d3e2/s-h510z7m8r3-1pxp20o.lock +0 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-3o4qownhl3d7n/s-h510ykifek-1uxasnk.lock +0 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-3o4qownhl3d7n/s-h510yyocki-11u37qm.lock +0 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-3o4qownhl3d7n/s-h510z0xc93-0pmy0zm.lock +0 -0
- data/ext/itsi_instrument_entry/Cargo.toml +15 -0
- data/ext/itsi_instrument_entry/src/lib.rs +31 -0
- data/ext/itsi_rb_helpers/Cargo.toml +3 -0
- data/ext/itsi_rb_helpers/src/heap_value.rs +139 -0
- data/ext/itsi_rb_helpers/src/lib.rs +140 -10
- data/ext/itsi_rb_helpers/target/debug/build/clang-sys-da71b0344e568175/out/common.rs +355 -0
- data/ext/itsi_rb_helpers/target/debug/build/clang-sys-da71b0344e568175/out/dynamic.rs +276 -0
- data/ext/itsi_rb_helpers/target/debug/build/clang-sys-da71b0344e568175/out/macros.rs +49 -0
- data/ext/itsi_rb_helpers/target/debug/build/rb-sys-eb9ed4ff3a60f995/out/bindings-0.9.110-mri-arm64-darwin23-3.4.2.rs +8865 -0
- data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-040pxg6yhb3g3/s-h5113n7a1b-03bwlt4.lock +0 -0
- data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-131g1u4dzkt1a/s-h51113xnh3-1eik1ip.lock +0 -0
- data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-131g1u4dzkt1a/s-h5111704jj-0g4rj8x.lock +0 -0
- data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-1q2d3drtxrzs5/s-h5113n79yl-0bxcqc5.lock +0 -0
- data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-374a9h7ovycj0/s-h51113xoox-10de2hp.lock +0 -0
- data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-374a9h7ovycj0/s-h5111704w7-0vdq7gq.lock +0 -0
- data/ext/itsi_scheduler/Cargo.toml +24 -0
- data/ext/itsi_scheduler/src/itsi_scheduler/io_helpers.rs +56 -0
- data/ext/itsi_scheduler/src/itsi_scheduler/io_waiter.rs +44 -0
- data/ext/itsi_scheduler/src/itsi_scheduler/timer.rs +44 -0
- data/ext/itsi_scheduler/src/itsi_scheduler.rs +308 -0
- data/ext/itsi_scheduler/src/lib.rs +38 -0
- data/ext/itsi_server/Cargo.lock +2956 -0
- data/ext/itsi_server/Cargo.toml +73 -13
- data/ext/itsi_server/extconf.rb +1 -1
- data/ext/itsi_server/src/env.rs +43 -0
- data/ext/itsi_server/src/lib.rs +100 -40
- data/ext/itsi_server/src/ruby_types/itsi_body_proxy/big_bytes.rs +109 -0
- data/ext/itsi_server/src/ruby_types/itsi_body_proxy/mod.rs +141 -0
- data/ext/itsi_server/src/ruby_types/itsi_grpc_request.rs +147 -0
- data/ext/itsi_server/src/ruby_types/itsi_grpc_response.rs +19 -0
- data/ext/itsi_server/src/ruby_types/itsi_grpc_stream/mod.rs +216 -0
- data/ext/itsi_server/src/ruby_types/itsi_http_request.rs +282 -0
- data/ext/itsi_server/src/ruby_types/itsi_http_response.rs +388 -0
- data/ext/itsi_server/src/ruby_types/itsi_server/file_watcher.rs +225 -0
- data/ext/itsi_server/src/ruby_types/itsi_server/itsi_server_config.rs +355 -0
- data/ext/itsi_server/src/ruby_types/itsi_server.rs +82 -0
- data/ext/itsi_server/src/ruby_types/mod.rs +55 -0
- data/ext/itsi_server/src/server/bind.rs +75 -31
- data/ext/itsi_server/src/server/bind_protocol.rs +37 -0
- data/ext/itsi_server/src/server/byte_frame.rs +32 -0
- data/ext/itsi_server/src/server/cache_store.rs +74 -0
- data/ext/itsi_server/src/server/io_stream.rs +104 -0
- data/ext/itsi_server/src/server/itsi_service.rs +172 -0
- data/ext/itsi_server/src/server/lifecycle_event.rs +12 -0
- data/ext/itsi_server/src/server/listener.rs +332 -132
- data/ext/itsi_server/src/server/middleware_stack/middleware.rs +153 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/allow_list.rs +47 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/auth_api_key.rs +58 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/auth_basic.rs +82 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/auth_jwt.rs +321 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/cache_control.rs +139 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/compression.rs +300 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/cors.rs +287 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/deny_list.rs +48 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/error_response.rs +127 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/etag.rs +191 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/grpc_service.rs +72 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/header_interpretation.rs +85 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/intrusion_protection.rs +195 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/log_requests.rs +82 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/mod.rs +82 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/proxy.rs +216 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/rate_limit.rs +124 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/redirect.rs +76 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/request_headers.rs +43 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/response_headers.rs +34 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/ruby_app.rs +93 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/static_assets.rs +162 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/string_rewrite.rs +158 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/token_source.rs +12 -0
- data/ext/itsi_server/src/server/middleware_stack/mod.rs +315 -0
- data/ext/itsi_server/src/server/mod.rs +15 -2
- data/ext/itsi_server/src/server/process_worker.rs +229 -0
- data/ext/itsi_server/src/server/rate_limiter.rs +565 -0
- data/ext/itsi_server/src/server/request_job.rs +11 -0
- data/ext/itsi_server/src/server/serve_strategy/cluster_mode.rs +337 -0
- data/ext/itsi_server/src/server/serve_strategy/mod.rs +30 -0
- data/ext/itsi_server/src/server/serve_strategy/single_mode.rs +421 -0
- data/ext/itsi_server/src/server/signal.rs +93 -0
- data/ext/itsi_server/src/server/static_file_server.rs +984 -0
- data/ext/itsi_server/src/server/thread_worker.rs +444 -0
- data/ext/itsi_server/src/server/tls/locked_dir_cache.rs +132 -0
- data/ext/itsi_server/src/server/tls.rs +187 -60
- data/ext/itsi_server/src/server/types.rs +43 -0
- data/ext/itsi_tracing/Cargo.toml +5 -0
- data/ext/itsi_tracing/src/lib.rs +225 -7
- data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-0994n8rpvvt9m/s-h510hfz1f6-1kbycmq.lock +0 -0
- data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-0bob7bf4yq34i/s-h5113125h5-0lh4rag.lock +0 -0
- data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-2fcodulrxbbxo/s-h510h2infk-0hp5kjw.lock +0 -0
- data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-2iak63r1woi1l/s-h510h2in4q-0kxfzw1.lock +0 -0
- data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-2kk4qj9gn5dg2/s-h5113124kv-0enwon2.lock +0 -0
- data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-2mwo0yas7dtw4/s-h510hfz1ha-1udgpei.lock +0 -0
- data/lib/itsi/http_request.rb +87 -0
- data/lib/itsi/http_response.rb +39 -0
- data/lib/itsi/server/Itsi.rb +119 -0
- data/lib/itsi/server/config/dsl.rb +506 -0
- data/lib/itsi/server/config.rb +131 -0
- data/lib/itsi/server/default_app/default_app.rb +38 -0
- data/lib/itsi/server/default_app/index.html +91 -0
- data/lib/itsi/server/grpc_interface.rb +213 -0
- data/lib/itsi/server/rack/handler/itsi.rb +27 -0
- data/lib/itsi/server/rack_interface.rb +94 -0
- data/lib/itsi/server/scheduler_interface.rb +21 -0
- data/lib/itsi/server/scheduler_mode.rb +10 -0
- data/lib/itsi/server/signal_trap.rb +29 -0
- data/lib/itsi/server/version.rb +1 -1
- data/lib/itsi/server.rb +90 -9
- data/lib/itsi/standard_headers.rb +86 -0
- metadata +122 -31
- data/ext/itsi_server/src/request/itsi_request.rs +0 -143
- data/ext/itsi_server/src/request/mod.rs +0 -1
- data/ext/itsi_server/src/server/itsi_ca/itsi_ca.crt +0 -32
- data/ext/itsi_server/src/server/itsi_ca/itsi_ca.key +0 -52
- data/ext/itsi_server/src/server/itsi_server.rs +0 -182
- data/ext/itsi_server/src/server/transfer_protocol.rs +0 -23
- data/ext/itsi_server/src/stream_writer/mod.rs +0 -21
- data/lib/itsi/request.rb +0 -39
| @@ -1,20 +1,115 @@ | |
| 1 1 | 
             
            use base64::{engine::general_purpose, Engine as _};
         | 
| 2 2 | 
             
            use itsi_error::Result;
         | 
| 3 | 
            -
            use itsi_tracing:: | 
| 4 | 
            -
            use  | 
| 3 | 
            +
            use itsi_tracing::info;
         | 
| 4 | 
            +
            use locked_dir_cache::LockedDirCache;
         | 
| 5 | 
            +
            use rcgen::{
         | 
| 6 | 
            +
                generate_simple_self_signed, CertificateParams, CertifiedKey, DnType, KeyPair, SanType,
         | 
| 7 | 
            +
            };
         | 
| 8 | 
            +
            use rustls::{
         | 
| 9 | 
            +
                pki_types::{CertificateDer, PrivateKeyDer},
         | 
| 10 | 
            +
                ClientConfig, RootCertStore,
         | 
| 11 | 
            +
            };
         | 
| 5 12 | 
             
            use rustls_pemfile::{certs, pkcs8_private_keys};
         | 
| 6 | 
            -
            use std::{ | 
| 7 | 
            -
             | 
| 8 | 
            -
             | 
| 9 | 
            -
             | 
| 10 | 
            -
             | 
| 11 | 
            -
             | 
| 12 | 
            -
             | 
| 13 | 
            -
             | 
| 14 | 
            -
             | 
| 15 | 
            -
             | 
| 16 | 
            -
             | 
| 17 | 
            -
             | 
| 13 | 
            +
            use std::{
         | 
| 14 | 
            +
                collections::HashMap,
         | 
| 15 | 
            +
                fs,
         | 
| 16 | 
            +
                io::{BufReader, Error},
         | 
| 17 | 
            +
                sync::Arc,
         | 
| 18 | 
            +
            };
         | 
| 19 | 
            +
            use tokio::sync::Mutex;
         | 
| 20 | 
            +
            use tokio_rustls::{rustls::ServerConfig, TlsAcceptor};
         | 
| 21 | 
            +
            use tokio_rustls_acme::{AcmeAcceptor, AcmeConfig, AcmeState};
         | 
| 22 | 
            +
             | 
| 23 | 
            +
            use crate::env::{
         | 
| 24 | 
            +
                ITSI_ACME_CACHE_DIR, ITSI_ACME_CA_PEM_PATH, ITSI_ACME_CONTACT_EMAIL, ITSI_ACME_DIRECTORY_URL,
         | 
| 25 | 
            +
                ITSI_LOCAL_CA_DIR,
         | 
| 26 | 
            +
            };
         | 
| 27 | 
            +
            mod locked_dir_cache;
         | 
| 28 | 
            +
             | 
| 29 | 
            +
            #[derive(Clone)]
         | 
| 30 | 
            +
            pub enum ItsiTlsAcceptor {
         | 
| 31 | 
            +
                Manual(TlsAcceptor),
         | 
| 32 | 
            +
                Automatic(
         | 
| 33 | 
            +
                    AcmeAcceptor,
         | 
| 34 | 
            +
                    Arc<Mutex<AcmeState<Error>>>,
         | 
| 35 | 
            +
                    Arc<ServerConfig>,
         | 
| 36 | 
            +
                ),
         | 
| 37 | 
            +
            }
         | 
| 38 | 
            +
             | 
| 39 | 
            +
            /// Generates a TLS configuration based on either :
         | 
| 40 | 
            +
            /// * Input "cert" and "key" options (either paths or Base64-encoded strings) or
         | 
| 41 | 
            +
            /// * Performs automatic certificate generation/retrieval. Generated certs use an internal self-signed Isti CA.
         | 
| 42 | 
            +
            ///
         | 
| 43 | 
            +
            /// If a non-local host or optional domain parameter is provided,
         | 
| 44 | 
            +
            /// an automated certificate will attempt to be fetched using let's encrypt.
         | 
| 45 | 
            +
            pub fn configure_tls(
         | 
| 46 | 
            +
                host: &str,
         | 
| 47 | 
            +
                query_params: &HashMap<String, String>,
         | 
| 48 | 
            +
            ) -> Result<ItsiTlsAcceptor> {
         | 
| 49 | 
            +
                let domains = query_params
         | 
| 50 | 
            +
                    .get("domains")
         | 
| 51 | 
            +
                    .map(|v| v.split(',').map(String::from).collect::<Vec<_>>())
         | 
| 52 | 
            +
                    .or_else(|| query_params.get("domain").map(|v| vec![v.to_string()]));
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                if query_params.get("cert").is_some_and(|c| c == "acme") {
         | 
| 55 | 
            +
                    if let Some(domains) = domains {
         | 
| 56 | 
            +
                        let directory_url = &*ITSI_ACME_DIRECTORY_URL;
         | 
| 57 | 
            +
                        info!(
         | 
| 58 | 
            +
                            domains = format!("{:?}", domains),
         | 
| 59 | 
            +
                            directory_url, "Requesting acme cert"
         | 
| 60 | 
            +
                        );
         | 
| 61 | 
            +
                        let acme_contact_email = query_params
         | 
| 62 | 
            +
                            .get("acme_email")
         | 
| 63 | 
            +
                            .map(|s| s.to_string())
         | 
| 64 | 
            +
                            .or_else(|| (*ITSI_ACME_CONTACT_EMAIL).as_ref().ok().map(|s| s.to_string()))
         | 
| 65 | 
            +
                            .ok_or_else(|| itsi_error::ItsiError::ArgumentError(
         | 
| 66 | 
            +
                                "acme_email query param or ITSI_ACME_CONTACT_EMAIL must be set before you can auto-generate let's encrypt certificates".to_string(),
         | 
| 67 | 
            +
                            ))?;
         | 
| 68 | 
            +
             | 
| 69 | 
            +
                        let acme_config = AcmeConfig::new(domains)
         | 
| 70 | 
            +
                            .contact([format!("mailto:{}", acme_contact_email)])
         | 
| 71 | 
            +
                            .cache(LockedDirCache::new(&*ITSI_ACME_CACHE_DIR))
         | 
| 72 | 
            +
                            .directory(directory_url);
         | 
| 73 | 
            +
             | 
| 74 | 
            +
                        let acme_state = if let Ok(ca_pem_path) = &*ITSI_ACME_CA_PEM_PATH {
         | 
| 75 | 
            +
                            let mut root_cert_store = RootCertStore::empty();
         | 
| 76 | 
            +
             | 
| 77 | 
            +
                            let ca_pem = fs::read(ca_pem_path).expect("failed to read CA pem file");
         | 
| 78 | 
            +
                            let mut ca_reader = BufReader::new(&ca_pem[..]);
         | 
| 79 | 
            +
                            let der_certs: Vec<CertificateDer> = certs(&mut ca_reader)
         | 
| 80 | 
            +
                                .collect::<std::result::Result<Vec<CertificateDer>, _>>()
         | 
| 81 | 
            +
                                .map_err(|e| {
         | 
| 82 | 
            +
                                    itsi_error::ItsiError::ArgumentError(format!(
         | 
| 83 | 
            +
                                        "Invalid ACME CA Pem path {:?}",
         | 
| 84 | 
            +
                                        e
         | 
| 85 | 
            +
                                    ))
         | 
| 86 | 
            +
                                })?;
         | 
| 87 | 
            +
                            root_cert_store.add_parsable_certificates(der_certs);
         | 
| 88 | 
            +
             | 
| 89 | 
            +
                            let client_config = ClientConfig::builder()
         | 
| 90 | 
            +
                                .with_root_certificates(root_cert_store)
         | 
| 91 | 
            +
                                .with_no_client_auth();
         | 
| 92 | 
            +
                            acme_config
         | 
| 93 | 
            +
                                .client_tls_config(Arc::new(client_config))
         | 
| 94 | 
            +
                                .state()
         | 
| 95 | 
            +
                        } else {
         | 
| 96 | 
            +
                            acme_config.state()
         | 
| 97 | 
            +
                        };
         | 
| 98 | 
            +
             | 
| 99 | 
            +
                        let mut rustls_config = ServerConfig::builder()
         | 
| 100 | 
            +
                            .with_no_client_auth()
         | 
| 101 | 
            +
                            .with_cert_resolver(acme_state.resolver());
         | 
| 102 | 
            +
             | 
| 103 | 
            +
                        rustls_config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()];
         | 
| 104 | 
            +
             | 
| 105 | 
            +
                        let acceptor = acme_state.acceptor();
         | 
| 106 | 
            +
                        return Ok(ItsiTlsAcceptor::Automatic(
         | 
| 107 | 
            +
                            acceptor,
         | 
| 108 | 
            +
                            Arc::new(Mutex::new(acme_state)),
         | 
| 109 | 
            +
                            Arc::new(rustls_config),
         | 
| 110 | 
            +
                        ));
         | 
| 111 | 
            +
                    }
         | 
| 112 | 
            +
                }
         | 
| 18 113 | 
             
                let (certs, key) = if let (Some(cert_path), Some(key_path)) =
         | 
| 19 114 | 
             
                    (query_params.get("cert"), query_params.get("key"))
         | 
| 20 115 | 
             
                {
         | 
| @@ -22,41 +117,20 @@ pub fn configure_tls(host: &str, query_params: &HashMap<String, String>) -> Resu | |
| 22 117 | 
             
                    let certs = load_certs(cert_path);
         | 
| 23 118 | 
             
                    let key = load_private_key(key_path);
         | 
| 24 119 | 
             
                    (certs, key)
         | 
| 25 | 
            -
                } else if query_params
         | 
| 26 | 
            -
                    .get("cert")
         | 
| 27 | 
            -
                    .map(|v| v == "auto")
         | 
| 28 | 
            -
                    .unwrap_or(false)
         | 
| 29 | 
            -
                {
         | 
| 30 | 
            -
                    let domain_param = query_params.get("domain");
         | 
| 31 | 
            -
                    let host_string = host.to_string();
         | 
| 32 | 
            -
                    let domain = domain_param.or_else(|| {
         | 
| 33 | 
            -
                        if host_string != "localhost" {
         | 
| 34 | 
            -
                            Some(&host_string)
         | 
| 35 | 
            -
                        } else {
         | 
| 36 | 
            -
                            None
         | 
| 37 | 
            -
                        }
         | 
| 38 | 
            -
                    });
         | 
| 39 | 
            -
             | 
| 40 | 
            -
                    if let Some(domain) = domain {
         | 
| 41 | 
            -
                        retrieve_acme_cert(domain)?
         | 
| 42 | 
            -
                    } else {
         | 
| 43 | 
            -
                        generate_ca_signed_cert(host)?
         | 
| 44 | 
            -
                    }
         | 
| 45 120 | 
             
                } else {
         | 
| 46 | 
            -
                    generate_ca_signed_cert(host)?
         | 
| 121 | 
            +
                    generate_ca_signed_cert(domains.unwrap_or(vec![host.to_owned()]))?
         | 
| 47 122 | 
             
                };
         | 
| 48 123 |  | 
| 49 124 | 
             
                let mut config = ServerConfig::builder()
         | 
| 50 | 
            -
                    .with_safe_defaults()
         | 
| 51 125 | 
             
                    .with_no_client_auth()
         | 
| 52 126 | 
             
                    .with_single_cert(certs, key)
         | 
| 53 127 | 
             
                    .expect("Failed to build TLS config");
         | 
| 54 128 |  | 
| 55 129 | 
             
                config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()];
         | 
| 56 | 
            -
                Ok(config)
         | 
| 130 | 
            +
                Ok(ItsiTlsAcceptor::Manual(TlsAcceptor::from(Arc::new(config))))
         | 
| 57 131 | 
             
            }
         | 
| 58 132 |  | 
| 59 | 
            -
            pub fn load_certs(path: &str) -> Vec< | 
| 133 | 
            +
            pub fn load_certs(path: &str) -> Vec<CertificateDer<'static>> {
         | 
| 60 134 | 
             
                let data = if let Some(stripped) = path.strip_prefix("base64:") {
         | 
| 61 135 | 
             
                    general_purpose::STANDARD
         | 
| 62 136 | 
             
                        .decode(stripped)
         | 
| @@ -74,14 +148,20 @@ pub fn load_certs(path: &str) -> Vec<Certificate> { | |
| 74 148 | 
             
                        })
         | 
| 75 149 | 
             
                        .collect::<Result<_>>()
         | 
| 76 150 | 
             
                        .expect("Failed to parse certificate file");
         | 
| 77 | 
            -
                    certs_der | 
| 151 | 
            +
                    certs_der
         | 
| 152 | 
            +
                        .into_iter()
         | 
| 153 | 
            +
                        .map(|vec| {
         | 
| 154 | 
            +
                            // Convert the owned Vec<u8> into a CertificateDer and force 'static.
         | 
| 155 | 
            +
                            unsafe { std::mem::transmute(CertificateDer::from(vec)) }
         | 
| 156 | 
            +
                        })
         | 
| 157 | 
            +
                        .collect()
         | 
| 78 158 | 
             
                } else {
         | 
| 79 | 
            -
                    vec![ | 
| 159 | 
            +
                    vec![CertificateDer::from(data)]
         | 
| 80 160 | 
             
                }
         | 
| 81 161 | 
             
            }
         | 
| 82 162 |  | 
| 83 163 | 
             
            /// Loads a private key from a file or Base64.
         | 
| 84 | 
            -
            pub fn load_private_key(path: &str) ->  | 
| 164 | 
            +
            pub fn load_private_key(path: &str) -> PrivateKeyDer<'static> {
         | 
| 85 165 | 
             
                let key_data = if let Some(stripped) = path.strip_prefix("base64:") {
         | 
| 86 166 | 
             
                    general_purpose::STANDARD
         | 
| 87 167 | 
             
                        .decode(stripped)
         | 
| @@ -100,39 +180,86 @@ pub fn load_private_key(path: &str) -> PrivateKey { | |
| 100 180 | 
             
                        .collect::<Result<_>>()
         | 
| 101 181 | 
             
                        .expect("Failed to parse private key");
         | 
| 102 182 | 
             
                    if !keys.is_empty() {
         | 
| 103 | 
            -
                        return  | 
| 183 | 
            +
                        return PrivateKeyDer::try_from(keys[0].clone()).unwrap();
         | 
| 104 184 | 
             
                    }
         | 
| 105 185 | 
             
                }
         | 
| 106 | 
            -
                 | 
| 186 | 
            +
                PrivateKeyDer::try_from(key_data).unwrap()
         | 
| 107 187 | 
             
            }
         | 
| 108 188 |  | 
| 109 | 
            -
            pub fn generate_ca_signed_cert( | 
| 110 | 
            -
                 | 
| 189 | 
            +
            pub fn generate_ca_signed_cert(
         | 
| 190 | 
            +
                domains: Vec<String>,
         | 
| 191 | 
            +
            ) -> Result<(Vec<CertificateDer<'static>>, PrivateKeyDer<'static>)> {
         | 
| 192 | 
            +
                info!(
         | 
| 193 | 
            +
                    domains = format!("{}", domains.join(", ")),
         | 
| 194 | 
            +
                    "Self signed cert",
         | 
| 195 | 
            +
                );
         | 
| 196 | 
            +
                info!(
         | 
| 197 | 
            +
                    "Add {} to your system's trusted cert store to resolve certificate errors.",
         | 
| 198 | 
            +
                    format!("{}/itsi_dev_ca.crt", ITSI_LOCAL_CA_DIR.to_str().unwrap())
         | 
| 199 | 
            +
                );
         | 
| 200 | 
            +
                info!("Dev CA path can be overridden by setting env var: `ITSI_LOCAL_CA_DIR`.");
         | 
| 201 | 
            +
                let (ca_key_pem, ca_cert_pem) = get_or_create_local_dev_ca()?;
         | 
| 111 202 |  | 
| 112 | 
            -
                let ca_kp = KeyPair::from_pem( | 
| 113 | 
            -
                let  | 
| 203 | 
            +
                let ca_kp = KeyPair::from_pem(&ca_key_pem).expect("Failed to load CA key");
         | 
| 204 | 
            +
                let ca_cert = CertificateParams::from_ca_cert_pem(&ca_cert_pem)
         | 
| 205 | 
            +
                    .expect("Failed to parse embedded CA certificate")
         | 
| 206 | 
            +
                    .self_signed(&ca_kp)
         | 
| 207 | 
            +
                    .expect("Failed to self-sign embedded CA cert");
         | 
| 114 208 |  | 
| 115 | 
            -
                let  | 
| 116 | 
            -
                let ee_key = KeyPair::generate().unwrap();
         | 
| 209 | 
            +
                let ee_key = KeyPair::generate_for(&rcgen::PKCS_ECDSA_P256_SHA256).unwrap();
         | 
| 117 210 | 
             
                let mut ee_params = CertificateParams::default();
         | 
| 118 211 |  | 
| 119 | 
            -
                 | 
| 120 | 
            -
             | 
| 121 | 
            -
                 | 
| 212 | 
            +
                use std::net::IpAddr;
         | 
| 213 | 
            +
             | 
| 214 | 
            +
                ee_params.subject_alt_names = domains
         | 
| 215 | 
            +
                    .iter()
         | 
| 216 | 
            +
                    .map(|domain| {
         | 
| 217 | 
            +
                        if let Ok(ip) = domain.parse::<IpAddr>() {
         | 
| 218 | 
            +
                            SanType::IpAddress(ip)
         | 
| 219 | 
            +
                        } else {
         | 
| 220 | 
            +
                            SanType::DnsName(domain.clone().try_into().unwrap())
         | 
| 221 | 
            +
                        }
         | 
| 222 | 
            +
                    })
         | 
| 223 | 
            +
                    .collect();
         | 
| 224 | 
            +
             | 
| 122 225 | 
             
                ee_params
         | 
| 123 226 | 
             
                    .distinguished_name
         | 
| 124 | 
            -
                    .push(DnType::CommonName,  | 
| 227 | 
            +
                    .push(DnType::CommonName, domains[0].clone());
         | 
| 125 228 |  | 
| 126 229 | 
             
                ee_params.use_authority_key_identifier_extension = true;
         | 
| 127 230 |  | 
| 128 | 
            -
                let ee_cert = ee_params.signed_by(&ee_key, &ca_cert, & | 
| 231 | 
            +
                let ee_cert = ee_params.signed_by(&ee_key, &ca_cert, &ca_kp).unwrap();
         | 
| 129 232 | 
             
                let ee_cert_der = ee_cert.der().to_vec();
         | 
| 130 | 
            -
                let ee_cert =  | 
| 131 | 
            -
                 | 
| 233 | 
            +
                let ee_cert = CertificateDer::from(ee_cert_der);
         | 
| 234 | 
            +
                let ca_cert = CertificateDer::from(ca_cert.der().to_vec());
         | 
| 235 | 
            +
                Ok((
         | 
| 236 | 
            +
                    vec![ee_cert, ca_cert],
         | 
| 237 | 
            +
                    PrivateKeyDer::try_from(ee_key.serialize_der()).unwrap(),
         | 
| 238 | 
            +
                ))
         | 
| 132 239 | 
             
            }
         | 
| 133 240 |  | 
| 134 | 
            -
             | 
| 135 | 
            -
             | 
| 136 | 
            -
                 | 
| 137 | 
            -
             | 
| 241 | 
            +
            fn get_or_create_local_dev_ca() -> Result<(String, String)> {
         | 
| 242 | 
            +
                let ca_dir = &*ITSI_LOCAL_CA_DIR;
         | 
| 243 | 
            +
                fs::create_dir_all(ca_dir)?;
         | 
| 244 | 
            +
             | 
| 245 | 
            +
                let key_path = ca_dir.join("itsi_dev_ca.key");
         | 
| 246 | 
            +
                let cert_path = ca_dir.join("itsi_dev_ca.crt");
         | 
| 247 | 
            +
             | 
| 248 | 
            +
                if key_path.exists() && cert_path.exists() {
         | 
| 249 | 
            +
                    // Already have a local CA
         | 
| 250 | 
            +
                    let key_pem = fs::read_to_string(&key_path)?;
         | 
| 251 | 
            +
                    let cert_pem = fs::read_to_string(&cert_path)?;
         | 
| 252 | 
            +
             | 
| 253 | 
            +
                    Ok((key_pem, cert_pem))
         | 
| 254 | 
            +
                } else {
         | 
| 255 | 
            +
                    let subject_alt_names = vec!["dev.itsi.fyi".to_string(), "localhost".to_string()];
         | 
| 256 | 
            +
             | 
| 257 | 
            +
                    let CertifiedKey { cert, key_pair } =
         | 
| 258 | 
            +
                        generate_simple_self_signed(subject_alt_names).unwrap();
         | 
| 259 | 
            +
             | 
| 260 | 
            +
                    fs::write(&key_path, key_pair.serialize_pem())?;
         | 
| 261 | 
            +
                    fs::write(&cert_path, cert.pem())?;
         | 
| 262 | 
            +
             | 
| 263 | 
            +
                    Ok((key_pair.serialize_pem(), cert.pem()))
         | 
| 264 | 
            +
                }
         | 
| 138 265 | 
             
            }
         | 
| @@ -0,0 +1,43 @@ | |
| 1 | 
            +
            use std::convert::Infallible;
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            use bytes::Bytes;
         | 
| 4 | 
            +
            use http::{Request, Response};
         | 
| 5 | 
            +
            use http_body_util::combinators::BoxBody;
         | 
| 6 | 
            +
            use hyper::body::Incoming;
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            pub type HttpResponse = Response<BoxBody<Bytes, Infallible>>;
         | 
| 9 | 
            +
            pub type HttpRequest = Request<Incoming>;
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            pub trait RequestExt {
         | 
| 12 | 
            +
                fn content_type(&self) -> Option<&str>;
         | 
| 13 | 
            +
                fn accept(&self) -> Option<&str>;
         | 
| 14 | 
            +
                fn header(&self, header_name: &str) -> Option<&str>;
         | 
| 15 | 
            +
                fn query_param(&self, query_name: &str) -> Option<&str>;
         | 
| 16 | 
            +
            }
         | 
| 17 | 
            +
             | 
| 18 | 
            +
            impl RequestExt for HttpRequest {
         | 
| 19 | 
            +
                fn content_type(&self) -> Option<&str> {
         | 
| 20 | 
            +
                    self.headers()
         | 
| 21 | 
            +
                        .get("content-type")
         | 
| 22 | 
            +
                        .map(|hv| hv.to_str().unwrap_or(""))
         | 
| 23 | 
            +
                }
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                fn accept(&self) -> Option<&str> {
         | 
| 26 | 
            +
                    self.headers()
         | 
| 27 | 
            +
                        .get("accept")
         | 
| 28 | 
            +
                        .map(|hv| hv.to_str().unwrap_or(""))
         | 
| 29 | 
            +
                }
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                fn header(&self, header_name: &str) -> Option<&str> {
         | 
| 32 | 
            +
                    self.headers()
         | 
| 33 | 
            +
                        .get(header_name)
         | 
| 34 | 
            +
                        .map(|hv| hv.to_str().unwrap_or(""))
         | 
| 35 | 
            +
                }
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                fn query_param(&self, query_name: &str) -> Option<&str> {
         | 
| 38 | 
            +
                    self.uri()
         | 
| 39 | 
            +
                        .query()
         | 
| 40 | 
            +
                        .and_then(|query| query.split('&').find(|param| param.starts_with(query_name)))
         | 
| 41 | 
            +
                        .map(|param| param.split('=').nth(1).unwrap_or(""))
         | 
| 42 | 
            +
                }
         | 
| 43 | 
            +
            }
         | 
    
        data/ext/itsi_tracing/Cargo.toml
    CHANGED
    
    
    
        data/ext/itsi_tracing/src/lib.rs
    CHANGED
    
    | @@ -1,11 +1,229 @@ | |
| 1 | 
            +
            use atty::{Stream, is};
         | 
| 2 | 
            +
            use std::{
         | 
| 3 | 
            +
                env,
         | 
| 4 | 
            +
                sync::{Mutex, OnceLock},
         | 
| 5 | 
            +
            };
         | 
| 1 6 | 
             
            pub use tracing::{debug, error, info, trace, warn};
         | 
| 2 | 
            -
            use  | 
| 7 | 
            +
            use tracing_appender::rolling;
         | 
| 8 | 
            +
            use tracing_subscriber::Layer;
         | 
| 9 | 
            +
            use tracing_subscriber::fmt::writer::BoxMakeWriter;
         | 
| 10 | 
            +
            use tracing_subscriber::{EnvFilter, fmt, prelude::*, reload};
         | 
| 3 11 |  | 
| 12 | 
            +
            // Global reload handle for changing the level at runtime.
         | 
| 13 | 
            +
            static RELOAD_HANDLE: OnceLock<
         | 
| 14 | 
            +
                Mutex<Option<reload::Handle<EnvFilter, tracing_subscriber::Registry>>>,
         | 
| 15 | 
            +
            > = OnceLock::new();
         | 
| 16 | 
            +
             | 
| 17 | 
            +
            /// Log format: Plain or JSON.
         | 
| 18 | 
            +
            #[derive(Debug, Clone)]
         | 
| 19 | 
            +
            pub enum LogFormat {
         | 
| 20 | 
            +
                Plain,
         | 
| 21 | 
            +
                Json,
         | 
| 22 | 
            +
            }
         | 
| 23 | 
            +
             | 
| 24 | 
            +
            /// Log target: STDOUT, File, or Both.
         | 
| 25 | 
            +
            #[derive(Debug, Clone)]
         | 
| 26 | 
            +
            pub enum LogTarget {
         | 
| 27 | 
            +
                Stdout,
         | 
| 28 | 
            +
                File(String), // file name (rotated daily)
         | 
| 29 | 
            +
                Both(String), // file name (rotated daily) plus STDOUT
         | 
| 30 | 
            +
            }
         | 
| 31 | 
            +
             | 
| 32 | 
            +
            /// Logger configuration.
         | 
| 33 | 
            +
            #[derive(Debug, Clone)]
         | 
| 34 | 
            +
            pub struct LogConfig {
         | 
| 35 | 
            +
                /// Log level as a string (e.g. "info", "debug").
         | 
| 36 | 
            +
                pub level: String,
         | 
| 37 | 
            +
                /// Format: Plain (with optional ANSI) or JSON.
         | 
| 38 | 
            +
                pub format: LogFormat,
         | 
| 39 | 
            +
                /// Target: STDOUT, File, or Both.
         | 
| 40 | 
            +
                pub target: LogTarget,
         | 
| 41 | 
            +
                /// Whether to enable ANSI coloring (for plain text).
         | 
| 42 | 
            +
                pub use_ansi: bool,
         | 
| 43 | 
            +
            }
         | 
| 44 | 
            +
             | 
| 45 | 
            +
            impl Default for LogConfig {
         | 
| 46 | 
            +
                fn default() -> Self {
         | 
| 47 | 
            +
                    let level = env::var("ITSI_LOG").unwrap_or_else(|_| "info".into());
         | 
| 48 | 
            +
                    let format = match env::var("ITSI_LOG_FORMAT").as_deref() {
         | 
| 49 | 
            +
                        Ok("json") => LogFormat::Json,
         | 
| 50 | 
            +
                        _ => LogFormat::Plain,
         | 
| 51 | 
            +
                    };
         | 
| 52 | 
            +
                    let target = match env::var("ITSI_LOG_TARGET").as_deref() {
         | 
| 53 | 
            +
                        Ok("file") => {
         | 
| 54 | 
            +
                            let file = env::var("ITSI_LOG_FILE").unwrap_or_else(|_| "app.log".into());
         | 
| 55 | 
            +
                            LogTarget::File(file)
         | 
| 56 | 
            +
                        }
         | 
| 57 | 
            +
                        Ok("both") => {
         | 
| 58 | 
            +
                            let file = env::var("ITSI_LOG_FILE").unwrap_or_else(|_| "app.log".into());
         | 
| 59 | 
            +
                            LogTarget::Both(file)
         | 
| 60 | 
            +
                        }
         | 
| 61 | 
            +
                        _ => LogTarget::Stdout,
         | 
| 62 | 
            +
                    };
         | 
| 63 | 
            +
                    // If ITSI_LOG_ANSI is set, use that; otherwise, use ANSI if stdout is a TTY.
         | 
| 64 | 
            +
                    let use_ansi = env::var("ITSI_LOG_ANSI")
         | 
| 65 | 
            +
                        .map(|s| s == "true")
         | 
| 66 | 
            +
                        .unwrap_or_else(|_| is(Stream::Stdout));
         | 
| 67 | 
            +
                    Self {
         | 
| 68 | 
            +
                        level,
         | 
| 69 | 
            +
                        format,
         | 
| 70 | 
            +
                        target,
         | 
| 71 | 
            +
                        use_ansi,
         | 
| 72 | 
            +
                    }
         | 
| 73 | 
            +
                }
         | 
| 74 | 
            +
            }
         | 
| 75 | 
            +
             | 
| 76 | 
            +
            /// Initialize the global tracing subscriber with the default configuration.
         | 
| 4 77 | 
             
            pub fn init() {
         | 
| 5 | 
            -
                 | 
| 6 | 
            -
             | 
| 7 | 
            -
             | 
| 8 | 
            -
             | 
| 9 | 
            -
             | 
| 10 | 
            -
             | 
| 78 | 
            +
                init_with_config(LogConfig::default());
         | 
| 79 | 
            +
            }
         | 
| 80 | 
            +
             | 
| 81 | 
            +
            /// Initialize the global tracing subscriber with a given configuration.
         | 
| 82 | 
            +
            pub fn init_with_config(config: LogConfig) {
         | 
| 83 | 
            +
                // Build an EnvFilter from the configured level.
         | 
| 84 | 
            +
                let env_filter = EnvFilter::new(config.level);
         | 
| 85 | 
            +
             | 
| 86 | 
            +
                // Build the formatting layer based on target and format.
         | 
| 87 | 
            +
                let fmt_layer = match config.target {
         | 
| 88 | 
            +
                    LogTarget::Stdout => match config.format {
         | 
| 89 | 
            +
                        LogFormat::Plain => fmt::layer()
         | 
| 90 | 
            +
                            .compact()
         | 
| 91 | 
            +
                            .with_file(false)
         | 
| 92 | 
            +
                            .with_line_number(false)
         | 
| 93 | 
            +
                            .with_target(false)
         | 
| 94 | 
            +
                            .with_thread_ids(false)
         | 
| 95 | 
            +
                            .with_writer(BoxMakeWriter::new(std::io::stdout))
         | 
| 96 | 
            +
                            .with_ansi(config.use_ansi)
         | 
| 97 | 
            +
                            .boxed(),
         | 
| 98 | 
            +
                        LogFormat::Json => fmt::layer()
         | 
| 99 | 
            +
                            .compact()
         | 
| 100 | 
            +
                            .with_file(false)
         | 
| 101 | 
            +
                            .with_line_number(false)
         | 
| 102 | 
            +
                            .with_target(false)
         | 
| 103 | 
            +
                            .with_thread_ids(false)
         | 
| 104 | 
            +
                            .with_writer(BoxMakeWriter::new(std::io::stdout))
         | 
| 105 | 
            +
                            .with_ansi(config.use_ansi)
         | 
| 106 | 
            +
                            .json()
         | 
| 107 | 
            +
                            .boxed(),
         | 
| 108 | 
            +
                    },
         | 
| 109 | 
            +
                    LogTarget::File(file) => match config.format {
         | 
| 110 | 
            +
                        LogFormat::Plain => fmt::layer()
         | 
| 111 | 
            +
                            .compact()
         | 
| 112 | 
            +
                            .with_file(false)
         | 
| 113 | 
            +
                            .with_line_number(false)
         | 
| 114 | 
            +
                            .with_target(false)
         | 
| 115 | 
            +
                            .with_thread_ids(false)
         | 
| 116 | 
            +
                            .with_writer(BoxMakeWriter::new({
         | 
| 117 | 
            +
                                let file = file.clone();
         | 
| 118 | 
            +
                                move || rolling::daily(".", file.clone())
         | 
| 119 | 
            +
                            }))
         | 
| 120 | 
            +
                            .with_ansi(false)
         | 
| 121 | 
            +
                            .boxed(),
         | 
| 122 | 
            +
                        LogFormat::Json => fmt::layer()
         | 
| 123 | 
            +
                            .compact()
         | 
| 124 | 
            +
                            .with_file(false)
         | 
| 125 | 
            +
                            .with_line_number(false)
         | 
| 126 | 
            +
                            .with_target(false)
         | 
| 127 | 
            +
                            .with_thread_ids(false)
         | 
| 128 | 
            +
                            .with_writer(BoxMakeWriter::new({
         | 
| 129 | 
            +
                                let file = file.clone();
         | 
| 130 | 
            +
                                move || rolling::daily(".", file.clone())
         | 
| 131 | 
            +
                            }))
         | 
| 132 | 
            +
                            .with_ansi(false)
         | 
| 133 | 
            +
                            .json()
         | 
| 134 | 
            +
                            .boxed(),
         | 
| 135 | 
            +
                    },
         | 
| 136 | 
            +
                    LogTarget::Both(file) => {
         | 
| 137 | 
            +
                        // For "Both" target, handle each format separately to avoid type mismatches
         | 
| 138 | 
            +
                        match config.format {
         | 
| 139 | 
            +
                            LogFormat::Plain => {
         | 
| 140 | 
            +
                                let stdout_layer = fmt::layer()
         | 
| 141 | 
            +
                                    .compact()
         | 
| 142 | 
            +
                                    .with_file(false)
         | 
| 143 | 
            +
                                    .with_line_number(false)
         | 
| 144 | 
            +
                                    .with_target(false)
         | 
| 145 | 
            +
                                    .with_thread_ids(false)
         | 
| 146 | 
            +
                                    .with_writer(BoxMakeWriter::new(std::io::stdout))
         | 
| 147 | 
            +
                                    .with_ansi(config.use_ansi);
         | 
| 148 | 
            +
             | 
| 149 | 
            +
                                let file_layer = fmt::layer()
         | 
| 150 | 
            +
                                    .compact()
         | 
| 151 | 
            +
                                    .with_file(false)
         | 
| 152 | 
            +
                                    .with_line_number(false)
         | 
| 153 | 
            +
                                    .with_target(false)
         | 
| 154 | 
            +
                                    .with_thread_ids(false)
         | 
| 155 | 
            +
                                    .with_writer(BoxMakeWriter::new({
         | 
| 156 | 
            +
                                        let file = file.clone();
         | 
| 157 | 
            +
                                        move || rolling::daily(".", file.clone())
         | 
| 158 | 
            +
                                    }))
         | 
| 159 | 
            +
                                    .with_ansi(false);
         | 
| 160 | 
            +
             | 
| 161 | 
            +
                                stdout_layer.and_then(file_layer).boxed()
         | 
| 162 | 
            +
                            }
         | 
| 163 | 
            +
                            LogFormat::Json => {
         | 
| 164 | 
            +
                                let stdout_layer = fmt::layer()
         | 
| 165 | 
            +
                                    .compact()
         | 
| 166 | 
            +
                                    .with_file(false)
         | 
| 167 | 
            +
                                    .with_line_number(false)
         | 
| 168 | 
            +
                                    .with_target(false)
         | 
| 169 | 
            +
                                    .with_thread_ids(false)
         | 
| 170 | 
            +
                                    .with_writer(BoxMakeWriter::new(std::io::stdout))
         | 
| 171 | 
            +
                                    .with_ansi(config.use_ansi)
         | 
| 172 | 
            +
                                    .json();
         | 
| 173 | 
            +
             | 
| 174 | 
            +
                                let file_layer = fmt::layer()
         | 
| 175 | 
            +
                                    .compact()
         | 
| 176 | 
            +
                                    .with_file(false)
         | 
| 177 | 
            +
                                    .with_line_number(false)
         | 
| 178 | 
            +
                                    .with_target(false)
         | 
| 179 | 
            +
                                    .with_thread_ids(false)
         | 
| 180 | 
            +
                                    .with_writer(BoxMakeWriter::new({
         | 
| 181 | 
            +
                                        let file = file.clone();
         | 
| 182 | 
            +
                                        move || rolling::daily(".", file.clone())
         | 
| 183 | 
            +
                                    }))
         | 
| 184 | 
            +
                                    .with_ansi(false)
         | 
| 185 | 
            +
                                    .json();
         | 
| 186 | 
            +
             | 
| 187 | 
            +
                                stdout_layer.and_then(file_layer).boxed()
         | 
| 188 | 
            +
                            }
         | 
| 189 | 
            +
                        }
         | 
| 190 | 
            +
                    }
         | 
| 191 | 
            +
                };
         | 
| 192 | 
            +
             | 
| 193 | 
            +
                // Create a reloadable filter layer so we can update the level at runtime.
         | 
| 194 | 
            +
                let (filter_layer, handle) = reload::Layer::new(env_filter);
         | 
| 195 | 
            +
             | 
| 196 | 
            +
                // Build the subscriber registry
         | 
| 197 | 
            +
                let subscriber = tracing_subscriber::registry()
         | 
| 198 | 
            +
                    .with(filter_layer)
         | 
| 199 | 
            +
                    .with(fmt_layer);
         | 
| 200 | 
            +
             | 
| 201 | 
            +
                tracing::subscriber::set_global_default(subscriber)
         | 
| 202 | 
            +
                    .expect("Unable to set global tracing subscriber");
         | 
| 203 | 
            +
             | 
| 204 | 
            +
                RELOAD_HANDLE.set(Mutex::new(Some(handle))).unwrap();
         | 
| 205 | 
            +
            }
         | 
| 206 | 
            +
             | 
| 207 | 
            +
            /// Change the log level at runtime.
         | 
| 208 | 
            +
            pub fn set_level(new_level: &str) {
         | 
| 209 | 
            +
                if let Some(handle) = RELOAD_HANDLE.get().unwrap().lock().unwrap().as_ref() {
         | 
| 210 | 
            +
                    handle
         | 
| 211 | 
            +
                        .modify(|filter| *filter = EnvFilter::new(new_level))
         | 
| 212 | 
            +
                        .expect("Failed to update log level");
         | 
| 213 | 
            +
                } else {
         | 
| 214 | 
            +
                    eprintln!("Reload handle not initialized; call init() first.");
         | 
| 215 | 
            +
                }
         | 
| 216 | 
            +
            }
         | 
| 217 | 
            +
             | 
| 218 | 
            +
            /// Run a function silently by temporarily setting a no-op subscriber.
         | 
| 219 | 
            +
            pub fn run_silently<F, R>(f: F) -> R
         | 
| 220 | 
            +
            where
         | 
| 221 | 
            +
                F: FnOnce() -> R,
         | 
| 222 | 
            +
            {
         | 
| 223 | 
            +
                let no_op_subscriber = tracing_subscriber::fmt()
         | 
| 224 | 
            +
                    .with_writer(std::io::sink)
         | 
| 225 | 
            +
                    .with_max_level(tracing_subscriber::filter::LevelFilter::OFF)
         | 
| 226 | 
            +
                    .finish();
         | 
| 227 | 
            +
                let dispatch = tracing::Dispatch::new(no_op_subscriber);
         | 
| 228 | 
            +
                tracing::dispatcher::with_default(&dispatch, f)
         | 
| 11 229 | 
             
            }
         | 
    
        data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-0994n8rpvvt9m/s-h510hfz1f6-1kbycmq.lock
    ADDED
    
    | 
            File without changes
         | 
    
        data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-0bob7bf4yq34i/s-h5113125h5-0lh4rag.lock
    ADDED
    
    | 
            File without changes
         | 
    
        data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-2fcodulrxbbxo/s-h510h2infk-0hp5kjw.lock
    ADDED
    
    | 
            File without changes
         | 
    
        data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-2iak63r1woi1l/s-h510h2in4q-0kxfzw1.lock
    ADDED
    
    | 
            File without changes
         | 
    
        data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-2kk4qj9gn5dg2/s-h5113124kv-0enwon2.lock
    ADDED
    
    | 
            File without changes
         | 
    
        data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-2mwo0yas7dtw4/s-h510hfz1ha-1udgpei.lock
    ADDED
    
    | 
            File without changes
         |