Icarus
Vehicle Simulation as a Transformable Computational Graph, built on Vulcan and Janus
Loading...
Searching...
No Matches
LogSink.hpp
Go to the documentation of this file.
1#pragma once
2
9
12
13#include <fstream>
14#include <functional>
15#include <iostream>
16#include <map>
17#include <memory>
18#include <string>
19
20namespace icarus {
21
25class LogSinks {
26 public:
29 static LogService::Sink Console(class Console console) {
30 return [console = std::move(console)](const std::vector<LogEntry> &entries) {
31 for (const auto &entry : entries) {
32 if (console.IsColorEnabled()) {
33 std::cout << entry.FormatColored(console) << "\n";
34 } else {
35 std::cout << entry.Format() << "\n";
36 }
37 }
38 std::cout.flush();
39 };
40 }
41
45 static LogService::Sink ConsoleGrouped(class Console console) {
46 return [console = std::move(console)](const std::vector<LogEntry> &entries) {
47 // Group by entity
48 std::map<std::string, std::vector<const LogEntry *>> grouped;
49 for (const auto &entry : entries) {
50 grouped[entry.context.entity].push_back(&entry);
51 }
52
53 // Print each group
54 for (const auto &[entity, group_entries] : grouped) {
55 // Entity header
56 std::string header = "─── " + (entity.empty() ? "(no entity)" : entity) + " ";
57 // Guard against underflow: clamp remaining to 0 if header is too long
58 std::size_t remaining = (header.size() >= 80) ? 0 : (80 - header.size());
59 for (std::size_t i = 0; i < remaining; ++i) {
60 header += "─";
61 }
62
63 if (console.IsColorEnabled()) {
64 std::cout << console.Colorize(header, AnsiColor::Dim) << "\n";
65 } else {
66 std::cout << header << "\n";
67 }
68
69 // Entries
70 for (const auto *entry : group_entries) {
71 if (console.IsColorEnabled()) {
72 std::cout << entry->FormatColored(console) << "\n";
73 } else {
74 std::cout << entry->Format() << "\n";
75 }
76 }
77 }
78 std::cout.flush();
79 };
80 }
81
83 static LogService::Sink File(const std::string &path) {
84 // Use shared_ptr to keep file open across calls
85 auto file = std::make_shared<std::ofstream>(path, std::ios::app);
86 return [file](const std::vector<LogEntry> &entries) {
87 if (!file->is_open()) {
88 return;
89 }
90 for (const auto &entry : entries) {
91 *file << entry.Format() << "\n";
92 }
93 file->flush();
94 };
95 }
96
98 static LogService::Sink RotatingFile(const std::string &path, std::size_t max_size_bytes,
99 std::size_t max_files) {
100 struct State {
101 std::string base_path;
102 std::size_t max_size;
103 std::size_t max_files;
104 std::size_t current_size = 0;
105 std::shared_ptr<std::ofstream> file;
106 };
107
108 auto state = std::make_shared<State>();
109 state->base_path = path;
110 state->max_size = max_size_bytes;
111 state->max_files = max_files;
112 state->file = std::make_shared<std::ofstream>(path, std::ios::app);
113
114 return [state](const std::vector<LogEntry> &entries) {
115 if (!state->file->is_open()) {
116 return;
117 }
118
119 for (const auto &entry : entries) {
120 std::string line = entry.Format() + "\n";
121 *state->file << line;
122 state->current_size += line.size();
123
124 // Check for rotation
125 if (state->current_size >= state->max_size) {
126 state->file->close();
127
128 // Rotate files
129 for (std::size_t i = state->max_files - 1; i > 0; --i) {
130 std::string old_name = state->base_path + "." + std::to_string(i);
131 std::string new_name = state->base_path + "." + std::to_string(i + 1);
132 std::rename(old_name.c_str(), new_name.c_str());
133 }
134 std::rename(state->base_path.c_str(), (state->base_path + ".1").c_str());
135
136 // Open new file
137 state->file = std::make_shared<std::ofstream>(state->base_path, std::ios::app);
138 state->current_size = 0;
139 }
140 }
141 state->file->flush();
142 };
143 }
144
146 static LogService::Sink JsonLines(const std::string &path) {
147 auto file = std::make_shared<std::ofstream>(path, std::ios::app);
148 return [file](const std::vector<LogEntry> &entries) {
149 if (!file->is_open()) {
150 return;
151 }
152 for (const auto &entry : entries) {
153 // Simple JSON formatting (no external deps)
154 *file << "{\"time\":" << entry.sim_time << ",\"level\":\""
155 << GetLevelString(entry.level) << "\",\"entity\":\""
156 << EscapeJson(entry.context.entity) << "\",\"component\":\""
157 << EscapeJson(entry.context.component) << "\",\"message\":\""
158 << EscapeJson(entry.message) << "\"}\n";
159 }
160 file->flush();
161 };
162 }
163
166 return [](const std::vector<LogEntry> & /*entries*/) {
167 // Do nothing
168 };
169 }
170
172 static LogService::Sink Callback(std::function<void(const LogEntry &)> handler) {
173 return [handler = std::move(handler)](const std::vector<LogEntry> &entries) {
174 for (const auto &entry : entries) {
175 handler(entry);
176 }
177 };
178 }
179
180 private:
181 [[nodiscard]] static const char *GetLevelString(LogLevel level) {
182 switch (level) {
183 case LogLevel::Trace:
184 return "TRACE";
185 case LogLevel::Debug:
186 return "DEBUG";
187 case LogLevel::Info:
188 return "INFO";
189 case LogLevel::Event:
190 return "EVENT";
192 return "WARNING";
193 case LogLevel::Error:
194 return "ERROR";
195 case LogLevel::Fatal:
196 return "FATAL";
197 }
198 return "UNKNOWN";
199 }
200
201 [[nodiscard]] static std::string EscapeJson(const std::string &s) {
202 std::string result;
203 result.reserve(s.size());
204 for (char c : s) {
205 switch (c) {
206 case '"':
207 result += "\\\"";
208 break;
209 case '\\':
210 result += "\\\\";
211 break;
212 case '\n':
213 result += "\\n";
214 break;
215 case '\r':
216 result += "\\r";
217 break;
218 case '\t':
219 result += "\\t";
220 break;
221 default:
222 result += c;
223 break;
224 }
225 }
226 return result;
227 }
228};
229
230} // namespace icarus
Console abstraction with ANSI color support.
Unified logging service for Icarus.
bool IsColorEnabled() const
Definition Console.hpp:120
std::string Colorize(std::string_view text, const char *color) const
Apply color if enabled.
Definition Console.hpp:177
std::function< void(const std::vector< LogEntry > &)> Sink
Sink callback type: receives batch of entries to output.
Definition LogService.hpp:292
Factory for common log sinks.
Definition LogSink.hpp:25
static LogService::Sink Null()
Null sink (for testing/benchmarking).
Definition LogSink.hpp:165
static LogService::Sink ConsoleGrouped(class Console console)
Definition LogSink.hpp:45
static LogService::Sink JsonLines(const std::string &path)
JSON Lines sink (for log aggregation).
Definition LogSink.hpp:146
static LogService::Sink Callback(std::function< void(const LogEntry &)> handler)
Callback sink (custom handling).
Definition LogSink.hpp:172
static LogService::Sink Console(class Console console)
Definition LogSink.hpp:29
static LogService::Sink RotatingFile(const std::string &path, std::size_t max_size_bytes, std::size_t max_files)
File sink with rotation.
Definition LogSink.hpp:98
static LogService::Sink File(const std::string &path)
File sink (plain text, no colors).
Definition LogSink.hpp:83
Definition AggregationTypes.hpp:13
LogLevel
Log severity levels.
Definition Console.hpp:35
@ Warning
Potential issues.
Definition Console.hpp:40
@ Info
Normal operation.
Definition Console.hpp:38
@ Fatal
Unrecoverable errors.
Definition Console.hpp:42
@ Error
Recoverable errors.
Definition Console.hpp:41
@ Event
Simulation events (phase changes, etc.).
Definition Console.hpp:39
@ Debug
Debugging info.
Definition Console.hpp:37
@ Trace
Most verbose, internal debugging.
Definition Console.hpp:36
static constexpr const char * Dim
Definition Console.hpp:55
A single log entry with full context.
Definition LogService.hpp:119