Implement file logging functionality with FileChannel class and update Log class to support file output

This commit is contained in:
whaffman 2025-09-20 22:55:14 +02:00
parent 6262cda801
commit 94cd396ac1
7 changed files with 131 additions and 17 deletions

1
.gitignore vendored
View File

@ -5,3 +5,4 @@ build
webserv.log

View File

@ -0,0 +1,55 @@
#include "webserv/log/LogLevel.hpp"
#include <webserv/log/FileChannel.hpp>
#include <chrono>
#include <iostream>
FileChannel::FileChannel(const std::string &filename)
: filename_(filename),
fileStream_(filename, std::ios::trunc)
{
if (!fileStream_.is_open())
{
std::cerr << "Failed to open log file: " << filename << '\n';
}
}
FileChannel::~FileChannel()
{
if (fileStream_.is_open())
{
fileStream_.close();
}
}
void FileChannel::log(LogLevel &logLevel, const std::string &message,
const std::map<std::string, std::string> &context)
{
if (!fileStream_.is_open())
{
std::cerr << "Log file is not open: " << filename_ << '\n';
return;
}
// Get the current time
auto now = std::chrono::system_clock::now();
auto now_c = std::chrono::system_clock::to_time_t(now);
std::tm *tm = std::localtime(&now_c);
// Format the log message
fileStream_ << "[" << std::put_time(tm, "%Y-%m-%d %H:%M:%S") << "] "
<< "[" << logLevelToString(logLevel) << "] " << message << '\n';
// Log the context if it exists
if (!context.empty())
{
fileStream_ << "Context:" << '\n';
for (const auto &[key, value] : context)
{
fileStream_ << " " << key << ": " << value << '\n';
}
}
fileStream_ << std::flush;
}

View File

@ -0,0 +1,27 @@
#pragma once
#include <webserv/log/Channel.hpp>
#include <webserv/log/LogLevel.hpp>
#include <string>
#include <fstream>
#include <map>
class FileChannel : public Channel
{
public:
FileChannel(const std::string &filename);
FileChannel(const FileChannel &other) = delete;
FileChannel(const FileChannel &&other) = delete;
FileChannel &operator=(const FileChannel &other) = delete;
FileChannel &&operator=(const FileChannel &&other) = delete;
~FileChannel();
void log(LogLevel &logLevel, const std::string &message,
const std::map<std::string, std::string> &context = {}) override;
private:
std::string filename_;
std::ofstream fileStream_;
};

View File

@ -1,16 +1,37 @@
#include "webserv/log/StdoutChannel.hpp"
#include <webserv/log/Log.hpp>
#include <chrono>
#include <filesystem>
#include <iostream>
#include <webserv/log/FileChannel.hpp>
#include <webserv/log/Log.hpp>
#include <webserv/log/StdoutChannel.hpp>
Log::Log()
{
// get start time
start_time_ = std::chrono::steady_clock::now();
channels_.insert({"stdout", std::unique_ptr<Channel>(new StdoutChannel())});
}
void Log::setFile(const std::string &filename)
{
Log &log = getInstance();
if (log.channels_.contains("file"))
{
log.channels_.erase("file");
}
try
{
log.channels_.insert({"file", std::unique_ptr<Channel>(new FileChannel(filename))});
}
catch (const std::exception &e)
{
std::cerr << "Failed to set log file: " << e.what() << '\n';
}
}
Log &Log::getInstance()
{
static Log instance;
return instance;
}
@ -24,15 +45,16 @@ void Log::log(LogLevel level, const std::string &message, const std::string &cha
}
}
void Log::log(LogLevel level, const std::string &message, const std::string &file, int line,
const std::string &function, const std::string &channel, const std::map<std::string, std::string> &context)
const std::string &function, const std::string &channel,
const std::map<std::string, std::string> &context)
{
auto it = channels_.find(channel);
if (it != channels_.end())
{
std::string extendedMessage;
std::string extendedMessage;
if (!file.empty())
{
extendedMessage += std::filesystem::path(file).filename().string();
extendedMessage += std::filesystem::path(file).filename().string();
}
if (line != -1)
{
@ -47,6 +69,14 @@ std::string extendedMessage;
}
}
int Log::getElapsedTime()
{
Log &log = Log::getInstance();
auto now = std::chrono::steady_clock::now();
auto elapsed = std::chrono::duration_cast<std::chrono::seconds>(now - log.start_time_).count();
return static_cast<int>(elapsed);
}
void Log::static_log(LogLevel level, const std::string &message, const std::string &file, int line,
const std::string &function, const std::string &channel,
const std::map<std::string, std::string> &context)

View File

@ -1,5 +1,6 @@
#pragma once
#include <chrono>
#include <map>
#include <memory>
#include <string>
@ -7,8 +8,7 @@
#include <webserv/log/Channel.hpp>
#include <webserv/log/LogLevel.hpp>
#define LOG(level, message) \
Log::static_log((level), (message), __FILE__, __LINE__, __FUNCTION__, "stdout", {})
#define LOG(level, message) Log::static_log((level), (message), __FILE__, __LINE__, __FUNCTION__, "file", {})
#define LOG_TRACE(message) LOG(LogLevel::LOGLVL_TRACE, message)
#define LOG_INFO(message) LOG(LogLevel::LOGLVL_INFO, message)
@ -27,6 +27,8 @@ class Log
Log &operator=(const Log &other) = delete;
Log &&operator=(const Log &&other) = delete;
static void setFile(const std::string &filename);
void log(LogLevel level, const std::string &message, const std::string &channel = "stdout",
const std::map<std::string, std::string> &context = {});
@ -34,10 +36,12 @@ class Log
const std::string &function = "", const std::string &channel = "stdout",
const std::map<std::string, std::string> &context = {});
static int getElapsedTime();
static void static_log(LogLevel level, const std::string &message, const std::string &file = "", int line = -1,
const std::string &function = "", const std::string &channel = "stdout",
const std::map<std::string, std::string> &context = {});
static void trace(const std::string &message, const std::map<std::string, std::string> &context = {});
static void debug(const std::string &message, const std::map<std::string, std::string> &context = {});
static void info(const std::string &message, const std::map<std::string, std::string> &context = {});
@ -47,8 +51,10 @@ class Log
private:
Log();
~Log() = default;
static Log &getInstance();
std::chrono::steady_clock::time_point start_time_;
std::unordered_map<std::string, std::unique_ptr<Channel>> channels_;
};

View File

@ -1,10 +1,12 @@
#include <iostream>
#include <map>
#include <webserv/log/StdoutChannel.hpp>
#include <iomanip>
void StdoutChannel::log(LogLevel &logLevel, const std::string &message,
const std::map<std::string, std::string> &context)
{
std::cout << "[" << std::setw(3) << std::setfill('0') << Log::getElapsedTime() << "] ";
std::string prefix = "[" + logLevelToColoredString(logLevel) + "] ";
std::cout << prefix;
std::cout << message;

View File

@ -15,16 +15,9 @@ int main(int argc, char **argv)
std::cerr << "Usage: " << argv[0] << " <config_file_path>\n"; // NOLINT
return 1;
}
Log::setFile("webserv.log");
ConfigManager::getInstance().init(argv[1]); // NOLINT
Server server(ConfigManager::getInstance());
LOG_TRACE("Trace message example.");
LOG_INFO("Info message example.");
LOG_DEBUG("Debug message example.");
LOG_WARN("Warning message example.");
LOG_ERROR("Error message example.");
LOG_FATAL("Fatal message example.");
Log::info("test log message: server starting...", {{"port", "8080"}, {"mode", "production"}});
server.start();
return 0;