mirror of
https://github.com/aaronpo97/the-biergarten-app.git
synced 2026-06-01 01:54:00 +00:00
Add multithreaded logging infrastructure for preparation for future designs (#225)
* Update class diagrams * Implement BoundedChannel and multithreaded logging infra * Integrate logging channel system * Update string concatenations to use std::format * Add pretty print log
This commit is contained in:
@@ -12,13 +12,15 @@
|
||||
#include <unordered_map>
|
||||
|
||||
#include "enrichment_service.h"
|
||||
#include "services/logging/logger.h"
|
||||
#include "web_client/web_client.h"
|
||||
|
||||
/// @brief Provides Wikipedia summary lookups backed by cached raw extracts.
|
||||
class WikipediaEnrichmentService final : public IEnrichmentService {
|
||||
public:
|
||||
/// @brief Creates a new Wikipedia service with the provided web client.
|
||||
explicit WikipediaEnrichmentService(std::unique_ptr<WebClient> client);
|
||||
explicit WikipediaEnrichmentService(std::unique_ptr<WebClient> client,
|
||||
std::shared_ptr<ILogger> logger);
|
||||
|
||||
/// @brief Returns the Wikipedia-derived context for a location.
|
||||
[[nodiscard]] std::string GetLocationContext(const Location& loc) override;
|
||||
@@ -26,6 +28,7 @@ class WikipediaEnrichmentService final : public IEnrichmentService {
|
||||
private:
|
||||
std::string FetchExtract(std::string_view query);
|
||||
std::unique_ptr<WebClient> client_;
|
||||
std::shared_ptr<ILogger> logger_;
|
||||
/// @brief Canonical cache for raw Wikipedia query extracts.
|
||||
std::unordered_map<std::string, std::string> extract_cache_;
|
||||
};
|
||||
|
||||
53
tooling/pipeline/includes/services/logging/log_dispatcher.h
Normal file
53
tooling/pipeline/includes/services/logging/log_dispatcher.h
Normal file
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* @file services/logging/log_dispatcher.h
|
||||
* @brief Dedicated log dispatcher for asynchronous pipeline logging.
|
||||
*
|
||||
* The dispatcher drains LogEntry values from a bounded channel and forwards
|
||||
* them to spdlog on a dedicated thread.
|
||||
*/
|
||||
|
||||
#ifndef BIERGARTEN_PIPELINE_INCLUDES_SERVICES_LOGGING_LOG_DISPATCHER_H_
|
||||
#define BIERGARTEN_PIPELINE_INCLUDES_SERVICES_LOGGING_LOG_DISPATCHER_H_
|
||||
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#include "concurrency/bounded_channel.h"
|
||||
#include "services/logging/log_entry.h"
|
||||
|
||||
/**
|
||||
* @class LogDispatcher
|
||||
* @brief Consumes log entries from a channel and forwards them to spdlog.
|
||||
*
|
||||
* Non-copyable and non-movable. Intended to run on its own dedicated thread
|
||||
* and exit once the channel has been closed and drained.
|
||||
*/
|
||||
class LogDispatcher {
|
||||
public:
|
||||
/**
|
||||
* @brief Construct a log dispatcher.
|
||||
*
|
||||
* @param channel Reference to the bounded channel used for log retrieval.
|
||||
*/
|
||||
explicit LogDispatcher(BoundedChannel<LogEntry>& channel);
|
||||
|
||||
LogDispatcher(const LogDispatcher&) = delete;
|
||||
LogDispatcher& operator=(const LogDispatcher&) = delete;
|
||||
LogDispatcher(LogDispatcher&&) = delete;
|
||||
LogDispatcher& operator=(LogDispatcher&&) = delete;
|
||||
~LogDispatcher() = default;
|
||||
|
||||
/**
|
||||
* @brief Drain the channel and forward entries to spdlog.
|
||||
*
|
||||
* Intended to be called once on a dedicated thread. The loop returns after
|
||||
* the channel has been closed and all queued entries have been processed.
|
||||
*/
|
||||
void Run();
|
||||
|
||||
private:
|
||||
BoundedChannel<LogEntry>& channel_;
|
||||
|
||||
static spdlog::level::level_enum ToSpdlogLevel(LogLevel level);
|
||||
};
|
||||
|
||||
#endif // BIERGARTEN_PIPELINE_INCLUDES_SERVICES_LOGGING_LOG_DISPATCHER_H_
|
||||
88
tooling/pipeline/includes/services/logging/log_entry.h
Normal file
88
tooling/pipeline/includes/services/logging/log_entry.h
Normal file
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
* @file services/logging/log_entry.h
|
||||
* @brief Structured log record shared by the pipeline logging infra.
|
||||
*
|
||||
* LogEntry is a lightweight value type that can be passed safely between the
|
||||
* logging producer and dispatcher through BoundedChannel<LogEntry>.
|
||||
*/
|
||||
|
||||
#ifndef BIERGARTEN_PIPELINE_INCLUDES_SERVICES_LOGGING_LOG_ENTRY_H_
|
||||
#define BIERGARTEN_PIPELINE_INCLUDES_SERVICES_LOGGING_LOG_ENTRY_H_
|
||||
|
||||
#include <chrono>
|
||||
#include <source_location>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
/**
|
||||
* @enum LogLevel
|
||||
* @brief Severity levels supported by the logging infra.
|
||||
*/
|
||||
enum class LogLevel {
|
||||
Debug, ///< Development/debugging information.
|
||||
Info, ///< General informational messages.
|
||||
Warn, ///< Warning conditions.
|
||||
Error, ///< Error conditions.
|
||||
};
|
||||
|
||||
/**
|
||||
* @enum PipelinePhase
|
||||
* @brief Pipeline execution phases used to tag log records.
|
||||
*
|
||||
* The phase tag makes it easier to correlate log output with the part of the
|
||||
* pipeline that emitted it.
|
||||
*/
|
||||
enum class PipelinePhase {
|
||||
Startup, ///< Initialization and validation.
|
||||
UserGeneration, ///< User profile generation.
|
||||
BreweryAndBeerGeneration, ///< Brewery and beer data generation.
|
||||
CheckinGeneration, ///< Checkin (visit) record generation.
|
||||
RatingGeneration, ///< Rating and review generation.
|
||||
FollowGeneration, ///< Follow relationship generation.
|
||||
Teardown, ///< Finalization and cleanup.
|
||||
};
|
||||
|
||||
/**
|
||||
* @struct LogDTO
|
||||
* @brief User-provided subset of log fields. Used to capture call-site info transparently.
|
||||
*/
|
||||
struct LogDTO {
|
||||
LogLevel level;
|
||||
PipelinePhase phase;
|
||||
std::string message;
|
||||
};
|
||||
|
||||
/**
|
||||
* @struct LogEntry
|
||||
* @brief Single structured log event.
|
||||
*
|
||||
* All fields are value types, which keeps transfer across the bounded channel
|
||||
* simple and avoids shared ownership.
|
||||
*
|
||||
* NOTE: timestamp, thread_id, and origin must be populated by ILogger::Log()
|
||||
* before the entry is dispatched.
|
||||
*/
|
||||
struct LogEntry {
|
||||
/// @brief Timestamp when the entry was created.
|
||||
std::chrono::system_clock::time_point timestamp{};
|
||||
|
||||
/// @brief Source location where the log call was made.
|
||||
std::source_location origin{};
|
||||
|
||||
/// @brief Thread responsible for emitting the log.
|
||||
std::thread::id thread_id{};
|
||||
|
||||
|
||||
/// @brief Severity level of this entry.
|
||||
LogLevel level;
|
||||
|
||||
/// @brief Pipeline phase associated with the entry.
|
||||
PipelinePhase phase;
|
||||
|
||||
/// @brief Log message text.
|
||||
std::string message;
|
||||
|
||||
};
|
||||
|
||||
#endif // BIERGARTEN_PIPELINE_INCLUDES_SERVICES_LOGGING_LOG_ENTRY_H_
|
||||
53
tooling/pipeline/includes/services/logging/log_producer.h
Normal file
53
tooling/pipeline/includes/services/logging/log_producer.h
Normal file
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* @file services/logging/log_producer.h
|
||||
* @brief Channel-backed log producer for asynchronous pipeline logging.
|
||||
*
|
||||
* The producer captures log records from application code and forwards them to
|
||||
* a bounded channel for later processing by the dispatcher.
|
||||
*/
|
||||
|
||||
#ifndef BIERGARTEN_PIPELINE_INCLUDES_SERVICES_LOGGING_CHANNEL_LOGGER_H_
|
||||
#define BIERGARTEN_PIPELINE_INCLUDES_SERVICES_LOGGING_CHANNEL_LOGGER_H_
|
||||
|
||||
#include <string_view>
|
||||
|
||||
#include "concurrency/bounded_channel.h"
|
||||
#include "services/logging/log_entry.h"
|
||||
#include "services/logging/logger.h"
|
||||
|
||||
/**
|
||||
* @class LogProducer
|
||||
* @brief ILogger implementation that forwards entries to a bounded channel.
|
||||
*
|
||||
* Non-copyable and non-movable. The channel reference is non-owning and must
|
||||
* remain valid for the lifetime of the producer.
|
||||
*/
|
||||
class LogProducer final : public ILogger {
|
||||
public:
|
||||
/**
|
||||
* @brief Construct a channel-backed producer.
|
||||
*
|
||||
* @param channel Reference to the bounded channel used for log transfer.
|
||||
*/
|
||||
explicit LogProducer(BoundedChannel<LogEntry>& channel);
|
||||
|
||||
LogProducer(const LogProducer&) = delete;
|
||||
LogProducer& operator=(const LogProducer&) = delete;
|
||||
LogProducer(LogProducer&&) = delete;
|
||||
LogProducer& operator=(LogProducer&&) = delete;
|
||||
|
||||
~LogProducer() override = default;
|
||||
|
||||
/**
|
||||
* @brief Queue a log message for asynchronous processing.
|
||||
*
|
||||
* Blocks while the channel applies backpressure. This blocking behavior
|
||||
* under heavy load is an accepted trade-off for simplicity.
|
||||
*/
|
||||
void DoLog(LogEntry log_entry) override;
|
||||
|
||||
private:
|
||||
BoundedChannel<LogEntry>& channel_;
|
||||
};
|
||||
|
||||
#endif // BIERGARTEN_PIPELINE_INCLUDES_SERVICES_LOGGING_CHANNEL_LOGGER_H_
|
||||
64
tooling/pipeline/includes/services/logging/logger.h
Normal file
64
tooling/pipeline/includes/services/logging/logger.h
Normal file
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* @file services/logging/logger.h
|
||||
* @brief Abstract logging interface used by pipeline components.
|
||||
*
|
||||
* The interface keeps application code independent from the concrete logging
|
||||
* transport, buffering, and formatting implementation.
|
||||
*/
|
||||
|
||||
#ifndef BIERGARTEN_PIPELINE_INCLUDES_SERVICES_LOGGING_LOGGER_H_
|
||||
#define BIERGARTEN_PIPELINE_INCLUDES_SERVICES_LOGGING_LOGGER_H_
|
||||
|
||||
#include <source_location>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "services/logging/log_entry.h"
|
||||
|
||||
/**
|
||||
* @class ILogger
|
||||
* @brief Minimal interface for submitting structured log messages.
|
||||
*
|
||||
* Implementations are non-copyable and non-movable. They are typically owned
|
||||
* by the composition root and injected into services that emit diagnostics.
|
||||
*/
|
||||
class ILogger {
|
||||
public:
|
||||
ILogger() = default;
|
||||
ILogger(const ILogger&) = delete;
|
||||
ILogger& operator=(const ILogger&) = delete;
|
||||
ILogger(ILogger&&) = delete;
|
||||
ILogger& operator=(ILogger&&) = delete;
|
||||
virtual ~ILogger() = default;
|
||||
|
||||
/**
|
||||
* @brief Submit a log message to the logging subsystem.
|
||||
*
|
||||
* @param payload User-provided log data (level, phase, message).
|
||||
* @param origin Auto-captured source location of the call site.
|
||||
*/
|
||||
void Log(LogDTO payload,
|
||||
std::source_location origin = std::source_location::current(),
|
||||
std::chrono::system_clock::time_point timestamp = std::chrono::system_clock::now(),
|
||||
std::thread::id thread_id = std::this_thread::get_id()) {
|
||||
LogEntry entry;
|
||||
entry.timestamp = timestamp;
|
||||
entry.thread_id = thread_id;
|
||||
entry.level = payload.level;
|
||||
entry.phase = payload.phase;
|
||||
entry.message = std::move(payload.message);
|
||||
entry.origin = origin;
|
||||
DoLog(std::move(entry));
|
||||
}
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief Underlying implementation to transport the log entry.
|
||||
*
|
||||
* Implementations must be thread-safe as DoLog can be called concurrently
|
||||
* from multiple worker threads.
|
||||
*/
|
||||
virtual void DoLog(LogEntry log_entry) = 0;
|
||||
};
|
||||
|
||||
#endif // BIERGARTEN_PIPELINE_INCLUDES_SERVICES_LOGGING_LOGGER_H_
|
||||
@@ -12,11 +12,14 @@
|
||||
*/
|
||||
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "services/logging/logger.h"
|
||||
|
||||
/**
|
||||
* @brief Interface for loading named prompt files.
|
||||
*/
|
||||
@@ -56,6 +59,8 @@ class PromptDirectory final : public IPromptDirectory {
|
||||
* directory.
|
||||
*/
|
||||
explicit PromptDirectory(const std::filesystem::path& prompt_dir);
|
||||
PromptDirectory(const std::filesystem::path& prompt_dir,
|
||||
std::shared_ptr<ILogger> logger);
|
||||
|
||||
/**
|
||||
* @brief Loads the prompt for @p key, caching the result.
|
||||
@@ -70,6 +75,7 @@ class PromptDirectory final : public IPromptDirectory {
|
||||
|
||||
private:
|
||||
std::filesystem::path prompt_dir_;
|
||||
std::shared_ptr<ILogger> logger_;
|
||||
std::unordered_map<std::string, std::string> cache_;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user