itsi-scheduler 0.1.0 → 0.1.2

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.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/Cargo.lock +219 -23
  3. data/Rakefile +7 -1
  4. data/ext/itsi_error/Cargo.toml +2 -0
  5. data/ext/itsi_error/src/from.rs +70 -0
  6. data/ext/itsi_error/src/lib.rs +10 -37
  7. data/ext/itsi_instrument_entry/Cargo.toml +15 -0
  8. data/ext/itsi_instrument_entry/src/lib.rs +31 -0
  9. data/ext/itsi_rb_helpers/Cargo.toml +2 -0
  10. data/ext/itsi_rb_helpers/src/heap_value.rs +121 -0
  11. data/ext/itsi_rb_helpers/src/lib.rs +90 -10
  12. data/ext/itsi_scheduler/Cargo.toml +9 -1
  13. data/ext/itsi_scheduler/extconf.rb +1 -1
  14. data/ext/itsi_scheduler/src/itsi_scheduler/io_helpers.rs +56 -0
  15. data/ext/itsi_scheduler/src/itsi_scheduler/io_waiter.rs +44 -0
  16. data/ext/itsi_scheduler/src/itsi_scheduler/timer.rs +44 -0
  17. data/ext/itsi_scheduler/src/itsi_scheduler.rs +308 -0
  18. data/ext/itsi_scheduler/src/lib.rs +31 -10
  19. data/ext/itsi_server/Cargo.toml +41 -0
  20. data/ext/itsi_server/extconf.rb +6 -0
  21. data/ext/itsi_server/src/body_proxy/big_bytes.rs +104 -0
  22. data/ext/itsi_server/src/body_proxy/itsi_body_proxy.rs +122 -0
  23. data/ext/itsi_server/src/body_proxy/mod.rs +2 -0
  24. data/ext/itsi_server/src/lib.rs +103 -0
  25. data/ext/itsi_server/src/request/itsi_request.rs +277 -0
  26. data/ext/itsi_server/src/request/mod.rs +1 -0
  27. data/ext/itsi_server/src/response/itsi_response.rs +347 -0
  28. data/ext/itsi_server/src/response/mod.rs +1 -0
  29. data/ext/itsi_server/src/server/bind.rs +168 -0
  30. data/ext/itsi_server/src/server/bind_protocol.rs +37 -0
  31. data/ext/itsi_server/src/server/io_stream.rs +104 -0
  32. data/ext/itsi_server/src/server/itsi_ca/itsi_ca.crt +13 -0
  33. data/ext/itsi_server/src/server/itsi_ca/itsi_ca.key +5 -0
  34. data/ext/itsi_server/src/server/itsi_server.rs +230 -0
  35. data/ext/itsi_server/src/server/lifecycle_event.rs +8 -0
  36. data/ext/itsi_server/src/server/listener.rs +259 -0
  37. data/ext/itsi_server/src/server/mod.rs +11 -0
  38. data/ext/itsi_server/src/server/process_worker.rs +196 -0
  39. data/ext/itsi_server/src/server/serve_strategy/cluster_mode.rs +253 -0
  40. data/ext/itsi_server/src/server/serve_strategy/mod.rs +27 -0
  41. data/ext/itsi_server/src/server/serve_strategy/single_mode.rs +238 -0
  42. data/ext/itsi_server/src/server/signal.rs +57 -0
  43. data/ext/itsi_server/src/server/thread_worker.rs +368 -0
  44. data/ext/itsi_server/src/server/tls.rs +152 -0
  45. data/ext/itsi_tracing/Cargo.toml +4 -0
  46. data/ext/itsi_tracing/src/lib.rs +36 -6
  47. data/lib/itsi/scheduler/version.rb +1 -1
  48. data/lib/itsi/scheduler.rb +137 -1
  49. metadata +38 -4
@@ -0,0 +1,368 @@
1
+ use super::itsi_server::RequestJob;
2
+ use crate::{request::itsi_request::ItsiRequest, ITSI_SERVER};
3
+ use itsi_rb_helpers::{
4
+ call_with_gvl, call_without_gvl, create_ruby_thread, kill_threads, HeapValue,
5
+ };
6
+ use itsi_tracing::{debug, error, info, warn};
7
+ use magnus::{
8
+ error::Result,
9
+ value::{InnerValue, Lazy, LazyId, Opaque, ReprValue},
10
+ Module, RClass, Ruby, Thread, Value,
11
+ };
12
+ use nix::unistd::Pid;
13
+ use parking_lot::{Mutex, RwLock};
14
+ use std::{
15
+ num::NonZeroU8,
16
+ ops::Deref,
17
+ sync::{
18
+ atomic::{AtomicBool, Ordering},
19
+ Arc,
20
+ },
21
+ thread,
22
+ time::{Duration, Instant},
23
+ };
24
+ use tokio::{runtime::Builder as RuntimeBuilder, sync::watch};
25
+ use tracing::instrument;
26
+ pub struct ThreadWorker {
27
+ pub id: String,
28
+ pub app: Opaque<Value>,
29
+ pub receiver: Arc<async_channel::Receiver<RequestJob>>,
30
+ pub sender: async_channel::Sender<RequestJob>,
31
+ pub thread: RwLock<Option<HeapValue<Thread>>>,
32
+ pub terminated: Arc<AtomicBool>,
33
+ pub scheduler_class: Option<Opaque<Value>>,
34
+ }
35
+
36
+ static ID_CALL: LazyId = LazyId::new("call");
37
+ static ID_ALIVE: LazyId = LazyId::new("alive?");
38
+ static ID_SCHEDULER: LazyId = LazyId::new("scheduler");
39
+ static ID_SCHEDULE: LazyId = LazyId::new("schedule");
40
+ static ID_BLOCK: LazyId = LazyId::new("block");
41
+ static ID_YIELD: LazyId = LazyId::new("yield");
42
+ static ID_CONST_GET: LazyId = LazyId::new("const_get");
43
+ static CLASS_FIBER: Lazy<RClass> = Lazy::new(|ruby| {
44
+ ruby.module_kernel()
45
+ .const_get::<_, RClass>("Fiber")
46
+ .unwrap()
47
+ });
48
+
49
+ pub struct TerminateWakerSignal(bool);
50
+
51
+ #[instrument(name = "Boot", parent=None, skip(threads, app, pid, scheduler_class))]
52
+ pub fn build_thread_workers(
53
+ pid: Pid,
54
+ threads: NonZeroU8,
55
+ app: Opaque<Value>,
56
+ scheduler_class: Option<String>,
57
+ ) -> Result<(Arc<Vec<ThreadWorker>>, async_channel::Sender<RequestJob>)> {
58
+ let (sender, receiver) = async_channel::bounded(20);
59
+ let receiver_ref = Arc::new(receiver);
60
+ let sender_ref = sender;
61
+ let (app, scheduler_class) = load_app(app, scheduler_class)?;
62
+ Ok((
63
+ Arc::new(
64
+ (1..=u8::from(threads))
65
+ .map(|id| {
66
+ info!(pid = pid.as_raw(), id, "Thread");
67
+ ThreadWorker::new(
68
+ format!("{:?}#{:?}", pid, id),
69
+ app,
70
+ receiver_ref.clone(),
71
+ sender_ref.clone(),
72
+ scheduler_class,
73
+ )
74
+ })
75
+ .collect::<Result<Vec<_>>>()?,
76
+ ),
77
+ sender_ref,
78
+ ))
79
+ }
80
+
81
+ pub fn load_app(
82
+ app: Opaque<Value>,
83
+ scheduler_class: Option<String>,
84
+ ) -> Result<(Opaque<Value>, Option<Opaque<Value>>)> {
85
+ call_with_gvl(|ruby| {
86
+ let app = app.get_inner_with(&ruby);
87
+ let app = Opaque::from(
88
+ app.funcall::<_, _, Value>(*ID_CALL, ())
89
+ .expect("Couldn't load app"),
90
+ );
91
+ let scheduler_class = if let Some(scheduler_class) = scheduler_class {
92
+ Some(Opaque::from(
93
+ ruby.module_kernel()
94
+ .funcall::<_, _, Value>(*ID_CONST_GET, (scheduler_class,))?,
95
+ ))
96
+ } else {
97
+ None
98
+ };
99
+ Ok((app, scheduler_class))
100
+ })
101
+ }
102
+ impl ThreadWorker {
103
+ pub fn new(
104
+ id: String,
105
+ app: Opaque<Value>,
106
+ receiver: Arc<async_channel::Receiver<RequestJob>>,
107
+ sender: async_channel::Sender<RequestJob>,
108
+ scheduler_class: Option<Opaque<Value>>,
109
+ ) -> Result<Self> {
110
+ let mut worker = Self {
111
+ id,
112
+ app,
113
+ receiver,
114
+ sender,
115
+ thread: RwLock::new(None),
116
+ terminated: Arc::new(AtomicBool::new(false)),
117
+ scheduler_class,
118
+ };
119
+ worker.run()?;
120
+ Ok(worker)
121
+ }
122
+
123
+ #[instrument(skip(self), fields(id = self.id))]
124
+ pub async fn request_shutdown(&self) {
125
+ match self.sender.send(RequestJob::Shutdown).await {
126
+ Ok(_) => {}
127
+ Err(err) => error!("Failed to send shutdown request: {}", err),
128
+ };
129
+ info!("Requesting shutdown");
130
+ }
131
+
132
+ #[instrument(skip(self, deadline), fields(id = self.id))]
133
+ pub fn poll_shutdown(&self, deadline: Instant) -> bool {
134
+ call_with_gvl(|_ruby| {
135
+ if let Some(thread) = self.thread.read().deref() {
136
+ if Instant::now() > deadline {
137
+ warn!("Worker shutdown timed out. Killing thread");
138
+ self.terminated.store(true, Ordering::SeqCst);
139
+ kill_threads(vec![thread.as_value()]);
140
+ }
141
+ if thread.funcall::<_, _, bool>(*ID_ALIVE, ()).unwrap_or(false) {
142
+ return true;
143
+ }
144
+ info!("Thread has shut down");
145
+ }
146
+ self.thread.write().take();
147
+
148
+ false
149
+ })
150
+ }
151
+
152
+ pub fn run(&mut self) -> Result<()> {
153
+ let id = self.id.clone();
154
+ let app = self.app;
155
+ let receiver = self.receiver.clone();
156
+ let terminated = self.terminated.clone();
157
+ let scheduler_class = self.scheduler_class;
158
+ call_with_gvl(|_| {
159
+ *self.thread.write() = Some(
160
+ create_ruby_thread(move || {
161
+ if let Some(scheduler_class) = scheduler_class {
162
+ if let Err(err) =
163
+ Self::fiber_accept_loop(id, app, receiver, scheduler_class, terminated)
164
+ {
165
+ error!("Error in fiber_accept_loop: {:?}", err);
166
+ }
167
+ } else {
168
+ Self::accept_loop(id, app, receiver, terminated);
169
+ }
170
+ })
171
+ .into(),
172
+ );
173
+ Ok::<(), magnus::Error>(())
174
+ })?;
175
+ Ok(())
176
+ }
177
+
178
+ pub fn build_scheduler_proc(
179
+ app: Opaque<Value>,
180
+ leader: &Arc<Mutex<Option<RequestJob>>>,
181
+ receiver: &Arc<async_channel::Receiver<RequestJob>>,
182
+ terminated: &Arc<AtomicBool>,
183
+ waker_sender: &watch::Sender<TerminateWakerSignal>,
184
+ ) -> magnus::block::Proc {
185
+ let leader = leader.clone();
186
+ let receiver = receiver.clone();
187
+ let terminated = terminated.clone();
188
+ let waker_sender = waker_sender.clone();
189
+ Ruby::get().unwrap().proc_from_fn(move |ruby, _args, _blk| {
190
+ let scheduler = ruby
191
+ .get_inner(&CLASS_FIBER)
192
+ .funcall::<_, _, Value>(*ID_SCHEDULER, ())
193
+ .unwrap();
194
+ let server = ruby.get_inner(&ITSI_SERVER);
195
+ let thread_current = ruby.thread_current();
196
+ let leader_clone = leader.clone();
197
+ let receiver = receiver.clone();
198
+ let terminated = terminated.clone();
199
+ let waker_sender = waker_sender.clone();
200
+ let mut batch = Vec::with_capacity(MAX_BATCH_SIZE as usize);
201
+
202
+ static MAX_BATCH_SIZE: i32 = 25;
203
+ call_without_gvl(move || loop {
204
+ let mut idle_counter = 0;
205
+ if let Some(v) = leader_clone.lock().take() {
206
+ match v {
207
+ RequestJob::ProcessRequest(itsi_request) => {
208
+ batch.push(RequestJob::ProcessRequest(itsi_request))
209
+ }
210
+ RequestJob::Shutdown => {
211
+ waker_sender.send(TerminateWakerSignal(true)).unwrap();
212
+ break;
213
+ }
214
+ }
215
+ }
216
+ for _ in 0..MAX_BATCH_SIZE {
217
+ if let Ok(req) = receiver.try_recv() {
218
+ batch.push(req);
219
+ } else {
220
+ break;
221
+ }
222
+ }
223
+
224
+ let shutdown_requested = call_with_gvl(|_| {
225
+ for req in batch.drain(..) {
226
+ match req {
227
+ RequestJob::ProcessRequest(request) => {
228
+ let response = request.response.clone();
229
+ if let Err(err) =
230
+ server.funcall::<_, _, Value>(*ID_SCHEDULE, (app, request))
231
+ {
232
+ ItsiRequest::internal_error(ruby, response, err)
233
+ }
234
+ }
235
+ RequestJob::Shutdown => return true,
236
+ }
237
+ }
238
+ false
239
+ });
240
+
241
+ if shutdown_requested || terminated.load(Ordering::Relaxed) {
242
+ waker_sender.send(TerminateWakerSignal(true)).unwrap();
243
+ break;
244
+ }
245
+
246
+ let yield_result = if receiver.is_empty() {
247
+ waker_sender.send(TerminateWakerSignal(false)).unwrap();
248
+ idle_counter = (idle_counter + 1) % 100;
249
+ call_with_gvl(|ruby| {
250
+ if idle_counter == 0 {
251
+ ruby.gc_start();
252
+ }
253
+ scheduler.funcall::<_, _, Value>(*ID_BLOCK, (thread_current, None::<u8>))
254
+ })
255
+ } else {
256
+ call_with_gvl(|_| scheduler.funcall::<_, _, Value>(*ID_YIELD, ()))
257
+ };
258
+
259
+ if yield_result.is_err() {
260
+ break;
261
+ }
262
+ })
263
+ })
264
+ }
265
+
266
+ #[instrument(skip_all, fields(thread_worker=id))]
267
+ pub fn fiber_accept_loop(
268
+ id: String,
269
+ app: Opaque<Value>,
270
+ receiver: Arc<async_channel::Receiver<RequestJob>>,
271
+ scheduler_class: Opaque<Value>,
272
+ terminated: Arc<AtomicBool>,
273
+ ) -> Result<()> {
274
+ let ruby = Ruby::get().unwrap();
275
+ let (waker_sender, waker_receiver) = watch::channel(TerminateWakerSignal(false));
276
+ let leader: Arc<Mutex<Option<RequestJob>>> = Arc::new(Mutex::new(None));
277
+ let server = ruby.get_inner(&ITSI_SERVER);
278
+ let scheduler_proc =
279
+ Self::build_scheduler_proc(app, &leader, &receiver, &terminated, &waker_sender);
280
+ let (scheduler, scheduler_fiber) = server.funcall::<_, _, (Value, Value)>(
281
+ "start_scheduler_loop",
282
+ (scheduler_class, scheduler_proc),
283
+ )?;
284
+ Self::start_waker_thread(
285
+ scheduler.into(),
286
+ scheduler_fiber.into(),
287
+ leader,
288
+ receiver,
289
+ waker_receiver,
290
+ );
291
+ Ok(())
292
+ }
293
+
294
+ #[allow(clippy::await_holding_lock)]
295
+ pub fn start_waker_thread(
296
+ scheduler: Opaque<Value>,
297
+ scheduler_fiber: Opaque<Value>,
298
+ leader: Arc<Mutex<Option<RequestJob>>>,
299
+ receiver: Arc<async_channel::Receiver<RequestJob>>,
300
+ mut waker_receiver: watch::Receiver<TerminateWakerSignal>,
301
+ ) {
302
+ create_ruby_thread(move || {
303
+ let scheduler = scheduler.get_inner_with(&Ruby::get().unwrap());
304
+ let leader = leader.clone();
305
+ call_without_gvl(|| {
306
+ RuntimeBuilder::new_current_thread()
307
+ .build()
308
+ .expect("Failed to build Tokio runtime")
309
+ .block_on(async {
310
+ loop {
311
+ waker_receiver.changed().await.ok();
312
+ if waker_receiver.borrow().0 {
313
+ break;
314
+ }
315
+ tokio::select! {
316
+ _ = waker_receiver.changed() => {
317
+ if waker_receiver.borrow().0 {
318
+ break;
319
+ }
320
+ },
321
+ next_msg = receiver.recv() => {
322
+ *leader.lock() = next_msg.ok();
323
+ call_with_gvl(|_| {
324
+ scheduler
325
+ .funcall::<_, _, Value>(
326
+ "unblock",
327
+ (None::<u8>, scheduler_fiber),
328
+ )
329
+ .ok();
330
+ });
331
+ }
332
+ }
333
+ }
334
+ })
335
+ });
336
+ });
337
+ }
338
+
339
+ #[instrument(skip_all, fields(thread_worker=id))]
340
+ pub fn accept_loop(
341
+ id: String,
342
+ app: Opaque<Value>,
343
+ receiver: Arc<async_channel::Receiver<RequestJob>>,
344
+ terminated: Arc<AtomicBool>,
345
+ ) {
346
+ let ruby = Ruby::get().unwrap();
347
+ let server = ruby.get_inner(&ITSI_SERVER);
348
+ call_without_gvl(|| loop {
349
+ match receiver.recv_blocking() {
350
+ Ok(RequestJob::ProcessRequest(request)) => {
351
+ if terminated.load(Ordering::Relaxed) {
352
+ break;
353
+ }
354
+ call_with_gvl(|_ruby| {
355
+ request.process(&ruby, server, app).ok();
356
+ })
357
+ }
358
+ Ok(RequestJob::Shutdown) => {
359
+ debug!("Shutting down thread worker");
360
+ break;
361
+ }
362
+ Err(_) => {
363
+ thread::sleep(Duration::from_micros(1));
364
+ }
365
+ }
366
+ });
367
+ }
368
+ }
@@ -0,0 +1,152 @@
1
+ use base64::{engine::general_purpose, Engine as _};
2
+ use itsi_error::Result;
3
+ use itsi_tracing::{info, warn};
4
+ use rcgen::{CertificateParams, DnType, KeyPair, SanType};
5
+ use rustls_pemfile::{certs, pkcs8_private_keys};
6
+ use std::{collections::HashMap, fs, io::BufReader};
7
+ use tokio_rustls::rustls::{Certificate, PrivateKey, ServerConfig};
8
+
9
+ const ITS_CA_CERT: &str = include_str!("./itsi_ca/itsi_ca.crt");
10
+ const ITS_CA_KEY: &str = include_str!("./itsi_ca/itsi_ca.key");
11
+
12
+ // Generates a TLS configuration based on either :
13
+ // * Input "cert" and "key" options (either paths or Base64-encoded strings) or
14
+ // * Performs automatic certificate generation/retrieval. Generated certs use an internal self-signed Isti CA.
15
+ // If a non-local host or optional domain parameter is provided,
16
+ // an automated certificate will attempt to be fetched using let's encrypt.
17
+ pub fn configure_tls(host: &str, query_params: &HashMap<String, String>) -> Result<ServerConfig> {
18
+ info!("TLS Options {:?}", query_params);
19
+ let (certs, key) = if let (Some(cert_path), Some(key_path)) =
20
+ (query_params.get("cert"), query_params.get("key"))
21
+ {
22
+ // Load from file or Base64
23
+ let certs = load_certs(cert_path);
24
+ let key = load_private_key(key_path);
25
+ (certs, key)
26
+ } else {
27
+ let domains_param = query_params
28
+ .get("domains")
29
+ .map(|v| v.split(',').map(String::from).collect());
30
+ let host_string = host.to_string();
31
+ let domains = domains_param.or_else(|| {
32
+ if host_string != "localhost" {
33
+ Some(vec![host_string])
34
+ } else {
35
+ None
36
+ }
37
+ });
38
+
39
+ if let Some(domains) = domains {
40
+ retrieve_acme_cert(domains)?
41
+ } else {
42
+ generate_ca_signed_cert(vec![host.to_owned()])?
43
+ }
44
+ };
45
+
46
+ let mut config = ServerConfig::builder()
47
+ .with_safe_defaults()
48
+ .with_no_client_auth()
49
+ .with_single_cert(certs, key)
50
+ .expect("Failed to build TLS config");
51
+
52
+ config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()];
53
+ Ok(config)
54
+ }
55
+
56
+ pub fn load_certs(path: &str) -> Vec<Certificate> {
57
+ let data = if let Some(stripped) = path.strip_prefix("base64:") {
58
+ general_purpose::STANDARD
59
+ .decode(stripped)
60
+ .expect("Invalid base64 certificate")
61
+ } else {
62
+ fs::read(path).expect("Failed to read certificate file")
63
+ };
64
+
65
+ if data.starts_with(b"-----BEGIN ") {
66
+ let mut reader = BufReader::new(&data[..]);
67
+ let certs_der: Vec<Vec<u8>> = certs(&mut reader)
68
+ .map(|r| {
69
+ r.map(|der| der.as_ref().to_vec())
70
+ .map_err(itsi_error::ItsiError::from)
71
+ })
72
+ .collect::<Result<_>>()
73
+ .expect("Failed to parse certificate file");
74
+ certs_der.into_iter().map(Certificate).collect()
75
+ } else {
76
+ vec![Certificate(data)]
77
+ }
78
+ }
79
+
80
+ /// Loads a private key from a file or Base64.
81
+ pub fn load_private_key(path: &str) -> PrivateKey {
82
+ let key_data = if let Some(stripped) = path.strip_prefix("base64:") {
83
+ general_purpose::STANDARD
84
+ .decode(stripped)
85
+ .expect("Invalid base64 private key")
86
+ } else {
87
+ fs::read(path).expect("Failed to read private key file")
88
+ };
89
+
90
+ if key_data.starts_with(b"-----BEGIN ") {
91
+ let mut reader = BufReader::new(&key_data[..]);
92
+ let keys: Vec<Vec<u8>> = pkcs8_private_keys(&mut reader)
93
+ .map(|r| {
94
+ r.map(|key| key.secret_pkcs8_der().to_vec())
95
+ .map_err(itsi_error::ItsiError::from)
96
+ })
97
+ .collect::<Result<_>>()
98
+ .expect("Failed to parse private key");
99
+ if !keys.is_empty() {
100
+ return PrivateKey(keys[0].clone());
101
+ }
102
+ }
103
+ PrivateKey(key_data)
104
+ }
105
+
106
+ pub fn generate_ca_signed_cert(domains: Vec<String>) -> Result<(Vec<Certificate>, PrivateKey)> {
107
+ info!("Generating New Itsi CA - Self signed Certificate. Use `itsi ca export` to export the CA certificate for import into your local trust store.");
108
+
109
+ let ca_kp = KeyPair::from_pem(ITS_CA_KEY).expect("Failed to load embedded CA key");
110
+ let ca_cert = CertificateParams::from_ca_cert_pem(ITS_CA_CERT)
111
+ .expect("Failed to parse embedded CA certificate")
112
+ .self_signed(&ca_kp)
113
+ .expect("Failed to self-sign embedded CA cert");
114
+
115
+ let ee_key = KeyPair::generate_for(&rcgen::PKCS_ECDSA_P256_SHA256).unwrap();
116
+ let mut ee_params = CertificateParams::default();
117
+
118
+ info!(
119
+ "Generated certificate will be valid for domains {:?}",
120
+ domains
121
+ );
122
+ use std::net::IpAddr;
123
+
124
+ ee_params.subject_alt_names = domains
125
+ .iter()
126
+ .map(|domain| {
127
+ if let Ok(ip) = domain.parse::<IpAddr>() {
128
+ SanType::IpAddress(ip)
129
+ } else {
130
+ SanType::DnsName(domain.clone().try_into().unwrap())
131
+ }
132
+ })
133
+ .collect();
134
+
135
+ ee_params
136
+ .distinguished_name
137
+ .push(DnType::CommonName, domains[0].clone());
138
+
139
+ ee_params.use_authority_key_identifier_extension = true;
140
+
141
+ let ee_cert = ee_params.signed_by(&ee_key, &ca_cert, &ca_kp).unwrap();
142
+ let ee_cert_der = ee_cert.der().to_vec();
143
+ let ee_cert = Certificate(ee_cert_der);
144
+ let ca_cert = Certificate(ca_cert.der().to_vec());
145
+ Ok((vec![ee_cert, ca_cert], PrivateKey(ee_key.serialize_der())))
146
+ }
147
+
148
+ /// TODO: Retrieves an ACME certificate for a given domain.
149
+ pub fn retrieve_acme_cert(domains: Vec<String>) -> Result<(Vec<Certificate>, PrivateKey)> {
150
+ warn!("Retrieving ACME cert for {}", domains.join(", "));
151
+ generate_ca_signed_cert(domains)
152
+ }
@@ -9,4 +9,8 @@ tracing-subscriber = { version = "0.3.19", features = [
9
9
  "env-filter",
10
10
  "std",
11
11
  "fmt",
12
+ "json",
13
+ "ansi",
12
14
  ] }
15
+ tracing-attributes = "0.1"
16
+ atty = "0.2.14"
@@ -1,11 +1,41 @@
1
+ use std::env;
2
+
3
+ use atty::{Stream, is};
1
4
  pub use tracing::{debug, error, info, trace, warn};
2
- use tracing_subscriber::{EnvFilter, fmt};
5
+ pub use tracing_attributes::instrument; // Explicitly export from tracing-attributes
6
+ use tracing_subscriber::{
7
+ EnvFilter,
8
+ fmt::{self, format},
9
+ };
3
10
 
11
+ #[instrument]
4
12
  pub fn init() {
5
- let env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info"));
6
- let format = fmt::format().with_level(true).with_target(false).compact();
7
- tracing_subscriber::fmt()
8
- .with_env_filter(env_filter)
13
+ let env_filter = EnvFilter::builder()
14
+ .with_env_var("ITSI_LOG")
15
+ .try_from_env()
16
+ .unwrap_or_else(|_| EnvFilter::new("info"));
17
+
18
+ let format = fmt::format()
19
+ .compact()
20
+ .with_file(false)
21
+ .with_level(true)
22
+ .with_line_number(false)
23
+ .with_source_location(false)
24
+ .with_target(false)
25
+ .with_thread_ids(false);
26
+
27
+ let is_tty = is(Stream::Stdout);
28
+
29
+ let subscriber = tracing_subscriber::fmt()
9
30
  .event_format(format)
10
- .init();
31
+ .with_env_filter(env_filter);
32
+
33
+ if (is_tty && env::var("ITSI_LOG_PLAIN").is_err()) || env::var("ITSI_LOG_ANSI").is_ok() {
34
+ subscriber.with_ansi(true).init();
35
+ } else {
36
+ subscriber
37
+ .fmt_fields(format::JsonFields::default())
38
+ .event_format(fmt::format().json())
39
+ .init();
40
+ }
11
41
  }
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Itsi
4
4
  class Scheduler
5
- VERSION = "0.1.0"
5
+ VERSION = "0.1.2"
6
6
  end
7
7
  end