Ormai da anni impiego log4net per implementare lo strato di logging nei miei progetti e mi trovo veramente molto bene.
Una delle caratteristiche che amo di più di log4net è la possibilità di cambiare log sink solamente tramite file di configurazione, permettendo così di fatto la gestione del log nella propria applicazione senza preoccupazioni di dove poi questi messaggi vadano a finire o se una certa destinazione del log (ad esempio un DB) sia o meno presente nell’ambiente di produzione.
La configurazione di questo motore di logging avviene tramite sezione del file di configurazione (web.config o <app>.config) e questa configurazione viene “caricata” all’esecuzione del comando:
log4net.Config.XmlConfigurator.Configure()
Vi sono dei casi però, nei quali non siamo sicuri che un’applicazione effettui questa chiamata. Mi riferisco a quei casi in cui la nostra DLL, splendidamente farcita di log, venga chiamata da una file eseguibile il quale, pur avendo un file di configurazione .config che comprende magari anche la sezione di configurazione di log4net, non chiami correttamente il metodo Configure().
In questi casi saremmo noi costretti a controllare se il logger è stato o meno configurato, il che renderebbe tutto molto meno pratico.
Per risolvere la problematica, si può impiegare la seguente strategia:
- Rimuovere la configurazione di log4net dal file .config e riportarla in un file log4net.config, da copiare ovviamente nella directory della nostra applicazione
- Impostare nel file AssemblyInfo di ogni progetto di tipo Class Library per la quale desideriamo attivare la configurazione del logging l’attributo
[assembly: log4net.Config.XmlConfigurator(ConfigFile="log4net.config", Watch=true)]
In questo modo otteniamo due interessanti benefici: il primo è quello di isolare la configurazione del logging in un file, rendendola di fatto facilmente riutilizzabile e modificabile. Cambiando infatti SOLO il file log4net.config otterremo un comportamento del logging differente.
Il secondo è quello di poter dare per configurato il loggining nelle nostre dll, che potranno quinidi essere distribuite anche a sviluppatori terzi, senza richiedere loro di referenziare log4net e richiamare il metodo Configure.
Oggi mi è stato sottoposto un problema veramente curioso. Una collega mi ha fatto notare che durante un’operazione di manutenzione di un DB SQL Server 2008 R2 ha lanciato una query di DELETE che non si è comportata come previsto. In pratica, questa query stava cancellando arbitrariamente i dati dalla tabella senza rispettare la clausola WHERE che compariva regolarmente nel testo della query.
Il comando dato al server è il seguente:
DELETE FROM dbo.Utenti WHERE IDUtente IN (SELECT IDUtente FROM dbo.Operatori)
Come mai il comportamento è strano? Perchè effettivamente in questa query c’è un errore! La tabella Operatori NON contiene il campo IDUtente, per cui, lanciato da solo, il comando
SELECT IDUtente FROM dbo.Operatori
Dà un errore di tipo:
Msg 207, Level 16, State 1, Line 1 Invalid column name 'IDUtente'.
Lo stesso errore non viene riportato se la query viene lanciata come subquery della clausola IN, bensì la query viene eseguita completamente, cancellando TUTTI i record dalla tabella Utenti, ignorando per cui la clausola WHERE IDUtente IN (…).
E’ da notare come utilizzando un nome di campo non esistente nè nella tabella Utenti, nè nella tabella Operatori la query vada in errore; per cui è da supporre che il comando non vada in errore proprio perchè il campo IDUtente compare nella tabella Utenti (cosa comunque errata).
Per ricreare il “problema” ed indagare ulteriormente, ho creato un piccolo DB con 2 tabelle, Utenti e Operatori, così strutturate:
- Utenti
- IDUtente [int] PK
- Nome [varchar(50)]
- Operatori
- IDOperatore [int] PK
- Nome [varchar(50)]
nella tabella Utenti ho 5 records (con IDUtente 1,2,3,4,5), mentre in quella Operatori ne ho 2 (con IDOperatore 1,2)
Questa query seleziona tutti gli utenti:
SELECT * FROM dbo.Utenti
Questa query seleziona solo gli utenti 1 e 2
SELECT * FROM dbo.Utenti WHERE IDUtente IN (SELECT IDOperatore FROM dbo.Operatori)
Questa query restituisce un errore:
SELECT IDUtente FROM dbo.Operatori
Msg 207, Level 16, State 1, Line 1 Invalid column name 'IDUtente'.
Questa query, stranamente, restituisce TUTTI gli utenti:
SELECT * FROM dbo.Utenti WHERE IDUtente IN (SELECT IDUtente FROM dbo.Operatori)
Non dovrebbe restituire tutti gli utenti, in quanto il campo IDUtente non fa parte della tabella Operatori. Altrettanto stranamente anche questa query restituisce tutti gli utenti:
SELECT * FROM dbo.Utenti WHERE IDUtente IN (SELECT IDUtente)
Per induzione, penso che SQL interpreti il campo IDUtente come campo dell’unica tabella elencata nella clausola FROM, anche se questo è a mio avviso fuorviante.
Quindi, quando fate una query di delete con una clausola IN, fate particolare attenzione ai nomi dei campi, e magari prima di eseguirla provate a fare una SELECT.