1use crate::llm::actions::{
4 client_trait::{Client, ClientActionResult},
5 protocol_trait::Protocol,
6 ActionDefinition, Parameter, ParameterDefinition,
7};
8use crate::protocol::ConnectContext;
9use crate::protocol::EventType;
10use crate::state::app_state::AppState;
11use anyhow::{Context, Result};
12use serde_json::json;
13use std::sync::LazyLock;
14
15pub static SYSLOG_CLIENT_CONNECTED_EVENT: LazyLock<EventType> = LazyLock::new(|| {
17 EventType::new(
18 "syslog_connected",
19 "Syslog client successfully connected to server",
20 json!({
21 "type": "send_syslog_message",
22 "facility": "user",
23 "severity": "info",
24 "message": "Connection established"
25 }),
26 )
27 .with_parameters(vec![
28 Parameter {
29 name: "remote_addr".to_string(),
30 type_hint: "string".to_string(),
31 description: "Remote syslog server address".to_string(),
32 required: true,
33 },
34 Parameter {
35 name: "protocol".to_string(),
36 type_hint: "string".to_string(),
37 description: "Transport protocol (tcp or udp)".to_string(),
38 required: true,
39 },
40 ])
41});
42
43pub struct SyslogClientProtocol;
45
46impl SyslogClientProtocol {
47 pub fn new() -> Self {
48 Self
49 }
50}
51
52impl Protocol for SyslogClientProtocol {
54 fn get_async_actions(&self, _state: &AppState) -> Vec<ActionDefinition> {
55 vec![
56 ActionDefinition {
57 name: "send_syslog_message".to_string(),
58 description: "Send a syslog message to the server".to_string(),
59 parameters: vec![
60 Parameter {
61 name: "facility".to_string(),
62 type_hint: "string".to_string(),
63 description: "Syslog facility (kern, user, mail, daemon, auth, syslog, lpr, news, uucp, cron, authpriv, ftp, ntp, security, console, solaris-cron, local0-local7)".to_string(),
64 required: true,
65 },
66 Parameter {
67 name: "severity".to_string(),
68 type_hint: "string".to_string(),
69 description: "Syslog severity (emerg, alert, crit, err, warning, notice, info, debug)".to_string(),
70 required: true,
71 },
72 Parameter {
73 name: "message".to_string(),
74 type_hint: "string".to_string(),
75 description: "The log message to send".to_string(),
76 required: true,
77 },
78 Parameter {
79 name: "hostname".to_string(),
80 type_hint: "string".to_string(),
81 description: "Hostname (optional, defaults to 'netget')".to_string(),
82 required: false,
83 },
84 Parameter {
85 name: "app_name".to_string(),
86 type_hint: "string".to_string(),
87 description: "Application name (optional, defaults to 'netget')".to_string(),
88 required: false,
89 },
90 Parameter {
91 name: "proc_id".to_string(),
92 type_hint: "string".to_string(),
93 description: "Process ID (optional, defaults to '-')".to_string(),
94 required: false,
95 },
96 Parameter {
97 name: "msg_id".to_string(),
98 type_hint: "string".to_string(),
99 description: "Message ID (optional, defaults to '-')".to_string(),
100 required: false,
101 },
102 ],
103 example: json!({
104 "type": "send_syslog_message",
105 "facility": "user",
106 "severity": "info",
107 "message": "Test log message from netget",
108 "hostname": "netget-host",
109 "app_name": "netget"
110 }),
111 log_template: None,
112 },
113 ActionDefinition {
114 name: "disconnect".to_string(),
115 description: "Disconnect from the syslog server (TCP only)".to_string(),
116 parameters: vec![],
117 example: json!({
118 "type": "disconnect"
119 }),
120 log_template: None,
121 },
122 ]
123 }
124 fn get_sync_actions(&self) -> Vec<ActionDefinition> {
125 vec![]
126 }
127 fn protocol_name(&self) -> &'static str {
128 "Syslog"
129 }
130 fn get_event_types(&self) -> Vec<EventType> {
131 vec![
132 EventType::new(
133 "syslog_connected",
134 "Triggered when syslog client connects to server",
135 json!({"type": "placeholder", "event_id": "syslog_connected"}),
136 ),
137 EventType::new(
138 "syslog_message_sent",
139 "Triggered when syslog message is sent",
140 json!({"type": "placeholder", "event_id": "syslog_message_sent"}),
141 ),
142 ]
143 }
144 fn stack_name(&self) -> &'static str {
145 "ETH>IP>TCP/UDP>Syslog"
146 }
147 fn keywords(&self) -> Vec<&'static str> {
148 vec!["syslog", "syslog client", "logging"]
149 }
150 fn metadata(&self) -> crate::protocol::metadata::ProtocolMetadataV2 {
151 use crate::protocol::metadata::{DevelopmentState, ProtocolMetadataV2};
152
153 ProtocolMetadataV2::builder()
154 .state(DevelopmentState::Experimental)
155 .implementation("RFC 5424 syslog over TCP or UDP")
156 .llm_control("Full control over facility, severity, and message content")
157 .e2e_testing("rsyslog or syslog-ng as test server")
158 .build()
159 }
160 fn description(&self) -> &'static str {
161 "Syslog client for sending log messages to remote syslog servers"
162 }
163 fn example_prompt(&self) -> &'static str {
164 "Send a syslog message with facility 'user' and severity 'info' to localhost:514"
165 }
166 fn group_name(&self) -> &'static str {
167 "Logging"
168 }
169 fn get_startup_parameters(&self) -> Vec<ParameterDefinition> {
170 vec![ParameterDefinition {
171 name: "protocol".to_string(),
172 description: "Transport protocol (tcp or udp, defaults to udp)".to_string(),
173 type_hint: "string".to_string(),
174 required: false,
175 example: json!("tcp"),
176 default: None,
177 }]
178 }
179 fn get_startup_examples(&self) -> crate::llm::actions::StartupExamples {
180 use crate::llm::actions::StartupExamples;
181 use serde_json::json;
182
183 StartupExamples::new(
184 json!({
186 "type": "open_client",
187 "remote_addr": "localhost:514",
188 "base_stack": "syslog",
189 "instruction": "Send a user info log message about application startup"
190 }),
191 json!({
193 "type": "open_client",
194 "remote_addr": "localhost:514",
195 "base_stack": "syslog",
196 "event_handlers": [{
197 "event_pattern": "syslog_connected",
198 "handler": {
199 "type": "script",
200 "language": "python",
201 "code": "<syslog_client_handler>"
202 }
203 }]
204 }),
205 json!({
207 "type": "open_client",
208 "remote_addr": "localhost:514",
209 "base_stack": "syslog",
210 "event_handlers": [
211 {
212 "event_pattern": "syslog_connected",
213 "handler": {
214 "type": "static",
215 "actions": [{
216 "type": "send_syslog_message",
217 "facility": "user",
218 "severity": "info",
219 "message": "NetGet client connected",
220 "hostname": "netget-host",
221 "app_name": "netget"
222 }]
223 }
224 }
225 ]
226 }),
227 )
228 }
229}
230
231impl Client for SyslogClientProtocol {
233 fn connect(
234 &self,
235 ctx: ConnectContext,
236 ) -> std::pin::Pin<
237 Box<dyn std::future::Future<Output = anyhow::Result<std::net::SocketAddr>> + Send>,
238 > {
239 Box::pin(async move {
240 use crate::client::syslog::SyslogClient;
241 SyslogClient::connect_with_llm_actions(
242 ctx.remote_addr,
243 ctx.llm_client,
244 ctx.state,
245 ctx.status_tx,
246 ctx.client_id,
247 ctx.startup_params,
248 )
249 .await
250 })
251 }
252 fn execute_action(&self, action: serde_json::Value) -> Result<ClientActionResult> {
253 let action_type = action
254 .get("type")
255 .and_then(|v| v.as_str())
256 .context("Missing 'type' field in action")?;
257
258 match action_type {
259 "send_syslog_message" => {
260 let facility = action
261 .get("facility")
262 .and_then(|v| v.as_str())
263 .context("Missing 'facility' field")?;
264
265 let severity = action
266 .get("severity")
267 .and_then(|v| v.as_str())
268 .context("Missing 'severity' field")?;
269
270 let message = action
271 .get("message")
272 .and_then(|v| v.as_str())
273 .context("Missing 'message' field")?;
274
275 let hostname = action
276 .get("hostname")
277 .and_then(|v| v.as_str())
278 .unwrap_or("netget");
279
280 let app_name = action
281 .get("app_name")
282 .and_then(|v| v.as_str())
283 .unwrap_or("netget");
284
285 let proc_id = action
286 .get("proc_id")
287 .and_then(|v| v.as_str())
288 .unwrap_or("-");
289
290 let msg_id = action.get("msg_id").and_then(|v| v.as_str()).unwrap_or("-");
291
292 Ok(ClientActionResult::Custom {
293 name: "send_syslog_message".to_string(),
294 data: json!({
295 "facility": facility,
296 "severity": severity,
297 "message": message,
298 "hostname": hostname,
299 "app_name": app_name,
300 "proc_id": proc_id,
301 "msg_id": msg_id,
302 }),
303 })
304 }
305 "disconnect" => Ok(ClientActionResult::Disconnect),
306 _ => Err(anyhow::anyhow!(
307 "Unknown syslog client action: {}",
308 action_type
309 )),
310 }
311 }
312}