OpenRoadCode
Public API reference for OpenRoadCode
Loading...
Searching...
No Matches
orc_logging.hpp
1// SPDX-FileCopyrightText: 2026 Mark G. Russell
2// SPDX-License-Identifier: MIT
3#pragma once
4
5#include <spdlog/spdlog.h>
6#include <spdlog/sinks/stdout_sinks.h>
7#include <rapidjson/stringbuffer.h>
8#include <rapidjson/writer.h>
9#include <chrono>
10#include <algorithm>
11#include <cctype>
12#include <cstdlib>
13#include <ctime>
14#include <iomanip>
15#include <memory>
16#include <sstream>
17#include <string>
18#include <unistd.h>
19
20namespace orc {
21inline std::string normalized(std::string value) {
22 const auto first = value.find_first_not_of(" \t\r\n");
23 if (first == std::string::npos) return "";
24 return value.substr(first, value.find_last_not_of(" \t\r\n") - first + 1);
25}
26inline spdlog::level::level_enum level(const std::string& input) {
27 std::string name = normalized(input);
28 std::transform(name.begin(), name.end(), name.begin(), [](unsigned char c) { return std::toupper(c); });
29 if (name == "DEBUG") return spdlog::level::debug;
30 if (name == "WARNING") return spdlog::level::warn;
31 if (name == "ERROR") return spdlog::level::err;
32 if (name == "CRITICAL") return spdlog::level::critical;
33 return spdlog::level::info;
34}
35inline spdlog::level::level_enum threshold(const std::string& component) {
36 const char* global = std::getenv("ORC_LOG_LEVEL");
37 auto selected = level(global ? global : "INFO");
38 const char* overrides = std::getenv("ORC_LOG_COMPONENT_LEVELS");
39 std::istringstream entries(overrides ? overrides : "");
40 std::string entry;
41 std::size_t longest = 0;
42 while (std::getline(entries, entry, ',')) {
43 const auto equals = entry.find('=');
44 if (equals == std::string::npos) continue;
45 auto prefix = normalized(entry.substr(0, equals));
46 if ((component == prefix || component.rfind(prefix + ".", 0) == 0) && prefix.size() >= longest) {
47 selected = level(entry.substr(equals + 1));
48 longest = prefix.size();
49 }
50 }
51 return selected;
52}
53inline void log(const char* severity, const char* component, const char* event,
54 const std::string& message, const std::string& operationId = "",
55 const std::string& command = "", const std::string& endpoint = "") {
56 const auto severityLevel = level(severity);
57 if (severityLevel < threshold(component)) return;
58 static auto logger = [] {
59 auto result = std::make_shared<spdlog::logger>("orc", std::make_shared<spdlog::sinks::stderr_sink_mt>());
60 result->set_pattern("%v");
61 result->set_level(spdlog::level::debug);
62 result->flush_on(spdlog::level::debug);
63 return result;
64 }();
65 const auto now = std::chrono::system_clock::now();
66 const auto seconds = std::chrono::system_clock::to_time_t(now);
67 std::tm utc{};
68 gmtime_r(&seconds, &utc);
69 const auto millis = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()).count() % 1000;
70 std::ostringstream timestamp;
71 timestamp << std::put_time(&utc, "%Y-%m-%dT%H:%M:%S") << '.' << std::setfill('0') << std::setw(3) << millis << 'Z';
72 rapidjson::StringBuffer buffer;
73 rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
74 writer.StartObject();
75 auto field = [&](const char* key, const std::string& value) {
76 writer.Key(key); writer.String(value.c_str(), static_cast<rapidjson::SizeType>(value.size()));
77 };
78 field("timestamp", timestamp.str());
79 field("level", severity);
80 field("component", component);
81 field("event", event);
82 field("message", message);
83 writer.Key("pid"); writer.Int(static_cast<int>(getpid()));
84 if (!operationId.empty()) field("operation_id", operationId);
85 if (!command.empty()) field("command", command);
86 if (!endpoint.empty()) field("endpoint", endpoint);
87 writer.EndObject();
88 logger->log(severityLevel, "{}", buffer.GetString());
89}
90} // namespace orc