package at.asitplus.eidas.specific.proxy.logger; import java.io.IOException; 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.databind.ObjectMapper; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.fasterxml.jackson.dataformat.javaprop.JavaPropsMapper; import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; import at.asitplus.eidas.specific.modules.msproxyservice.MsProxyServiceConstants; 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 AdvancedStatisticLogger implements IStatisticLogger { private static final String SUCCES_CODE = "success.00"; private static final String SUCCES_MSG = "success"; private static final String DATEFORMATER = "yyyy.MM.dd-HH:mm:ss,SSS"; private static final String DEFAULT_NO_IDP_ID = "no idpId available"; private static final ObjectMapper mapper = new ObjectMapper(); private static final JavaPropsMapper propsMapper = new JavaPropsMapper(); private final IStatusMessenger messageService; /** * Build a JSON based statistic logger that uses error messages from a specific source. * * @param source i18n message source */ public AdvancedStatisticLogger(IStatusMessenger source) { this.messageService = source; } @Override public void logSuccessOperation(IRequest protocolRequest, IAuthData authData, boolean isSsoSession) { final StatisticLogEntry entry = buildCoreEntry(protocolRequest); entry.setSuccess(SuccessEntry.builder() .statusCode(SUCCES_CODE) .statusMessage(SUCCES_MSG) .spSector(protocolRequest.getServiceProviderConfiguration().getAreaSpecificTargetIdentifier()) .spCountry(protocolRequest.getServiceProviderConfiguration().getConfigurationValue( MsProxyServiceConstants.CONIG_PROPS_CONNECTOR_COUNTRYCODE)) .citizenCountryCode("AT") .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.setSuccess(SuccessEntry.builder() .statusCode(messageService.getResponseErrorCode(throwable)) .statusMessage(throwable.getMessage()) .spSector(errorRequest.getServiceProviderConfiguration() != null ? errorRequest.getServiceProviderConfiguration().getAreaSpecificTargetIdentifier() : "") .spCountry(errorRequest.getServiceProviderConfiguration() != null ? errorRequest.getServiceProviderConfiguration().getConfigurationValue( MsProxyServiceConstants.CONIG_PROPS_CONNECTOR_COUNTRYCODE) : "") .citizenCountryCode("AT") .build()); 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(propsMapper.writeValueAsProperties(entry))); } catch (final IOException 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.ALWAYS) 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("result") private SuccessEntry success; } @Getter @Setter @Builder @JsonInclude(Include.ALWAYS) private static class SuccessEntry { @JsonProperty("code") private final String statusCode; @JsonProperty("msg") private final String statusMessage; @JsonProperty("spSector") private final String spSector; @JsonProperty("spCountry") private final String spCountry; @JsonProperty("citizenCountryCode") private final String citizenCountryCode; @JsonProperty("withMandates") private final MandateProcess withMandates; } }