Skip to content

Commit 66202f0

Browse files
author
Andy Ford
authored
Merge pull request #556 from AndyTWF/send-exception-info-to-api
feat: log fatal exceptions to the api
2 parents daba080 + 4ce2292 commit 66202f0

12 files changed

Lines changed: 260 additions & 4 deletions

src/plugin/integration/OutboundIntegrationMessageHandler.cpp

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include "IntegrationConnection.h"
44
#include "MessageInterface.h"
55
#include "OutboundIntegrationMessageHandler.h"
6+
#include "log/ApiLoggerInterface.h"
67

78
namespace UKControllerPlugin::Integration {
89
OutboundIntegrationMessageHandler::OutboundIntegrationMessageHandler(
@@ -13,7 +14,30 @@ namespace UKControllerPlugin::Integration {
1314

1415
void OutboundIntegrationMessageHandler::SendEvent(std::shared_ptr<MessageInterface> message) const
1516
{
16-
LogDebug("Sending integration message: " + message->ToJson().dump());
17+
try {
18+
LogDebug("Sending integration message: " + message->ToJson().dump());
19+
} catch (const std::exception& exception) {
20+
if (apiLoggedTypes.find(message->GetMessageType().type) == apiLoggedTypes.end()) {
21+
LogError(
22+
"Failed to log integration message, something's wrong with the JSON: " +
23+
message->GetMessageType().type);
24+
std::string messageType = message->GetMessageType().type;
25+
26+
// Add the message type to the set so we don't log it again
27+
apiLoggedTypes.insert(messageType);
28+
29+
const auto metadata = nlohmann::json{
30+
{"json_without_strict",
31+
message->ToJson().dump(-1, ' ', false, nlohmann::json::error_handler_t::replace)},
32+
{"exception", exception.what()}};
33+
34+
ApiLogger().Log("INTEGRATION_INVALID_JSON", "Failed to log integration message", metadata);
35+
};
36+
37+
// We'll just have to accept that we can't send this message to integrations
38+
return;
39+
}
40+
1741
std::for_each(
1842
this->clientManager->cbegin(),
1943
this->clientManager->cend(),

src/plugin/integration/OutboundIntegrationMessageHandler.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,8 @@ namespace UKControllerPlugin::Integration {
1717

1818
private:
1919
const std::shared_ptr<IntegrationClientManager> clientManager;
20+
21+
// Array to ensure we only log the same message type once
22+
mutable std::set<std::string> apiLoggedTypes;
2023
};
2124
} // namespace UKControllerPlugin::Integration

src/utils/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,9 @@ set(log
132132
"log/LoggerBootstrap.h"
133133
"log/LoggerFunctions.cpp"
134134
"log/LoggerFunctions.h"
135+
log/ApiLogger.cpp
136+
log/ApiLogger.h
137+
log/ApiLoggerInterface.h
135138
)
136139
source_group("log" FILES ${log})
137140

src/utils/api/ApiBootstrap.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include "curl/CurlApi.h"
1111
#include "eventhandler/EventBus.h"
1212
#include "eventhandler/EventHandlerFlags.h"
13+
#include "log/ApiLogger.h"
1314
#include "setting/SettingRepository.h"
1415
#include "setting/JsonFileSettingProvider.h"
1516

@@ -42,6 +43,10 @@ namespace UKControllerPluginUtils::Api {
4243
EventHandler::EventHandlerFlags::Async);
4344

4445
SetApiRequestFactory(factory);
46+
47+
// Create an API logger here and set globally
48+
SetApiLoggerInstance(std::make_shared<Log::ApiLogger>());
49+
4550
return factory;
4651
}
4752

src/utils/log/ApiLogger.cpp

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
#include "ApiLogger.h"
2+
#include "api/ApiRequestFactory.h"
3+
#include "api/ApiRequestException.h"
4+
#include "update/PluginVersion.h"
5+
6+
namespace UKControllerPluginUtils::Log {
7+
8+
struct ApiLogger::Impl
9+
{
10+
[[nodiscard]] auto CreatePayloadNoMetadata(const std::string& type, const std::string& message) const
11+
-> nlohmann::json
12+
{
13+
return {{"type", type}, {"message", message}, {"metadata", PluginVersionMetadata().dump()}};
14+
}
15+
16+
[[nodiscard]] auto
17+
CreatePayload(const std::string& type, const std::string& message, const nlohmann::json& metadata) const
18+
-> nlohmann::json
19+
{
20+
auto metadataWithVersion = PluginVersionMetadata();
21+
metadataWithVersion.update(metadata);
22+
return {{"type", type}, {"message", message}, {"metadata", metadataWithVersion.dump()}};
23+
}
24+
25+
[[nodiscard]] auto PluginVersionMetadata() const -> nlohmann::json
26+
{
27+
return {{"plugin_version", UKControllerPlugin::Plugin::PluginVersion::version}};
28+
}
29+
30+
void WriteLog(const nlohmann::json& data)
31+
{
32+
ApiRequest()
33+
.Post("plugin/logs", data)
34+
.Then([](const UKControllerPluginUtils::Api::Response& response) {
35+
const auto data = response.Data();
36+
if (!data.is_object() || !data.contains("id") || !data["id"].is_string()) {
37+
LogError("Failed to send log to API, response was not as expected");
38+
return;
39+
}
40+
41+
LogInfo("Log sent to API with ID " + data["id"].get<std::string>());
42+
})
43+
.Catch([](const Api::ApiRequestException& exception) {
44+
LogError(
45+
"Failed to send log to API, status code was " +
46+
std::to_string(static_cast<uint64_t>(exception.StatusCode())));
47+
})
48+
.Await();
49+
}
50+
51+
void WriteLogAsync(const nlohmann::json& data)
52+
{
53+
ApiRequest().Post("plugin/logs", data).Catch([](const Api::ApiRequestException& exception) {
54+
LogError(
55+
"Failed to send log to API, status code was " +
56+
std::to_string(static_cast<uint64_t>(exception.StatusCode())));
57+
});
58+
}
59+
};
60+
61+
ApiLogger::ApiLogger() : impl(std::make_unique<Impl>())
62+
{
63+
}
64+
65+
ApiLogger::~ApiLogger() = default;
66+
67+
void ApiLogger::Log(const std::string& type, const std::string& message) const
68+
{
69+
impl->WriteLog(impl->CreatePayloadNoMetadata(type, message));
70+
}
71+
72+
void ApiLogger::Log(const std::string& type, const std::string& message, const nlohmann::json& metadata) const
73+
{
74+
impl->WriteLog(impl->CreatePayload(type, message, metadata));
75+
}
76+
77+
void ApiLogger::LogAsync(const std::string& type, const std::string& message) const
78+
{
79+
impl->WriteLogAsync(impl->CreatePayloadNoMetadata(type, message));
80+
}
81+
82+
void ApiLogger::LogAsync(const std::string& type, const std::string& message, const nlohmann::json& metadata) const
83+
{
84+
impl->WriteLogAsync(impl->CreatePayload(type, message, metadata));
85+
}
86+
} // namespace UKControllerPluginUtils::Log

src/utils/log/ApiLogger.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#pragma once
2+
#include "ApiLoggerInterface.h"
3+
4+
namespace UKControllerPluginUtils::Log {
5+
class ApiLogger : public ApiLoggerInterface
6+
{
7+
public:
8+
ApiLogger();
9+
~ApiLogger() override;
10+
void Log(const std::string& type, const std::string& message) const override;
11+
void Log(const std::string& type, const std::string& message, const nlohmann::json& metadata) const override;
12+
void LogAsync(const std::string& type, const std::string& message) const override;
13+
void
14+
LogAsync(const std::string& type, const std::string& message, const nlohmann::json& metadata) const override;
15+
16+
private:
17+
struct Impl;
18+
std::unique_ptr<Impl> impl;
19+
};
20+
} // namespace UKControllerPluginUtils::Log

src/utils/log/ApiLoggerInterface.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#pragma once
2+
3+
namespace UKControllerPluginUtils::Log {
4+
/**
5+
* An interface for logging things to the API where we need more information
6+
* or just want to know what's going on.
7+
*/
8+
class ApiLoggerInterface
9+
{
10+
public:
11+
virtual ~ApiLoggerInterface() = default;
12+
virtual void Log(const std::string& type, const std::string& message) const = 0;
13+
virtual void Log(const std::string& type, const std::string& message, const nlohmann::json& metadata) const = 0;
14+
virtual void LogAsync(const std::string& type, const std::string& message) const = 0;
15+
virtual void
16+
LogAsync(const std::string& type, const std::string& message, const nlohmann::json& metadata) const = 0;
17+
};
18+
} // namespace UKControllerPluginUtils::Log

src/utils/log/LoggerFunctions.cpp

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
#include "log/ApiLoggerInterface.h"
12
#include "log/LoggerFunctions.h"
23

34
std::shared_ptr<spdlog::logger> logger;
5+
std::shared_ptr<UKControllerPluginUtils::Log::ApiLoggerInterface> apiLogger;
46

57
void LogCritical(std::string message)
68
{
@@ -45,13 +47,21 @@ void ShutdownLogger(void)
4547
LogInfo("Logger shutdown");
4648
spdlog::drop_all();
4749
logger.reset();
50+
apiLogger.reset();
4851
}
4952

5053
void LogFatalExceptionAndRethrow(const std::string& source, const std::exception& exception)
5154
{
52-
logger->critical(
53-
"Critical exception of type " + std::string(typeid(exception).name()) + " at " + source + ": " +
54-
exception.what());
55+
const auto exceptionMessage = "Critical exception of type " + std::string(typeid(exception).name()) + " at " +
56+
source + ": " + exception.what();
57+
logger->critical(exceptionMessage);
58+
59+
try {
60+
ApiLogger().Log("FATAL_EXCEPTION", exceptionMessage);
61+
} catch (const std::exception& e) {
62+
LogCritical("Exception caught in LogFatalExceptionAndRethrow: " + std::string(e.what()));
63+
}
64+
5565
throw;
5666
}
5767

@@ -60,3 +70,22 @@ void LogFatalExceptionAndRethrow(
6070
{
6171
LogFatalExceptionAndRethrow(source + "::" + subsource, exception);
6272
}
73+
74+
void SetApiLoggerInstance(std::shared_ptr<UKControllerPluginUtils::Log::ApiLoggerInterface> instance)
75+
{
76+
if (apiLogger) {
77+
return;
78+
}
79+
80+
apiLogger = instance;
81+
}
82+
83+
auto ApiLogger() -> const UKControllerPluginUtils::Log::ApiLoggerInterface&
84+
{
85+
if (!apiLogger) {
86+
LogError("ApiLogger not set");
87+
throw std::runtime_error("ApiLogger not set");
88+
}
89+
90+
return *apiLogger;
91+
}

src/utils/log/LoggerFunctions.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@ namespace spdlog {
44
class logger;
55
} // namespace spdlog
66

7+
namespace UKControllerPluginUtils::Log {
8+
class ApiLoggerInterface;
9+
}
10+
11+
[nodiscard] auto ApiLogger() -> const UKControllerPluginUtils::Log::ApiLoggerInterface&;
712
void LogFatalExceptionAndRethrow(const std::string& source, const std::exception& exception);
813
void LogFatalExceptionAndRethrow(
914
const std::string& source, const std::string& subsource, const std::exception& exception);
@@ -13,4 +18,5 @@ void LogError(std::string message);
1318
void LogInfo(std::string message);
1419
void LogWarning(std::string message);
1520
void SetLoggerInstance(std::shared_ptr<spdlog::logger> instance);
21+
void SetApiLoggerInstance(std::shared_ptr<UKControllerPluginUtils::Log::ApiLoggerInterface> instance);
1622
void ShutdownLogger(void);

test/testingutils/test/ApiTestCase.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
#pragma once
22
#include "ApiMethodExpectation.h"
3+
#include "ApiUriExpectation.h"
4+
#include "ApiRequestExpectation.h"
5+
#include "ApiResponseExpectation.h"
36

47
namespace UKControllerPluginUtils::Api {
58
class ApiFactory;

0 commit comments

Comments
 (0)