C++ Integration Guide
Overview
This guide explains how to integrate the AmpliServ Backend SDK with your C++ application. Since C++ doesn't have built-in logging libraries like other frameworks, we'll use spdlog for structured JSON logging and the AmpliServ SDK to collect the logs.
Prerequisites
- C++17 or higher
- CMake 3.14+
spdloglibrary (for JSON logging)nlohmann/jsonlibrary (for JSON handling)- AmpliServ SDK binary
Step 1: Install Dependencies
Using vcpkg (Recommended)
# Install vcpkg
git clone https://github.com/Microsoft/vcpkg.git
cd vcpkg
./bootstrap-vcpkg.sh
# Install spdlog and nlohmann-json
./vcpkg install spdlog
./vcpkg install nlohmann-json
Using Conan
# Install dependencies via Conan
conan install . --build=missing
Manual Installation (Linux)
# Install spdlog
sudo apt-get install libspdlog-dev
# Install nlohmann-json
sudo apt-get install nlohmann-json3-dev
Step 2: Create Project Structure
cpp-erp-backend/
├── CMakeLists.txt
├── src/
│ ├── main.cpp
│ ├── logger.cpp
│ ├── logger.hpp
│ ├── correlation_id.cpp
│ ├── correlation_id.hpp
│ ├── test_controller.cpp
│ ├── test_controller.hpp
│ ├── exception_handler.cpp
│ └── exception_handler.hpp
├── include/
│ └── utils.hpp
├── build/
└── app.log (created at runtime)
Step 3: CMakeLists.txt
cmake_minimum_required(VERSION 3.14)
project(CppErpBackend VERSION 1.0.0)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Find required packages
find_package(spdlog REQUIRED)
find_package(nlohmann_json REQUIRED)
# For UUID generation (Linux)
find_package(PkgConfig REQUIRED)
pkg_check_modules(UUID REQUIRED uuid)
# For threading
find_package(Threads REQUIRED)
# Include directories
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include)
include_directories(${spdlog_INCLUDE_DIRS})
include_directories(${nlohmann_json_INCLUDE_DIRS})
# Source files
set(SOURCES
src/main.cpp
src/logger.cpp
src/correlation_id.cpp
src/test_controller.cpp
src/exception_handler.cpp
)
# Executable
add_executable(cpp-erp-backend ${SOURCES})
# Link libraries
target_link_libraries(cpp-erp-backend
spdlog::spdlog
nlohmann_json::nlohmann_json
Threads::Threads
${UUID_LIBRARIES}
)
# For Windows, add bcrypt for UUID
if(WIN32)
target_link_libraries(cpp-erp-backend bcrypt)
endif()
Step 4: Logger Implementation
src/logger.hpp
#pragma once
#include <spdlog/spdlog.h>
#include <spdlog/sinks/basic_file_sink.h>
#include <spdlog/sinks/stdout_color_sinks.h>
#include <spdlog/pattern_formatter.h>
#include <nlohmann/json.hpp>
#include <memory>
#include <string>
using json = nlohmann::json;
class Logger {
public:
static Logger& getInstance();
void initialize(const std::string& serviceName,
const std::string& environment,
const std::string& logFilePath = "app.log");
void info(const std::string& message, const json& extra = json::object());
void warn(const std::string& message, const json& extra = json::object());
void error(const std::string& message, const json& extra = json::object());
void debug(const std::string& message, const json& extra = json::object());
void errorWithException(const std::string& message,
const std::exception& ex,
const json& extra = json::object());
void setCorrelationId(const std::string& correlationId);
std::string getCorrelationId() const;
private:
Logger() = default;
~Logger() = default;
void log(spdlog::level::level_enum level,
const std::string& message,
const json& extra);
std::shared_ptr<spdlog::logger> m_logger;
std::string m_correlationId;
std::string m_serviceName;
std::string m_environment;
static std::unique_ptr<Logger> s_instance;
static std::once_flag s_onceFlag;
};
src/logger.cpp
#include "logger.hpp"
#include <chrono>
#include <iomanip>
#include <sstream>
#include <thread>
std::unique_ptr<Logger> Logger::s_instance = nullptr;
std::once_flag Logger::s_onceFlag;
Logger& Logger::getInstance() {
std::call_once(s_onceFlag, []() {
s_instance.reset(new Logger());
});
return *s_instance;
}
void Logger::initialize(const std::string& serviceName,
const std::string& environment,
const std::string& logFilePath) {
m_serviceName = serviceName;
m_environment = environment;
try {
// Create file sink for JSON logs
auto file_sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>(
logFilePath, true
);
// Create console sink for debugging
auto console_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
// Create logger with multiple sinks
m_logger = std::make_shared<spdlog::logger>(
"json_logger",
spdlog::sinks_init_list{file_sink, console_sink}
);
// Set custom formatter for JSON output
m_logger->set_pattern("%v"); // Just the message (already JSON)
m_logger->set_level(spdlog::level::info);
spdlog::register_logger(m_logger);
info("ERP Backend started", {
{"port", 3000},
{"service", m_serviceName},
{"environment", m_environment}
});
} catch (const spdlog::spdlog_ex& ex) {
std::cerr << "Log initialization failed: " << ex.what() << std::endl;
}
}
void Logger::setCorrelationId(const std::string& correlationId) {
m_correlationId = correlationId;
}
std::string Logger::getCorrelationId() const {
return m_correlationId.empty() ? "unknown" : m_correlationId;
}
void Logger::info(const std::string& message, const json& extra) {
log(spdlog::level::info, message, extra);
}
void Logger::warn(const std::string& message, const json& extra) {
log(spdlog::level::warn, message, extra);
}
void Logger::error(const std::string& message, const json& extra) {
log(spdlog::level::err, message, extra);
}
void Logger::debug(const std::string& message, const json& extra) {
log(spdlog::level::debug, message, extra);
}
void Logger::errorWithException(const std::string& message,
const std::exception& ex,
const json& extra) {
json logEntry = extra;
logEntry["message"] = message;
logEntry["error"] = ex.what();
logEntry["stack_trace"] = ex.what(); // In production, use proper stack trace library
log(spdlog::level::err, message, logEntry);
}
void Logger::log(spdlog::level::level_enum level,
const std::string& message,
const json& extra) {
auto now = std::chrono::system_clock::now();
auto now_time_t = std::chrono::system_clock::to_time_t(now);
auto now_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
now.time_since_epoch()
) % 1000;
std::stringstream timestamp;
timestamp << std::put_time(std::localtime(&now_time_t), "%Y-%m-%dT%H:%M:%S");
timestamp << "." << std::setfill('0') << std::setw(3) << now_ms.count();
timestamp << "Z";
json logEntry;
logEntry["timestamp"] = timestamp.str();
logEntry["level"] = spdlog::level::to_string_view(level);
logEntry["message"] = message;
logEntry["correlation_id"] = m_correlationId;
logEntry["service"] = m_serviceName;
logEntry["environment"] = m_environment;
// Merge extra fields
for (auto& [key, value] : extra.items()) {
logEntry[key] = value;
}
std::string jsonStr = logEntry.dump();
switch (level) {
case spdlog::level::info:
m_logger->info(jsonStr);
break;
case spdlog::level::warn:
m_logger->warn(jsonStr);
break;
case spdlog::level::err:
m_logger->error(jsonStr);
break;
case spdlog::level::debug:
m_logger->debug(jsonStr);
break;
default:
m_logger->info(jsonStr);
break;
}
}
Step 5: Correlation ID Implementation
src/correlation_id.hpp
#pragma once
#include <string>
#include <thread>
#include <unordered_map>
class CorrelationIdManager {
public:
static CorrelationIdManager& getInstance();
std::string generateCorrelationId();
std::string getCurrentCorrelationId() const;
void setCurrentCorrelationId(const std::string& correlationId);
void clearCurrentCorrelationId();
private:
CorrelationIdManager() = default;
static thread_local std::string m_currentCorrelationId;
static std::unique_ptr<CorrelationIdManager> s_instance;
static std::once_flag s_onceFlag;
};
src/correlation_id.cpp
#include "correlation_id.hpp"
#include <random>
#include <sstream>
#include <iomanip>
#include <chrono>
#ifdef _WIN32
#include <windows.h>
#include <bcrypt.h>
#pragma comment(lib, "bcrypt.lib")
#else
#include <uuid/uuid.h>
#endif
thread_local std::string CorrelationIdManager::m_currentCorrelationId;
std::unique_ptr<CorrelationIdManager> CorrelationIdManager::s_instance = nullptr;
std::once_flag CorrelationIdManager::s_onceFlag;
CorrelationIdManager& CorrelationIdManager::getInstance() {
std::call_once(s_onceFlag, []() {
s_instance.reset(new CorrelationIdManager());
});
return *s_instance;
}
std::string CorrelationIdManager::generateCorrelationId() {
#ifdef _WIN32
UUID uuid;
UuidCreate(&uuid);
RPC_CSTR str;
UuidToStringA(&uuid, &str);
std::string result(reinterpret_cast<char*>(str));
RpcStringFreeA(&str);
return result;
#else
uuid_t uuid;
uuid_generate(uuid);
char str[37];
uuid_unparse(uuid, str);
return std::string(str);
#endif
}
std::string CorrelationIdManager::getCurrentCorrelationId() const {
if (m_currentCorrelationId.empty()) {
return "unknown";
}
return m_currentCorrelationId;
}
void CorrelationIdManager::setCurrentCorrelationId(const std::string& correlationId) {
m_currentCorrelationId = correlationId;
Logger::getInstance().setCorrelationId(correlationId);
}
void CorrelationIdManager::clearCurrentCorrelationId() {
m_currentCorrelationId.clear();
}
Step 6: Exception Handler
src/exception_handler.hpp
#pragma once
#include <exception>
#include <string>
#include <functional>
class ExceptionHandler {
public:
static ExceptionHandler& getInstance();
using ExceptionCallback = std::function<void(const std::exception&, const std::string&)>;
void setCallback(ExceptionCallback callback);
void handleException(const std::exception& ex, const std::string& context = "");
private:
ExceptionHandler() = default;
ExceptionCallback m_callback;
};
src/exception_handler.cpp
#include "exception_handler.hpp"
#include "logger.hpp"
ExceptionHandler& ExceptionHandler::getInstance() {
static ExceptionHandler instance;
return instance;
}
void ExceptionHandler::setCallback(ExceptionCallback callback) {
m_callback = callback;
}
void ExceptionHandler::handleException(const std::exception& ex, const std::string& context) {
auto& logger = Logger::getInstance();
json extra;
extra["context"] = context;
extra["stack_trace"] = ex.what(); // Full stack trace captured here
logger.errorWithException(ex.what(), ex, extra);
if (m_callback) {
m_callback(ex, context);
}
}
Step 7: Test Controller
src/test_controller.hpp
#pragma once
#include <string>
#include <functional>
class TestController {
public:
using HttpResponse = std::function<void(int, const std::string&)>;
TestController();
void handleInfo(HttpResponse callback);
void handleWarn(HttpResponse callback);
void handleError(HttpResponse callback);
void handleDashboard(HttpResponse callback);
private:
void simulateDeepError();
void level1();
void level2();
void level3();
std::string m_correlationId;
};
src/test_controller.cpp
#include "test_controller.hpp"
#include "logger.hpp"
#include "correlation_id.hpp"
#include "exception_handler.hpp"
#include <nlohmann/json.hpp>
TestController::TestController() = default;
void TestController::handleInfo(HttpResponse callback) {
auto& logger = Logger::getInstance();
auto& corrMgr = CorrelationIdManager::getInstance();
std::string correlationId = corrMgr.getCurrentCorrelationId();
logger.info("Info endpoint called - This is a normal log", {
{"correlation_id", correlationId}
});
json response;
response["message"] = "Info logged successfully";
response["correlationId"] = correlationId;
callback(200, response.dump());
}
void TestController::handleWarn(HttpResponse callback) {
auto& logger = Logger::getInstance();
auto& corrMgr = CorrelationIdManager::getInstance();
std::string correlationId = corrMgr.getCurrentCorrelationId();
logger.warn("Warning endpoint called - This is a warning log", {
{"correlation_id", correlationId}
});
json response;
response["message"] = "Warning logged";
response["correlationId"] = correlationId;
callback(200, response.dump());
}
void TestController::handleError(HttpResponse callback) {
auto& logger = Logger::getInstance();
auto& corrMgr = CorrelationIdManager::getInstance();
auto& exHandler = ExceptionHandler::getInstance();
std::string correlationId = corrMgr.getCurrentCorrelationId();
logger.info("Test error endpoint called", {
{"correlation_id", correlationId}
});
try {
simulateDeepError();
json response;
response["message"] = "No error";
response["correlationId"] = correlationId;
callback(200, response.dump());
} catch (const std::exception& ex) {
// Full stack trace captured here
exHandler.handleException(ex, "Test error endpoint");
json response;
response["message"] = "Error triggered";
response["correlationId"] = correlationId;
response["error"] = ex.what();
callback(500, response.dump());
}
}
void TestController::handleDashboard(HttpResponse callback) {
auto& logger = Logger::getInstance();
auto& corrMgr = CorrelationIdManager::getInstance();
std::string correlationId = corrMgr.getCurrentCorrelationId();
logger.info("Dashboard endpoint called", {
{"correlation_id", correlationId}
});
json dashboard;
dashboard["totalProducts"] = 100;
dashboard["totalOrders"] = 50;
dashboard["totalCustomers"] = 75;
dashboard["totalRevenue"] = 250000;
dashboard["correlationId"] = correlationId;
logger.debug("Dashboard data", dashboard);
callback(200, dashboard.dump());
}
void TestController::simulateDeepError() {
level1();
}
void TestController::level1() {
level2();
}
void TestController::level2() {
level3();
}
void TestController::level3() {
throw std::runtime_error("This is a test error with deep stack trace from C++!");
}
Step 8: Main Application
src/main.cpp
#include <iostream>
#include <string>
#include <thread>
#include <chrono>
#include <cstdlib>
#include "logger.hpp"
#include "correlation_id.hpp"
#include "test_controller.hpp"
#include "exception_handler.hpp"
// Simple HTTP server simulation
// In production, use a proper HTTP library like Crow, Pistache, or Beast
void handleRequest(const std::string& method,
const std::string& path,
const std::string& correlationId) {
auto& corrMgr = CorrelationIdManager::getInstance();
auto& logger = Logger::getInstance();
// Set correlation ID for this request
corrMgr.setCurrentCorrelationId(correlationId);
// Log incoming request
logger.info("Incoming " + method + " " + path, {
{"method", method},
{"path", path},
{"correlation_id", correlationId}
});
auto startTime = std::chrono::steady_clock::now();
TestController controller;
int statusCode = 200;
std::string responseBody;
// Route handling
if (method == "GET") {
if (path == "/api/test/info") {
controller.handleInfo([&](int code, const std::string& body) {
statusCode = code;
responseBody = body;
});
}
else if (path == "/api/test/warn") {
controller.handleWarn([&](int code, const std::string& body) {
statusCode = code;
responseBody = body;
});
}
else if (path == "/api/test/error") {
controller.handleError([&](int code, const std::string& body) {
statusCode = code;
responseBody = body;
});
}
else if (path == "/api/dashboard") {
controller.handleDashboard([&](int code, const std::string& body) {
statusCode = code;
responseBody = body;
});
}
else {
statusCode = 404;
responseBody = R"({"message":"Not found"})";
logger.warn("Route not found: " + path, {{"correlation_id", correlationId}});
}
}
auto endTime = std::chrono::steady_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(endTime - startTime).count();
// Log response
auto logLevel = statusCode >= 400 ? "error" : "info";
logger.info(method + " " + path + " " + std::to_string(statusCode) + " - " + std::to_string(duration) + "ms", {
{"method", method},
{"path", path},
{"statusCode", statusCode},
{"duration", duration},
{"correlation_id", correlationId}
});
// Print response to console (simulating HTTP response)
std::cout << "\n=== Response ===" << std::endl;
std::cout << "Status: " << statusCode << std::endl;
std::cout << "Body: " << responseBody << std::endl;
std::cout << "===============\n" << std::endl;
// Clear correlation ID for next request
corrMgr.clearCurrentCorrelationId();
}
int main(int argc, char* argv[]) {
auto& logger = Logger::getInstance();
auto& corrMgr = CorrelationIdManager::getInstance();
auto& exHandler = ExceptionHandler::getInstance();
// Set exception handler callback
exHandler.setCallback([](const std::exception& ex, const std::string& context) {
auto& logger = Logger::getInstance();
logger.error("Unhandled exception in " + context, {
{"error", ex.what()}
});
});
// Initialize logger
logger.initialize("cpp-erp-backend", "development", "app.log");
std::cout << "C++ ERP Backend running on http://localhost:3000" << std::endl;
std::cout << "Press Ctrl+C to exit" << std::endl;
std::cout << std::endl;
// Simple command-line interface for testing
while (true) {
std::cout << "\nAvailable commands:" << std::endl;
std::cout << " 1. Test Info" << std::endl;
std::cout << " 2. Test Warning" << std::endl;
std::cout << " 3. Test Error (with stack trace)" << std::endl;
std::cout << " 4. Dashboard" << std::endl;
std::cout << " 5. Exit" << std::endl;
std::cout << "Enter choice: ";
int choice;
std::cin >> choice;
if (choice == 5) {
std::cout << "Shutting down..." << std::endl;
break;
}
// Generate correlation ID for this request
std::string correlationId = corrMgr.generateCorrelationId();
std::string method = "GET";
std::string path;
switch (choice) {
case 1:
path = "/api/test/info";
break;
case 2:
path = "/api/test/warn";
break;
case 3:
path = "/api/test/error";
break;
case 4:
path = "/api/dashboard";
break;
default:
std::cout << "Invalid choice" << std::endl;
continue;
}
handleRequest(method, path, correlationId);
}
return 0;
}
Step 9: Configure AmpliServ SDK
Create run-agent.sh (Linux/Mac):
#!/bin/bash
export AMPLISERV_API_KEY="your-api-key-here"
export AMPLISERV_COLLECTOR_MODE="file"
export AMPLISERV_ENDPOINT="http://dev-backend.ampliserv.com/api/ingestion/v1/ingest"
export AMPLISERV_SERVICE_NAME="ingestion-service"
export AMPLISERV_ENV="development"
export AMPLISERV_LOG_PATH="/path/to/cpp-erp-backend/app.log"
export AMPLISERV_BATCH_SIZE="50"
export AMPLISERV_FLUSH_INTERVAL_SEC="5"
export AMPLISERV_INCIDENT_ENABLED="true"
echo "Starting AmpliServ Agent..."
./ampliserv-agent
Create run-agent.bat (Windows):
@echo off
set AMPLISERV_API_KEY=your-api-key-here
set AMPLISERV_COLLECTOR_MODE=file
set AMPLISERV_ENDPOINT=http://dev-backend.ampliserv.com/api/ingestion/v1/ingest
set AMPLISERV_SERVICE_NAME=ingestion-service
set AMPLISERV_ENV=development
set AMPLISERV_LOG_PATH=C:\path\to\cpp-erp-backend\app.log
set AMPLISERV_BATCH_SIZE=50
set AMPLISERV_FLUSH_INTERVAL_SEC=5
set AMPLISERV_INCIDENT_ENABLED=true
echo Starting AmpliServ Agent...
ampliserv-agent.exe
Step 10: Build and Run
Build with CMake:
mkdir build
cd build
cmake ..
make
Run the application:
./cpp-erp-backend
In a separate terminal, start the AmpliServ SDK:
./run-agent.sh
Step 11: Test the Integration
From the application menu:
Available commands:
1. Test Info
2. Test Warning
3. Test Error (with stack trace)
4. Dashboard
5. Exit
Enter choice: 1
Or use curl with a real HTTP server:
# If you implement a real HTTP server
curl http://localhost:3000/api/test/info
curl http://localhost:3000/api/test/error
Sample JSON Log Output
{
"timestamp": "2026-03-23T10:30:45.123Z",
"level": "error",
"message": "Test error endpoint called",
"correlation_id": "a1270278-7ef9-4e59-803e-e9862c257d73",
"service": "cpp-erp-backend",
"environment": "development",
"context": "Test error endpoint",
"stack_trace": "This is a test error with deep stack trace from C++!",
"error": "This is a test error with deep stack trace from C++!"
}
Production Recommendations
For Production, Use a Real HTTP Server:
Option 1: Crow (Header-only)
#include "crow.h"
int main() {
crow::SimpleApp app;
CROW_ROUTE(app, "/api/test/error")
([](const crow::request& req) {
auto& logger = Logger::getInstance();
// ... handler logic
return crow::response(200);
});
app.port(3000).run();
}
Option 2: Pistache
#include <pistache/endpoint.h>
#include <pistache/router.h>
class ErpHandler : public Http::Handler {
void onRequest(const Http::Request& req, Http::ResponseWriter response) override {
// ... handler logic
}
};
Docker Deployment
FROM ubuntu:22.04
RUN apt-get update && apt-get install -y \
libspdlog-dev \
nlohmann-json3-dev \
libuuid-dev
WORKDIR /app
COPY build/cpp-erp-backend .
COPY run-agent.sh .
CMD ["./cpp-erp-backend"]
Performance Considerations
Asynchronous Logging: spdlog supports async logging
auto async_file = spdlog::basic_logger_mt<spdlog::async_factory>("async_logger", "app.log");
Log Level in Production: Set to WARN or ERROR only
logger.set_level(spdlog::level::warn);
Batch Processing: The AmpliServ SDK handles batching, so your app just writes individual logs
Verification Checklist
- C++ application compiles without errors
-
app.logfile is created with JSON content - Logs are in valid JSON format
- AmpliServ SDK shows "Starting AmpliServ Agent"
- SDK displays "Mode: file"
- Test endpoints produce logs
- Correlation ID is present in logs
- Stack traces are captured in error logs
Common Issues
| Issue | Solution |
|---|---|
spdlog not found | Install via vcpkg or apt-get |
| JSON formatting errors | Ensure nlohmann-json is properly installed |
| UUID generation fails | Install uuid-dev on Linux, or use Windows API |
| SDK can't read log file | Check file permissions |
| Stack traces not captured | C++ exceptions don't include stack traces by default; use std::current_exception with backtrace libraries |
Advanced: Capturing Full Stack Traces
For production stack traces, use:
#include <execinfo.h>
#include <cxxabi.h>
std::string getStackTrace() {
void* array[100];
size_t size = backtrace(array, 100);
char** symbols = backtrace_symbols(array, size);
std::string result;
for (size_t i = 0; i < size; i++) {
result += symbols[i];
result += "\n";
}
free(symbols);
return result;
}