Vai al contenuto

Audit Logging

Panoramica

SGV GE.CO implementa un sistema di logging a due livelli:

  1. MLogger2 — Framework di logging file-based per tutti i moduli applicativi
  2. 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:

14:23:45 INFO  [REPARTO_A662][admin][ScadenziarioDao] Ricerca scadenze completata

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🇲🇲ss 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:

LOG_[reparto]_[ApplicationName]=[LEVEL]

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 file
  • maxBackupIndex: 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