JuniorCodeRareNot answered yet
Write a Logger class that logs to console or file
Implement a Logger that writes timestamped, level-prefixed lines to either the console or a file.
Requirements:
- take a
std::ostream&by reference in the constructor, so a caller can passstd::cout, anstd::ofstream, or anyostream— do not store it by value - support
INFO,WARN,ERRORlevels and prepend a timestamp to each line - make the class non-copyable (a stream reference cannot be copied/reseated)
- flush after each line so messages are not lost on a crash
enum class LogLevel { INFO, WARN, ERROR };
class Logger {
public:
explicit Logger(std::ostream& out);
void log(LogLevel level, std::string_view msg);
void info(std::string_view msg);
void warn(std::string_view msg);
void error(std::string_view msg);
// your code here
};
Write the implementation.
Logger wraps a std::ostream& and writes timestamped, level-prefixed lines. Accepting the stream by reference lets callers pass std::cout, an std::ofstream, or any ostream. The class must be non-copyable since streams are not copyable.
- ✗Storing the stream by value instead of reference — streams are not copyable
- ✗Not flushing after each log line — the last messages may be lost on crash
- ✗Ignoring thread safety — two threads writing simultaneously produce interleaved output; use a
std::mutex
- →How would you add log levels so that
DEBUGmessages are filtered out in production? - →How do you make Logger thread-safe with minimal lock contention?
Contents
Task
Implement a Logger class that:
- takes
std::ostream&in the constructor (std::coutor an openstd::ofstream) - supports
INFO,WARN,ERRORlevels - prepends a timestamp to every line
- is non-copyable
Solution
#include <chrono>
#include <iomanip>
#include <iostream>
#include <fstream>
#include <sstream>
#include <string_view>
#include <cassert>
enum class LogLevel { INFO, WARN, ERROR };
class Logger {
public:
explicit Logger(std::ostream& out) : out_(out) {}
Logger(const Logger&) = delete;
Logger& operator=(const Logger&) = delete;
void log(LogLevel level, std::string_view msg) {
out_ << timestamp() << " [" << levelStr(level) << "] " << msg << '\n';
out_.flush();
}
void info (std::string_view msg) { log(LogLevel::INFO, msg); }
void warn (std::string_view msg) { log(LogLevel::WARN, msg); }
void error(std::string_view msg) { log(LogLevel::ERROR, msg); }
private:
std::ostream& out_;
static std::string timestamp() {
using namespace std::chrono;
auto now = system_clock::now();
auto time = system_clock::to_time_t(now);
std::ostringstream ss;
ss << std::put_time(std::localtime(&time), "%Y-%m-%d %H:%M:%S");
return ss.str();
}
static const char* levelStr(LogLevel l) {
switch (l) {
case LogLevel::INFO: return "INFO ";
case LogLevel::WARN: return "WARN ";
case LogLevel::ERROR: return "ERROR";
}
return "?????";
}
};
int main() {
Logger consoleLog(std::cout);
consoleLog.info("server started");
consoleLog.warn("high memory usage");
consoleLog.error("connection refused");
std::ofstream f("app.log");
assert(f.is_open());
Logger fileLog(f);
fileLog.info("writing to file");
return 0;
}
Key points
| Aspect | Decision |
|---|---|
| Stream storage | std::ostream& — works with both cout and ofstream |
| Non-copyable | = delete on copy ctor and copy assign |
| Flush | flush() after every line to avoid lost messages on crash |
| Timestamp | std::put_time + std::localtime |
| Thread safety | Not included; add std::mutex for concurrent use |
Contents