2

Suppose I have a logger function class implementation which provides me four public functions:

class Logger
{
public:
    error(const string& caller, const string& msg);
    warn(const string& caller, const string& msg);
    info(const string& caller, const string& msg);
    debug(const string& caller, const string& msg);
};

What this class does is basically logging to a file and printing depending on the selected log level like this: cout << "[" << caller << "]: " << msg << endl. I want to have only one instance of this class and pass that instance to multiple different classes (dependency injection) and depending on where I use this instance I change the 'caller' argument to e.g. 'Foo' or 'Bar'.

class Foo
{
public:
    Foo(Logger* log) { log->info("Foo", "Hello"); };
};

class Bar
{
    Bar(Logger* log) { log->info("Bar", "Hello"); };
}

Now my question is if there is a way to 'automatically' fill the first argument to avoid having to provide the argument every time I want to log something. What came to my mind first was inhertance and then overloading the functions but that creates more objects of the class of which I only want to have one instance of. Another idea was to define new functions for the other classes which take just the message argument and then call the logger function with the added caller argument. I feel like there has to be a smarter way to do this.

SillyGoose
  • 53
  • 6
  • Not with the current `Logger` class, because its interface demands those two parameters. – Botje Aug 09 '23 at 12:54
  • 1
    Just use C++20 [source_location](https://en.cppreference.com/w/cpp/utility/source_location) if your compiler supports it. – Marek R Aug 09 '23 at 12:56
  • Otherwise you can have a go at something like __FUNCTION__, or __PRETTY_FUNCTION__ see [here](https://stackoverflow.com/questions/4384765/whats-the-difference-between-pretty-function-function-func). Also if you're going to do dependency injection make your base a pure abstract baseclass. – Pepijn Kramer Aug 09 '23 at 13:22
  • Design feedback, I usually do NOT inject logging (interfaces) into my code. I define an interface with outgoing methods, like `virtual void report_something_bad_happened(args_t...) = 0` and then I write a small adapter to the logging system of the day (which also can do the string formatting/localization if needed etc). And inject that, this really keeps actual reporting/logging out of my functional code. And in unit tests I can inject mocks that check if the reporting is done at the correct moments. With these methods you will automatically know the origin (each call should be unique) – Pepijn Kramer Aug 09 '23 at 13:25
  • Are you using multiple threads or tasks? Logging gets *interesting* with multiple threads and tasks. – Thomas Matthews Aug 09 '23 at 20:23

2 Answers2

2

From C++20 onwards, you can do (simplified):

#include <iostream>
#include <string>
#include <source_location>

void log_info (const std::string& msg, const std::string &caller = std::source_location::current ().function_name ())
{
    std::cout << caller << ": " << msg << "\n";
}

class Foo
{
public:
    Foo () { log_info ("Hello"); };
};

int main ()
{
    Foo foo;
}

Output:

Foo::Foo(): Hello

Not quite what you asked for, but your logger can always tidy up caller before printing it.

Paul Sanders
  • 24,133
  • 4
  • 26
  • 48
  • IMO implicit conversion of `std::source_location` to `std::string` is bad approach. It is better just pass `std::source_location` into a function. – Marek R Aug 09 '23 at 13:21
  • @MarekR I'm inclined to agree, but I was mindful of the fact that the OP might want to call the function with an explicit `caller` string of his own. But I guess you could always add an overload for that. – Paul Sanders Aug 09 '23 at 13:27
2

A simple wrapper seems to do the job:

struct NamedLog
{
    NamedLog(std::string name, Log* log) : name(std::move(name)), log(log) {}

    void error(const string& msg) { log->error(name, msg); }
    void warn(const string& msg) { log->warn(name, msg); }
    void info(const string& msg) { log->info(name, msg); }
    void debug(const string& msg) { log->debug(name, msg); }

private:
    std::string name;
    Log* logger;
};

And then

class Foo
{
public:
    Foo(Logger* log) : log("Foo", log) { log.info("Hello"); };

    NamedLog log;
};

class Bar
{
public:
    Bar(Logger* log) : log("Bar", log) { log.info("Hello"); };

    NamedLog log;
};
Jarod42
  • 203,559
  • 14
  • 181
  • 302