freeswitch_log_parser/message/
classify.rs1use crate::codec::CodecMedia;
5
6use super::dtmf::parse_dtmf;
7use super::kind::MessageKind;
8use super::lifecycle::{classify_channel_prefixed, detect_channel_lifecycle};
9use super::media::{detect_media, detect_sdp_direction};
10use super::parts::{
11 dialplan_parts, execute_parts, parse_bracketed_value, set_export_parts, strip_channel_prefix,
12};
13
14fn parse_execute(msg: &str) -> MessageKind {
15 let parts = execute_parts(msg);
16 MessageKind::Execute {
17 depth: parts.depth,
18 channel: parts.channel.to_string(),
19 application: parts.application.to_string(),
20 arguments: parts.arguments.to_string(),
21 }
22}
23fn parse_dialplan(msg: &str) -> MessageKind {
24 let (channel, detail) = dialplan_parts(msg);
25 MessageKind::Dialplan {
26 channel: channel.to_string(),
27 detail: detail.to_string(),
28 }
29}
30pub fn classify_message(msg: &str) -> MessageKind {
35 if msg.starts_with("EXECUTE ") || msg.starts_with("Execute ") {
36 return parse_execute(msg);
37 }
38
39 if msg.starts_with("RECV DTMF ")
40 || msg.starts_with("RTP RECV DTMF ")
41 || msg.starts_with("INFO DTMF(")
42 {
43 if let Some(dtmf) = parse_dtmf(msg) {
44 return dtmf;
45 }
46 }
47
48 if msg.starts_with("Dialplan: ") || msg.starts_with("Chatplan: ") {
49 return parse_dialplan(msg);
50 }
51
52 if msg.starts_with("Processing ")
53 && (msg.contains(" in context ") || msg.contains("recursive conditions"))
54 {
55 return parse_dialplan_processing(msg);
56 }
57
58 if msg.contains("CHANNEL_DATA") {
59 return MessageKind::ChannelData;
60 }
61
62 if msg.starts_with("variable_") {
63 if let Some((name, value)) = parse_bracketed_value(msg, 0) {
64 return MessageKind::Variable {
65 name: name.to_string(),
66 value: value.to_string(),
67 };
68 }
69 }
70
71 if let Some(direction) = detect_sdp_direction(msg) {
72 return MessageKind::SdpMarker { direction };
73 }
74
75 if msg.contains("State Change") || msg.contains("Callstate Change") {
76 return MessageKind::StateChange {
77 detail: msg.to_string(),
78 };
79 }
80
81 if msg.starts_with("SET ")
83 || msg.starts_with("EXPORT ")
84 || msg.starts_with("PUSH ")
85 || msg.starts_with("UNSHIFT ")
86 {
87 if let Some(sv) = parse_set_or_export(msg) {
88 return sv;
89 }
90 }
91
92 if msg.starts_with("Audio Codec Compare ") {
93 return MessageKind::CodecNegotiation {
94 media: CodecMedia::Audio,
95 };
96 }
97
98 if msg.starts_with("Video Codec Compare ") {
99 return MessageKind::CodecNegotiation {
100 media: CodecMedia::Video,
101 };
102 }
103
104 if msg.starts_with("CoreSession::setVariable(") {
105 return parse_core_session_set_variable(msg);
106 }
107
108 if msg.starts_with("UNSET ") {
109 return parse_unset(msg);
110 }
111
112 if let Some(rest) = msg.strip_prefix("set variable ") {
114 if let Some((name, value)) = rest.split_once('=') {
115 return MessageKind::Variable {
116 name: format!("variable_{name}"),
117 value: value.to_string(),
118 };
119 }
120 }
121
122 if msg.starts_with("Transfer ") {
123 return MessageKind::Dialplan {
124 channel: String::new(),
125 detail: msg.to_string(),
126 };
127 }
128
129 if msg.starts_with('(') {
131 if msg.contains(") State ") {
132 return MessageKind::StateChange {
133 detail: msg.to_string(),
134 };
135 }
136 return MessageKind::ChannelLifecycle {
137 detail: msg.to_string(),
138 };
139 }
140
141 if msg.starts_with("SOFIA ") {
143 return MessageKind::StateChange {
144 detail: msg.to_string(),
145 };
146 }
147
148 if msg.starts_with("checking condition") || msg.starts_with("action(") {
150 return MessageKind::ChannelLifecycle {
151 detail: msg.to_string(),
152 };
153 }
154
155 if msg.starts_with("Event Socket Command") {
156 return MessageKind::EventSocket {
157 detail: msg.to_string(),
158 };
159 }
160
161 if let Some(kind) = detect_media(msg) {
163 return kind;
164 }
165
166 if let Some(kind) = detect_channel_lifecycle(msg) {
168 return kind;
169 }
170
171 if let Some((channel_part, rest)) = strip_channel_prefix(msg) {
173 return classify_channel_prefixed(channel_part, rest);
174 }
175
176 if let Some((name, value)) = parse_bracketed_value(msg, 0) {
179 let name_bytes = name.as_bytes();
180 if !name_bytes.is_empty()
181 && !name.contains(' ')
182 && name_bytes[0].is_ascii_alphabetic()
183 && (name.contains('-') || name.starts_with("Channel-"))
184 {
185 return MessageKind::ChannelField {
186 name: name.to_string(),
187 value: value.to_string(),
188 };
189 }
190 }
191
192 MessageKind::General
193}
194fn parse_core_session_set_variable(msg: &str) -> MessageKind {
195 let rest = &msg["CoreSession::setVariable(".len()..];
196 if let Some(end) = rest.strip_suffix(')') {
197 if let Some(comma) = end.find(", ") {
198 return MessageKind::Variable {
199 name: format!("variable_{}", &end[..comma]),
200 value: end[comma + 2..].to_string(),
201 };
202 }
203 }
204 MessageKind::Variable {
205 name: String::new(),
206 value: msg.to_string(),
207 }
208}
209
210fn parse_unset(msg: &str) -> MessageKind {
211 let rest = &msg["UNSET ".len()..];
212 let name = if let Some(inner) = rest.strip_prefix('[') {
213 inner.strip_suffix(']').unwrap_or(inner)
214 } else {
215 rest
216 };
217 MessageKind::Variable {
218 name: format!("variable_{name}"),
219 value: String::new(),
220 }
221}
222
223fn parse_dialplan_processing(msg: &str) -> MessageKind {
224 let rest = &msg["Processing ".len()..];
225 MessageKind::Dialplan {
226 channel: String::new(),
227 detail: rest.to_string(),
228 }
229}
230fn parse_set_or_export(msg: &str) -> Option<MessageKind> {
231 let parts = set_export_parts(msg)?;
232 Some(MessageKind::Variable {
233 name: format!("variable_{}", parts.name),
234 value: parts.value.to_string(),
235 })
236}