I don't see the need for macros at all. The G_LOG_DOMAIN seems to be a red herring. The underlying g_log() function accepts the domain as a parameter. As far as I can see, G_LOG_DOMAIN is used only by the g_error() and similar macros so you don't have to provide the domain at every point where a log message is produced. We can do the same thing by having the logger instance hang onto the domain and supply it when a message is written with g_log(). No need for a macro that way. We can't have the implementation without using a pimpl. It needs to be split into a public and internal API, otherwise we can't change the implementation without breaking ABI. The only data member of the Logger (and the LogWriter, see below) should be a unique_ptr to the implementation in the internal namespace. Here is how I would approach it: class Logger { public: Logger(std::string const& domain); Logger(Logger const&); Logger(Logger&&); Logger& operator=(Logger const&); Logger& operator=(Logger&&); virtual ~Logger(); // ... }; That's a well-behaved class with the big five taken care of. It does *not* derive from ostringstream. It simply is a class that remembers its domain in a data member (of the internal Impl class). Now add a method to read the domain (for completeness) and a method that writes a log message: class Logger { public: // ... std::string domain() const; LogWriter operator()(LogLevel l); }; With that, I can write: Logger log("mydomain"); log(LogLevel::debug) << "hello"; To make things a little more convenient, we can add these: class Logger { public: // ... LogWriter operator()(); // Synonym for debug() LogWriter debug(); LogWriter info(); LogWriter message(); LogWriter warning(); LogWriter critical(); LogWriter error(); }; Now I can write: Logger log("mydomain"); log() << "debug"; log.warning() << "warning"; The LogWriter class takes care of writing the actual log message (note the derivation from ostringstream). I've omitted the pimpl here for brevity. The only data member should be a unique_ptr; the two data members here should be part of LogWriterImpl: class LogWriter : public std::ostringstream { public: LogWriter(LogWriter&&); LogWriter& operator=(LogWriter&&) = delete; ~LogWriter(); private: LogWriter(std::string const& domain); // There should be a unique_ptr here instead. // This is only for exposition: std::string domain_; std::ostringstream str_; friend class Logger; }; The LogWriter can be constructed with a domain only by the Logger. The API client can only move-construct a LogWriter, which enforces that the only way to use it is via the methods on Logger that return a LogWriter. The LogWriter move constructor looks like this: LogWriter::LogWriter(LogWriter&& other) : ostringstream(std::move(other)) , domain_(other.domain_) { } The destructor writes the message. Here, I'm just writing to clog, but the method could write using g_log() instead just as easily, passing in the domain: LogWriter::~LogWriter() { string msg = str(); if (!msg.empty()) { if (msg.back() != '\n') { msg.append("\n"); } clog << msg; clog.flush(); } } This ensures that, if I have a long-lived logger, messages are written immediately and don't pile up in memory indefinitely. We could even get fancy and allow a functor to be passed to the Logger constructor, which, in turn, would pass the functor through to the LogWriter. Instead of hard-coding the g_log() implementation, this would allow me to pass in a lambda (or anything that's callable) that does the actual writing of the message, and the destructor would just call the functor to write the message. This would make the API much more flexible because, by simply passing in a function, I can redirect everything to a completely different logging mechanism. (You could have a default argument for the Logger constructor to provide the glib functor.) I think this is the right approach. It needs a bit more fleshing out, but it does everything that's needed. If we want to get fancy, we can add functors that prepend date and time, for example. Now there is no need for macros, and the loggers have a well-defined life cycle. If people want to insist on using a global logger (completely evil, of course), they can instantiate one wherever it suits them and make it available throughout their code. And, if someone *really must* be able to write debug() << "debug"; they can trivially define their own function or macro that does this. No need for us to muck around with macros or to pollute the global namespace, and no issues with the life cycle of global objects and destructor ordering (which is what caused me to eventually rip out the boost implementation; it's useless for libraries).