Skip to content

Commit

Permalink
feat: implement basic request id to trace requests #303
Browse files Browse the repository at this point in the history
  • Loading branch information
fengelniederhammer committed Feb 27, 2024
1 parent 374a0be commit 4defb59
Show file tree
Hide file tree
Showing 8 changed files with 166 additions and 6 deletions.
31 changes: 31 additions & 0 deletions endToEndTests/test/requestId.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { expect } from 'chai';
import { headerToHaveDataVersion, server } from './common.js';
import { describe, it } from 'node:test';

const X_REQUEST_ID = 'x-request-id';

describe('The request id', () => {
it('should be returned when explicitly specified', async () => {
const requestID = 'hardcodedRequestIdInTheTest';

await server
.post('/query')
.set(X_REQUEST_ID, requestID)
.send({ action: { type: 'Aggregated' }, filterExpression: { type: 'True' } })
.expect(200)
.expect(X_REQUEST_ID, requestID);
});

it('should be generated when none is specified', async () => {
await server
.post('/query')
.send({ action: { type: 'Aggregated' }, filterExpression: { type: 'True' } })
.expect(200)
.expect(response => {
const headers = response.headers;
expect(headers).to.have.property(X_REQUEST_ID);
expect(headers[X_REQUEST_ID]).to.be.a('string');
expect(headers[X_REQUEST_ID]).length.to.be.at.least(1);
});
});
});
28 changes: 28 additions & 0 deletions include/silo_api/request_id_handler.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#pragma once

#include <memory>

#include <Poco/Net/HTTPRequestHandler.h>
#include <Poco/Net/HTTPServerRequest.h>
#include <Poco/Net/HTTPServerResponse.h>

namespace silo_api {

constexpr auto REQUEST_ID_HEADER = "X-Request-Id";

class RequestIdHandler : public Poco::Net::HTTPRequestHandler {
private:
std::unique_ptr<Poco::Net::HTTPRequestHandler> wrapped_handler;

public:
explicit RequestIdHandler(Poco::Net::HTTPRequestHandler* wrapped_handler);

void handleRequest(
Poco::Net::HTTPServerRequest& request,
Poco::Net::HTTPServerResponse& response
) override;

private:
static std::string getRequestId(Poco::Net::HTTPServerRequest& request);
};
} // namespace silo_api
4 changes: 4 additions & 0 deletions src/silo_api/error_request_handler.test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
#include "silo_api/error_request_handler.h"
#include "silo_api/manual_poco_mocks.test.h"

namespace {

class MockRequestHandler : public Poco::Net::HTTPRequestHandler {
public:
MOCK_METHOD(
Expand All @@ -20,6 +22,8 @@ const silo_api::StartupConfig TEST_STARTUP_CONFIG = {
std::nullopt
};

} // namespace

TEST(ErrorRequestHandler, handlesRuntimeErrors) {
auto* wrapped_handler_mock = new MockRequestHandler;

Expand Down
11 changes: 9 additions & 2 deletions src/silo_api/logging_request_handler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,18 @@ void LoggingRequestHandler::handleRequest(
Poco::Net::HTTPServerRequest& request,
Poco::Net::HTTPServerResponse& response
) {
SPDLOG_INFO("Handling {} {}", request.getMethod(), request.getURI());
const auto request_id = response.get("X-Request-Id");
SPDLOG_INFO(
"Request Id [{}] - Handling {} {}", request_id, request.getMethod(), request.getURI()
);

wrapped_handler->handleRequest(request, response);

SPDLOG_INFO("Responding with status code {}", static_cast<uint32_t>(response.getStatus()));
SPDLOG_INFO(
"Request Id [{}] - Responding with status code {}",
request_id,
static_cast<uint32_t>(response.getStatus())
);
}

} // namespace silo_api
6 changes: 4 additions & 2 deletions src/silo_api/query_handler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,13 @@ void QueryHandler::post(
Poco::Net::HTTPServerRequest& request,
Poco::Net::HTTPServerResponse& response
) {
const auto request_id = response.get("X-Request-Id");

std::string query;
std::istream& istream = request.stream();
Poco::StreamCopier::copyToString(istream, query);

SPDLOG_INFO("received query: {}", query);
SPDLOG_INFO("Request Id [{}] - received query: {}", request_id, query);

response.setContentType("application/json");
try {
Expand All @@ -40,7 +42,7 @@ void QueryHandler::post(
std::ostream& out_stream = response.send();
out_stream << nlohmann::json(query_result);
} catch (const silo::QueryParseException& ex) {
SPDLOG_INFO("Query is invalid: " + query);
SPDLOG_INFO("Query is invalid: " + query + " - exception: " + ex.what());
response.setStatusAndReason(Poco::Net::HTTPResponse::HTTP_BAD_REQUEST);
std::ostream& out_stream = response.send();
out_stream << nlohmann::json(ErrorResponse{"Bad request", ex.what()});
Expand Down
9 changes: 7 additions & 2 deletions src/silo_api/request_handler_factory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@
#include "silo_api/logging_request_handler.h"
#include "silo_api/not_found_handler.h"
#include "silo_api/query_handler.h"
#include "silo_api/request_id_handler.h"

using silo_api::ErrorRequestHandler;
using silo_api::LoggingRequestHandler;
using silo_api::RequestIdHandler;

namespace silo_api {

Expand All @@ -24,8 +29,8 @@ SiloRequestHandlerFactory::SiloRequestHandlerFactory(
Poco::Net::HTTPRequestHandler* SiloRequestHandlerFactory::createRequestHandler(
const Poco::Net::HTTPServerRequest& request
) {
return new silo_api::LoggingRequestHandler(
new silo_api::ErrorRequestHandler(routeRequest(request), startup_config)
return new RequestIdHandler(
new LoggingRequestHandler(new ErrorRequestHandler(routeRequest(request), startup_config))
);
}

Expand Down
34 changes: 34 additions & 0 deletions src/silo_api/request_id_handler.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#include "silo_api/request_id_handler.h"

#include <spdlog/spdlog.h>
#include <boost/uuid/uuid_generators.hpp>
#include <boost/uuid/uuid_io.hpp>

using boost::uuids::random_generator;

namespace silo_api {

RequestIdHandler::RequestIdHandler(Poco::Net::HTTPRequestHandler* wrapped_handler)
: wrapped_handler(wrapped_handler) {}

void RequestIdHandler::handleRequest(
Poco::Net::HTTPServerRequest& request,
Poco::Net::HTTPServerResponse& response
) {
const auto request_id = getRequestId(request);
response.set(REQUEST_ID_HEADER, request_id);

wrapped_handler->handleRequest(request, response);
}

std::string RequestIdHandler::getRequestId(Poco::Net::HTTPServerRequest& request) {
try {
return request.get(REQUEST_ID_HEADER);
} catch (const Poco::NotFoundException& not_found_exception) {
random_generator generator;
const auto request_id = generator();
return boost::uuids::to_string(request_id);
}
}

} // namespace silo_api
49 changes: 49 additions & 0 deletions src/silo_api/request_id_handler.test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#include <Poco/Net/HTTPResponse.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>

#include "silo_api/manual_poco_mocks.test.h"
#include "silo_api/request_id_handler.h"

using silo_api::RequestIdHandler;

namespace {

class MockRequestHandler : public Poco::Net::HTTPRequestHandler {
public:
MOCK_METHOD(
void,
handleRequest,
(Poco::Net::HTTPServerRequest & request, Poco::Net::HTTPServerResponse& response),
()
);
};

} // namespace

TEST(RequestIdHandler, givenNoRequestIdIsSet_thenGeneratesOne) {
auto* wrapped_handler_mock = new MockRequestHandler;
auto under_test = RequestIdHandler(wrapped_handler_mock);
EXPECT_CALL(*wrapped_handler_mock, handleRequest);

silo_api::test::MockResponse response;
silo_api::test::MockRequest request(response);
under_test.handleRequest(request, response);

EXPECT_THAT(response.get("X-Request-Id"), ::testing::ContainsRegex("-[A-Za-z0-9]{4}-"));
}

TEST(RequestIdHandler, givenRequestIdIsSet_thenResponseAlsoContainsIt) {
const std::string request_id_value = "request id value";

auto* wrapped_handler_mock = new MockRequestHandler;
auto under_test = RequestIdHandler(wrapped_handler_mock);
EXPECT_CALL(*wrapped_handler_mock, handleRequest);

silo_api::test::MockResponse response;
silo_api::test::MockRequest request(response);
request.set("X-Request-Id", request_id_value);
under_test.handleRequest(request, response);

EXPECT_EQ(response.get("X-Request-Id"), request_id_value);
}

0 comments on commit 4defb59

Please sign in to comment.