Skip to content

deps: Implement UDP and gRPC transport using common_system interfaces #273

Description

@kcenon

Summary

Complete the network transport implementation for monitoring_system by implementing UDP (StatsD) and gRPC (OTLP) transports using the unified interfaces from common_system.

5W1H Specification

  • Who: monitoring_system maintainers
  • What: Implement UDP and gRPC transports using common_system IUdpClient interface
  • Where: include/kcenon/monitoring/exporters/
  • When: After common_system#233 is merged (transport interfaces)
  • Why:
    • HTTP transport is already implemented via network_http_transport
    • UDP (StatsD) and gRPC (OTLP) transports are currently stub implementations
    • Production observability requires actual metric export capability
  • How: Implement adapters that use common_system transport interfaces with network_system backends

Priority

HIGH - Enables production observability

Current State Analysis

Transport Target Current Status Implementation
HTTP/Prometheus Push metrics Complete network_http_transport
UDP/StatsD Push metrics Stub statsd_exporter::send_udp_batch() returns ok()
gRPC/OTLP Push metrics + traces Stub otlp_metrics_exporter::send_otlp_batch() returns ok()

Proposed Implementation

File: include/kcenon/monitoring/exporters/udp_transport.h

#pragma once

#include "../core/result_types.h"
#include <kcenon/common/interfaces/transport.h>
#include <memory>
#include <span>

namespace kcenon::monitoring {

class udp_transport {
public:
    virtual ~udp_transport() = default;
    virtual auto connect(const std::string& host, uint16_t port) -> result_void = 0;
    virtual auto send(std::span<const uint8_t> data) -> result_void = 0;
    virtual auto is_connected() const -> bool = 0;
};

/// Stub implementation for when network_system is unavailable
class stub_udp_transport : public udp_transport {
public:
    result_void connect(const std::string&, uint16_t) override { 
        return ok(); 
    }
    result_void send(std::span<const uint8_t> data) override {
        bytes_sent_ += data.size();
        return ok();
    }
    bool is_connected() const override { return true; }
    
    std::size_t bytes_sent() const { return bytes_sent_; }
private:
    std::size_t bytes_sent_ = 0;
};

#ifdef MONITORING_HAS_NETWORK_SYSTEM

/// Real implementation using common_system IUdpClient
class network_udp_transport : public udp_transport {
public:
    explicit network_udp_transport(
        std::shared_ptr<common::interfaces::IUdpClient> client)
        : client_(std::move(client)) {}

    result_void connect(const std::string& host, uint16_t port) override {
        return client_->connect(host, port)
            .map_err([](auto&& e) { 
                return make_error(monitoring_error_code::connection_failed, e.message()); 
            });
    }

    result_void send(std::span<const uint8_t> data) override {
        if (!client_->is_connected()) {
            return make_error(monitoring_error_code::not_connected, 
                             "UDP client not connected");
        }
        return client_->send(data)
            .map_err([](auto&& e) { 
                return make_error(monitoring_error_code::send_failed, e.message()); 
            });
    }

    bool is_connected() const override { 
        return client_->is_connected(); 
    }

private:
    std::shared_ptr<common::interfaces::IUdpClient> client_;
};

#endif // MONITORING_HAS_NETWORK_SYSTEM

/// Factory function with automatic backend selection
inline auto create_udp_transport() -> std::unique_ptr<udp_transport> {
#ifdef MONITORING_HAS_NETWORK_SYSTEM
    // Runtime: check if network_system client is available
    auto client = /* get from DI container or factory */;
    if (client) {
        return std::make_unique<network_udp_transport>(std::move(client));
    }
#endif
    return std::make_unique<stub_udp_transport>();
}

} // namespace kcenon::monitoring

Tasks

  • Wait for common_system#233 (transport interfaces) to merge
  • Create udp_transport.h with interface and stub implementation
  • Implement network_udp_transport using common::interfaces::IUdpClient
  • Update statsd_exporter to use udp_transport factory
  • Create grpc_transport.h following same pattern
  • Update otlp_metrics_exporter to use gRPC transport
  • Add integration tests with mock network backend
  • Update CMake to detect common_system transport availability

CMake Integration

# Check for common_system transport interfaces
if(TARGET kcenon::common_system)
    get_target_property(COMMON_INTERFACE_DIR kcenon::common_system INTERFACE_INCLUDE_DIRECTORIES)
    if(EXISTS "${COMMON_INTERFACE_DIR}/kcenon/common/interfaces/transport.h")
        target_compile_definitions(${PROJECT_NAME} PUBLIC MONITORING_HAS_TRANSPORT_INTERFACES)
    endif()
endif()

# Check for network_system implementation
if(MONITORING_USE_NETWORK_SYSTEM AND TARGET kcenon::network_system)
    target_compile_definitions(${PROJECT_NAME} PUBLIC MONITORING_HAS_NETWORK_SYSTEM)
    target_link_libraries(${PROJECT_NAME} PUBLIC kcenon::network_system)
endif()

Acceptance Criteria

  • statsd_exporter sends real UDP packets when network_system is available
  • otlp_metrics_exporter sends real gRPC requests when network_system is available
  • Graceful fallback to stub implementation when network_system unavailable
  • No compile-time dependency on network_system (interface-based)
  • Integration tests pass with both real and mock backends

Dependencies

Parent Epic

Related Issues

Metadata

Metadata

Assignees

Labels

area/metricsMetric collection and processingenhancementNew feature or requestpriority/highHigh priority - Critical for production

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions