Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
EventLogComponent.cs
1using System;
2using System.Collections.Generic;
3using System.Text;
4using System.Threading.Tasks;
5using System.Xml;
7using Waher.Events;
14
16{
22 {
26 public const string NamespaceEventLog = "urn:xmpp:eventlog";
27
31 public const string NamespaceSearch = "jabber:iq:search";
32
36 public const string NamespaceDataForm = "jabber:x:data";
37
46 : base(Server, Subdomain, Name)
47 {
48 this.RegisterMessageHandler("log", NamespaceEventLog, this.LogHandler, true);
49
50 this.RegisterIqGetHandler("query", NamespaceSearch, this.SearchQueryHandler, true);
51 this.RegisterIqSetHandler("query", NamespaceSearch, this.SearchHandler, false);
52 }
53
57 public override void Dispose()
58 {
59 this.UnregisterMessageHandler("log", NamespaceEventLog, this.LogHandler, true);
60
61 this.UnregisterIqGetHandler("query", NamespaceSearch, this.SearchQueryHandler, true);
62 this.UnregisterIqSetHandler("query", NamespaceSearch, this.SearchHandler, false);
63 }
64
69 public override bool SupportsAccounts => false;
70
71 private async Task LogHandler(object Sender, MessageEventArgs e)
72 {
73 CaseInsensitiveString FromBareJid = e.From.BareJid;
74 int i = FromBareJid.IndexOf('@');
75 if (i < 0)
76 return; // Ignore log events from non-client senders.
77
78 CaseInsensitiveString FromDomain = FromBareJid.Substring(i + 1);
79 if (!this.Server.IsServerDomain(FromDomain, true))
80 return; // Ignore log events from clients not connected to the current server.
81
82 CaseInsensitiveString FromUserName = FromBareJid.Substring(0, i);
83
84 IAccount Account = await XmppServerModule.GetAccountAsync(FromUserName);
85 if (Account is null)
86 return; // Ignore from senders that are not accounts.
87
88 XmlElement E = e.Content;
89 XmlElement E2;
90 List<KeyValuePair<string, object>> Tags = new List<KeyValuePair<string, object>>();
91 DateTime Timestamp = XML.Attribute(E, "timestamp", DateTime.MinValue);
92 string EventId = XML.Attribute(E, "id");
93 EventType Type = XML.Attribute(E, "type", EventType.Informational);
94 EventLevel Level = XML.Attribute(E, "level", EventLevel.Minor);
95 string Object = XML.Attribute(E, "object");
96 string Actor = XML.Attribute(E, "subject");
97 string Facility = XML.Attribute(E, "facility");
98 string Module = XML.Attribute(E, "module");
99 string Message = string.Empty;
100 string StackTrace = string.Empty;
101
102 foreach (XmlNode N in E.ChildNodes)
103 {
104 switch (N.LocalName)
105 {
106 case "message":
107 Message = N.InnerText;
108 break;
109
110 case "tag":
111 E2 = (XmlElement)N;
112 string TagName = XML.Attribute(E2, "name");
113 string TagValue = XML.Attribute(E2, "value");
114 string TagType = XML.Attribute(E2, "type");
115
116 if (!XmppEventReceptor.TryParse(TagValue, TagType, out object TagValueParsed))
117 TagValueParsed = TagValue;
118
119 Tags.Add(new KeyValuePair<string, object>(TagName, TagValueParsed));
120 break;
121
122 case "stackTrace":
123 StackTrace = N.InnerText;
124 break;
125 }
126 }
127
128 if (string.IsNullOrEmpty(Facility))
129 Facility = FromBareJid;
130
131 Event Event = new Event(Timestamp, Type, Message, Object, Actor, EventId, Level, Facility, Module, StackTrace, Tags.ToArray());
132
133 Log.Event(Event);
134
135 RegistryAccount RegistryAccount = await Provisioning.ProvisioningComponent.GetAccount(FromBareJid);
137 }
138
139 private async Task SearchQueryHandler(object Sender, IqEventArgs e)
140 {
141 if (!await XmppServerModule.IsAdmin(e.From.Address))
142 {
143 await e.IqErrorForbidden(e.To, "Access denied. Not sufficient privileges to perform search.", "en");
144 return;
145 }
146
147 StringBuilder Xml = new StringBuilder();
148 string Instructions = "Limit the search on events by entering values in one or more fields below. Use asterisks (*) for wildcard searches.";
149
150 Xml.Append("<query xmlns='");
151 Xml.Append(NamespaceSearch);
152 Xml.Append("'><instructions>");
153 Xml.Append(Instructions);
154 Xml.Append("</instructions>");
155 Xml.Append("<x xmlns='");
156 Xml.Append(NamespaceDataForm);
157 Xml.Append("' xmlns:xdv='http://jabber.org/protocol/xdata-validate' type='form'><title>Event Log Search</title><instructions>");
158 Xml.Append(Instructions);
159 Xml.Append("</instructions><field type='hidden' var='FORM_TYPE'><value>");
160 Xml.Append(NamespaceSearch);
161 Xml.Append("</value></field>");
162
163 Xml.Append("<field type='text-single' label ='From:' var='From'>");
164 Xml.Append("<desc>Search for events newer than this.</desc>");
165 Xml.Append("<required/>");
166 Xml.Append("<xdv:validate datatype='xs:dateTime'/>");
167 Xml.Append("<value>");
168 Xml.Append(XML.Encode(DateTime.Now.AddHours(-1)));
169 Xml.Append("</value>");
170 Xml.Append("</field>");
171
172 Xml.Append("<field type='text-single' label ='To:' var='To'>");
173 Xml.Append("<desc>Search for events older than this.</desc>");
174 Xml.Append("<required/>");
175 Xml.Append("<xdv:validate datatype='xs:dateTime'/>");
176 Xml.Append("<value>");
177 Xml.Append(XML.Encode(DateTime.Now));
178 Xml.Append("</value>");
179 Xml.Append("</field>");
180
181 Xml.Append("<field type='list-single' label ='Type:' var='Type'>");
182 Xml.Append("<desc>Choose event types.</desc>");
183 Xml.Append("<value>All</value>");
184 Xml.Append("<option label='All'><value>All</value></option>");
185
186 foreach (string Name in Enum.GetNames(typeof(EventType)))
187 {
188 Xml.Append("<option label='");
189 Xml.Append(XML.Encode(Name));
190 Xml.Append("'><value>");
191 Xml.Append(XML.Encode(Name));
192 Xml.Append("</value></option>");
193 }
194
195 Xml.Append("</field>");
196
197 Xml.Append("<field type='list-single' label ='Level:' var='Level'>");
198 Xml.Append("<desc>Choose event levels.</desc>");
199 Xml.Append("<value>All</value>");
200 Xml.Append("<option label='All'><value>All</value></option>");
201
202 foreach (string Name in Enum.GetNames(typeof(EventLevel)))
203 {
204 Xml.Append("<option label='");
205 Xml.Append(XML.Encode(Name));
206 Xml.Append("'><value>");
207 Xml.Append(XML.Encode(Name));
208 Xml.Append("</value></option>");
209 }
210
211 Xml.Append("</field>");
212
213 Xml.Append("<field type='text-single' label ='Message:' var='Message'>");
214 Xml.Append("<desc>Search for events containing a specific message text.</desc>");
215 Xml.Append("<xdv:validate datatype='xs:string'/>");
216 Xml.Append("<value/>");
217 Xml.Append("</field>");
218
219 Xml.Append("<field type='text-single' label ='Object:' var='Object'>");
220 Xml.Append("<desc>Search for events relating to a specific object.</desc>");
221 Xml.Append("<xdv:validate datatype='xs:string'/>");
222 Xml.Append("<value/>");
223 Xml.Append("</field>");
224
225 Xml.Append("<field type='text-single' label ='Actor:' var='Actor'>");
226 Xml.Append("<desc>Search for events relating to a specific actor.</desc>");
227 Xml.Append("<xdv:validate datatype='xs:string'/>");
228 Xml.Append("<value/>");
229 Xml.Append("</field>");
230
231 Xml.Append("<field type='text-single' label ='Event ID:' var='EventId'>");
232 Xml.Append("<desc>Search for events having a specific event identity.</desc>");
233 Xml.Append("<xdv:validate datatype='xs:string'/>");
234 Xml.Append("<value/>");
235 Xml.Append("</field>");
236
237 Xml.Append("<field type='text-single' label ='Facility:' var='Facility'>");
238 Xml.Append("<desc>Search for events coming from a specific facility.</desc>");
239 Xml.Append("<xdv:validate datatype='xs:string'/>");
240 Xml.Append("<value/>");
241 Xml.Append("</field>");
242
243 Xml.Append("<field type='text-single' label ='Module:' var='Module'>");
244 Xml.Append("<desc>Search for events originating in a specific module.</desc>");
245 Xml.Append("<xdv:validate datatype='xs:string'/>");
246 Xml.Append("<value/>");
247 Xml.Append("</field>");
248
249 Xml.Append("<field type='text-single' label ='Stack Trace:' var='StackTrace'>");
250 Xml.Append("<desc>Search for events containing a specific stack trace.</desc>");
251 Xml.Append("<xdv:validate datatype='xs:string'/>");
252 Xml.Append("<value/>");
253 Xml.Append("</field>");
254
255 Xml.Append("</x></query>");
256
257 await e.IqResult(Xml.ToString(), e.To);
258 }
259
260 private async Task SearchHandler(object Sender, IqEventArgs e)
261 {
262 if (!await XmppServerModule.IsAdmin(e.From.Address))
263 {
264 await e.IqErrorForbidden(e.To, "Access denied. Not sufficient privileges to perform search.", "en");
265 return;
266 }
267
268 List<Filter> Filters = new List<Filter>();
269
270 foreach (XmlNode N in e.Query.ChildNodes)
271 {
272 if (N is XmlElement E && E.LocalName == "x" && E.NamespaceURI == NamespaceDataForm)
273 {
274 foreach (XmlNode N2 in E.ChildNodes)
275 {
276 if (N2 is XmlElement E2 && E2.LocalName == "field")
277 {
278 XmlElement Value = E2["value"];
279 if (Value is null)
280 continue;
281
282 string Var = XML.Attribute(E2, "var");
283
284 switch (Var)
285 {
286 case "From":
287 if (!XML.TryParse(Value.InnerText, out DateTime From))
288 {
289 await e.IqErrorBadRequest(e.To, "Invalid From value.", "en");
290 return;
291 }
292
293 Filters.Add(new FilterFieldGreaterOrEqualTo("Timestamp", From));
294 break;
295
296 case "To":
297 if (!XML.TryParse(Value.InnerText, out DateTime To))
298 {
299 await e.IqErrorBadRequest(e.To, "Invalid To value.", "en");
300 return;
301 }
302
303 Filters.Add(new FilterFieldLesserOrEqualTo("Timestamp", To));
304 break;
305
306 case "Type":
307 string s = E2.InnerText;
308 if (s != "All")
309 {
310 if (!Enum.TryParse<EventType>(s, out EventType Type))
311 {
312 await e.IqErrorBadRequest(e.To, "Invalid Type value.", "en");
313 return;
314 }
315
316 Filters.Add(new FilterFieldEqualTo("Type", Type));
317 }
318 break;
319
320 case "Level":
321 s = E2.InnerText;
322 if (s != "All")
323 {
324 if (!Enum.TryParse<EventLevel>(s, out EventLevel Level))
325 {
326 await e.IqErrorBadRequest(e.To, "Invalid Level value.", "en");
327 return;
328 }
329
330 Filters.Add(new FilterFieldEqualTo("Level", Level));
331 }
332 break;
333
334 case "Message":
335 case "Object":
336 case "Actor":
337 case "EventId":
338 case "Facility":
339 case "Module":
340 case "StackTrace":
341 s = E2.InnerText;
342 if (!string.IsNullOrEmpty(s))
343 {
344 if (s.IndexOf('*') >= 0)
345 Filters.Add(new FilterFieldLikeRegEx(Var, Database.WildcardToRegex(s, "*")));
346 else
347 Filters.Add(new FilterFieldEqualTo(Var, s));
348 }
349 break;
350 }
351 }
352 }
353 }
354 }
355
357
358 switch (Filters.Count)
359 {
360 case 0:
361 await e.IqErrorBadRequest(e.To, "Invalid search request.", "en");
362 return;
363
364 case 1:
365 Filter = Filters[0];
366 break;
367
368 default:
369 Filter = new FilterAnd(Filters.ToArray());
370 break;
371 }
372
373 StringBuilder Xml = new StringBuilder();
374 Dictionary<string, string> Headers = new Dictionary<string, string>();
375 List<KeyValuePair<string, string>> HeadersOrdered = new List<KeyValuePair<string, string>>()
376 {
377 new KeyValuePair<string, string>("Timestamp", "Timestamp"),
378 new KeyValuePair<string, string>("Type", "Type"),
379 new KeyValuePair<string, string>("Level", "Level"),
380 new KeyValuePair<string, string>("Message", "Message"),
381 new KeyValuePair<string, string>("Object", "Object"),
382 new KeyValuePair<string, string>("Actor", "Actor"),
383 new KeyValuePair<string, string>("EventId", "Event ID"),
384 new KeyValuePair<string, string>("Facility", "Facility"),
385 new KeyValuePair<string, string>("Module", "Module"),
386 new KeyValuePair<string, string>("StackTrace", "Stack Trace")
387 };
388
389 foreach (KeyValuePair<string, string> P in HeadersOrdered)
390 Headers[P.Key] = P.Value;
391
392 foreach (PersistedEvent Event in await Database.Find<PersistedEvent>(Filter, "Timestamp"))
393 {
394 Xml.Append("<item>");
395
396 Xml.Append("<field var='Timestamp'><value>");
397 Xml.Append(XML.Encode(Event.Timestamp));
398 Xml.Append("</value></field>");
399
400 Xml.Append("<field var='Type'><value>");
401 Xml.Append(Event.Type.ToString());
402 Xml.Append("</value></field>");
403
404 Xml.Append("<field var='Level'><value>");
405 Xml.Append(Event.Level.ToString());
406 Xml.Append("</value></field>");
407
408 Xml.Append("<field var='Message'><value>");
409
410 if (!(Event.Message is null))
411 Xml.Append(XML.Encode(Event.Message));
412
413 Xml.Append("</value></field>");
414
415 Xml.Append("<field var='Object'><value>");
416
417 if (!(Event.Object is null))
418 Xml.Append(XML.Encode(Event.Object));
419
420 Xml.Append("</value></field>");
421
422 Xml.Append("<field var='Actor'><value>");
423
424 if (!(Event.Actor is null))
425 Xml.Append(XML.Encode(Event.Actor));
426
427 Xml.Append("</value></field>");
428
429 Xml.Append("<field var='EventId'><value>");
430
431 if (!(Event.EventId is null))
432 Xml.Append(XML.Encode(Event.EventId));
433
434 Xml.Append("</value></field>");
435
436 Xml.Append("<field var='Facility'><value>");
437
438 if (!(Event.Facility is null))
439 Xml.Append(XML.Encode(Event.Facility));
440
441 Xml.Append("</value></field>");
442
443 Xml.Append("<field var='Module'><value>");
444
445 if (!(Event.Module is null))
446 Xml.Append(XML.Encode(Event.Module));
447
448 Xml.Append("</value></field>");
449 Xml.Append("<field var='StackTrace'><value>");
450
451 if (!(Event.StackTrace is null))
452 Xml.Append(XML.Encode(Event.StackTrace));
453
454 Xml.Append("</value></field>");
455
456 if (!(Event.Tags is null))
457 {
458 foreach (PersistedTag Tag in Event.Tags)
459 {
460 if (!(Tag.Name is null))
461 {
462 if (!Headers.ContainsKey(Tag.Name))
463 {
464 Headers[Tag.Name] = Tag.Name;
465 HeadersOrdered.Add(new KeyValuePair<string, string>(Tag.Name, Tag.Name));
466 }
467 }
468
469 Xml.Append("<field var='");
470
471 if (!(Tag.Name is null))
472 Xml.Append(XML.Encode(Tag.Name));
473
474 Xml.Append("'><value>");
475
476 if (!(Tag.Value is null))
477 Xml.Append(XML.Encode(Tag.Value.ToString()));
478
479 Xml.Append("</value></field>");
480 }
481 }
482
483 Xml.Append("</item>");
484 }
485
486 StringBuilder Xml2 = new StringBuilder();
487
488 Xml2.Append("<query xmlns='");
489 Xml2.Append(NamespaceSearch);
490 Xml2.Append("'><x xmlns='");
491 Xml2.Append(NamespaceDataForm);
492 Xml2.Append("' type='result'><field type='hidden' var='FORM_TYPE'><value>");
493 Xml2.Append(NamespaceSearch);
494 Xml2.Append("</value></field><reported>");
495
496 foreach (KeyValuePair<string, string> Header in HeadersOrdered)
497 {
498 Xml2.Append("<field var='");
499 Xml2.Append(XML.Encode(Header.Key));
500 Xml2.Append("' label='");
501 Xml2.Append(XML.Encode(Header.Value));
502 Xml2.Append("' type='text-single'/>");
503 }
504
505 Xml2.Append("</reported>");
506 Xml2.Append(Xml.ToString());
507 Xml2.Append("</x></query>");
508
509 await e.IqResult(Xml2.ToString(), e.To);
510 }
511
512 }
513}
Helps with common XML-related tasks.
Definition: XML.cs:19
static string Attribute(XmlElement E, string Name)
Gets the value of an XML attribute.
Definition: XML.cs:914
static string Encode(string s)
Encodes a string for use in XML.
Definition: XML.cs:27
static bool TryParse(string s, out DateTime Value)
Tries to decode a string encoded DateTime.
Definition: XML.cs:744
Class representing an event.
Definition: Event.cs:10
string Message
Free-text event message.
Definition: Event.cs:131
EventType Type
Type of event.
Definition: Event.cs:121
string Object
Object related to the event.
Definition: Event.cs:136
EventLevel Level
Event Level.
Definition: Event.cs:126
string Actor
Actor responsible for the action causing the event.
Definition: Event.cs:141
string Module
Module where the event is reported.
Definition: Event.cs:156
DateTime Timestamp
Timestamp of event.
Definition: Event.cs:116
KeyValuePair< string, object >[] Tags
Variable set of tags providing event-specific information.
Definition: Event.cs:166
string EventId
Computer-readable Event ID identifying type of even.
Definition: Event.cs:146
string Facility
Facility can be either a facility in the network sense or in the system sense.
Definition: Event.cs:151
string StackTrace
Stack Trace of event.
Definition: Event.cs:161
Static class managing the application event log. Applications and services log events on this static ...
Definition: Log.cs:13
static async void Event(Event Event)
Logs an event. It will be distributed to registered event sinks.
Definition: Log.cs:128
Class representing a persisted event.
Class representing a persisted tag.
Definition: PersistedTag.cs:10
This class handles incoming events from the XMPP network. The default behaviour is to log incoming ev...
static bool TryParse(string Value, string Type, out object ParsedValue)
Tries to parse a simple value.
Base class for components.
Definition: Component.cs:16
bool UnregisterMessageHandler(string LocalName, string Namespace, EventHandlerAsync< MessageEventArgs > Handler, bool RemoveNamespaceAsFeature)
Unregisters a message handler.
Definition: Component.cs:297
void RegisterIqSetHandler(string LocalName, string Namespace, EventHandlerAsync< IqEventArgs > Handler, bool PublishNamespaceAsFeature)
Registers an IQ-Set handler.
Definition: Component.cs:161
CaseInsensitiveString Subdomain
Subdomain name.
Definition: Component.cs:76
void RegisterIqGetHandler(string LocalName, string Namespace, EventHandlerAsync< IqEventArgs > Handler, bool PublishNamespaceAsFeature)
Registers an IQ-Get handler.
Definition: Component.cs:149
async Task< bool > Message(string Type, string Id, XmppAddress To, XmppAddress From, string Language, Stanza Stanza, ISender Sender)
Message stanza.
Definition: Component.cs:481
XmppServer Server
XMPP Server.
Definition: Component.cs:96
bool UnregisterIqGetHandler(string LocalName, string Namespace, EventHandlerAsync< IqEventArgs > Handler, bool RemoveNamespaceAsFeature)
Unregisters an IQ-Get handler.
Definition: Component.cs:249
bool UnregisterIqSetHandler(string LocalName, string Namespace, EventHandlerAsync< IqEventArgs > Handler, bool RemoveNamespaceAsFeature)
Unregisters an IQ-Set handler.
Definition: Component.cs:262
void RegisterMessageHandler(string LocalName, string Namespace, EventHandlerAsync< MessageEventArgs > Handler, bool PublishNamespaceAsFeature)
Registers a message handler.
Definition: Component.cs:190
Event arguments for IQ queries.
Definition: IqEventArgs.cs:12
XmppAddress From
From address attribute
Definition: IqEventArgs.cs:93
Task IqResult(string Xml, string From)
Returns a response to the current request.
Definition: IqEventArgs.cs:113
XmlElement Query
Query element, if found, null otherwise.
Definition: IqEventArgs.cs:70
XmppAddress To
To address attribute
Definition: IqEventArgs.cs:88
Task IqErrorBadRequest(XmppAddress From, string ErrorText, string Language)
Returns a bad-request error.
Definition: IqEventArgs.cs:159
Task IqErrorForbidden(XmppAddress From, string ErrorText, string Language)
Returns a forbidden error.
Definition: IqEventArgs.cs:229
XmppAddress From
From address attribute
XmlElement Content
Content element, if found, null otherwise.
CaseInsensitiveString Address
XMPP Address
Definition: XmppAddress.cs:37
CaseInsensitiveString BareJid
Bare JID
Definition: XmppAddress.cs:45
bool IsServerDomain(CaseInsensitiveString Domain, bool IncludeAlternativeDomains)
Checks if a domain is the server domain, or optionally, an alternative domain.
Definition: XmppServer.cs:861
Represents a case-insensitive string.
int IndexOf(CaseInsensitiveString value, StringComparison comparisonType)
Reports the zero-based index of the first occurrence of the specified string in the current System....
CaseInsensitiveString Substring(int startIndex, int length)
Retrieves a substring from this instance. The substring starts at a specified character position and ...
Static interface for database persistence. In order to work, a database provider has to be assigned t...
Definition: Database.cs:19
static string WildcardToRegex(string s, string Wildcard)
Converts a wildcard string to a regular expression string.
Definition: Database.cs:1631
static Task< IEnumerable< object > > Find(string Collection, params string[] SortOrder)
Finds objects in a given collection.
Definition: Database.cs:247
This filter selects objects that conform to all child-filters provided.
Definition: FilterAnd.cs:10
This filter selects objects that have a named field equal to a given value.
This filter selects objects that have a named field greater or equal to a given value.
This filter selects objects that have a named field lesser or equal to a given value.
This filter selects objects that have a named field matching a given regular expression.
Base class for all filter classes.
Definition: Filter.cs:15
Event log component, as defined in XEP-0337. https://xmpp.org/extensions/xep-0337....
override void Dispose()
IDisposable.Dispose
EventLogComponent(XmppServer Server, CaseInsensitiveString Subdomain, string Name)
Event log component, as defined in XEP-0337. https://xmpp.org/extensions/xep-0337....
override bool SupportsAccounts
If the component supports accounts (true), or if the subdomain name is the only valid address.
long NrLogMessages
Number of log messages received from this entity.
Service Module hosting the XMPP broker and its components.
Interface for XMPP user accounts.
Definition: IAccount.cs:9
EventLevel
Event level.
Definition: EventLevel.cs:7
EventType
Type of event.
Definition: EventType.cs:7