Skip to main content

netget/client/syslog/
actions.rs

1//! Syslog client protocol actions implementation
2
3use 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
15/// Syslog client connected event
16pub 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
43/// Syslog client protocol action handler
44pub struct SyslogClientProtocol;
45
46impl SyslogClientProtocol {
47    pub fn new() -> Self {
48        Self
49    }
50}
51
52// Implement Protocol trait (common functionality)
53impl 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            // LLM mode: LLM controls syslog messages
185            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            // Script mode: Code-based log generation
192            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            // Static mode: Fixed log messages
206            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
231// Implement Client trait (client-specific functionality)
232impl 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}