Audit Logging¶
Panoramica¶
SGV GE.CO implementa un sistema di logging a due livelli:
- MLogger2 — Framework di logging file-based per tutti i moduli applicativi
- Audit Trail asincrono — Sistema JMS-based per tracciare modifiche alle entita JPA (INSERT/UPDATE/DELETE)
MLogger2 Framework¶
Architettura¶
graph LR
subgraph "Application Code"
APP["Codice Applicativo"]
end
subgraph "MLogger2 Framework"
ML["MLogger2"]
FMT["MLoggerFormatter"]
AN["ApplicationName"]
FAC["MLoggerFactory"]
end
subgraph "Output"
FILE["File di Log<br/>[wildfly]/log/[reparto]/[app]/log_*.log"]
end
APP --> ML
ML --> FMT
ML --> AN
FAC --> ML
ML --> FILE
Classi Principali¶
| Classe | Package | Descrizione |
|---|---|---|
MLogger2 |
it.mdatasystem.mlib.mlogger2 |
Logger principale con 6 livelli |
MLoggerFormatter |
it.mdatasystem.mlib.mlogger2 |
Formattazione messaggi (4 tipi) |
MLoggerFactory |
it.mdatasystem.mlib.mlogger2 |
Factory per creare istanze MLogger2 |
ApplicationName |
it.mdatasystem.mlib.mlogger2 |
Enum dei 26 contesti applicativi |
Livelli di Log¶
MLogger2 supporta 6 livelli (dal meno al piu verboso):
| Livello | Metodi | Uso |
|---|---|---|
FATAL |
fatal(msg), fatal(msg, t) |
Errori critici che impediscono il funzionamento |
ERROR |
error(msg), error(msg, t) |
Errori recuperabili |
WARN |
warn(msg), warn(msg, t) |
Situazioni anomale ma non bloccanti |
INFO |
info(msg), info(msg, t) |
Informazioni operative (azioni utente, risultati) |
DEBUG |
debug(msg), debug(msg, t) |
Informazioni di debug per sviluppo |
TRACE |
trace(msg), trace(msg, t) |
Dettaglio massimo (attenzione GDPR) |
Ogni metodo ha due varianti: solo messaggio e messaggio + Throwable per stack trace.
Formato dei Messaggi¶
MLoggerFormatter supporta 4 tipi di formattazione:
| FormatterType | Formato Output | Uso Tipico |
|---|---|---|
MAIN |
[REPARTO][ClassOrProcess] messaggio |
Logger principale del sistema |
MESS_USER_WRITER |
[User][ClassOrProcess] messaggio |
Log con contesto utente |
MESS_WRITER |
[ClassOrProcess] messaggio |
Log senza contesto utente |
ALL |
[REPARTO][User][ClassOrProcess] messaggio |
Log completo con tutti i campi |
Esempio output:
Costruzione Logger¶
// Factory method — formatter MAIN
MLoggerFormatter formatter = MLoggerFormatter.buildMainFormatter(
"C_A662", // reparto (uppercase)
"ScadenziarioDao" // classe/processo
);
MLogger2 logger = new MLogger2(ApplicationName.GECO, formatter);
// Factory method — formatter completo
MLoggerFormatter formatter = MLoggerFormatter.buildFormatter(
FormatterType.ALL,
"C_A662", // reparto
"admin", // utente
"ScadenziarioDao" // classe/processo
);
Contesti Applicativi (ApplicationName)¶
L'enum ApplicationName definisce 26 contesti di logging. Ogni contesto genera una directory di log dedicata.
Tabella Completa¶
| ApplicationName | Descrizione | Modulo |
|---|---|---|
MAIN |
Logger root del sistema | Sistema |
GECO |
Applicazione GeCo principale (tutte le scelte GUI) | GeCo.Alfa |
ACCERTAMENTI |
Elaborazione accertamenti | ElaborazioneService |
APPARECCHIATURE_ACQ |
Acquisizione da apparecchiature (autovelox, etc.) | ElaborazioneService |
INSSOES |
Integrazione assicurativa SOES | ElaborazioneService |
NOTIFICA_PROD |
Produzione notifiche domestiche | ElaborazioneService |
NOTIFICA_TRASM |
Trasmissione notifiche | ElaborazioneService |
NOTIFICA_TRASM_PEC |
Trasmissione notifiche via PEC | ElaborazioneService |
NOTIFICA_RICEZ |
Ricezione notifiche | ElaborazioneService |
NOTIFICA_RICEZ_PEC |
Ricezione notifiche PEC | ElaborazioneService |
NOTIFICA_ESTERO_PROD |
Produzione notifiche estero | ElaborazioneService |
NOTIFICA_ESTERO_TRASM |
Trasmissione notifiche estero | ElaborazioneService |
NOTIFICA_ESTERO_RICEZ |
Ricezione notifiche estero | ElaborazioneService |
NOTIFICHE_PND |
Notifiche PND (Punto di Notifica Digitale) | ElaborazioneService |
PATPUNTI_PROD |
Produzione punti patente | ElaborazioneService |
PATPUNTI_TRASM |
Trasmissione punti patente | ElaborazioneService |
PATPUNTI_RICEZ |
Ricezione punti patente | ElaborazioneService |
SANA_TRASM |
Trasmissione sanazioni (recuperi) | ElaborazioneService |
SANZIONI_ACC_PROD |
Produzione sanzioni accessorie | ElaborazioneService |
PRE_RUOLO_PROD |
Produzione pre-ruolo (lista di carico) | ElaborazioneService |
PRE_RUOLO_TRASM |
Trasmissione pre-ruolo | ElaborazioneService |
PRE_RUOLO_TRASM_PEC |
Trasmissione pre-ruolo via PEC | ElaborazioneService |
PRE_RUOLO_RICEZ |
Ricezione pre-ruolo | ElaborazioneService |
PRE_RUOLO_RICEZ_PEC |
Ricezione pre-ruolo via PEC | ElaborazioneService |
RUOLO_TRASM |
Trasmissione ruolo (lista di carico ufficiale) | ElaborazioneService |
MAIL_FTP_PROD |
Produzione email/FTP | ElaborazioneService |
RECUPERO_RICEVUTE_PEC |
Recupero ricevute PEC | ElaborazioneService |
Struttura Directory Log¶
Ogni ApplicationName genera una directory dedicata sotto la home WildFly:
[wildfly_home]/log/
└── [codice_reparto]/ # es: C_A662
├── GECO/
│ └── log_2026-02-16.log
├── NOTIFICA_PROD/
│ └── log_2026-02-16.log
├── PATPUNTI_PROD/
│ └── log_2026-02-16.log
└── ...
Audit Trail Asincrono¶
Architettura¶
L'audit trail traccia le modifiche alle entita JPA attraverso un meccanismo asincrono basato su JMS Topic.
sequenceDiagram
participant APP as Codice Applicativo
participant AF as AuditFacade<br/>(EJB @Stateless)
participant JMS as JMS Topic<br/>(Constant.AUDIT_TOPIC)
participant MDB as AuditMDB<br/>(@MessageDriven)
participant OUT as Output
APP->>AF: submit(AuditModel)
Note over AF: @TransactionAttribute<br/>(REQUIRES_NEW)
AF->>JMS: jmsContext.createProducer()<br/>.send(auditTopic, model)
Note over JMS: Messaggio asincrono<br/>su Topic pub-sub
JMS->>MDB: onMessage(Message)
Note over MDB: maxSession=2<br/>transactionTimeout=180s
MDB->>MDB: message.getBody(AuditModel.class)
MDB->>OUT: System.out.println(domain, type, entity)
Componenti¶
AuditModel¶
DTO serializzabile per gli eventi di audit.
// it.soes.eclipselink.AuditModel
public class AuditModel implements Serializable {
public enum Type { INSERT, UPDATE, DELETE }
private String domain; // Package/dominio dell'entita
private Type type; // Tipo operazione
private Serializable entity; // Entita JPA modificata
}
| Campo | Tipo | Descrizione |
|---|---|---|
domain |
String |
Identificativo del dominio/package dell'entita |
type |
AuditModel.Type |
Tipo di operazione: INSERT, UPDATE, DELETE |
entity |
Serializable |
L'entita JPA oggetto della modifica |
AuditFacade¶
EJB Stateless che pubblica eventi di audit sul JMS Topic.
// it.soes.eclipselink.AuditFacade
@Stateless
public class AuditFacade {
@Inject private JMSContext jmsContext;
@Resource(name = Constant.AUDIT_TOPIC) private Topic auditTopic;
@TransactionAttribute(REQUIRES_NEW)
public void submit(AuditModel model) {
jmsContext.createProducer().send(auditTopic, model);
}
}
Caratteristiche:
@TransactionAttribute(REQUIRES_NEW): L'audit viene eseguito in una transazione separata — se il main business transaction fallisce, l'evento di audit e comunque registrato- Il Topic JMS usa pattern pub-sub (non point-to-point)
- Nessun batching: ogni evento e inviato singolarmente
AuditMDB¶
Message-Driven Bean che consuma gli eventi di audit dal Topic.
// it.soes.eclipselink.AuditMDB
@MessageDriven(activationConfig = {
@ActivationConfigProperty(propertyName = "destinationType",
propertyValue = "javax.jms.Topic"),
@ActivationConfigProperty(propertyName = "destination",
propertyValue = Constant.AUDIT_TOPIC),
@ActivationConfigProperty(propertyName = "maxSession",
propertyValue = "2"),
@ActivationConfigProperty(propertyName = "transactionTimeout",
propertyValue = "180")
})
| Proprieta | Valore | Descrizione |
|---|---|---|
destinationType |
javax.jms.Topic |
Tipo pub-sub |
destination |
Constant.AUDIT_TOPIC |
Nome JNDI del topic |
maxSession |
2 |
Max 2 consumer concorrenti |
transactionTimeout |
180 |
Timeout transazione: 3 minuti |
Implementazione Incompleta
L'attuale implementazione di AuditMDB.onMessage() scrive solo su System.out.println() senza persistere gli eventi su database o file strutturato. Questa e un'area di miglioramento prioritaria (vedi Threat Model — A09).
Configurazione Log4j¶
GeCo.Alfa (log4j.xml)¶
<log4j:configuration debug="true">
<appender name="fileAppender"
class="org.apache.log4j.RollingFileAppender">
<param name="append" value="false" />
<param name="file" value="log4j.log" />
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern"
value="%d{ABSOLUTE} %-5p [%c{1}] %m%n" />
</layout>
</appender>
<root>
<level value="DEBUG" />
<appender-ref ref="fileAppender" />
</root>
</log4j:configuration>
Pattern di formattazione:
| Componente | Significato | Esempio |
|---|---|---|
%d{ABSOLUTE} |
Timestamp HH |
14:23:45 |
%-5p |
Livello log (5 char, left-aligned) | INFO |
[%c{1}] |
Nome classe semplificato | [ScadenziarioDao] |
%m |
Messaggio | Ricerca completata |
%n |
Newline |
Output esempio:
14:23:45 INFO [ScadenziarioDao] Ricerca scadenze completata per reparto C_A662
14:23:46 ERROR [LoginPresenter] Tentativo login fallito - IP: 192.168.1.100
Livelli Dinamici¶
I livelli di log possono essere configurati dinamicamente per reparto e applicazione tramite variabili di sistema:
Esempio: LOG_C_A662_GECO=DEBUG
Regole GDPR per il Logging¶
MAI Loggare¶
| Dato | Motivo | Esempio Errato |
|---|---|---|
| Password | Credenziali | logger.info("Password: " + pwd) |
| Token di autenticazione | Credenziali | logger.info("Bearer " + token) |
| Codice fiscale completo | PII | logger.info("CF: " + cf) |
| Numero patente completo | PII | logger.info("Patente: " + numPatente) |
| IBAN / carta di credito | Dati finanziari | logger.info("IBAN: " + iban) |
| Contenuto certificati | Chiavi crittografiche | logger.info("Cert: " + certContent) |
| Chiavi API / credenziali servizi | Segreti | logger.info("API Key: " + apiKey) |
SEMPRE Loggare (Audit Trail)¶
| Evento | Livello | Esempio Corretto |
|---|---|---|
| Login riuscito/fallito | INFO/WARN | logger.info("Login OK - operatore: " + opId) |
| Creazione/modifica verbale | INFO | logger.info("Verbale " + vId + " modificato da " + opId) |
| Operazioni su ricorsi | INFO | logger.info("Ricorso " + rId + " inserito") |
| Registrazione pagamento | INFO | logger.info("Pagamento registrato per verbale " + vId) |
| Chiamate API PA (esito) | INFO/ERROR | logger.info("PND invio notifica: " + statusCode) |
| Accesso a dati anagrafici | INFO | logger.info("Ricerca anagrafica da " + opId) |
| Errori di sistema | ERROR | logger.error("Errore connessione DB", exception) |
Mascheramento Dati Sensibili¶
Quando e necessario loggare un riferimento a dati personali, usare il mascheramento:
// Corretto — CF mascherato
logger.info("Verbale per soggetto CF:****" + cf.substring(cf.length()-4));
// Corretto — solo ID di riferimento
logger.info("Verbale {} creato per figura {}", verbaleId, figuraId);
// ERRATO — dati personali in chiaro
logger.info("Verbale per " + nome + " " + cognome + " CF:" + cf);
Retention e Rotazione¶
Configurazione Attuale¶
| Parametro | Valore | Note |
|---|---|---|
| Appender | RollingFileAppender |
Rotazione automatica |
| Append mode | false |
Sovrascrittura al riavvio |
| File base | log4j.log |
Path relativo a WildFly |
| Root level | DEBUG |
Tutti i livelli >= DEBUG |
Parametri di rotazione
La configurazione attuale in log4j.xml non specifica maxFileSize o maxBackupIndex. Si consiglia di aggiungere:
maxFileSize: 100MB per filemaxBackupIndex: 10 file di backup- Retention: Minimo 90 giorni per compliance normativa
Raccomandazioni Retention¶
| Tipo di Log | Retention Minima | Motivazione |
|---|---|---|
| Audit trail (azioni utente) | 10 anni | Art. 209 CdS, normativa archivistica |
| Log applicativi | 90 giorni | Troubleshooting e diagnostica |
| Log di sicurezza (login/logout) | 1 anno | GDPR art. 30, registro trattamenti |
| Log di errore | 30 giorni | Debug e manutenzione |