-----ETC-----/C++ 고급 프로그래밍과 응용 프로젝트 시리즈

[C++ 고급 프로그래밍과 응용 프로젝트 시리즈] Day 19: 프로젝트 1 - 에러 핸들링 및 로깅

cogito21_cpp 2024. 8. 1. 22:36
반응형

에러 핸들링 및 로깅

에러 핸들링은 프로그램의 안정성과 신뢰성을 높이기 위해 필수적인 부분입니다. 로깅은 프로그램의 상태와 동작을 기록하여 디버깅과 문제 해결에 도움을 줍니다. 오늘은 웹 서버에서 에러를 핸들링하고 로깅을 구현해보겠습니다.

 

에러 핸들링

에러 핸들링은 프로그램이 예상치 못한 상황에서도 정상적으로 동작하도록 하는 방법입니다. 웹 서버에서는 요청 처리 중 발생할 수 있는 다양한 에러를 적절히 처리하여 클라이언트에게 적절한 응답을 반환해야 합니다.

 

1. 에러 응답 생성

서버가 에러 상태를 클라이언트에 전달하기 위해 에러 응답을 생성합니다.

 

response.h 수정

#ifndef RESPONSE_H
#define RESPONSE_H

#include <string>
#include <unordered_map>

class Response {
public:
    Response(int statusCode);

    void setHeader(const std::string& name, const std::string& value);
    void setBody(const std::string& body);
    std::string toString() const;

private:
    int statusCode_;
    std::unordered_map<std::string, std::string> headers_;
    std::string body_;
    std::string statusMessage_;
    std::string getStatusMessage() const;
};

#endif // RESPONSE_H

 

response.cpp 수정

#include "response.h"
#include <sstream>

Response::Response(int statusCode) : statusCode_(statusCode) {
    statusMessage_ = getStatusMessage();
}

void Response::setHeader(const std::string& name, const std::string& value) {
    headers_[name] = value;
}

void Response::setBody(const std::string& body) {
    body_ = body;
}

std::string Response::toString() const {
    std::ostringstream response;
    response << "HTTP/1.1 " << statusCode_ << " " << statusMessage_ << "\r\n";
    for (const auto& header : headers_) {
        response << header.first << ": " << header.second << "\r\n";
    }
    response << "\r\n" << body_;
    return response.str();
}

std::string Response::getStatusMessage() const {
    switch (statusCode_) {
        case 200: return "OK";
        case 400: return "Bad Request";
        case 404: return "Not Found";
        case 500: return "Internal Server Error";
        default: return "Unknown Status";
    }
}

 

 

로깅

로깅은 서버의 동작과 상태를 기록하여 디버깅과 문제 해결에 도움을 줍니다. 로깅을 구현하여 서버의 각종 이벤트를 파일에 기록하겠습니다.

 

1. 로깅 클래스 정의

로깅을 위한 클래스를 정의합니다.

 

logger.h

#ifndef LOGGER_H
#define LOGGER_H

#include <string>
#include <fstream>
#include <mutex>

class Logger {
public:
    static Logger& getInstance();

    void log(const std::string& message);

private:
    Logger();
    ~Logger();

    std::ofstream logFile_;
    std::mutex mutex_;
};

#endif // LOGGER_H

 

logger.cpp

#include "logger.h"
#include <iostream>
#include <chrono>
#include <ctime>

Logger::Logger() {
    logFile_.open("server.log", std::ios::app);
    if (!logFile_.is_open()) {
        std::cerr << "Failed to open log file" << std::endl;
    }
}

Logger::~Logger() {
    if (logFile_.is_open()) {
        logFile_.close();
    }
}

Logger& Logger::getInstance() {
    static Logger instance;
    return instance;
}

void Logger::log(const std::string& message) {
    std::lock_guard<std::mutex> lock(mutex_);
    auto now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
    logFile_ << std::ctime(&now) << ": " << message << std::endl;
}

 

 

로깅 통합

로깅을 서버에 통합하여 다양한 이벤트를 기록합니다.

 

server.cpp 수정

#include "server.h"
#include "logger.h"
#include <iostream>
#include <unistd.h>
#include <arpa/inet.h>

Server::Server(const std::string& address, int port, size_t numThreads)
    : address_(address), port_(port), running_(false), threadPool_(numThreads) {
    serverSocket_ = socket(AF_INET, SOCK_STREAM, 0);
    if (serverSocket_ == -1) {
        throw std::runtime_error("Failed to create socket");
    }

    serverAddr_.sin_family = AF_INET;
    serverAddr_.sin_addr.s_addr = inet_addr(address.c_str());
    serverAddr_.sin_port = htons(port);

    if (bind(serverSocket_, (struct sockaddr*)&serverAddr_, sizeof(serverAddr_)) == -1) {
        throw std::runtime_error("Failed to bind socket");
    }

    if (listen(serverSocket_, 10) == -1) {
        throw std::runtime_error("Failed to listen on socket");
    }

    Logger::getInstance().log("Server started on " + address + ":" + std::to_string(port));
}

Server::~Server() {
    stop();
    close(serverSocket_);
}

void Server::start() {
    running_ = true;
    while (running_) {
        int clientSocket = accept(serverSocket_, nullptr, nullptr);
        if (clientSocket == -1) {
            Logger::getInstance().log("Failed to accept client");
            continue;
        }
        Logger::getInstance().log("Client connected");
        threadPool_.enqueue([this, clientSocket] { handleClient(clientSocket); });
    }
}

void Server::stop() {
    running_ = false;
    Logger::getInstance().log("Server stopped");
}

void Server::addRoute(const std::string& path, Router::Handler handler) {
    router_.addRoute(path, handler);
}

void Server::handleClient(int clientSocket) {
    char buffer[1024];
    int bytesReceived = recv(clientSocket, buffer, sizeof(buffer) - 1, 0);
    if (bytesReceived < 0) {
        Logger::getInstance().log("Failed to receive request");
        close(clientSocket);
        return;
    }
    buffer[bytesReceived] = '\0';

    std::string requestStr(buffer);
    Request request(requestStr);

    std::string path = request.getPath();

    Logger::getInstance().log("Request: " + request.getMethod() + " " + path);

    router_.handleRequest(path, clientSocket);

    close(clientSocket);
    Logger::getInstance().log("Client disconnected");
}

 

 

테스트

서버를 실행하여 에러 핸들링과 로깅이 제대로 작동하는지 테스트합니다.

 

main.cpp

#include "server.h"
#include "response.h"

void handleRoot(int clientSocket) {
    Response response(200);
    response.setHeader("Content-Type", "text/html");
    response.setBody("<html><body><h1>Welcome to the Home Page!</h1></body></html>");

    std::string responseStr = response.toString();
    send(clientSocket, responseStr.c_str(), responseStr.size(), 0);
}

void handleAbout(int clientSocket) {
    Response response(200);
    response.setHeader("Content-Type", "text/html");
    response.setBody("<html><body><h1>About Us</h1></body></html>");

    std::string responseStr = response.toString();
    send(clientSocket, responseStr.c_str(), responseStr.size(), 0);
}

int main() {
    try {
        Server server("127.0.0.1", 8080, 4);
        server.addRoute("/", handleRoot);
        server.addRoute("/about", handleAbout);
        server.start();
    } catch (const std::exception& e) {
        Logger::getInstance().log("Error: " + std::string(e.what()));
    }

    return 0;
}

 

이제 서버를 실행하고 다양한 요청과 에러 상황을 테스트합니다. server.log 파일에서 서버의 로그를 확인할 수 있습니다.

 

이제 19일차의 학습을 마쳤습니다. 에러 핸들링과 로깅을 구현하여 웹 서버의 안정성과 디버깅 능력을 향상시켰습니다.

 

질문이나 피드백이 있으면 언제든지 댓글로 남겨 주세요. 내일은 "최적화 및 성능 테스트"에 대해 학습하고 구현하겠습니다.

반응형