C API: add logger bindings#15816
Conversation
06e5956 to
d5d7b4b
Compare
xokdvium
left a comment
There was a problem hiding this comment.
Do we not want to also expose progress in the C API?
d5d7b4b to
b398d2c
Compare
Isn't that what |
Ah right yeah, it's a thin wrapper around |
|
Tbh I love this idea and the possibility of writing a C plugin that implements something like |
Yes, working on that.
That would be cool. Having a more Nix native |
b398d2c to
fd7ee66
Compare
| nix::Verbosity lvl, | ||
| nix::ActivityType type, | ||
| const std::string & s, | ||
| const Fields & /*fields*/, |
There was a problem hiding this comment.
We should probably also expose the fields to the callback, since it contains valueable info about which URL is being downloaded e.t.c.
| std::vector<nix_logger_field> storage; | ||
| storage.reserve(fields.size()); |
There was a problem hiding this comment.
Should we maybe allocate this as a small_vector? Typically there are only a handful of fields (<4), so we could avoid the allocation overhead there completely?
| uint64_t i; | ||
| const char * str; | ||
| unsigned int len; |
There was a problem hiding this comment.
Should this be an anonymous union with anonymous struct members? Pretty sure it wouldn't pose much of a risk to ABI backwards compatibility if we added a type of field for example (as long we only extend the union). We should also document that the size of this type could grow in a backwards compatible way.
There was a problem hiding this comment.
I was thinking about that but remembered some languages with C FFI, like Zig and Dart, don't quite like anonymous struct members.
There was a problem hiding this comment.
Hm maybe we can avoid the GNU extension and just do regular unions? Though I guess it doesn't matter much if it poses a compat risk
There was a problem hiding this comment.
Hmm, yeah we could do a regular union we typedef. Maybe call it nix_logger_field_value.
| if (fields.empty()) | ||
| return; |
There was a problem hiding this comment.
Are we sure that we don't want to signal the callback even when there are no fields?
There was a problem hiding this comment.
I'm not sure, I'll remove the check and we'll see.
| storage.push_back(f); | ||
| } | ||
|
|
||
| std::vector<const nix_logger_field *> cfields; |
There was a problem hiding this comment.
Small vector would work nicely here too.
There was a problem hiding this comment.
I don't see small vector in the STL docs.
There was a problem hiding this comment.
We polyfill with boost's small_vector... in libexpr and other places at least
f3bc162 to
93e3022
Compare
xokdvium
left a comment
There was a problem hiding this comment.
I guess one open question is how / whether a logger can report errors? Should it be able to or should it just swallow all errors that it might encounter internally? I guess it would be good for robustness.
xokdvium
left a comment
There was a problem hiding this comment.
Looking great. Now just the tiny question of error handling and how we want to address that (or whether we want to at all)
| }); | ||
| } | ||
|
|
||
| boost::container::small_vector<const nix_logger_field *, 1> cfields; |
There was a problem hiding this comment.
Could we bump the capacity to 4? That should be plenty and it's nothing in terms of stack space
| if (!vtable.result) | ||
| return; | ||
|
|
||
| boost::container::small_vector<nix_logger_field, 1> storage; |
| }); | ||
| } | ||
|
|
||
| boost::container::small_vector<const nix_logger_field *, 1> cfields; |
| if (!vtable.start_activity) | ||
| return; | ||
|
|
||
| boost::container::small_vector<nix_logger_field, 1> storage; |
| t = NIX_LOGGER_FIELD_TYPE_INT; | ||
| v.i = field.i; | ||
| } else { | ||
| continue; |
There was a problem hiding this comment.
Should this be an unreachable instead? If this happens it's a bug certainly...
If we think about forward compatibility, it would be the C logger's implementation to ensure that it ignores unknown values if we ever add new ones.
Shouldn't |
93e3022 to
07886fb
Compare
|
I meant the case of "nix sent garbage to the logger that it doesn't understand" and whether we need a way to provide a way to the logger to handle the case, or should it swallow all errors? |
|
Hmm, I think logger problems should be treated as being a non-error. Usually with what I've seen, loggers will just try their best. I think we'd have it swallow the errors. |
|
Makes sense generally, though I was thinking of some sophisticated loggers that reimplemented |
|
For a V1, loggers could use the user data stuff to push their logging errors into wherever it should go. I'm sure this will not be the first iteration. I was just hoping this would be enough for at least basic integration. |
|
Yup, we can always extend the vtable I think to add error handling if needed. |
xokdvium
left a comment
There was a problem hiding this comment.
Hopefully the last pass of review. Looking almost perfect! Sorry for the tedious review 🥲
I guess another useful addition could be to have a way for the logger to ask nix to strip away ANSI escape sequences automatically, but that's probably good to leave for V2 iteration.
| t = NIX_LOGGER_FIELD_TYPE_INT; | ||
| v.i = field.i; | ||
| } else { | ||
| std::unreachable(); |
There was a problem hiding this comment.
Huh std::unreachable actually invoked UB. We have nix::unreachable to trigger std::terminate and crash though.
| boost::container::small_vector<nix_logger_field, 4> storage; | ||
| storage.reserve(fields.size()); | ||
| for (const auto & field : fields) { | ||
| nix_logger_field_type t; | ||
| nix_logger_field_value v{}; | ||
| if (field.type == nix::Logger::Field::tString) { | ||
| t = NIX_LOGGER_FIELD_TYPE_STR; | ||
|
|
||
| nix_logger_field_value_string str; | ||
| str.value = field.s.data(); | ||
| str.len = field.s.size(); | ||
|
|
||
| v.str = str; | ||
| } else if (field.type == nix::Logger::Field::tInt) { | ||
| t = NIX_LOGGER_FIELD_TYPE_INT; | ||
| v.i = field.i; | ||
| } else { | ||
| std::unreachable(); | ||
| } | ||
|
|
||
| storage.push_back( | ||
| nix_logger_field{ | ||
| .type = t, | ||
| .value = v, | ||
| }); | ||
| } | ||
|
|
||
| boost::container::small_vector<const nix_logger_field *, 4> cfields; | ||
| cfields.reserve(storage.size()); | ||
| for (const auto & f : storage) | ||
| cfields.push_back(&f); |
There was a problem hiding this comment.
This can be factored out into a helper function, right? There are 2 places that do this now.
| * @note Must be kept in sync with `nix::ActivityType`. | ||
| */ | ||
| enum nix_activity_type { | ||
| NIX_ACTIVITY_TYPE_UNKNOWN = 0, |
There was a problem hiding this comment.
Maybe NONE would be more in-line with what's documented above?
| std::vector<std::tuple< | ||
| nix_activity_id, | ||
| nix_verbosity, | ||
| nix_activity_type, | ||
| std::string, | ||
| std::vector<CapturedField>, | ||
| nix_activity_id>> | ||
| starts; |
There was a problem hiding this comment.
Could this maybe just be a struct? A bunch of std::get calls with indices aren't very readable.
| * @brief Replace the global Nix logger with one driven by C callbacks. | ||
| * | ||
| * After this call, log messages, activities and supported results | ||
| * produced anywhere in Nix are routed to the supplied callbacks. |
There was a problem hiding this comment.
Another cool addition would be to have a way to get the old logger back or to chain the logger (like the json one is doing, I presume that for detnixd). Also not needed for the initial implementation though :)
Motivation
Closes #11036, upstreaming of DeterminateSystems#448
Context
Having Nix directly use stderr/stdout can cause problems with certain logging libraries in Rust or C. Being able to override the logger so the program integrating Nix via the C API allows for unified logging. I've tested this with Rust and it works great with tracing.
Add 👍 to pull requests you find important.
The Nix maintainer team uses a GitHub project board to schedule and track reviews.