OSSIA
Open Scenario System for Interactive Application
Loading...
Searching...
No Matches
websocket_log_sink.hpp
1#pragma once
2#include <ossia/detail/hash_map.hpp>
3#include <ossia/detail/thread.hpp>
4#include <ossia/detail/json.hpp>
5#include <ossia/detail/nullable_variant.hpp>
6#include <ossia/network/sockets/websocket_client.hpp>
7
8#include <spdlog/sinks/stdout_sinks.h>
9#include <spdlog/spdlog.h>
10
11#include <readerwriterqueue.h>
12
13#include <atomic>
14
15namespace ossia
16{
17struct websocket_threaded_connection
18{
19 websocket_threaded_connection(const std::string& ip)
20 : socket([](auto&&...) {})
21 {
22 running = true;
23 thread = ossia::thread([this, ip] {
24 auto log = spdlog::get("websocket");
25 if(!log)
26 log = spdlog::stderr_logger_mt("websocket");
27 try
28 {
29 while(running)
30 {
31 socket.connect_and_run(ip);
32 if(running)
33 {
34 // Try to reconnect
35 log->critical("Logger could not connect to {}.", ip);
36 std::this_thread::sleep_for(std::chrono::seconds(1));
37 }
38 }
39 log->critical("Logger stopping.");
40 }
41 catch(const websocketpp::exception& e)
42 {
43 log->critical("Logger error: ", e.what());
44 }
45 catch(const std::exception& e)
46 {
47 log->critical("Logger error: ", e.what());
48 }
49 catch(...)
50 {
51 log->critical("Logger error");
52 }
53 });
54 }
55
56 ~websocket_threaded_connection()
57 {
58 running = false;
59 if(!socket.after_connect())
60 std::this_thread::sleep_for(std::chrono::milliseconds(500));
61 socket.stop();
62 if(thread.joinable())
63 thread.join();
64 }
65
66 ossia::net::websocket_client socket;
67 std::atomic_bool running{};
68 ossia::thread thread;
69};
70
72struct websocket_log_sink final
73 : public spdlog::sinks::sink
74 , public Nano::Observer
75{
76 websocket_log_sink(std::shared_ptr<websocket_threaded_connection> s, std::string send)
77 : socket{std::move(s)}
78 , sender{std::move(send)}
79 {
80 socket->socket.on_open.connect<&websocket_log_sink::open_fun>(this);
81 }
82
83 void open_fun()
84 {
85 std::string m;
86 while(logs.try_dequeue(m))
87 {
88 socket->socket.send_message(m);
89 }
90 }
91
92 ~websocket_log_sink() override
93 {
94 socket->socket.on_open.disconnect<&websocket_log_sink::open_fun>(this);
95 }
96
97 void make_message(const spdlog::details::log_msg& msg)
98 {
99 buffer.Clear();
100
101 ossia::json_writer writer{buffer};
102
103 writer.StartObject();
104
105 writer.Key("operation");
106 writer.String("log");
107
108 writer.Key("level");
109 switch(msg.level)
110 {
111 case spdlog::level::trace:
112 writer.String("trace");
113 break;
114 case spdlog::level::debug:
115 writer.String("debug");
116 break;
117 case spdlog::level::info:
118 writer.String("info");
119 break;
120 case spdlog::level::warn:
121 writer.String("warn");
122 break;
123 case spdlog::level::err:
124 writer.String("error");
125 break;
126 case spdlog::level::critical:
127 writer.String("critical");
128 break;
129 case spdlog::level::off:
130 writer.String("off");
131 break;
132 }
133
134 writer.Key("sender");
135 writer.String(sender.data(), sender.size());
136
137 writer.Key("message");
138 writer.String(msg.payload.data(), msg.payload.size());
139
140 writer.EndObject();
141 }
142
143 void send_message(const spdlog::details::log_msg& msg)
144 {
145 make_message(msg);
146 socket->socket.send_message(buffer);
147 }
148
149 void log(const spdlog::details::log_msg& msg) override
150 {
151 if(!socket->socket.connected())
152 {
153 make_message(msg);
154 logs.enqueue(std::string{buffer.GetString(), buffer.GetSize()});
155 return;
156 }
157 else
158 {
159 send_message(msg);
160 }
161 }
162
163 void flush() override { }
164
165 void set_pattern(const std::string& pattern) override { }
166 void set_formatter(std::unique_ptr<spdlog::formatter> sink_formatter) override { }
167
168private:
169 rapidjson::StringBuffer buffer;
170 std::shared_ptr<websocket_threaded_connection> socket;
171 std::string sender;
172
173 moodycamel::ReaderWriterQueue<std::string> logs;
174};
175
177struct websocket_heartbeat : public Nano::Observer
178{
179public:
180 websocket_heartbeat(
181 std::shared_ptr<websocket_threaded_connection> t, std::string s,
182 std::chrono::seconds dur)
183 : interval{dur}
184 , sender{s}
185 , conn{t}
186 {
187 thread = ossia::thread([this] {
188 while(running)
189 {
190 if(init && conn->socket.connected())
191 {
192 buffer.Clear();
193 ossia::json_writer writer{buffer};
194 writer.StartObject();
195
196 writer.Key("operation");
197 writer.String("alive");
198
199 writer.Key("sender");
200 writer.String(sender.data(), sender.size());
201
202 writer.EndObject();
203
204 conn->socket.send_message(buffer);
205 }
206
207 for(int i = 0; i < 100; i++)
208 {
209 std::this_thread::sleep_for(
210 std::chrono::duration_cast<std::chrono::milliseconds>(interval) / 100.0);
211 if(!running)
212 return;
213 }
214 }
215 });
216
217 t->socket.on_open.connect<&websocket_heartbeat::open_fun>(*this);
218 }
219
220 void open_fun()
221 {
222 conn->socket.send_message(init_msg);
223 init = true;
224 }
225
226 ~websocket_heartbeat()
227 {
228 conn->socket.on_open.disconnect<&websocket_heartbeat::open_fun>(*this);
229 running = false;
230 if(thread.joinable())
231 thread.join();
232 }
233
234 void
235 send_init(const ossia::hash_map<std::string, ossia::variant<std::string, int>>& map)
236 {
237 rapidjson::StringBuffer buffer;
238 ossia::json_writer writer{buffer};
239 writer.StartObject();
240
241 writer.Key("operation");
242 writer.String("initWatchdog");
243
244 writer.Key("sender");
245 writer.String(sender.data(), sender.size());
246
247 writer.Key("aliveTime");
248 writer.Int(interval.count());
249
250 struct
251 {
252 ossia::json_writer& writer;
253 void operator()(const std::string& s) { writer.String(s); }
254
255 void operator()(int s) { writer.Int(s); }
256 } sw{writer};
257
258 for(const auto& pair : map)
259 {
260 writer.Key(pair.first);
261 ossia::visit(sw, pair.second);
262 }
263
264 writer.EndObject();
265
266 if(conn->socket.connected())
267 {
268 conn->socket.send_message(buffer);
269 init = true;
270 }
271 else
272 {
273 init_msg = std::string(buffer.GetString(), buffer.GetSize());
274 }
275 }
276
277private:
278 rapidjson::StringBuffer buffer;
279 ossia::thread thread;
280 std::chrono::seconds interval;
281 std::string sender;
282 std::string init_msg;
283 std::shared_ptr<websocket_threaded_connection> conn;
284 std::atomic_bool running{true};
285 std::atomic_bool init{false};
286};
287}
Definition thread.hpp:28
Definition git_info.h:7
A most simple pair type, to reduce binary bloat.
Definition enum_map.hpp:15