Skip to content
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

Add a settings option for log-format #3961

Closed
wants to merge 4 commits into from
Closed

Add a settings option for log-format #3961

wants to merge 4 commits into from

Conversation

bqv
Copy link
Contributor

@bqv bqv commented Aug 26, 2020

This removes the need to pass --log-format to every nix invocation for your log-format of choice

src/libmain/loggers.cc Outdated Show resolved Hide resolved
@edolstra
Copy link
Member

This appears to break the --log-format command line option, for example

nix eval --expr '1 + 2'  --json --log-format internal-json -vvvv

no longer prints any JSON messages.

src/libmain/loggers.cc Outdated Show resolved Hide resolved
@bqv
Copy link
Contributor Author

bqv commented Aug 28, 2020

This appears to break the --log-format command line option, for example

I'm unsure why this is, as of now. I'll get back to this a little later

@cole-h
Copy link
Member

cole-h commented Sep 1, 2020

I tried debugging the issue where --log-format no longer works, but wasn't able to get anywhere due to my lack of familiarity with the nix codebase. All I figured out was that settings.logFormat.get() always returns the default value (an empty string).

Seeing that flags like --extra-substituters work, I imagine settings.<name>.get() is the proper way to get the value, but I don't know why the value of --log-format is not being set in the first place...

(BTW: log-format can be safely removed from MixCommonArgs, as it doesn't actually appear to do anything -- I put an abort(); in the handler and nothing happened when passing --log-format asdf.)

@bqv bqv force-pushed the master branch 2 times, most recently from e830b11 to 9dc53a2 Compare September 5, 2020 06:04
@bqv
Copy link
Contributor Author

bqv commented Sep 5, 2020

It works!

Though I ended up juggling around more things than would have been ideal...

@cole-h
Copy link
Member

cole-h commented Sep 6, 2020

Applying the following patch allows both --option log-format and --log-format to work (by moving the createDefaultLogger() call into the setter), as well as removes --log-format from MixCommonArgs and fixes the build (by fixing the toJSON() function): https://gist.github.com/cole-h/ec8e4334e6595c2848252e9dda54941d.

EDIT: Do note, however, that this means libstore depends on libmain (for access to createDefaultLogger()). I don't know if this is desirable or not (or even extremely undesirable), but I can't see any other way.

@bqv bqv force-pushed the master branch 2 times, most recently from 18188ad to fa311d1 Compare September 7, 2020 06:49
@Kloenk
Copy link
Member

Kloenk commented Sep 10, 2020

So its ready to be merged? Would need this for one of my prs.
CC @edolstra

@bqv bqv requested a review from edolstra September 10, 2020 23:33
@bqv bqv force-pushed the master branch 2 times, most recently from b1600b3 to b024a78 Compare September 14, 2020 12:00
Copy link
Member

@thufschmitt thufschmitt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EDIT: Do note, however, that this means libstore depends on libmain (for access to createDefaultLogger()). I don't know if this is desirable
or not (or even extremely undesirable), but I can't see any other way.

That's a bit annoying imho. IIRC there was a good reason to keep the progress bar in libmain, but a good (?) way to break this would be to mak
e the logger pluggable like we do for the stores. Something like

// Somewhere in libutil                                                                                                                       
struct LoggerBuilder {                                                                                                                        
  std::string name;                                                                                                                           
  std::function<(Logger*) void*> builder;                                                                                                     
};                                                                                                                                            
                                                                                                                                              
static std::vector<std::shared_ptr<LoggerBuilder>> registeredLoggers;                                                                         
                                                                                                                                              
void registerLogger(std::string name, std::function<(Logger*) void> builder) {                                                                
  LoggerBuilder lBuilder{ .name = name, .builder = builder };                                                                                 
  registeredLoggers.push_back(std::shared_ptr<LoggerBuilder>(lBuilder));
}

Logger* makeDefaultLogger() {
  for (auto & builder : registeredLoggers) {
    if (builder.name == settings.logFormat) {
      return builder.builder();
    }
  }
  throw Error("Unknown logger %s", settings.logFormat);
}

// Can be called in the file defining the logger
registerLogger("bar", []() -> Logger* { makeProgressBar(false); });
registerLogger("bar-with-logs", []() -> Logger* { makeProgressBar(true); });
registerLogger("json", []() -> Logger* { makeJSONLogger(); });

And then we can recover the list of values for the logger option by introspecting the value of registeredLoggers:

std::string listLogFormats()
{
  std::string res;
  for (auto & builder : registeredLoggers) {
    if (res) res += ", ";
    res += builder.name;
  }
  return res;
}

    Setting<LogFormat> logFormat{this, LogFormat::bar, "log-format",
        format("Default build output logging format; %s.", listLogFormats()};

else abort();
}

template<> nlohmann::json BaseSetting<LogFormat>::toJSON()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't correct anymore in master (toJSON() isn't expected to be equal to toString() anymore). You can get the templated definition by including abstractsettingtojson.hh

Copy link
Member

@cole-h cole-h Sep 18, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW, abstractsettingtojson.hh is already included by globals.hh, so there was no need to include it in globals.cc as well.


template<> void BaseSetting<LogFormat>::convertToArg(Args & args, const std::string & category)
{
args.addFlag({
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a way to define custom completers for the cli, you could use that so that the autocompletion suggest the different log formats

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've done so (based on the completion function for hashTypes), but I don't know how to test if it really works (nor can I, AFAIK, as I use fish rather than bash or zsh).

@cole-h
Copy link
Member

cole-h commented Sep 18, 2020

Thanks @regnat for the help! Following your advice (both here and on IRC), I refactored the loggers stuff to be pluggable (letting us drop the libmain <- libstore dependency).

There's one thing I'm still unhappy with, however: the fact that man nix.conf shows the default value as "3" (because it's an enum variant), instead of "bar". I don't know if there's a way to make whatever generates these defaults be run through a to_string() of some sort, but that would be swell. I've left a FIXME indicating this lack of satisfaction (input welcome).

One idea I just had would be to create a new log format type that's really just a string (as done with Path in libutil/types.hh), in order to override the default logic and also show the logger's string name in the manpage. Thoughts, anyone?

@@ -12,6 +13,8 @@

namespace nix {

std::string listLogFormats();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't really like that this is outside the Settings class, but putting it anywhere inside gives undefined references errors (due to its use in the logFormat description). Happy to hear alternatives.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you're allowed to make it a static member of the Settings class if you want to

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With @LnL7's help, I created a new type for LogFormatSetting, which lets me remove listLogFormats from publicity and instead just call it in the constructor for that setting.

(Your static idea didn't seem to work.)

@thufschmitt
Copy link
Member

Thanks @cole-h , I like that a lot!

There's one thing I'm still unhappy with, however: the fact that man nix.conf shows the default value as "3" (because it's an enum variant), instead of "bar". I don't know if there's a way to make whatever generates these defaults be run through a to_string() of some sort, but that would be swell. I've left a FIXME indicating this lack of satisfaction (input welcome).

One idea I just had would be to create a new log format type that's really just a string (as done with Path in libutil/types.hh), in order to override the default logic and also show the logger's string name in the manpage. Thoughts, anyone?

That sounds definitely a good thing (The static enum doesn't really make sense now that the thing is pluggable imho).

Regarding your IRC message:

in the generated nix.conf.5 manpage, bar and bar-with-logs won't show up because they're declared outside of libutil

I think I found the reason why: both settings and registeredLoggers (and all the registerLoggers calls) are static variables. And in C++ the order in which they will be initialized isn't defined. This means that it's totally possible that settings will be instantiated while only part of the loggers have been registered, which in turn means that only this subset of loggers will show-up in the help (and the manual).
A way out of that is to replace the toplevel Settings settings; (in globals.hh) by a function Settings* settings() that will lazily initialize a Settings object and return a pointer to it (that way we're guaranteed that it will be instantiated after all the loggers have been registered). I've tried it in tweag@b52f6be, and it definitely works, except that it's quite invasive because it requires rewriting all the accesses to settings.foo into settings()->foo (and there's a lot of these). So it might not be such a good idea.
Another option would be to allow making the description of a setting lazy (so that we only evaluate listLogFormats() once everything is registered), but I don't see how to do that nicely.

@cole-h cole-h force-pushed the master branch 3 times, most recently from 99f6976 to 1422880 Compare September 19, 2020 18:40
@cole-h
Copy link
Member

cole-h commented Sep 19, 2020

Because @LnL7 showed me how to get the json library to specialize enum conversion, the string wrapper is no longer necessary.

I also moved progress-bar.{hh,cc} and names.{hh,cc} to libutil, which allows all the loggers to be registered in libutil (so they all show up in the manpage).

I think this is ready for merge (or at least "final review"). Thanks everyone for your help (especially @regnat)!

Copy link
Member

@thufschmitt thufschmitt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice 👍 I don't have the commit rights for the repo, but it looks all good on my side

(I'm not utterly fond of having both a static list of known loggers and a registration mechanism, but it's definitely not a blocker for me).

@cole-h cole-h force-pushed the master branch 3 times, most recently from cbc9f1d to 8f4c71e Compare September 28, 2020 16:37
@cole-h
Copy link
Member

cole-h commented Sep 28, 2020

Ping @edolstra -- would appreciate another review (or, even better, a merge) on this, when you get a chance.

@Ericson2314
Copy link
Member

This is really cool! I'm especially excited because I think it might make it easier for hydra alternatives to stream logs per derivation?

Hope to see this land soon :).

@bqv
Copy link
Contributor Author

bqv commented Oct 15, 2020

@edolstra ping, have you had a chance to look over this?

@cole-h cole-h force-pushed the master branch 2 times, most recently from 6cc4cd9 to a67a66f Compare October 24, 2020 03:13
bqv and others added 2 commits October 30, 2020 10:42
Co-authored-by: Cole Helbling <cole.e.helbling@outlook.com>
Thanks to some precision guidance by regnat, we can remove libstore's
dependency on libmain by just making the logging mechanisms pluggable.

This:
* moves loggers.{hh,cc} from libmain to libutil
* removes the now-unnecessary (and incorrect) `toJSON` specialization
* moves LogFormat from types.hh to loggers.hh
* adds a completer (untested)

To add another logger, the following steps should be all that's
necessary:
1) Add enum variant in libutil/loggers.hh and name to the `std::set` in
loggers.cc
2) Modify the specialized `set` and `to_string` methods to include the
new variant
3) Use the `OnStartup` class to `registerLogger` the new logger method

Co-authored-by: regnat <rg@regnat.ovh>
cole-h and others added 2 commits October 30, 2020 11:42
It is no longer necessary to add the logger name to the `logFormats` set
when adding a new logger (due to its removal).

Additionally, the manpage now displays the string "bar" as the default,
rather than an unhelpfully opaque integer.

Co-authored-by: regnat <rg@regnat.ovh>
Co-authored-by: Daiderd Jordan <daiderd@gmail.com>
This fixes manpage generation excluding the `bar` and `bar-with-logs`
log formats that were registered outside libutil; now all formats
appear.

Note that any loggers registered outside of libutil might not appear in
the manpage -- probably due to the fact that C++ doesn't define the
order in which static variables are initialized (as regnat noted).

This also removes the unused store-api.hh include in progress-bar.cc.
@bqv
Copy link
Contributor Author

bqv commented Nov 18, 2020

Seems eelco was ignoring this because he has other plans 490861e

@bqv
Copy link
Contributor Author

bqv commented Dec 22, 2020

Close in favor of #4296

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants