-----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일차의 학습을 마쳤습니다. 에러 핸들링과 로깅을 구현하여 웹 서버의 안정성과 디버깅 능력을 향상시켰습니다.
질문이나 피드백이 있으면 언제든지 댓글로 남겨 주세요. 내일은 "최적화 및 성능 테스트"에 대해 학습하고 구현하겠습니다.
반응형