Il log di Enterprise Library - parte 3 - personalizzazione
Scritto da
Massimo Bonanni
il
mercoledì 10 agosto 2011
Linguaggio:
•
Framework:
•
Livello: 100
Download sorgenti
Download pdf
In questo articolo della serie sull'Application Block di Logging
di Enterprise Library ci occuperemo di come sia possibile
customizzare lo stesso per adattarlo alle nostre esigenze.
Il modulo di logging è stato pensato, infatti, per essere
facilmente esteso, in particolare vedremo come realizzare le nostre
versioni del trace listener, del log formatter e del log
filter.
Trace Listener
Il trace listener è il componente del log che si
occupa di scrivere effettivamente i dati che lo sviluppatore ha
deciso di loggare.
Enterprise Library mette a disposizione una serie abbastanza
completa di Listener "di serie":
- EmailTraceListener: log su email ;
- FlatFileTraceListener,
RollingFlatFileTraceListener: log su file di testo;
- MsmqTraceListener : log su windows message
queue;
- WmiTraceListener : logging tramite eventi
WMI;
- XmlTraceListener: log su file XML;
ma permette di utilizzare anche:
- EventLogTraceListener: log su registro eventi
(presente in System.Diagnostic).
Se tra i precedenti non troviamo il listener che fa per noi,
possiamno sempre crearne uno nostro (custom) derivando la classe da
CustomTraceListener.
CustomTraceListener altro non è che una classe derivata
direttamente da TraceListener di System.Diagnostic e che ci obbliga
ad implementare i seguenti metodi:
- Write : viene richiamato quando si ha la
necessità di scrivere una stringa nel listener;
- WriteLine : viene richiamato quando si ha la
necessità di scrivere una stringa seguita da un "ritorno a
capo";
Anche se, per creare un listener friubile, sono sufficienti
questi due metodi, è consigliabile scrivere l'override dei due
overload del metodo TraceData.
I metodi Write e WriteLine vengono, infatti, richiamati da
Enterprise Library con il semplice messaggio inserito nella
LogEntry inviata al log, mentre nel TraceData abbiamo l'intera
istanza di LogEntry.
Per fare un esempio supponiamo di voler realizzare un TraceListener
che invii il log sotto forma di SMS e supponiamo di avere a
disposizione un servizio (non ci interessa come sia fatto) che
invii un testo ad un numero.
Imports Microsoft.Practices.EnterpriseLibrary.Logging
Imports Microsoft.Practices.EnterpriseLibrary.Common.Configuration
Imports Microsoft.Practices.EnterpriseLibrary.Logging.Configuration
Imports Microsoft.Practices.EnterpriseLibrary.Logging.TraceListeners
<ConfigurationElementType(GetType(CustomTraceListenerData))> _
Public Class SmsTraceListener
Inherits CustomTraceListener
Private _SmsService As ISmsService
Public Sub New()
_SmsService = CreateSmsService()
End Sub
Public Overloads Overrides Sub Write(message As String)
End Sub
Public Overloads Overrides Sub WriteLine(message As String)
End Sub
Public Overrides Sub TraceData(eventCache As System.Diagnostics.TraceEventCache, source As String, eventType As System.Diagnostics.TraceEventType, id As Integer, data As Object)
Dim logEntry = CType(data, LogEntry)
Dim smsMessage = GetSmsMessage(logEntry)
Dim number = Me.Attributes("Number")
_SmsService.Send(number, smsMessage)
End Sub
Private Function CreateSmsService() As ISmsService
Return New SmsService()
End Function
Private Function GetSmsMessage(logEntry As LogEntry) As String
Dim smsMessage = String.Format("{0:dd/MM/yyyy HH:mm:ss} - {1}", logEntry.TimeStamp, logEntry.Message)
If smsMessage.Length > 160 Then
smsMessage = String.Concat(smsMessage.Substring(0, 157), "...")
End If
Return smsMessage
End Function
End Class
Osserviamo che:
- recuperiamo il log entry inviato al modulo di log andando ad
effettuare il cast dell'argomento Data del metodo TraceData;
- utilizziamo la collezione Attributes per contenere il numero a
cui inviare il messaggio. Questa collezione, come vedremo tra un
pò, è definibile con valori presi dal file di configurazione e la
cosa ci fa particolarmente comodo;
- la nostra classe è decorata con l'attributo
ConfigurationElementType che è necessario per far si che il nostro
listener sia in grado di essere gestito dalla console di
configurazione di Enterprise Library. In questo esempio abbiamo
deciso di utilizzare la sezione di configurazione
CustomTraceListenerData che è la sezione di configurazione del
listener custom fornita di serie da Enterprise Library.
Una volta creato il listener è opportuno configurare la
nostra applicazione affinchè sia in grado di utilizzarlo.
Per fare questo apriamo la console di configurazione cliccando sul
file di configurazione della nostra applicazione, premendo il tasto
destro del mouse e scegliendo l'opzione "Edit Configuration File"
contraddistinta dall'iconetta di Enterprise Library.
Per inserire la configurazione del nostro trace listener è
sufficiente utilizzare il comando (identificato dal simbolo "+")
presente nella colonna dei trace listener:

Selezionando l'opzione "Add Custom Trace Listener" verrà aperta
la finestra per la selezione della classe da importare come trace
listener:

Come possiamo notare dalla precedente immagine, abbiamo la
possibilità di scegliere uno dei tipi presenti all'interno del
nostro progetto, presente in un assembly della GAC oppure caricare
un assembly che lo contiene.
Una volta selezionato la classe, l'interfaccia di configurazione
presenta il pannello di configurazione:

In questo troviamo:
- il nome che vogliamo dare all'istanza del listener (possiamo
avere anche più istanze dello stesso listener identificate da
diversi nomi);
- gli eventuali attributi che verranno passati al listener nella
collezione Attributes (nel nostro caso "Number");
- l'eventuale formatter da applicare al listener che possiamo
utilizzare, all'interno dello stesso, tramite la proprietà
Formatter della classe TraceListener;
- il filtro della severity (il cui funzionamento è analogo a
quello dei listener di serie);
- le opzioni di output per aggiungere uteriori dati al log.
Una volta inserito il trace listener all'interno della
configurazione, il suo utilizzo è del tutto analogo ad un qualsiasi
altro listener.
Log Entry Formatter
Nel precedente paragrafo abbiamo visto come realizzare un trace
listener custom in cui abbiamo gestito, anche, la formattazione del
messaggio da inviare (metodo GetSmsMessage). Nella realtà dei
fatti, non è compito del listener occuparsi di formattare l'output
del log, ma l'operazione è demandata ad un formatter.
Il formatter è una classe che implementa l'interfaccia
ILogFormatter e che, grazie al metodo Format (implementazione
dell'interfaccia) è in grado di prendere una LogEntry e restituire
una stringa formattata pronta per essere scritta nel log.
Nel modulo di log dell'Enterprise Library troviamo due formatter
quali il TextFormatter che permette di formattare la LogEntry in
base ad una stringa di formato definita in configurazione e
BinaryLogFormatter che consente di formattare la LogEntry in un
formato binario pensato per poter essere trasmesso verso altri
dispositivi.
Questi formatter sono assolutamente sufficienti per la maggior
parte degli scenari che possono capitarci, ma se abbiamo la
necessità di formattare la LogEntry in maniera particolare,
possiamo creare il nostro formatter custom.
Nell'esempio del listener che invia il log con un messaggio sms,
potremmo avere la necessità di formattare la LogEntry considerando
la data di creazione e il messaggio di testo ma in modo che il
numero di caratteri non superi un numero impostato in
configurazione (ad esempio 160).
Per creare il nostro formatter è sufficiente implementare
l'interfaccia ILogFormatter e il metodo Format:
Imports Microsoft.Practices.EnterpriseLibrary.Logging.Configuration
Imports Microsoft.Practices.EnterpriseLibrary.Common.Configuration
Imports Microsoft.Practices.EnterpriseLibrary.Logging.Formatters
Imports System.Collections.Specialized
<ConfigurationElementType(GetType(CustomFormatterData))>
Public Class SmsLogFormatter
Implements ILogFormatter
Public Sub New(params As NameValueCollection)
If params("SmsLenght") IsNot Nothing Then
Integer.TryParse(params("SmsLenght"), _SmsLength)
End If
End Sub
Private _SmsLength As Integer = 160
Public Function Format(log As Microsoft.Practices.EnterpriseLibrary.Logging.LogEntry) As String Implements Microsoft.Practices.EnterpriseLibrary.Logging.Formatters.ILogFormatter.Format
Return GetSmsMessage(log)
End Function
Private Function GetSmsMessage(logEntry As Microsoft.Practices.EnterpriseLibrary.Logging.LogEntry) As String
Dim smsMessage = String.Format("{0:dd/MM/yyyy HH:mm:ss} - {1}", logEntry.TimeStamp, logEntry.Message)
If smsMessage.Length > _SmsLength Then
smsMessage = String.Concat(smsMessage.Substring(0, _SmsLength - 3), "...")
End If
Return smsMessage
End Function
End Class
Osserviamo che:
- è stato implementato un costruttore che accetta un'istanza di
NameValueCollection in input. Questo è necessario (pena una
eccezione da parte del modulo di log di Enterprise Library) e
permette di ricevere le impostazioni di configurazione;
- la classe è decorata con l'attributo ConfigurationElementType.
Anche in questo caso ha lo scopo di rendere il formatter
configurabile all'interno della console di configurazione di
Enterprise Library.
Per poter utilizzare il formatter procediamo in maniera analoga
a quanto fatto per il listener: è sufficiente utilizzare il comando
(identificato dal simbolo "+") presente nella colonna dei
formatter:

Anche in questo caso, come per il listener, dobbiamo
selezionare il tipo da utilizzare come formatter:


Nel momento in cui associamo il
formatter al listner (come mostrato nella precedente figura),
possiamo modificare il listener stesso per disaccoppiare la
funzionalità di formattazione del messaggio:
Imports Microsoft.Practices.EnterpriseLibrary.Logging
Imports Microsoft.Practices.EnterpriseLibrary.Common.Configuration
Imports Microsoft.Practices.EnterpriseLibrary.Logging.Configuration
Imports Microsoft.Practices.EnterpriseLibrary.Logging.TraceListeners
<ConfigurationElementType(GetType(CustomTraceListenerData))> _
Public Class SmsTraceListener
Inherits CustomTraceListener
Private _SmsService As ISmsService
Public Sub New()
_SmsService = CreateSmsService()
End Sub
Public Overloads Overrides Sub Write(message As String)
End Sub
Public Overloads Overrides Sub WriteLine(message As String)
End Sub
Public Overrides Sub TraceData(eventCache As System.Diagnostics.TraceEventCache, source As String, eventType As System.Diagnostics.TraceEventType, id As Integer, data As Object)
Dim logEntry = CType(data, LogEntry)
If Me.Formatter IsNot Nothing Then
Dim message = Me.Formatter.Format(logEntry)
Dim number = Me.Attributes("Number")
_SmsService.Send(number, message)
End If
End Sub
Private Function CreateSmsService() As ISmsService
Return New SmsService()
End Function
End Class
La proprietà Formatter del listener viene iniettata con il
formatter associato (se presente) e possiamo quindi utilizzare
liberamente il metodo Format senza preoccuparci a runtime di ciò
che c'è dietro.
Log Filter
Un altro componente che possiamo customizzare è il log
filter.
La funzione del log filter è quella di eseguire una scrematura
sulle LogEntry che vengono inviate al modulo di log prima che
queste finiscano in pasto ai listener configurati.
In questo caso, un log filter personalizzato è una classe che
implementa l'interfaccia ILogFilter cioè il metodo Filter e la
proprietà Name.
La proprietà Name serve all'infrastruttura dell'Enterprise Library
per gestire il filtro mentre il metodo Filter ha lo scopo di
decidere se una LogEntry (passata per argomento) può essere inviata
al log o meno.
Nel nostro caso implementiamo un filtro che ci consenta di far
passare le LogEntry solamente in un intervallo di tempo tra due
orari passati in configurazione:
Imports Microsoft.Practices.EnterpriseLibrary.Common.Configuration
Imports Microsoft.Practices.EnterpriseLibrary.Logging.Configuration
Imports Microsoft.Practices.EnterpriseLibrary.Logging.Filters
Imports System.Collections.Specialized
<ConfigurationElementType(GetType(CustomLogFilterData))> _
Public Class TimeLogFilter
Implements ILogFilter
Public Sub New(params As NameValueCollection)
_TimeFrom = GetTimeSpan(params("TimeFrom"))
_TimeTo = GetTimeSpan(params("TimeTo"))
End Sub
Private _TimeFrom As TimeSpan
Private _TimeTo As TimeSpan
Public Function Filter(log As Microsoft.Practices.EnterpriseLibrary.Logging.LogEntry) As Boolean Implements Microsoft.Practices.EnterpriseLibrary.Logging.Filters.ILogFilter.Filter
If _TimeFrom.CompareTo(log.TimeStamp.ToLocalTime.TimeOfDay) <= 0 And _TimeTo.CompareTo(log.TimeStamp.ToLocalTime.TimeOfDay) >= 0 Then
Return True
Else
Return False
End If
End Function
Public ReadOnly Property Name As String Implements Microsoft.Practices.EnterpriseLibrary.Logging.Filters.ILogFilter.Name
Get
Return "TimeLogFilter"
End Get
End Property
Private Function GetTimeSpan(strTime As String) As TimeSpan
Dim retval As TimeSpan = New TimeSpan(0, 0, 0)
If Not String.IsNullOrWhiteSpace(strTime) Then
Dim times = strTime.Split(":"c)
If times.Length >= 3 Then
Try
retval = New TimeSpan(CInt(times(0)), CInt(times(1)), CInt(times(2)))
Catch ex As Exception
End Try
End If
End If
Return retval
End Function
End Class
Possiamo osservare che:
- abbiamo definito un costruttore con un parametro di tipo
NameValueCollection che verrà richiamato dall'infrastruttura di
Enterprise Library fornendoci I valori inseriti nel file di
configurazione;
- nel nostro caso tali valori sono due orari, nel formato
HH:mm:ss, che definiscono l'ora minima e l'ora massima della fascia
di scrittura log;
- abbiamo implementato il metodo Filter che restitusce True se il
timestamp della LogEntry rientra tra i due orari suddetti;
- anche in questo caso, come nei precedenti, la classe è decorata
con l'attributo ConfigurationElementType per definire la tipologia
di sezione utilizzata nel file di configurazione.
Da notare, inoltre, che il timestamp della LogEntry è quello del
meridiano di Greenwich, quindi dobbiamo applicare il metodo
ToLocalTime per ottenere l'orario locale.
Una volta compilata la libreria con la classe appena creata,
possiamo utilizzarla in maniera del tutto analoga a quanto fatto
per i il trace listener o il formatter:

Negli attributi possiamo inserire i valori che definiscono la
fascia oraria in cui le LogEntry vengono inviate al log e che
verranno passati al costruttore del nostro filtro.
Ogni volta che usiamo l'istruzione Write della classe LogWriter,
verrà richiamato il metodo Filter e, in caso, di risultato positivo
la LogEntry continuerà il suo viaggio verso il listener e il
formatter.
Conclusioni
Una caratteristica peculiare dell'Enterprise Library è quella di
fornire tanti punti di estensibilità e il modulo di logging, pur se
già molto completo, non è da meno.
Estendere il comportamento del log è, come abbiamo visto,
un'attività per nulla complessa ma che può riservarci molte
soddisfazioni.
Riferimenti
[1] Enterprise Library Web Site: http://entlib.codeplex.com/
[2] Logging Application Block MSDN: http://msdn.microsoft.com/en-us/library/ff664569(v=PandP.50).aspx
Tags: Enterprise Library 5.0