-
Notifications
You must be signed in to change notification settings - Fork 49
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Enable relaying log events via FFI #542
Conversation
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #542 +/- ##
==========================================
+ Coverage 81.54% 81.63% +0.08%
==========================================
Files 67 68 +1
Lines 14544 14874 +330
Branches 14544 14874 +330
==========================================
+ Hits 11860 12142 +282
- Misses 2107 2145 +38
- Partials 577 587 +10 ☔ View full report in Codecov by Sentry. |
/// `2022-02-15T18:40:14.289898Z INFO fmt: preparing to shave yaks number_of_yaks=3` | ||
FULL, | ||
/// A variant of the FULL formatter, optimized for short line lengths. Fields from the context | ||
/// are appended to the fields of the formatted event, and targets are not shown. | ||
/// Example: | ||
/// `2022-02-17T19:51:05.809287Z INFO fmt_compact: preparing to shave yaks number_of_yaks=3` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think I understand the difference between these two?
(the example doesn't seem to show any meaningful difference)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I've filled an upstream issue: tokio-rs/tracing#3157
ffi/src/ffi_tracing.rs
Outdated
None => KernelStringSlice { | ||
ptr: std::ptr::null(), | ||
len: 0, | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should ideally define a KernelStringSlice::empty()
method and use that instead?
(apparently this is the first time we need an empty string slice in the code)
Doing that would allow simply:
let file = metadata.file().map_or(KernelSlice::empty(), |f| kernel_string_slice!(f));
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could also just do:
let file = metadata.file().unwrap_or("");
and then
let event = Event {
...
file: kernel_string_slice!(file),
};
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think having empty
is probably good
ffi/src/ffi_tracing.rs
Outdated
S: Subscriber + for<'a> LookupSpan<'a>, | ||
{ | ||
fn on_event(&self, _: &TracingEvent<'_>, _context: Context<'_, S>) { | ||
let mut buf = self.buf.lock().unwrap(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
panic in tracing code... not fun to debug
(again below)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good call, fixed. not sure if spamming the log of the engine with an error is good, but it feels better than silent failure.
// This repeats some code, but avoids some insane generic wrangling if we try to abstract the | ||
// type of `fmt_layer` over the formatter |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Problem being, that e.g. Layer::compact
returns Format<Compact, _>
, so we need a different return type for each call? (but everything else is the same)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's also when we call things like without_time
which effects the second generic for Format
.
The real issue is, we want to return this. Really we just need an impl Subscriber
, but because the concrete types all differ it gets really messy.
I can play around a bit more, but this felt overall more understandable and actually cleaner
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also note that the tracing_core
crate introduces Dispatch
exactly to abstract over all this where they erase the type. But we can't do that so easily because we want to retain methods like with_filter
and pretty()
, etc.
Thanks for the review. I've updated some stuff to remove a bad panic when setting global subscriber. Will address the rest of the comments when I'm back from Thanksgiving. |
@@ -176,7 +176,6 @@ mod private { | |||
{ | |||
/// Obtains a mutable reference to the handle's underlying object. Unsafe equivalent to | |||
/// [`AsMut::as_mut`]. | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
clippy fix
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Generally looks good, just a couple of small (but important) items:
- NULL pointer in string slice is UB
- Deceptively unprotective use of
kernel_string_slice!
ffi/src/ffi_tracing.rs
Outdated
fn is_valid(value: u32) -> bool { | ||
static VALID_VALUES: &[u32] = &[0, 1, 2, 3, 4]; | ||
VALID_VALUES.contains(&value) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would this be easier to use?
fn is_valid(value: u32) -> bool { | |
static VALID_VALUES: &[u32] = &[0, 1, 2, 3, 4]; | |
VALID_VALUES.contains(&value) | |
fn is_valid(self) -> bool { | |
static VALID_VALUES: &[u32] = &[0, 1, 2, 3, 4]; | |
VALID_VALUES.contains(&(self as u32)) |
(e.g. max_level.is_valid()
?)
(ok to consume self
because Copy
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yep, that's nicer, thanks
ffi/src/ffi_tracing.rs
Outdated
file, | ||
file: metadata | ||
.file() | ||
.map_or(KernelStringSlice::empty(), |f| kernel_string_slice!(f)), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FYI: This kernel_string_slice
invocation is not obviously (syntactically) safe, because the identifier f
goes out of scope immediately after the slice is created. In this specific case, f
has lifetime of metadata
so we're ok... but the macro didn't help enforce that at all.
I still wonder why not just use ""
as the empty string slice?
let file = metadata.file().unwrap_or("");
let event = Event {
...
file: kernel_string_slice!(file),
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's fair. I liked the idea of empty
for string slice, but you're probably right that just having it be the actual empty string makes the most sense. I'll switch to that
.expect("Failed to set global subscriber"); | ||
} | ||
}; | ||
($($transform:ident()).*) => {{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh clever -- I didn't realize we could capture ()
and .
like that!
(only works for these no-arg invocations tho...)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, macros are pretty nice. You can handle arg cases too with nested and repeated args but the macro will get really ugly. I initially wrote a pretty complex macro because I was using the with_ansi(ansi)
call, and then realized you can simplify a lot if you get rid of the arg and the need to have a call at all.
ffi/src/lib.rs
Outdated
/// Create a new empty `KernelStringSlice`. `len` will be 0, and `ptr` will be `null`. | ||
pub(crate) fn empty() -> Self { | ||
Self { | ||
ptr: std::ptr::null(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The TryFromSlice
docs specifically state:
The slice must be a valid (non-null) pointer
... which is required because we're going to turn the pointer into a reference, and a null reference is UB even if never accessed. We should really use NonNull
and its associated dangling()
instead -- see e.g. KernelBoolSlice
). Tho I suspect that ptr: NonNull::from("")
would work just as well for this specific case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ahh, good catch! I've actually just removed this and we can just use ""
since it avoids all the mess
} | ||
} | ||
|
||
impl From<&tracing::Level> for Level { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is tracing::Level
not Copy
, that we have to consume a ref instead?
Or does it just so happen all the call sites already have a reference and this avoids the need for (*level).into()
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah, I use this only as metadata.level().into()
, and metadata.level() returns &Level
, so this makes it a little cleaner at the usage site.
/// Note that setting up such a call back can only be done ONCE. Calling any of | ||
/// `enable_event_tracing`, `enable_log_line_tracing`, or `enable_formatted_log_line_tracing` more | ||
/// than once is a no-op. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this also be part of the safety contract?
(seems like an unusually sharp edge to have inherited from rust code)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I didn't read very carefully... the error case becomes a no-op, so no safety issues whatsoever.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah. We should probably discuss if we should do anything other than just "return false" if you call twice, but for now it's safe. I could turn it into a proper ExternResult
with more info, but had the feeling that was just more overhead for the engine which probably just wants to know if it set up logging okay or not.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
just a little question but LGTM and i ran locally to take all those pretty colors for a spin! looks awesome!!
ffi/src/ffi_tracing.rs
Outdated
|
||
// utility code below for setting up the tracing subscriber for events | ||
|
||
pub fn set_global_default(dispatch: tracing_core::Dispatch) -> DeltaResult<()> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this supposed to be pub
? is it just if consumers want to do something different than enable_event_tracing
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nope, good catch, removed
What changes are proposed in this pull request?
This PR provides 3 ways for engines to hook into our logging:
Event
type with relevant information that they can formatNote that the
Compact
format seems a bit broken upstream. I've filled tokio-rs/tracing#3157 for that.Adds 3 new public ffi APIs
How was this change tested?
Running in
read_table
, and unit tests