Skip to content

Commit 5b033d0

Browse files
authored
feat: hook in runtime logs (#568)
* feat: simple wasm log to send over unix socket * feat: hook up logs subscription * refactor: extra comments * refactor: simpler interface * refactor: combine JsonVisitors * refactor: clippy suggestions * refactor: update tests * refactor: extra comment * refactor: increase channel for better performance * refactor: move off stderr
1 parent 8324824 commit 5b033d0

File tree

15 files changed

+640
-159
lines changed

15 files changed

+640
-159
lines changed

Cargo.lock

+2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

codegen/src/next/mod.rs

+17-7
Original file line numberDiff line numberDiff line change
@@ -262,26 +262,36 @@ pub(crate) fn wasi_bindings(app: App) -> proc_macro2::TokenStream {
262262
#[no_mangle]
263263
#[allow(non_snake_case)]
264264
pub extern "C" fn __SHUTTLE_Axum_call(
265-
fd_3: std::os::wasi::prelude::RawFd,
266-
fd_4: std::os::wasi::prelude::RawFd,
267-
fd_5: std::os::wasi::prelude::RawFd,
265+
logs_fd: std::os::wasi::prelude::RawFd,
266+
parts_fd: std::os::wasi::prelude::RawFd,
267+
body_read_fd: std::os::wasi::prelude::RawFd,
268+
body_write_fd: std::os::wasi::prelude::RawFd,
268269
) {
269270
use axum::body::HttpBody;
271+
use shuttle_common::wasm::Logger;
270272
use std::io::{Read, Write};
271273
use std::os::wasi::io::FromRawFd;
274+
use tracing_subscriber::prelude::*;
272275

273-
println!("inner handler awoken; interacting with fd={},{},{}", fd_3, fd_4, fd_5);
276+
println!("inner handler awoken; interacting with fd={},{},{},{}", logs_fd, parts_fd, body_read_fd, body_write_fd);
277+
278+
// file descriptor 2 for writing logs to
279+
let logs_fd = unsafe { std::fs::File::from_raw_fd(logs_fd) };
280+
281+
tracing_subscriber::registry()
282+
.with(Logger::new(logs_fd))
283+
.init(); // this sets the subscriber as the global default and also adds a compatibility layer for capturing `log::Record`s
274284

275285
// file descriptor 3 for reading and writing http parts
276-
let mut parts_fd = unsafe { std::fs::File::from_raw_fd(fd_3) };
286+
let mut parts_fd = unsafe { std::fs::File::from_raw_fd(parts_fd) };
277287

278288
let reader = std::io::BufReader::new(&mut parts_fd);
279289

280290
// deserialize request parts from rust messagepack
281291
let wrapper: shuttle_common::wasm::RequestWrapper = rmp_serde::from_read(reader).unwrap();
282292

283293
// file descriptor 4 for reading http body into wasm
284-
let mut body_read_stream = unsafe { std::fs::File::from_raw_fd(fd_4) };
294+
let mut body_read_stream = unsafe { std::fs::File::from_raw_fd(body_read_fd) };
285295

286296
let mut reader = std::io::BufReader::new(&mut body_read_stream);
287297
let mut body_buf = Vec::new();
@@ -306,7 +316,7 @@ pub(crate) fn wasi_bindings(app: App) -> proc_macro2::TokenStream {
306316
parts_fd.write_all(&response_parts).unwrap();
307317

308318
// file descriptor 5 for writing http body to host
309-
let mut body_write_stream = unsafe { std::fs::File::from_raw_fd(fd_5) };
319+
let mut body_write_stream = unsafe { std::fs::File::from_raw_fd(body_write_fd) };
310320

311321
// write body if there is one
312322
if let Some(body) = futures_executor::block_on(body.data()) {

common/Cargo.toml

+4-1
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,16 @@ serde = { workspace = true, features = ["derive"] }
2323
serde_json = { workspace = true, optional = true }
2424
strum = { version = "0.24.1", features = ["derive"] }
2525
tracing = { workspace = true }
26+
tracing-subscriber = { workspace = true, optional = true }
2627
uuid = { workspace = true, features = ["v4", "serde"] }
2728

2829
[dev-dependencies]
30+
cap-std = "1.0.2"
2931
hyper = "0.14.3"
3032

3133
[features]
3234
backend = ["async-trait", "axum"]
3335
display = ["comfy-table", "crossterm"]
34-
axum-wasm = ["http-serde", "http", "rmp-serde"]
36+
tracing = ["serde_json"]
37+
axum-wasm = ["http-serde", "http", "rmp-serde", "tracing", "tracing-subscriber"]
3538
models = ["anyhow", "async-trait", "display", "http", "reqwest", "serde_json"]

common/src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ pub mod log;
77
pub mod models;
88
pub mod project;
99
pub mod storage_manager;
10+
#[cfg(feature = "tracing")]
11+
pub mod tracing;
1012
#[cfg(feature = "axum-wasm")]
1113
pub mod wasm;
1214

common/src/tracing.rs

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
use serde_json::json;
2+
use tracing::field::Visit;
3+
4+
// Boilerplate for extracting the fields from the event
5+
#[derive(Default)]
6+
pub struct JsonVisitor {
7+
pub fields: serde_json::Map<String, serde_json::Value>,
8+
pub target: Option<String>,
9+
pub file: Option<String>,
10+
pub line: Option<u32>,
11+
}
12+
13+
impl JsonVisitor {
14+
/// Ignores log metadata as it is included in the other LogItem fields (target, file, line...)
15+
fn filter_insert(&mut self, field: &tracing::field::Field, value: serde_json::Value) {
16+
match field.name() {
17+
"log.line" => self.line = value.as_u64().map(|u| u as u32),
18+
"log.target" => self.target = value.as_str().map(ToOwned::to_owned),
19+
"log.file" => self.file = value.as_str().map(ToOwned::to_owned),
20+
"log.module_path" => {}
21+
name => {
22+
self.fields.insert(name.to_string(), json!(value));
23+
}
24+
}
25+
}
26+
}
27+
impl Visit for JsonVisitor {
28+
fn record_str(&mut self, field: &tracing::field::Field, value: &str) {
29+
self.filter_insert(field, json!(value));
30+
}
31+
fn record_bool(&mut self, field: &tracing::field::Field, value: bool) {
32+
self.filter_insert(field, json!(value));
33+
}
34+
fn record_u64(&mut self, field: &tracing::field::Field, value: u64) {
35+
self.filter_insert(field, json!(value));
36+
}
37+
fn record_i64(&mut self, field: &tracing::field::Field, value: i64) {
38+
self.filter_insert(field, json!(value));
39+
}
40+
fn record_f64(&mut self, field: &tracing::field::Field, value: f64) {
41+
self.filter_insert(field, json!(value));
42+
}
43+
fn record_error(
44+
&mut self,
45+
field: &tracing::field::Field,
46+
value: &(dyn std::error::Error + 'static),
47+
) {
48+
self.filter_insert(field, json!(value.to_string()));
49+
}
50+
fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) {
51+
self.filter_insert(field, json!(format!("{value:?}")));
52+
}
53+
}

0 commit comments

Comments
 (0)