package at.asitplus.eidas.specific.proxy.logger; import java.time.LocalDateTime; import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; import at.gv.egiz.eaaf.core.api.IRequest; import at.gv.egiz.eaaf.core.api.IStatusMessenger; import at.gv.egiz.eaaf.core.api.data.PvpAttributeDefinitions; import at.gv.egiz.eaaf.core.api.idp.IAuthData; import at.gv.egiz.eaaf.core.api.idp.IEidAuthData; import at.gv.egiz.eaaf.core.api.logging.IStatisticLogger; import lombok.Builder; import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.Setter; import lombok.extern.slf4j.Slf4j; /** * Extended statistic logger that use a JSON based data model. * * @author tlenz * */ @Slf4j public class AdvancedStatisicLogger implements IStatisticLogger { private static final String DATEFORMATER = "yyyy.MM.dd-HH:mm:ss"; private static final String DEFAULT_NO_IDP_ID = "no idpId available"; private static final ObjectMapper mapper = new ObjectMapper(); private final IStatusMessenger messageService; /** * Build a JSON based statistic logger that uses error messages from a specific source. * * @param source i18n message source */ public AdvancedStatisicLogger(IStatusMessenger source) { this.messageService = source; } @Override public void logSuccessOperation(IRequest protocolRequest, IAuthData authData, boolean isSsoSession) { final StatisticLogEntry entry = buildCoreEntry(protocolRequest); entry.setSuccess(SuccessEntry.builder() .spSector(protocolRequest.getServiceProviderConfiguration().getAreaSpecificTargetIdentifier()) .withMandates(selectMandateProcessType(authData)) .build()); writeEntryToLog(entry); } @Override public void logErrorOperation(Throwable throwable) { log.trace("Advanced statistic logger only logs on SP level"); } @Override public void logErrorOperation(Throwable throwable, IRequest errorRequest) { final StatisticLogEntry entry = buildCoreEntry(errorRequest); entry.setError(new ErrorEntry(messageService.getResponseErrorCode(throwable), throwable.getMessage())); writeEntryToLog(entry); } @Override public void internalTesting() throws Exception { log.trace("Not implemented for a File-based logger"); } private StatisticLogEntry buildCoreEntry(IRequest pendingRequest) { return new StatisticLogEntry( LocalDateTime.now(), pendingRequest.getUniqueTransactionIdentifier(), pendingRequest.getServiceProviderConfiguration() != null && pendingRequest.getServiceProviderConfiguration().getUniqueIdentifier() != null ? pendingRequest.getServiceProviderConfiguration().getUniqueIdentifier() : DEFAULT_NO_IDP_ID); } private MandateProcess selectMandateProcessType(IAuthData authData) { if (authData instanceof IEidAuthData && ((IEidAuthData)authData).isUseMandate()) { return authData.getGenericData(PvpAttributeDefinitions.MANDATE_LEG_PER_SOURCE_PIN_NAME, String.class) != null ? MandateProcess.FOR_LEGAL_PERSON : MandateProcess.FOR_NATURAL_PERSON; } else { return MandateProcess.NONE; } } private void writeEntryToLog(StatisticLogEntry entry) { try { log.info(mapper.writeValueAsString(entry)); } catch (final JsonProcessingException e) { log.error("Can NOT generate statistic entry for logging", e); } } public enum MandateProcess { NONE, FOR_NATURAL_PERSON, FOR_LEGAL_PERSON } @Getter @Setter @RequiredArgsConstructor @JsonInclude(Include.NON_NULL) private static class StatisticLogEntry { @JsonSerialize(using = LocalDateTimeSerializer.class) @JsonDeserialize(using = LocalDateTimeDeserializer.class) @JsonFormat(pattern = DATEFORMATER) @JsonProperty("timestamp") private final LocalDateTime timestamp; @JsonProperty("txId") private final String transactionId; @JsonProperty("entityId") private final String idpEntityId; @JsonProperty("success") private SuccessEntry success; @JsonProperty("error") private ErrorEntry error; } @Getter @Setter @Builder @JsonInclude(Include.NON_NULL) private static class SuccessEntry { @JsonProperty("spSector") private final String spSector; @JsonProperty("withMandates") private final MandateProcess withMandates; } @Getter @Setter @RequiredArgsConstructor private static class ErrorEntry { @JsonProperty("code") private final String errorCode; @JsonProperty("msg") private final String errorMessage; } }