1pub mod actions;
3pub mod codec;
4use crate::protocol::{Event, SpawnContext};
5use crate::server::{
6 accept_bounded::{accept_bounded, ConnectionLimiter, DEFAULT_MAX_CONNECTIONS},
7 connection::ConnectionId,
8};
9use crate::state::{
10 server::{ConnectionState, ConnectionStatus, ProtocolConnectionInfo},
11 AccessLogOwner,
12};
13use crate::{console_error, console_info};
14use anyhow::{Context, Result};
15use codec::{Message, Reassembler, Transport};
16use serde_json::json;
17use std::{
18 net::SocketAddr,
19 time::{Duration, SystemTime, UNIX_EPOCH},
20};
21use tokio::{
22 io::AsyncReadExt,
23 net::{TcpListener, TcpStream},
24};
25
26pub const READ_TIMEOUT: Duration = Duration::from_secs(30);
27pub struct GelfServer;
28impl GelfServer {
29 pub async fn spawn(ctx: SpawnContext) -> Result<SocketAddr> {
30 let llm_fallback = ctx
31 .startup_params
32 .as_ref()
33 .map(|p| p.get_optional_bool("llm_fallback"))
34 .transpose()?
35 .flatten()
36 .unwrap_or(codec::DEFAULT_LLM_FALLBACK);
37 let transport = Transport::parse(
38 &ctx.startup_params
39 .as_ref()
40 .map(|p| p.get_optional_string("transport"))
41 .transpose()?
42 .flatten()
43 .unwrap_or_else(|| codec::DEFAULT_TRANSPORT.into()),
44 )?;
45 if transport == Transport::Udp {
46 return udp(ctx, llm_fallback).await;
47 }
48 let listener = TcpListener::bind(ctx.legacy_listen_addr()).await?;
49 let local = listener.local_addr()?;
50 console_info!(
51 ctx.status_tx,
52 "GELF TCP listening on {} (llm_fallback={})",
53 local,
54 llm_fallback
55 );
56 let registrar = ctx.state.clone();
57 let server_id = ctx.server_id;
58 let task = tokio::spawn(async move {
59 let limiter = ConnectionLimiter::new(DEFAULT_MAX_CONNECTIONS);
60 loop {
61 let (socket, peer, permit) =
63 match accept_bounded(&listener, &limiter, b"", "GELF", Some(&ctx.status_tx))
64 .await
65 {
66 Ok(accepted) => accepted,
67 Err(error) => {
68 console_error!(ctx.status_tx, "GELF accept failed: {}", error);
69 break;
70 }
71 };
72 let id = ConnectionId::new(ctx.state.get_next_unified_id().await);
73 let now = crate::utils::clock::Instant::now();
74 ctx.state
75 .add_connection_to_server(
76 server_id,
77 ConnectionState {
78 id,
79 remote_addr: peer,
80 local_addr: local,
81 bytes_sent: 0,
82 bytes_received: 0,
83 packets_sent: 0,
84 packets_received: 0,
85 last_activity: now,
86 status: ConnectionStatus::Active,
87 status_changed_at: now,
88 protocol_info: ProtocolConnectionInfo::empty(),
89 },
90 )
91 .await;
92 let child_ctx = ctx.clone();
93 let child = tokio::spawn(async move {
94 let _permit = permit;
95 if let Err(error) = session(socket, peer, id, &child_ctx, llm_fallback).await {
96 console_error!(child_ctx.status_tx, "GELF peer {} closed: {}", peer, error);
97 child_ctx
98 .state
99 .record_access_log(
100 AccessLogOwner::Server(server_id.as_u32()),
101 "GELF",
102 Some(id.as_u32()),
103 "gelf_invalid_stream",
104 json!({"source_addr":peer.to_string()}),
105 vec![json!({"error":error.to_string()})],
106 )
107 .await;
108 }
109 child_ctx
110 .state
111 .remove_connection_from_server(server_id, id)
112 .await;
113 let _ = child_ctx.status_tx.send("__UPDATE_UI__".into());
114 });
115 ctx.state.register_server_task(server_id, child).await;
116 }
117 });
118 registrar.register_server_task(server_id, task).await;
119 Ok(local)
120 }
121}
122async fn session(
123 mut socket: TcpStream,
124 peer: SocketAddr,
125 id: ConnectionId,
126 ctx: &SpawnContext,
127 llm_fallback: bool,
128) -> Result<()> {
129 let mut decoder = codec::TcpDecoder::default();
130 let mut buffer = [0u8; 8192];
131 loop {
132 let deadline = tokio::time::Instant::now() + READ_TIMEOUT;
133 let message = loop {
134 if let Some(message) = decoder.next_message()? {
135 break message;
136 }
137 let n = tokio::time::timeout_at(deadline, socket.read(&mut buffer))
138 .await
139 .context("GELF frame read deadline exceeded")??;
140 if n == 0 {
141 return decoder.finish();
142 }
143 ctx.state
144 .update_connection_stats(ctx.server_id, id, Some(n as u64), None, Some(1), None)
145 .await;
146 decoder.feed(&buffer[..n])?;
147 };
148 dispatch(message, peer, id, ctx, Transport::Tcp, llm_fallback).await?;
149 }
150}
151async fn dispatch(
152 mut message: Message,
153 peer: SocketAddr,
154 id: ConnectionId,
155 ctx: &SpawnContext,
156 transport: Transport,
157 llm_fallback: bool,
158) -> Result<()> {
159 if message.timestamp.is_none() {
160 message.timestamp = Some(SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs_f64());
161 }
162 if message.level.is_none() {
163 message.level = Some(1);
164 }
165 let event = Event::new(
166 &actions::GELF_MESSAGE_EVENT,
167 json!({"message":message,"source_addr":peer.to_string(),"transport":transport.as_str()}),
168 );
169 let configured = ctx
170 .state
171 .get_event_handler_config(ctx.server_id)
172 .await
173 .is_some_and(|c| c.find_handler("gelf_message").is_some());
174 if llm_fallback || configured {
175 let result = crate::llm::action_helper::call_llm(
176 &ctx.llm_client,
177 &ctx.state,
178 ctx.server_id,
179 Some(id),
180 &event,
181 &actions::GelfProtocol::new(),
182 )
183 .await?;
184 if !result.failures.is_empty() {
185 console_error!(
186 ctx.status_tx,
187 "GELF decision=fail_closed_handler_action_error failed_actions={}",
188 result.failures.len()
189 );
190 ctx.state.record_access_log(
191 AccessLogOwner::Server(ctx.server_id.as_u32()), "GELF", Some(id.as_u32()),
192 "gelf_handler_failed", event.data,
193 vec![json!({"decision":"fail_closed_handler_action_error","failed_action_count":result.failures.len()})],
194 ).await;
195 anyhow::bail!("GELF handler action failed");
196 }
197 for message in result.messages {
198 console_info!(ctx.status_tx, "{}", message);
199 }
200 } else {
201 ctx.state
202 .record_access_log(
203 AccessLogOwner::Server(ctx.server_id.as_u32()),
204 "GELF",
205 Some(id.as_u32()),
206 "gelf_message",
207 event.data,
208 vec![json!({"type":"collect_gelf_message"})],
209 )
210 .await;
211 }
212 let _ = ctx.status_tx.send("__UPDATE_UI__".into());
213 Ok(())
214}
215async fn udp(ctx: SpawnContext, llm_fallback: bool) -> Result<SocketAddr> {
216 let socket = tokio::net::UdpSocket::bind(ctx.legacy_listen_addr()).await?;
217 let local = socket.local_addr()?;
218 console_info!(
219 ctx.status_tx,
220 "GELF UDP listening on {} (llm_fallback={})",
221 local,
222 llm_fallback
223 );
224 let registrar = ctx.state.clone();
225 let server_id = ctx.server_id;
226 let task = tokio::spawn(async move {
227 let mut buffer = vec![0; codec::MAX_DATAGRAM_BYTES + 1];
228 let mut reassembler = Reassembler::default();
229 let mut expiry = tokio::time::interval(Duration::from_secs(1));
230 loop {
231 let (n, peer) = tokio::select! {
232 _=expiry.tick()=>{reassembler.expire(tokio::time::Instant::now());continue;},
233 result=socket.recv_from(&mut buffer)=>match result {Ok(r)=>r,Err(error)=>{console_error!(ctx.status_tx,"GELF receive failed: {}",error);break;}}
234 };
235 let message = match reassembler.push(peer, &buffer[..n], tokio::time::Instant::now()) {
236 Ok(Some(m)) => m,
237 Ok(None) => continue,
238 Err(error) => {
239 ctx.state
240 .record_access_log(
241 AccessLogOwner::Server(server_id.as_u32()),
242 "GELF",
243 None,
244 "gelf_invalid_datagram",
245 json!({"source_addr":peer.to_string(),"received_bytes":n}),
246 vec![json!({"error":error.to_string()})],
247 )
248 .await;
249 continue;
250 }
251 };
252 let id = ConnectionId::new(ctx.state.get_next_unified_id().await);
253 let now = crate::utils::clock::Instant::now();
254 ctx.state
255 .add_connection_to_server(
256 server_id,
257 ConnectionState {
258 id,
259 remote_addr: peer,
260 local_addr: local,
261 bytes_sent: 0,
262 bytes_received: n as u64,
263 packets_sent: 0,
264 packets_received: 1,
265 last_activity: now,
266 status: ConnectionStatus::Active,
267 status_changed_at: now,
268 protocol_info: ProtocolConnectionInfo::empty(),
269 },
270 )
271 .await;
272 if let Err(error) =
273 dispatch(message, peer, id, &ctx, Transport::Udp, llm_fallback).await
274 {
275 console_error!(
276 ctx.status_tx,
277 "GELF decision=fail_closed_handler_error: {}",
278 error
279 );
280 }
281 ctx.state.remove_connection_from_server(server_id, id).await;
282 }
283 });
284 registrar.register_server_task(server_id, task).await;
285 Ok(local)
286}