Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions include/interceptors/log_sink.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#pragma once
#include <absl/log/log_sink.h>
#include <absl/log/log_sink_registry.h>
#include <absl/log/log_entry.h>
#include <absl/log/initialize.h>
#include <absl/log/globals.h>
#include <iostream>

namespace moderation::logging {

class ReadableLogSink : public absl::LogSink {
public:
std::atomic<bool> installed{false};

void Send(const absl::LogEntry& entry) override {
if (!installed) return;

std::string_view level;
switch (entry.log_severity()) {
case absl::LogSeverity::kInfo: level = "INFO"; break;
case absl::LogSeverity::kWarning: level = "WARNING"; break;
case absl::LogSeverity::kError: level = "ERROR"; break;
case absl::LogSeverity::kFatal: level = "FATAL"; break;
default: level = "UNKNOWN"; break;
}
std::cerr
<< "[" << level << "] "
<< entry.source_basename() << ":" << entry.source_line() << " "
<< entry.text_message() << "\n";
}

void Flush() override { std::cerr.flush(); }
};

inline ReadableLogSink* GetSink() {
static ReadableLogSink sink;
return &sink;
}

inline void InstallReadableSink() {
absl::AddLogSink(GetSink());
}

inline void ActivateReadableSink() {
absl::InitializeLog();
absl::SetStderrThreshold(absl::LogSeverityAtLeast::kInfinity);
GetSink()->installed = true;
}

} // namespace moderation::logging
206 changes: 183 additions & 23 deletions include/interceptors/logger.hpp
Original file line number Diff line number Diff line change
@@ -1,30 +1,190 @@
#pragma once

#include <grpcpp/grpcpp.h>
#include <grpcpp/impl/codegen/server_interceptor.h>
#include <grpcpp/server_context.h>
#include <grpcpp/support/interceptor.h>
#include <grpcpp/support/server_interceptor.h>
#include <iostream>
#include <absl/log/log.h>
#include <absl/strings/ascii.h>
#include <absl/strings/str_cat.h>
#include <absl/strings/string_view.h>
#include <absl/time/clock.h>
#include <absl/time/time.h>

#include <grpcpp/support/status.h>
#include <librdkafka/rdkafkacpp.h>
#include <unicode/utypes.h>
#include <string>

class LoggerInterceptor final : public grpc::experimental::Interceptor {
public:
explicit LoggerInterceptor(grpc::experimental::ServerRpcInfo* info) {
std::string method = info->method();
if (method == "unknown") {
std::cout << "[LoggerInterceptor] Unknown method called.\n";
} else {
std::cout << "[LoggerInterceptor] Method called: " << method << "\n";
}
}
void Intercept(grpc::experimental::InterceptorBatchMethods* methods) { methods->Proceed(); }
namespace moderation::logging {

enum class Subsystem {
kGrpc,
kKafka,
kIcu,
kAlgorithm,
kRepository,
kService,
kServer,
kConfig,
kUnknown
};

class LoggerInterceptorFactory : public grpc::experimental::ServerInterceptorFactoryInterface {
public:
grpc::experimental::Interceptor*
CreateServerInterceptor(grpc::experimental::ServerRpcInfo* info) {
return new LoggerInterceptor(info);
inline absl::TimeZone GetLocalTZ() {
static absl::TimeZone timezone = [] {
absl::TimeZone time;
absl::LoadTimeZone("Europe/Berlin", &time);
return time;
}();
return timezone;
}

inline absl::string_view SubsystemName(Subsystem systemName) {
switch (systemName) {
case Subsystem::kGrpc: return "grpc";
case Subsystem::kKafka: return "kafka";
case Subsystem::kIcu: return "icu";
case Subsystem::kAlgorithm: return "algorithm";
case Subsystem::kRepository: return "repository";
case Subsystem::kService: return "service";
case Subsystem::kServer: return "server";
case Subsystem::kConfig: return "config";
default: return "unknown";
}
};
}

// --- explanation mapping per subsystem ---

inline absl::string_view Explain(Subsystem, grpc::StatusCode code) {
switch (code) {
case grpc::StatusCode::DEADLINE_EXCEEDED:
return "client or server timeout, check network or increase deadline";
case grpc::StatusCode::UNAVAILABLE:
return "service unavailable, check endpoint health and connectivity";
case grpc::StatusCode::INVALID_ARGUMENT:
return "request validation failed, verify input fields";
default:
return "grpc request failed, inspect status code and service logs";
}
}

inline absl::string_view Explain(Subsystem, grpc::Status status) {
return Explain(Subsystem::kGrpc, status.error_code());
}

inline absl::string_view Explain(Subsystem, RdKafka::ErrorCode code) {
switch (code) {
case RdKafka::ERR__TRANSPORT:
return "broker unreachable, check connectivity and bootstrap config";
case RdKafka::ERR__TIMED_OUT:
return "operation timed out, check broker responsiveness and timeouts";
case RdKafka::ERR__AUTHENTICATION:
return "authentication failed, verify SASL/SSL credentials";
default:
return "kafka operation failed, inspect broker/client configuration";
}
}

inline absl::string_view Explain(Subsystem, UErrorCode code) {
switch (code) {
case U_BUFFER_OVERFLOW_ERROR:
return "output buffer too small for the converted string";
case U_INVALID_CHAR_FOUND:
return "invalid unicode sequence found in input data";
case U_MEMORY_ALLOCATION_ERROR:
return "memory allocation failed during unicode operation";
default:
return "ICU operation failed, verify unicode input and ICU configuration";
}
}

// fallback for custom codes

inline absl::string_view Explain(Subsystem subsystem, absl::string_view code) {
if (subsystem == Subsystem::kAlgorithm) {
if (code == "TOKENIZE_FAIL") return "tokenization failed, input normalization may be invalid";
if (code == "NORMALIZE_FAIL") return "normalization failed, inspect text and ICU preprocessing";
}
return "operation failed, inspect subsystem-specific context";
}

inline absl::string_view GrpcCodeName(grpc::StatusCode code) {
switch (code) {
case grpc::StatusCode::OK: return "OK";
case grpc::StatusCode::CANCELLED: return "CANCELLED";
case grpc::StatusCode::UNKNOWN: return "UNKNOWN";
case grpc::StatusCode::INVALID_ARGUMENT: return "INVALID_ARGUMENT";
case grpc::StatusCode::DEADLINE_EXCEEDED: return "DEADLINE_EXCEEDED";
case grpc::StatusCode::NOT_FOUND: return "NOT_FOUND";
case grpc::StatusCode::ALREADY_EXISTS: return "ALREADY_EXISTS";
case grpc::StatusCode::PERMISSION_DENIED: return "PERMISSION_DENIED";
case grpc::StatusCode::RESOURCE_EXHAUSTED: return "RESOURCE_EXHAUSTED";
case grpc::StatusCode::FAILED_PRECONDITION: return "FAILED_PRECONDITION";
case grpc::StatusCode::ABORTED: return "ABORTED";
case grpc::StatusCode::OUT_OF_RANGE: return "OUT_OF_RANGE";
case grpc::StatusCode::UNIMPLEMENTED: return "UNIMPLEMENTED";
case grpc::StatusCode::INTERNAL: return "INTERNAL";
case grpc::StatusCode::UNAVAILABLE: return "UNAVAILABLE";
case grpc::StatusCode::DATA_LOSS: return "DATA_LOSS";
case grpc::StatusCode::UNAUTHENTICATED: return "UNAUTHENTICATED";
default: return "UNRECOGNIZED";
}
}

inline std::string FormatError(Subsystem subsystem, grpc::StatusCode code, absl::string_view message) {
const auto timespan = absl::FormatTime("%Y-%m-%dT%H:%M:%E3S%Ez", absl::Now(), GetLocalTZ());
return absl::StrCat(
"timespan=", timespan,
" subsystem=", SubsystemName(subsystem),
" code=", GrpcCodeName(code),
" message=\"", message, "\"",
" explanation=\"", Explain(subsystem, code), "\"");
}

inline std::string FormatError(Subsystem subsystem, grpc::Status status, absl::string_view message) {
return FormatError(subsystem, status.error_code(),
absl::StrCat(message, " status_message=\"", status.error_message(), "\""));
}

inline std::string FormatError(Subsystem subsystem, RdKafka::ErrorCode code, absl::string_view message) {
const auto timespan = absl::FormatTime("%Y-%m-%dT%H:%M:%E3S%Ez", absl::Now(), GetLocalTZ());
return absl::StrCat(
"timespan=", timespan,
" subsystem=", SubsystemName(subsystem),
" code=", RdKafka::err2str(code),
" message=\"", message, "\"",
" explanation=\"", Explain(subsystem, code), "\"");
}

inline std::string FormatError(Subsystem subsystem, UErrorCode code, absl::string_view message) {
const auto timespan = absl::FormatTime("%Y-%m-%dT%H:%M:%E3S%Ez", absl::Now(), GetLocalTZ());
return absl::StrCat(
"timespan=", timespan,
" subsystem=", SubsystemName(subsystem),
" code=", u_errorName(code),
" message=\"", message, "\"",
" explanation=\"", Explain(subsystem, code), "\"");
}

inline std::string FormatError(Subsystem subsystem, absl::string_view code, absl::string_view message) {
const auto timespan = absl::FormatTime("%Y-%m-%dT%H:%M:%E3S%Ez", absl::Now(), GetLocalTZ());
return absl::StrCat(
"timespan=", timespan,
" subsystem=", SubsystemName(subsystem),
" code=", code,
" message=\"", message, "\"",
" explanation=\"", Explain(subsystem, code), "\"");
}

inline std::string FormatError(Subsystem subsystem, const std::string& code,
absl::string_view message) {
return FormatError(subsystem, absl::string_view(code), message);
}

inline std::string FormatError(Subsystem subsystem, const char* code, absl::string_view message) {
return FormatError(subsystem, absl::string_view(code ? code : "UNKNOWN"), message);
}

} // namespace moderation::logging

#define SERVICE_LOG_ERROR(subsystem, code, message) \
LOG(ERROR).AtLocation(__FILE__, __LINE__) << ::moderation::logging::FormatError((subsystem), (code), (message))

#define SERVICE_LOG_INFO(message) LOG(INFO).AtLocation(__FILE__, __LINE__) << (message)
#define SERVICE_VLOG1(message) VLOG(1) << (message)
53 changes: 53 additions & 0 deletions include/interceptors/logger_interceptor.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#pragma once
#include "logger.hpp"

#include <absl/strings/str_cat.h>
#include <absl/time/clock.h>
#include <absl/time/time.h>
#include <grpcpp/support/interceptor.h>
#include <grpcpp/support/server_interceptor.h>

class LoggerInterceptor final : public grpc::experimental::Interceptor {
public:
explicit LoggerInterceptor(grpc::experimental::ServerRpcInfo* info)
: method_(info->method() ? info->method() : "unknown"),
start_(absl::Now()) {}

void Intercept(grpc::experimental::InterceptorBatchMethods* methods) override {
if (method_ == "/grpc.health.v1.Health/Check") {
methods->Proceed();
return;
}

const grpc::Status* status = methods->GetRecvStatus();
if (status != nullptr) {
const auto latency_ms = absl::ToInt64Milliseconds(absl::Now() - start_);
const auto code = status ? status->error_code() : grpc::StatusCode::UNKNOWN;

if (code == grpc::StatusCode::OK) {
SERVICE_VLOG1(absl::StrCat("grpc method=", method_,
" status=OK latency_ms=", latency_ms));
} else {
SERVICE_LOG_ERROR(moderation::logging::Subsystem::kGrpc, code,
absl::StrCat("method=", method_,
" status_msg=", (status ? status->error_message() : "unknown"),
" latency_ms=", latency_ms));
}
}

methods->Proceed();
}

private:
std::string method_;
absl::Time start_;
};

class LoggerInterceptorFactory
: public grpc::experimental::ServerInterceptorFactoryInterface {
public:
grpc::experimental::Interceptor*
CreateServerInterceptor(grpc::experimental::ServerRpcInfo* info) override {
return new LoggerInterceptor(info);
}
};
11 changes: 6 additions & 5 deletions src/moderationservice/handler/handler.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#include "handler/handler.hpp"
#include <iostream>
#include "interceptors/logger.hpp"
#include <string>
#include <utility>

Expand All @@ -11,8 +11,9 @@ grpc::Status ModerationHandler::ModerateObject(grpc::ServerContext* context,
moderation::ModerateObjectResponse* response) {
if (request->text().empty() || request->id() == 0) {
response->set_success(false);
std::cerr << "[Handler] ERROR: Invalid moderation request. Text is empty or request ID is "
"zero.\n";
SERVICE_LOG_ERROR(moderation::logging::Subsystem::kGrpc,
grpc::StatusCode::INVALID_ARGUMENT,
"invalid moderation request: text empty or request id is zero");
return {grpc::INVALID_ARGUMENT, "Invalid moderation request"};
}

Expand All @@ -23,8 +24,8 @@ grpc::Status ModerationHandler::ModerateObject(grpc::ServerContext* context,

if (!success) {
response->set_success(false);
std::cerr << "[Handler] ERROR: Failed to process moderation request for ID: " << requestId
<< "\n";
SERVICE_LOG_ERROR(moderation::logging::Subsystem::kGrpc, grpc::StatusCode::INTERNAL,
absl::StrCat("failed to process moderation request request_id=", requestId));
return {grpc::INTERNAL, "Failed to process moderation request"};
}

Expand Down
Loading
Loading