itsi-scheduler 0.1.5 → 0.1.14
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-scheduler might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CODE_OF_CONDUCT.md +7 -0
- data/Cargo.lock +83 -22
- data/README.md +5 -0
- data/_index.md +7 -0
- data/ext/itsi_error/src/from.rs +26 -29
- data/ext/itsi_error/src/lib.rs +10 -1
- 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_rb_helpers/Cargo.toml +1 -0
- data/ext/itsi_rb_helpers/src/heap_value.rs +18 -0
- data/ext/itsi_rb_helpers/src/lib.rs +59 -9
- 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_server/Cargo.lock +2956 -0
- data/ext/itsi_server/Cargo.toml +69 -26
- data/ext/itsi_server/src/env.rs +43 -0
- data/ext/itsi_server/src/lib.rs +81 -75
- data/ext/itsi_server/src/{body_proxy → ruby_types/itsi_body_proxy}/big_bytes.rs +10 -5
- data/ext/itsi_server/src/{body_proxy/itsi_body_proxy.rs → ruby_types/itsi_body_proxy/mod.rs} +22 -3
- 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/{request/itsi_request.rs → ruby_types/itsi_http_request.rs} +108 -103
- data/ext/itsi_server/src/{response/itsi_response.rs → ruby_types/itsi_http_response.rs} +79 -38
- 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 +33 -20
- 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/itsi_service.rs +172 -0
- data/ext/itsi_server/src/server/lifecycle_event.rs +3 -0
- data/ext/itsi_server/src/server/listener.rs +197 -106
- 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 +264 -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 +8 -1
- data/ext/itsi_server/src/server/process_worker.rs +44 -11
- 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 +129 -46
- data/ext/itsi_server/src/server/serve_strategy/mod.rs +9 -6
- data/ext/itsi_server/src/server/serve_strategy/single_mode.rs +337 -163
- data/ext/itsi_server/src/server/signal.rs +25 -2
- data/ext/itsi_server/src/server/static_file_server.rs +984 -0
- data/ext/itsi_server/src/server/thread_worker.rs +164 -88
- data/ext/itsi_server/src/server/tls/locked_dir_cache.rs +55 -17
- data/ext/itsi_server/src/server/tls.rs +104 -28
- data/ext/itsi_server/src/server/types.rs +43 -0
- data/ext/itsi_tracing/Cargo.toml +1 -0
- data/ext/itsi_tracing/src/lib.rs +222 -34
- 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/scheduler/version.rb +1 -1
- data/lib/itsi/scheduler.rb +2 -2
- metadata +79 -14
- data/ext/itsi_server/extconf.rb +0 -6
- data/ext/itsi_server/src/body_proxy/mod.rs +0 -2
- data/ext/itsi_server/src/request/mod.rs +0 -1
- data/ext/itsi_server/src/response/mod.rs +0 -1
- data/ext/itsi_server/src/server/itsi_ca/itsi_ca.crt +0 -13
- data/ext/itsi_server/src/server/itsi_ca/itsi_ca.key +0 -5
- data/ext/itsi_server/src/server/itsi_server.rs +0 -244
@@ -2,21 +2,29 @@ use base64::{engine::general_purpose, Engine as _};
|
|
2
2
|
use itsi_error::Result;
|
3
3
|
use itsi_tracing::info;
|
4
4
|
use locked_dir_cache::LockedDirCache;
|
5
|
-
use rcgen::{
|
6
|
-
|
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
|
+
};
|
7
12
|
use rustls_pemfile::{certs, pkcs8_private_keys};
|
8
13
|
use std::{
|
9
14
|
collections::HashMap,
|
10
|
-
|
15
|
+
fs,
|
11
16
|
io::{BufReader, Error},
|
12
17
|
sync::Arc,
|
13
18
|
};
|
14
19
|
use tokio::sync::Mutex;
|
15
20
|
use tokio_rustls::{rustls::ServerConfig, TlsAcceptor};
|
16
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
|
+
};
|
17
27
|
mod locked_dir_cache;
|
18
|
-
const ITS_CA_CERT: &str = include_str!("./itsi_ca/itsi_ca.crt");
|
19
|
-
const ITS_CA_KEY: &str = include_str!("./itsi_ca/itsi_ca.key");
|
20
28
|
|
21
29
|
#[derive(Clone)]
|
22
30
|
pub enum ItsiTlsAcceptor {
|
@@ -28,35 +36,72 @@ pub enum ItsiTlsAcceptor {
|
|
28
36
|
),
|
29
37
|
}
|
30
38
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
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.
|
36
45
|
pub fn configure_tls(
|
37
46
|
host: &str,
|
38
47
|
query_params: &HashMap<String, String>,
|
39
48
|
) -> Result<ItsiTlsAcceptor> {
|
40
49
|
let domains = query_params
|
41
50
|
.get("domains")
|
42
|
-
.map(|v| v.split(',').map(String::from).collect::<Vec<_>>())
|
51
|
+
.map(|v| v.split(',').map(String::from).collect::<Vec<_>>())
|
52
|
+
.or_else(|| query_params.get("domain").map(|v| vec![v.to_string()]));
|
43
53
|
|
44
|
-
if query_params.get("cert").
|
54
|
+
if query_params.get("cert").is_some_and(|c| c == "acme") {
|
45
55
|
if let Some(domains) = domains {
|
46
|
-
let directory_url =
|
47
|
-
.unwrap_or_else(|_| "https://acme-v02.api.letsencrypt.org/directory".to_string());
|
56
|
+
let directory_url = &*ITSI_ACME_DIRECTORY_URL;
|
48
57
|
info!(
|
49
58
|
domains = format!("{:?}", domains),
|
50
59
|
directory_url, "Requesting acme cert"
|
51
60
|
);
|
52
|
-
let
|
53
|
-
.
|
54
|
-
.
|
55
|
-
.
|
56
|
-
.
|
57
|
-
|
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()
|
58
100
|
.with_no_client_auth()
|
59
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
|
+
|
60
105
|
let acceptor = acme_state.acceptor();
|
61
106
|
return Ok(ItsiTlsAcceptor::Automatic(
|
62
107
|
acceptor,
|
@@ -73,7 +118,7 @@ pub fn configure_tls(
|
|
73
118
|
let key = load_private_key(key_path);
|
74
119
|
(certs, key)
|
75
120
|
} else {
|
76
|
-
generate_ca_signed_cert(vec![host.to_owned()])?
|
121
|
+
generate_ca_signed_cert(domains.unwrap_or(vec![host.to_owned()]))?
|
77
122
|
};
|
78
123
|
|
79
124
|
let mut config = ServerConfig::builder()
|
@@ -144,10 +189,19 @@ pub fn load_private_key(path: &str) -> PrivateKeyDer<'static> {
|
|
144
189
|
pub fn generate_ca_signed_cert(
|
145
190
|
domains: Vec<String>,
|
146
191
|
) -> Result<(Vec<CertificateDer<'static>>, PrivateKeyDer<'static>)> {
|
147
|
-
info!(
|
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()?;
|
148
202
|
|
149
|
-
let ca_kp = KeyPair::from_pem(
|
150
|
-
let ca_cert = CertificateParams::from_ca_cert_pem(
|
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)
|
151
205
|
.expect("Failed to parse embedded CA certificate")
|
152
206
|
.self_signed(&ca_kp)
|
153
207
|
.expect("Failed to self-sign embedded CA cert");
|
@@ -155,10 +209,6 @@ pub fn generate_ca_signed_cert(
|
|
155
209
|
let ee_key = KeyPair::generate_for(&rcgen::PKCS_ECDSA_P256_SHA256).unwrap();
|
156
210
|
let mut ee_params = CertificateParams::default();
|
157
211
|
|
158
|
-
info!(
|
159
|
-
"Generated certificate will be valid for domains {:?}",
|
160
|
-
domains
|
161
|
-
);
|
162
212
|
use std::net::IpAddr;
|
163
213
|
|
164
214
|
ee_params.subject_alt_names = domains
|
@@ -187,3 +237,29 @@ pub fn generate_ca_signed_cert(
|
|
187
237
|
PrivateKeyDer::try_from(ee_key.serialize_der()).unwrap(),
|
188
238
|
))
|
189
239
|
}
|
240
|
+
|
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
|
+
}
|
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,41 +1,229 @@
|
|
1
|
-
use std::env;
|
2
|
-
|
3
1
|
use atty::{Stream, is};
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
EnvFilter,
|
8
|
-
fmt::{self, format},
|
2
|
+
use std::{
|
3
|
+
env,
|
4
|
+
sync::{Mutex, OnceLock},
|
9
5
|
};
|
6
|
+
pub use tracing::{debug, error, info, trace, warn};
|
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};
|
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
|
+
}
|
10
44
|
|
11
|
-
|
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.
|
12
77
|
pub fn init() {
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
.
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
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");
|
35
213
|
} else {
|
36
|
-
|
37
|
-
.fmt_fields(format::JsonFields::default())
|
38
|
-
.event_format(fmt::format().json())
|
39
|
-
.init();
|
214
|
+
eprintln!("Reload handle not initialized; call init() first.");
|
40
215
|
}
|
41
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)
|
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
|
data/lib/itsi/scheduler.rb
CHANGED
@@ -73,7 +73,7 @@ module Itsi
|
|
73
73
|
fiber.resume
|
74
74
|
end
|
75
75
|
rescue StandardError => e
|
76
|
-
warn "
|
76
|
+
warn "Fiber #{fiber} terminated on exception: #{e.message}"
|
77
77
|
end
|
78
78
|
|
79
79
|
def resume_fiber_with_readiness((token, readiness))
|
@@ -81,7 +81,7 @@ module Itsi
|
|
81
81
|
fiber.resume(readiness)
|
82
82
|
end
|
83
83
|
rescue StandardError => e
|
84
|
-
warn "
|
84
|
+
warn "Fiber #{fiber} terminated on exception: #{e.message}"
|
85
85
|
end
|
86
86
|
|
87
87
|
def resume_blocked(fiber)
|